private GraphicTextSymbolizer CreateGraphicTextSymbolizer(NodePropertyValue[] properties)
    {
      GraphicTextSymbolizer symText = new GraphicTextSymbolizer();
      symText.Clip = true;
      symText.TextLayout.Alignment = TextAlignment.CenterAligned;
      ExternalGraphicSymbol gsImage = new ExternalGraphicSymbol();
      symText.Graphic.GraphicSymbols.Add(gsImage);

      GeometryTransformInfo geomTrans = new GeometryTransformInfo();

      LabelPlacementInfo lpi = new LabelPlacementInfo();
      lpi.Placement = "point";
      lpi.Symbolizer = (int)SymbolizerType.Shield;

      int blockIndex = -1;
      TextLayoutBlock textBlock = new TextLayoutBlock();
      string textTransform = null;
      int nProps = properties.Length;
      float text_dx = 0F, text_dy = 0F;

      NodePropertyValue pv = null;

      try
      {
        for (int i = 0; i < nProps; i++)
        {
          pv = properties[i];

          switch (pv.Name)
          {
            case "shield-name":
              if (pv.Value != null)
              {
                string textExpr = ToExpression(pv.Value);
                if (!string.IsNullOrEmpty(textTransform))
                  textExpr = GetTextTransform(textExpr, textTransform);
                textBlock.TextExpression = textExpr;

                blockIndex++;
                if (blockIndex > 0)
                  textBlock = new TextLayoutBlock();

                symText.TextLayout.Blocks.Add(textBlock);
              }
              break;
            case "shield-face-name":
              SetFontName(textBlock, pv.Value);
              break;
            case "shield-file":
              gsImage.Path = ToPath(pv.Value);
              break;
            case "shield-text-transform":
              if (string.IsNullOrEmpty(textBlock.TextExpression))
                textTransform = pv.Value;
              else
                textBlock.TextExpression = GetTextTransform(textBlock.TextExpression, pv.Value);
              break;
            case "shield-fill":
              textBlock.TextFormat.TextStyle.Color = ColorUtility.FromHtml(pv.Value);
              break;
            case "shield-text-opacity":
              textBlock.TextFormat.TextStyle.Opacity = Convert.ToSingle(pv.Value);
              break;
            case "shield-opacity":
              gsImage.Opacity = Convert.ToSingle(pv.Value);
              break;
            case "shield-size":
              textBlock.TextFormat.TextStyle.Font.Size = Convert.ToSingle(pv.Value);
              break;
            case "shield-halo-radius":
              textBlock.TextFormat.TextStyle.Halo.Radius = Convert.ToSingle(pv.Value);
              break;
            case "shield-halo-fill":
              Color clr = ColorUtility.FromHtml(pv.Value);
              textBlock.TextFormat.TextStyle.Halo.Color = clr;
              if (clr.A != 255)
                textBlock.TextFormat.TextStyle.Halo.Opacity = clr.A / 255.0F;
              break;
            case "shield-halo-opacity":
              textBlock.TextFormat.TextStyle.Halo.Opacity = Convert.ToSingle(pv.Value);
              break;
            case "shield-clip":
              symText.Clip = Convert.ToBoolean(pv.Value);
              break;
            case "shield-horizontal-alignment":
              // TODO
              break;
            case "shield-vertical-alignment":
              // TODO
              break;
            case "shield-justify-alignment":
              // TODO
              break;
            case "shield-transform":
              //TODO
              break;
            case "shield-wrap-width":
              textBlock.TextFormat.TextWrapping.MaxWidth = Convert.ToUInt32(pv.Value);
              textBlock.TextFormat.TextWrapping.Mode = Drawing.Text.TextWrapMode.WrapByMaxWidthPixels;
              break;
            case "shield-wrap-before":
              TextWrapping tw = textBlock.TextFormat.TextWrapping;
              if (tw.Characters == null)
                tw.Characters = new WrapCharacter[] { new WrapCharacter() };

              tw.Characters[0].WrapType = CharacterWrapType.Before;
              break;
            case "shield-wrap-character":
              TextWrapping tw2 = textBlock.TextFormat.TextWrapping;
              if (tw2.Characters == null)
                tw2.Characters = new WrapCharacter[] { new WrapCharacter() };

              tw2.Characters[0].Character = pv.Value;
              break;
            case "shield-character-spacing":
              textBlock.TextFormat.TextSpacing.CharacterSpacing = Convert.ToSingle(pv.Value);
              break;
            case "shield-line-spacing":
              textBlock.TextFormat.TextSpacing.Leading = Convert.ToSingle(pv.Value);
              break;
            case "shield-allow-overlap":
              symText.LabelBehaviour.AllowOverlap = Convert.ToBoolean(pv.Value);
              break;
            case "shield-min-distance":
              symText.LabelBehaviour.CollisionMeasures.Add(string.Format("MinimumDistance({0})", pv.Value));
              break;
            case "shield-avoid-edges":
              symText.LabelBehaviour.AvoidEdges = Convert.ToBoolean(pv.Value);
              break;
            case "shield-spacing":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "shield-min-padding":
              UnsupportedProperty(pv);
              break;
            case "shield-placement-type":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "shield-placements":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "shield-text-dx":
              text_dx = Convert.ToSingle(pv.Value);
              break;
            case "shield-text-dy":
              text_dy = Convert.ToSingle(pv.Value);
              break;
            case "shield-dx":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "shield-dy":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "shield-comp-op":
              UnsupportedProperty(pv);
              break;
            default:
              break;
          }
        }
      }
      catch (Exception ex)
      {
        ThrowParsingException(ex, pv);
      }

      if (text_dx != 0F || text_dy != 0F)
        symText.Graphic.Size = new SizeF(text_dx, text_dy);

      ApplyTextBlockFormat(symText.TextLayout);
      symText.LabelPlacement = CreateLabelPlacement(lpi);
      if ("point".Equals(lpi.Placement))
        geomTrans.DisplacementX = geomTrans.DisplacementY = string.Empty;
      symText.GeometryExpression = ToGeometryExpression(geomTrans);

      return symText;
    }
    private TextSymbolizer CreateTextSymbolizer(NodePropertyValue[] properties)
    {
      TextSymbolizer symText = new TextSymbolizer();
      symText.Clip = true;
      symText.TextLayout.Alignment = TextAlignment.CenterAligned;

      GeometryTransformInfo geomTrans = new GeometryTransformInfo();
      LabelPlacementInfo lpi = new LabelPlacementInfo();
      lpi.Symbolizer = (int)SymbolizerType.Text;
      TextLayoutBlock textBlock = new TextLayoutBlock();
      string textTransform = null;
      int blockIndex = -1;
      int nProps = properties.Length;

      NodePropertyValue pv = null;

      try
      {
        for (int i = 0; i < nProps; i++)
        {
          pv = properties[i];

          switch (pv.Name)
          {
            case "text-name":
              if (pv.Value != null)
              {
                string textExpr = ToExpression(pv.Value);
                if (!string.IsNullOrEmpty(textTransform))
                  textExpr = GetTextTransform(textExpr, textTransform);
                textBlock.TextExpression = textExpr;

                // TODO
                if (textBlock.TextExpression == string.Empty)
                  symText.Enabled = false;

                blockIndex++;
                if (blockIndex > 0)
                  textBlock = new TextLayoutBlock();

                symText.TextLayout.Blocks.Add(textBlock);
              }
              break;
            case "text-face-name":
              SetFontName(textBlock, pv.Value);
              break;
            case "text-transform":
              if (string.IsNullOrEmpty(textBlock.TextExpression))
                textTransform = pv.Value;
              else
                textBlock.TextExpression = GetTextTransform(textBlock.TextExpression, pv.Value);
              break;
            case "text-fill":
              Color clr = ColorUtility.FromHtml(pv.Value);
              textBlock.TextFormat.TextStyle.Color = clr;
              if (clr.A != 255)
                textBlock.TextFormat.TextStyle.Opacity = clr.A / 255.0F;
              break;
            case "text-opacity":
              textBlock.TextFormat.TextStyle.Opacity = Convert.ToSingle(pv.Value);
              break;
            case "text-size":
              textBlock.TextFormat.TextStyle.Font.Size = Convert.ToSingle(pv.Value);
              break;
            case "text-halo-radius":
              textBlock.TextFormat.TextStyle.Halo.Radius = Convert.ToSingle(pv.Value);
              break;
            case "text-halo-fill":
              Color clr2 = ColorUtility.FromHtml(pv.Value);
              textBlock.TextFormat.TextStyle.Halo.Color = clr2;
              if (clr2.A != 255)
                textBlock.TextFormat.TextStyle.Halo.Opacity = clr2.A / 255F;
              break;
            case "text-halo-opacity": // This property does not exist in the specification.
              textBlock.TextFormat.TextStyle.Halo.Opacity = Convert.ToSingle(pv.Value);
              break;
            case "text-halo-rasterizer":
              // TODO
              break;
            case "text-ratio":
              //TODO
              break;
            case "text-clip":
              symText.Clip = Convert.ToBoolean(pv.Value);
              break;
            case "text-align":
              textBlock.TextFormat.TextAlignment = ToTextAlignment(pv.Value);
              break;
            case "text-horizontal-alignment":
              // TODO
              break;
            case "text-vertical-alignment":
              // TODO
              break;
            case "text-wrap-width":
              textBlock.TextFormat.TextWrapping.MaxWidth = Convert.ToUInt32(pv.Value);
              textBlock.TextFormat.TextWrapping.Mode = Drawing.Text.TextWrapMode.WrapByMaxWidthPixels;
              break;
            case "text-wrap-before":
              TextWrapping tw = textBlock.TextFormat.TextWrapping;
              if (tw.Characters == null || tw.Characters.Length == 0)
                tw.Characters = new WrapCharacter[] { new WrapCharacter() };
              tw.Characters[0].WrapType = CharacterWrapType.Before;
              break;
            case "text-wrap-character":
              TextWrapping tw2 = textBlock.TextFormat.TextWrapping;
              if (tw2.Characters == null || tw2.Characters.Length == 0)
                tw2.Characters = new WrapCharacter[] { new WrapCharacter() };
              tw2.Characters[0].Character = pv.Value;
              break;
            case "text-character-spacing":
              textBlock.TextFormat.TextSpacing.CharacterSpacing = Convert.ToSingle(pv.Value);
              break;
            case "text-line-spacing":
              textBlock.TextFormat.TextSpacing.Leading = Convert.ToSingle(pv.Value);
              break;
            case "text-allow-overlap":
              symText.LabelBehaviour.AllowOverlap = Convert.ToBoolean(pv.Value);
              break;
            case "text-min-distance":
              symText.LabelBehaviour.CollisionMeasures.Add(string.Format("MinimumDistance({0})", pv.Value));
              break;
            case "text-avoid-edges":
              symText.LabelBehaviour.AvoidEdges = Convert.ToBoolean(pv.Value);
              break;
            case "text-spacing":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-max-char-angle-delta":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-label-position-tolerance":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-min-padding":
              UnsupportedProperty(pv);
              break;
            case "text-min-path-length":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-orientation":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-placement":
              lpi.Placement = pv.Value;
              if (lpi.Placement == "line")
                geomTrans.OffsetCurve = true;
              break;
            case "text-placement-type":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-placements":
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-dx":
              geomTrans.DisplacementX = pv.Value;
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-dy":
              geomTrans.DisplacementY = pv.Value;
              lpi.Properties.Add(new KeyValuePair<string, string>(pv.Name, pv.Value));
              break;
            case "text-comp-op":
              UnsupportedProperty(pv);
              break;
            default:
              break;
          }
        }
      }
      catch (Exception ex)
      {
        ThrowParsingException(ex, pv);
      }

      ApplyTextBlockFormat(symText.TextLayout);
      symText.LabelPlacement = CreateLabelPlacement(lpi);
      if ("point".Equals(lpi.Placement))
        geomTrans.DisplacementX = geomTrans.DisplacementY = string.Empty;
      symText.GeometryExpression = ToGeometryExpression(geomTrans);

      return symText;
    }
    private void SetFontName(TextLayoutBlock textBlock, string value)
    {
      if (!string.IsNullOrEmpty(value))
      {
        string strFace = RemoveQuotes(value);
        if (this.FontSets != null)
        {
          FontSet fontSet = null;
          if (this.FontSets.TryGetValue(strFace, out fontSet))
          {
            textBlock.FontSetName = fontSet.Name;
            return;
          }
        }

        string[] fontParts = value.Split(new char[] { ' ' });

        string fontName = fontParts[0];
        FontStyles style = FontStyles.Normal;
        FontWeight weight = FontWeight.Normal;
        for (int i = 1; i < fontParts.Length; i++)
        {
          string part = fontParts[i];
          if (part.Equals("Oblique", StringComparison.OrdinalIgnoreCase))
            style = FontStyles.Oblique;
          else if (part.Equals("Italic", StringComparison.OrdinalIgnoreCase))
            style = FontStyles.Italic;
          else if (part.Equals("Bold", StringComparison.OrdinalIgnoreCase))
            weight = FontWeight.Bold;
          else
            fontName += " " + part;
        }

        textBlock.FontSetName = string.Empty;
        textBlock.TextFormat.TextStyle.Font.Name = fontName.Trim(new char[] { ' ' });
        textBlock.TextFormat.TextStyle.Font.Style = style;
        textBlock.TextFormat.TextStyle.Font.Weight = weight;
      }
    }