Skip to content

TextStyle Guide

Namespace: ThinkGeo.Core
Applies to: All ThinkGeo products (WPF, WinForms, Blazor, MAUI, GIS Server, etc.)


Overview

TextStyle draws text labels derived from feature attribute columns. It is always paired with a geometry style — a PointStyle, LineStyle, or AreaStyle — that draws the feature shape itself. The TextStyle is responsible only for rendering the label.

Every TextStyle has three required parameters: the column name whose value becomes the label text, a GeoFont describing the typeface and size, and a GeoBrush for the text fill color. All other behavior — placement, halo, spline, overlap handling — is set via object-initializer properties.


Constructor

There is one primary constructor form used across all platforms:

new TextStyle(columnName, geoFont, textBrush)

columnName is the exact string name of the feature attribute column whose value will be rendered as the label.

GeoFont — the typeface argument

GeoFont accepts a font family name, a size in points, and an optional DrawingFontStyles flags enum:

new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold)
new GeoFont("Segoe UI", 10, DrawingFontStyles.Bold)
new GeoFont("Segoe UI", 6, DrawingFontStyles.Bold)
new GeoFont("Arial", 10, DrawingFontStyles.Bold)
new GeoFont("Arial", 10)                            // no style flags — regular weight

DrawingFontStyles is a flags enum: Regular, Bold, Italic, Underline, Strikeout. These can be combined with |.

Sources: wpfHowDoI:Samples/VectorDataStyling/Renderlabels.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderLabels.cs, blazorHowDoI:Shared/LayerBuilder.cs, mauiHowDoI:Samples/VectorDataStyling/CreateTextStyle.xaml.cs.

textBrush — the fill color argument

Pass any GeoBrush. In practice the samples always use a GeoBrushes named constant directly:

new TextStyle("NAME",      new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkRed)
new TextStyle("FULL_NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.MidnightBlue)
new TextStyle("NAME",      new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkGreen)
new TextStyle("AREANAME",  new GeoFont("Arial", 10),                            new GeoSolidBrush(GeoColors.Black))

Sources: all four platforms, various styling samples.


Static Factory Method

TextStyle.CreateSimpleTextStyle

Shorthand factory for a basic label style with explicit x/y pixel offsets. Used most often for DynamicIsoLineLayer labeling where the column name comes from the layer itself rather than a shapefile attribute:

var textStyle = TextStyle.CreateSimpleTextStyle(
    columnName,           // e.g., dynamicIsoLineLayer.DataValueColumnName
    "Arial",
    10,
    DrawingFontStyles.Bold,
    GeoColors.Black,
    0,                    // xOffsetInPixel
    0);                   // yOffsetInPixel

After creation, additional properties are set directly on the returned instance (see the ISO line pattern below).

Sources: wpfHowDoI:Samples/VectorDataStyling/DisplayISOLine.xaml.cs, winformHowDoI:Samples/VectorDataStyling/DisplayISOLine.cs, mauiHowDoI:Samples/VectorDataStyling/ISOLineLayer.xaml.cs.


Properties

Label Positioning

Property Type Description
TextPlacement TextPlacement Where the label sits relative to the feature. Common values: Lower, Center, Upper, Right, Left.
XOffsetInPixel float Horizontal offset in pixels from the default label anchor.
YOffsetInPixel float Vertical offset in pixels. Positive = down, negative = up.
// Hotel name label below the point marker, nudged down 2px
new TextStyle("NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkRed)
{
    TextPlacement  = TextPlacement.Lower,
    YOffsetInPixel = 2,
    ...
}

// Hotel label centered over the icon, shifted up 10px
new TextStyle("NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkRed)
{
    TextPlacement  = TextPlacement.Center,
    YOffsetInPixel = -10,
    ...
}

Sources: wpfHowDoI:Samples/VectorDataStyling/Renderlabels.xaml.cs, mauiHowDoI:Samples/VectorDataStyling/CreateTextStyle.xaml.cs.

Visual Appearance

Property Type Description
HaloPen GeoPen Outline drawn behind the text — creates a readable "glow" effect against any background. Almost always new GeoPen(GeoBrushes.White, 2).
DrawingLevel DrawingLevel Z-order bucket. Always set to DrawingLevel.LabelLevel so labels draw on top of all geometry.
AllowLineCarriage bool Allows the label to wrap onto multiple lines. Use true for point labels where the name may be long.
{
    HaloPen          = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel     = DrawingLevel.LabelLevel,
    AllowLineCarriage = true
}

Sources: all four platforms, all label samples.

Line Label Behavior

Property Type Description
SplineType SplineType Controls whether labels follow the curve of line features. SplineType.StandardSplining makes street names curve along the road.
GridSize int Minimum pixel distance between repeated labels along the same line. Default 100. Set to 20 for dense street networks.
// Street name that curves along the road centerline
new TextStyle("FULL_NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.MidnightBlue)
{
    SplineType   = SplineType.StandardSplining,
    HaloPen      = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel = DrawingLevel.LabelLevel
}

// With explicit GridSize for dense zoom levels
new TextStyle("FULL_NAME", new GeoFont("Segoe UI", 6, DrawingFontStyles.Bold), GeoBrushes.Black)
{
    SplineType   = SplineType.StandardSplining,
    HaloPen      = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel = DrawingLevel.LabelLevel,
    GridSize     = 20
}

Sources: wpfHowDoI:Samples/VectorDataStyling/Renderlabels.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderLabels.cs, wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.xaml.cs.

Polygon Label Behavior

Property Type Description
FittingPolygon bool When true, tries to place the label inside the polygon bounds. When false, places at the polygon centroid regardless of whether it's inside a concave shape.
FittingPolygonInScreen bool Additionally clips label placement to the visible screen extent. Set to true to prevent labels appearing for off-screen polygon centroids.
// WPF — parks: force label inside the park polygon, clip to screen
new TextStyle("NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkGreen)
{
    FittingPolygon          = true,
    FittingPolygonInScreen  = true,
    HaloPen                 = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel            = DrawingLevel.LabelLevel,
    AllowLineCarriage       = true
}

// WinForms / MAUI — same parks layer: centroid placement, no screen clipping
new TextStyle("NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkGreen)
{
    FittingPolygon    = false,
    HaloPen           = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel      = DrawingLevel.LabelLevel,
    AllowLineCarriage = true
}

Sources: wpfHowDoI:Samples/VectorDataStyling/Renderlabels.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderLabels.cs, mauiHowDoI:Samples/VectorDataStyling/CreateTextStyle.xaml.cs.

Label Collision and Density Control

These properties are used together on DynamicIsoLineLayer labeling and any layer where label density must be carefully managed:

Property Type Description
OverlappingRule LabelOverlappingRule NoOverlapping = suppress a label if it would overlap an already-placed label. AllowOverlapping = place all labels regardless.
DuplicateRule LabelDuplicateRule UnlimitedDuplicateLabels = allow the same text value to be rendered multiple times. NoDuplicateLabels = render a text value only once per tile.
TextLineSegmentRatio double Minimum ratio of the label text width to the line segment length it sits on. Set to a very large number (9999999) to disable this constraint and allow labels on short segments.
FittingLineInScreen bool When true, only places the label if the line segment it would sit on is fully visible in the current view.
SuppressPartialLabels bool When true, suppresses labels that would be clipped by the tile or screen edge.
// ISO line labeling — full set of density properties
var textStyle = TextStyle.CreateSimpleTextStyle(
    dynamicIsoLineLayer.DataValueColumnName, "Arial", 10, DrawingFontStyles.Bold, GeoColors.Black, 0, 0);

textStyle.HaloPen              = new GeoPen(GeoColors.White, 2);
textStyle.OverlappingRule      = LabelOverlappingRule.NoOverlapping;
textStyle.SplineType           = SplineType.StandardSplining;
textStyle.DuplicateRule        = LabelDuplicateRule.UnlimitedDuplicateLabels;
textStyle.TextLineSegmentRatio = 9999999;
textStyle.FittingLineInScreen  = true;
textStyle.SuppressPartialLabels = true;

dynamicIsoLineLayer.CustomStyles.Add(textStyle);

Sources: wpfHowDoI:Samples/VectorDataStyling/DisplayISOLine.xaml.cs, winformHowDoI:Samples/VectorDataStyling/DisplayISOLine.cs, mauiHowDoI:Samples/VectorDataStyling/ISOLineLayer.xaml.cs.


Attaching a Style to a Layer

DefaultTextStyle — single scale or scale-range case

DefaultTextStyle is set when this is the only text style at a zoom level. Always combine with ApplyUntilZoomLevel:

// Point layer — hotel name at zoom 16+, combined with DefaultPointStyle at same level
hotelsLayer.ZoomLevelSet.ZoomLevel16.DefaultPointStyle = new PointStyle(PointSymbolType.Circle, 12,
    GeoBrushes.DarkRed, new GeoPen(GeoBrushes.White, 2));
hotelsLayer.ZoomLevelSet.ZoomLevel16.DefaultTextStyle = new TextStyle("NAME",
    new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkRed)
{
    TextPlacement  = TextPlacement.Lower,
    YOffsetInPixel = 4,
    HaloPen        = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel   = DrawingLevel.LabelLevel,
    AllowLineCarriage = true
};
hotelsLayer.ZoomLevelSet.ZoomLevel16.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

// Line layer — street name at zoom 13+
streetsLayer.ZoomLevelSet.ZoomLevel13.DefaultTextStyle = new TextStyle("FULL_NAME",
    new GeoFont("Segoe UI", 6, DrawingFontStyles.Bold), GeoBrushes.Black)
{
    SplineType   = SplineType.StandardSplining,
    HaloPen      = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel = DrawingLevel.LabelLevel,
    GridSize     = 20
};

Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.cs, mauiHowDoI:Samples/VectorDataStyling/StylingAcrossMultipleScales.xaml.cs.

CustomStyles — when mixing with geometry styles

CustomStyles is the standard choice when a layer needs both a geometry style and a text style at the same zoom level. Add the geometry style first, then the TextStyle:

// Hotels: point marker + name label
var pointStyle = new PointStyle(PointSymbolType.Circle, 4, GeoBrushes.Brown,
    new GeoPen(GeoBrushes.DarkRed, 2));
var textStyle = new TextStyle("NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkRed)
{
    TextPlacement  = TextPlacement.Lower,
    YOffsetInPixel = 2,
    HaloPen        = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel   = DrawingLevel.LabelLevel,
    AllowLineCarriage = true
};
hotelsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(pointStyle);
hotelsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(textStyle);
hotelsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

// Streets: road line + curved street name
var lineStyle = new LineStyle(new GeoPen(GeoBrushes.DimGray, 6), new GeoPen(GeoBrushes.WhiteSmoke, 4));
var streetTextStyle = new TextStyle("FULL_NAME",
    new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.MidnightBlue)
{
    SplineType   = SplineType.StandardSplining,
    HaloPen      = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel = DrawingLevel.LabelLevel
};
streetsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(lineStyle);
streetsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(streetTextStyle);
streetsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

// Parks: area fill + polygon label
var areaStyle = new AreaStyle(GeoPens.DimGray, GeoBrushes.PastelGreen);
var parkTextStyle = new TextStyle("NAME",
    new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkGreen)
{
    FittingPolygon    = false,
    HaloPen           = new GeoPen(GeoBrushes.White, 2),
    DrawingLevel      = DrawingLevel.LabelLevel,
    AllowLineCarriage = true
};
parksLayer.ZoomLevelSet.ZoomLevel14.CustomStyles.Add(areaStyle);
parksLayer.ZoomLevelSet.ZoomLevel14.CustomStyles.Add(parkTextStyle);
parksLayer.ZoomLevelSet.ZoomLevel14.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Sources: wpfHowDoI:Samples/VectorDataStyling/Renderlabels.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderLabels.cs, mauiHowDoI:Samples/VectorDataStyling/CreateTextStyle.xaml.cs.


Scale-Dependent Label Appearance

Labels are commonly added only at higher zoom levels where there is enough space to render them without clutter. The canonical pattern from the hotel/street samples is:

// Zoom 12-13: show a small point only, no label yet
hotelsLayer.ZoomLevelSet.ZoomLevel12.DefaultPointStyle =
    new PointStyle(PointSymbolType.Circle, 4, GeoBrushes.DarkRed, new GeoPen(GeoBrushes.White, 2));
hotelsLayer.ZoomLevelSet.ZoomLevel12.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level13;

// Zoom 14-15: slightly larger point, still no label
hotelsLayer.ZoomLevelSet.ZoomLevel14.DefaultPointStyle =
    new PointStyle(PointSymbolType.Circle, 8, GeoBrushes.DarkRed, new GeoPen(GeoBrushes.White, 2));
hotelsLayer.ZoomLevelSet.ZoomLevel14.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level15;

// Zoom 16+: full point + name label
hotelsLayer.ZoomLevelSet.ZoomLevel16.DefaultPointStyle =
    new PointStyle(PointSymbolType.Circle, 12, GeoBrushes.DarkRed, new GeoPen(GeoBrushes.White, 2));
hotelsLayer.ZoomLevelSet.ZoomLevel16.DefaultTextStyle =
    new TextStyle("NAME", new GeoFont("Segoe UI", 12, DrawingFontStyles.Bold), GeoBrushes.DarkRed)
    {
        TextPlacement  = TextPlacement.Lower,
        YOffsetInPixel = 4,
        HaloPen        = new GeoPen(GeoBrushes.White, 2),
        DrawingLevel   = DrawingLevel.LabelLevel,
        AllowLineCarriage = true
    };
hotelsLayer.ZoomLevelSet.ZoomLevel16.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnScales.cs, mauiHowDoI:Samples/VectorDataStyling/StylingAcrossMultipleScales.xaml.cs.


Separating Shape and Label Layers (Dynamic Labeling)

Labels can be placed in a separate overlay from their geometry. This allows runtime toggling of label visibility independent of the shape layer, and in MAUI enables a non-rotating label overlay so labels stay upright when the map is rotated.

WPF — FeatureLayerWpfDrawingOverlay

In WPF, FeatureLayerWpfDrawingOverlay is an alternative overlay type that keeps labels non-tiled and re-renders them dynamically on every view change. The same ShapeFileFeatureLayer instances (with both geometry and text styles) are added to both the LayerOverlay and the FeatureLayerWpfDrawingOverlay; the UI toggles between them:

// Tiled overlay (default)
_layerOverlay = new LayerOverlay();
_layerOverlay.Layers.Add(parksLayer);
_layerOverlay.Layers.Add(streetsLayer);
_layerOverlay.Layers.Add(hotelsLayer);
_layerOverlay.TileType = TileType.SingleTile;
MapView.Overlays.Add(_layerOverlay);

// Non-tiled drawing overlay for dynamic labels
_featureLayerWpfDrawingOverlay = new FeatureLayerWpfDrawingOverlay();
_featureLayerWpfDrawingOverlay.FeatureLayers.Add(parksLayer);
_featureLayerWpfDrawingOverlay.FeatureLayers.Add(streetsLayer);
_featureLayerWpfDrawingOverlay.FeatureLayers.Add(hotelsLayer);
MapView.Overlays.Add(_featureLayerWpfDrawingOverlay);

Source: wpfHowDoI:Samples/VectorDataStyling/Renderlabels.xaml.cs.

MAUI — LayerNonRotationGraphicsViewOverlay

In MAUI, LayerNonRotationGraphicsViewOverlay keeps labels rendered at screen-north regardless of map rotation. The pattern is to load shapes into a regular LayerOverlay (shape-only styles) and labels into a separate LayerNonRotationGraphicsViewOverlay (label-only styles), using separate ShapeFileFeatureLayer instances for each:

// Shape overlay — layers styled with geometry only (no TextStyle)
var layerOverlay = new LayerOverlay();
// ... add shape-only styled layers ...
MapView.Overlays.Add(layerOverlay);

// Non-rotation label overlay — same data, label styles only
_dynamicLabelOverlay = new LayerNonRotationGraphicsViewOverlay();
await _dynamicLabelOverlay.OpenAsync(MapView);
// ... add label-only styled layers ...
MapView.Overlays.Add(_dynamicLabelOverlay);

Label visibility is then controlled per-layer:

_hotelsLabelLayer.IsVisible  = LabelHotelsCheckBox.IsChecked;
_parksLabelLayer.IsVisible   = LabelParksCheckBox.IsChecked;
_streetsLabelLayer.IsVisible = LabelStreetsCheckBox.IsChecked;
await _dynamicLabelOverlay.RefreshAsync();

Source: mauiHowDoI:Samples/VectorDataStyling/CreateTextStyle.xaml.cs.


Common Patterns and Pitfalls

Always set DrawingLevel = DrawingLevel.LabelLevel. Without this, labels draw in the same z-order bucket as geometry and will be covered by polygon fills or line strokes from layers added after them. LabelLevel ensures labels always render on top.

HaloPen width is additive on each side. A GeoPen of width 2 adds 2 pixels of outline on every side of the text. For small fonts (size 6–8), a halo width of 2 is standard; for larger fonts (12+), 2 is still typical.

TextPlacement.Lower vs TextPlacement.Center. Lower places the text below the feature anchor point; combined with a positive YOffsetInPixel, it nudges the label further from the icon. Center places the label centered on the feature, which works better when no point marker is visible or when using a negative YOffsetInPixel to push it above.

SplineType.StandardSplining is for line features only. Setting it on a point or polygon layer has no effect; do not include it unless the layer contains line geometry.

GridSize only applies when SplineType is active. If you set GridSize = 20 on a non-splined label, it has no visible effect.

FittingPolygon = true can hide labels on small or concave polygons. If a polygon is too small for the text to fit inside it, the label is suppressed entirely. When in doubt, use FittingPolygon = false and rely on centroid placement.

OverlappingRule = LabelOverlappingRule.NoOverlapping is tile-scoped. Labels are placed per-tile. A label suppressed in one tile may appear in the adjacent tile if it does not overlap any labels in that tile. For globally deduped labels, use DuplicateRule = LabelDuplicateRule.NoDuplicateLabels.


Source References

Sample Namespace TextStyle usage
VectorDataStyling/Renderlabels WPF Hotels (Lower/YOffset/AllowLineCarriage), streets (SplineType), parks (FittingPolygon=true, FittingPolygonInScreen); FeatureLayerWpfDrawingOverlay pattern
VectorDataStyling/RenderLabels WinForms Hotels, streets, parks all three geometry types; FittingPolygon=false
VectorDataStyling/CreateTextStyle MAUI Hotels (Center/YOffset=-10), streets (SplineType), parks (FittingPolygon=false); LayerNonRotationGraphicsViewOverlay dynamic label pattern
VectorDataStyling/RenderBasedOnScales WPF, WinForms Scale-dependent DefaultTextStyle at ZoomLevel16 with DefaultPointStyle; GridSize=20 on street labels
VectorDataStyling/StylingAcrossMultipleScales MAUI Same scale-dependent pattern; GridSize=20 at each zoom step
VectorDataStyling/DisplayISOLine WPF, WinForms CreateSimpleTextStyle + full density-control property set on DynamicIsoLineLayer
VectorDataStyling/ISOLineLayer MAUI Same CreateSimpleTextStyle + density pattern
Shared/LayerBuilder.cs Blazor new TextStyle("AREANAME", new GeoFont("Arial", 10), new GeoSolidBrush(GeoColors.Black)) — minimal city-name label