static void PrepareGlyphArcInfo (CTLine line, long glyphCount, GlyphArcInfo[] glyphArcInfo) { var runArray = line.GetGlyphRuns (); // Examine each run in the line, updating glyphOffset to track how far along the run is // in terms of glyphCount. long glyphOffset = 0; nfloat ascent = 0; nfloat descent = 0; nfloat leading = 0; foreach (var run in runArray) { var runGlyphCount = run.GlyphCount; // Ask for the width of each glyph in turn. var runGlyphIndex = 0; for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) { glyphArcInfo [runGlyphIndex + glyphOffset].width = (float)run.GetTypographicBounds (new NSRange (runGlyphIndex, 1), out ascent, out descent, out leading); } glyphOffset += runGlyphCount; } var lineLength = line.GetTypographicBounds (out ascent, out descent, out leading); var prevHalfWidth = glyphArcInfo [0].width / 2.0; glyphArcInfo [0].angle = (float)((prevHalfWidth / lineLength) * Math.PI); var lineGlyphIndex = 1; for (; lineGlyphIndex < glyphCount; lineGlyphIndex++) { var halfWidth = glyphArcInfo [lineGlyphIndex].width / 2.0; var prevCenterToCenter = prevHalfWidth + halfWidth; glyphArcInfo [lineGlyphIndex].angle = (float)((prevCenterToCenter / lineLength) * Math.PI); prevHalfWidth = halfWidth; } }
partial void MeasureCharacterRangeImp(string str, Font font, StringFormat format, CharacterRange range, ref RectangleF ret) { var attributedString = CreateAttributedString(str, font, format, null); CTLine line = new CTLine(attributedString); CTRun[] runArray = line.GetGlyphRuns(); if (runArray.Length > 0) { context.TextPosition = new CGPoint(); ret = runArray[0].GetImageBounds(context, new NSRange(range.First, range.Length)).ToRectangle(); } }
public void GetBaseAdvancesAndOrigins() { TestRuntime.AssertXcodeVersion(11, 0); using (var attributedString = new NSAttributedString("Hello world.")) using (var line = new CTLine(attributedString)) { var runs = line.GetGlyphRuns(); Assert.That(runs.Length, Is.EqualTo(1), "runs"); runs [0].GetBaseAdvancesAndOrigins(new NSRange(0, 10), out var advances, out var origins); Assert.IsNotNull(advances, "advances"); Assert.IsNotNull(origins, "origins"); } }
static void PrepareGlyphArcInfo(CTLine line, long glyphCount, GlyphArcInfo[] glyphArcInfo) { var runArray = line.GetGlyphRuns(); // Examine each run in the line, updating glyphOffset to track how far along the run is // in terms of glyphCount. long glyphOffset = 0; nfloat ascent = 0; nfloat descent = 0; nfloat leading = 0; foreach (var run in runArray) { var runGlyphCount = run.GlyphCount; // Ask for the width of each glyph in turn. var runGlyphIndex = 0; for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) { glyphArcInfo[runGlyphIndex + glyphOffset].width = (float)run.GetTypographicBounds(new NSRange(runGlyphIndex, 1), out ascent, out descent, out leading); } glyphOffset += runGlyphCount; } var lineLength = line.GetTypographicBounds(out ascent, out descent, out leading); var prevHalfWidth = glyphArcInfo[0].width / 2.0; glyphArcInfo[0].angle = (float)((prevHalfWidth / lineLength) * Math.PI); var lineGlyphIndex = 1; for (; lineGlyphIndex < glyphCount; lineGlyphIndex++) { var halfWidth = glyphArcInfo[lineGlyphIndex].width / 2.0; var prevCenterToCenter = prevHalfWidth + halfWidth; glyphArcInfo[lineGlyphIndex].angle = (float)((prevCenterToCenter / lineLength) * Math.PI); prevHalfWidth = halfWidth; } }
public override void DrawRect(CGRect dirtyRect) { // Don't draw if we don't have a font or a title. if (Font == null || Title == string.Empty) { return; } // Initialize the text matrix to a known value CGContext context = NSGraphicsContext.CurrentContext.GraphicsPort; context.TextMatrix = CGAffineTransform.MakeIdentity(); // Draw a white background NSColor.White.Set(); context.FillRect(dirtyRect); CTLine line = new CTLine(AttributedString); int glyphCount = (int)line.GlyphCount; if (glyphCount == 0) { return; } GlyphArcInfo[] glyphArcInfo = new GlyphArcInfo[glyphCount]; PrepareGlyphArcInfo(line, glyphCount, glyphArcInfo); // Move the origin from the lower left of the view nearer to its center. context.SaveState(); context.TranslateCTM(dirtyRect.GetMidX(), dirtyRect.GetMidY() - Radius / 2); // Stroke the arc in red for verification. context.BeginPath(); context.AddArc(0, 0, Radius, (float)Math.PI, 0, true); context.SetStrokeColor(1, 0, 0, 1); context.StrokePath(); // Rotate the context 90 degrees counterclockwise. context.RotateCTM((float)PI_2); /* * Now for the actual drawing. The angle offset for each glyph relative to the previous * glyph has already been calculated; with that information in hand, draw those glyphs * overstruck and centered over one another, making sure to rotate the context after each * glyph so the glyphs are spread along a semicircular path. */ CGPoint textPosition = new CGPoint(0, Radius); context.TextPosition = textPosition; var runArray = line.GetGlyphRuns(); var runCount = runArray.Count(); var glyphOffset = 0; var runIndex = 0; for (; runIndex < runCount; runIndex++) { var run = runArray [runIndex]; var runGlyphCount = run.GlyphCount; bool drawSubstitutedGlyphsManually = false; CTFont runFont = run.GetAttributes().Font; // Determine if we need to draw substituted glyphs manually. Do so if the runFont is not // the same as the overall font. var description = NSFontDescriptor.FromNameSize(runFont.FamilyName, runFont.Size); NSFont rrunFont = NSFont.FromDescription(description, runFont.Size); // used for comparison if (DimsSubstitutedGlyphs && Font != rrunFont) { drawSubstitutedGlyphsManually = true; } var runGlyphIndex = 0; for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) { var glyphRange = new NSRange(runGlyphIndex, 1); context.RotateCTM(-(glyphArcInfo [runGlyphIndex + glyphOffset].angle)); // Center this glyph by moving left by half its width. var glyphWidth = glyphArcInfo [runGlyphIndex + glyphOffset].width; var halfGlyphWidth = glyphWidth / 2.0; var positionForThisGlyph = new CGPoint(textPosition.X - (float)halfGlyphWidth, textPosition.Y); // Glyphs are positioned relative to the text position for the line, so offset text position leftwards by this glyph's // width in preparation for the next glyph. textPosition.X -= glyphWidth; CGAffineTransform textMatrix = run.TextMatrix; textMatrix.x0 = positionForThisGlyph.X; textMatrix.y0 = positionForThisGlyph.Y; context.TextMatrix = textMatrix; if (!drawSubstitutedGlyphsManually) { run.Draw(context, glyphRange); } else { // We need to draw the glyphs manually in this case because we are effectively applying a graphics operation by // setting the context fill color. Normally we would use kCTForegroundColorAttributeName, but this does not apply // as we don't know the ranges for the colors in advance, and we wanted demonstrate how to manually draw. var cgFont = runFont.ToCGFont(); var glyph = run.GetGlyphs(glyphRange); var position = run.GetPositions(glyphRange); context.SetFont(cgFont); context.SetFontSize(runFont.Size); context.SetFillColor(0.25f, 0.25f, 0.25f, 1); context.ShowGlyphsAtPositions(glyph, position, 1); } // Draw the glyph bounds if (ShowsGlyphBounds) { var glyphBounds = run.GetImageBounds(context, glyphRange); context.SetStrokeColor(0, 0, 1, 1); context.StrokeRect(glyphBounds); } // Draw the bounding boxes defined by the line metrics if (ShowsLineMetrics) { var lineMetrics = new CGRect(); nfloat ascent = 0; nfloat descent = 0; nfloat leading = 0; run.GetTypographicBounds(glyphRange, out ascent, out descent, out leading); // The glyph is centered around the y-axis lineMetrics.Location = new CGPoint(-(float)halfGlyphWidth, positionForThisGlyph.Y - descent); lineMetrics.Size = new CGSize(glyphWidth, ascent + descent); context.SetStrokeColor(0, 1, 0, 1); context.StrokeRect(lineMetrics); } } glyphOffset += (int)runGlyphCount; } context.RestoreState(); }
/// <summary> /// Draws text along a given line. /// </summary> /// <param name="target"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="color"></param> /// <param name="size"></param> /// <param name="text">Text.</param> protected override void DrawLineText(Target2DWrapper <CGContextWrapper> target, double[] xa, double[] ya, string text, int color, double size, int?haloColor, int?haloRadius, string fontName) { float textSize = this.ToPixels(size) * _scaleFactor; // transform first. double[] xTransformed = new double[xa.Length]; double[] yTransformed = new double[ya.Length]; for (int idx = 0; idx < xa.Length; idx++) { double[] transformed = this.Transform(xa[idx], ya[idx]); xTransformed[idx] = transformed[0]; yTransformed[idx] = transformed[1]; } // set the fill color as the regular text-color. target.Target.CGContext.InterpolationQuality = CGInterpolationQuality.High; target.Target.CGContext.SetAllowsFontSubpixelQuantization(true); target.Target.CGContext.SetAllowsFontSmoothing(true); target.Target.CGContext.SetAllowsAntialiasing(true); target.Target.CGContext.SetAllowsSubpixelPositioning(true); target.Target.CGContext.SetShouldAntialias(true); // get the glyhps/paths from the font. CTFont font = this.GetFont(fontName, textSize); CTStringAttributes stringAttributes = new CTStringAttributes { ForegroundColorFromContext = true, Font = font }; NSAttributedString attributedString = new NSAttributedString(text, stringAttributes); CTLine line = new CTLine(attributedString); RectangleF textBounds = line.GetBounds(CTLineBoundsOptions.UseOpticalBounds); CTRun[] runs = line.GetGlyphRuns(); var lineLength = Polyline2D.Length(xTransformed, yTransformed); // set the correct tranformations to draw the resulting paths. target.Target.CGContext.SaveState(); //target.Target.CGContext.TranslateCTM (xPixels, yPixels); //target.Target.CGContext.ConcatCTM (new CGAffineTransform (1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f)); foreach (CTRun run in runs) { ushort[] glyphs = run.GetGlyphs(); PointF[] positions = run.GetPositions(); float[] characterWidths = new float[glyphs.Length]; float previous = 0; float textLength = (float)positions[positions.Length - 1].X; //float textLength = (float)this.FromPixels(_target, _view, positions [positions.Length - 1].X); if (lineLength > textLength * 1.2f) { for (int idx = 0; idx < characterWidths.Length - 1; idx++) { //characterWidths [idx] = (float)this.FromPixels(_target, _view, positions [idx + 1].X - previous); characterWidths[idx] = (float)(positions[idx + 1].X - previous); previous = positions[idx + 1].X; } characterWidths[characterWidths.Length - 1] = characterWidths[characterWidths.Length - 2]; float characterHeight = textBounds.Height; this.DrawLineTextSegment(target, xTransformed, yTransformed, glyphs, color, haloColor, haloRadius, lineLength / 2f, characterWidths, textLength, characterHeight, font); } } target.Target.CGContext.RestoreState(); }
/// <summary> /// Draws text. /// </summary> /// <param name="target"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="text"></param> /// <param name="size"></param> /// <param name="color">Color.</param> protected override void DrawText(Target2DWrapper <CGContextWrapper> target, double x, double y, string text, int color, double size, int?haloColor, int?haloRadius, string fontName) { double[] transformed = this.Transform(x, y); float xPixels = (float)transformed[0]; float yPixels = (float)transformed[1]; float textSize = this.ToPixels(size) * _scaleFactor; // get the glyhps/paths from the font. CTFont font = this.GetFont(fontName, textSize); CTStringAttributes stringAttributes = new CTStringAttributes { ForegroundColorFromContext = true, Font = font }; NSAttributedString attributedString = new NSAttributedString(text, stringAttributes); CTLine line = new CTLine(attributedString); CTRun[] runs = line.GetGlyphRuns(); // set the correct tranformations to draw the resulting paths. target.Target.CGContext.SaveState(); target.Target.CGContext.TranslateCTM(xPixels, yPixels); target.Target.CGContext.ConcatCTM(new CGAffineTransform(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f)); foreach (CTRun run in runs) { ushort[] glyphs = run.GetGlyphs(); PointF[] positions = run.GetPositions(); float previousOffset = 0; for (int idx = 0; idx < glyphs.Length; idx++) { CGPath path = font.GetPathForGlyph(glyphs[idx]); target.Target.CGContext.TranslateCTM(positions[idx].X - previousOffset, 0); previousOffset = positions[idx].X; if (haloRadius.HasValue && haloColor.HasValue) { // also draw the halo. using (CGPath haloPath = path.CopyByStrokingPath( haloRadius.Value * 2, CGLineCap.Round, CGLineJoin.Round, 0)) { SimpleColor haloSimpleColor = SimpleColor.FromArgb(haloColor.Value); target.Target.CGContext.SetFillColor(haloSimpleColor.R / 256.0f, haloSimpleColor.G / 256.0f, haloSimpleColor.B / 256.0f, haloSimpleColor.A / 256.0f); target.Target.CGContext.BeginPath(); target.Target.CGContext.AddPath(haloPath); target.Target.CGContext.DrawPath(CGPathDrawingMode.Fill); } } // set the fill color as the regular text-color. SimpleColor simpleColor = SimpleColor.FromArgb(color); target.Target.CGContext.SetFillColor(simpleColor.R / 256.0f, simpleColor.G / 256.0f, simpleColor.B / 256.0f, simpleColor.A / 256.0f); // draw the text paths. target.Target.CGContext.BeginPath(); target.Target.CGContext.AddPath(path); target.Target.CGContext.DrawPath(CGPathDrawingMode.Fill); } } target.Target.CGContext.RestoreState(); }
private List <CTLine> CreateLines(Font font, NSAttributedString attributedString, SizeF layoutBox, StringFormat format, float lineHeight) { bool noWrap = (format.FormatFlags & StringFormatFlags.NoWrap) != 0; bool wholeLines = (format.FormatFlags & StringFormatFlags.LineLimit) != 0; using (var typesetter = new CTTypesetter(attributedString)) { var lines = new List <CTLine>(); int start = 0; int length = (int)attributedString.Length; float y = 0; while (start < length && y < layoutBox.Height && (!wholeLines || y + lineHeight <= layoutBox.Height)) { if (format.Trimming != StringTrimming.None) { // Keep the last line in full when trimming is enabled bool lastLine; if (!wholeLines) { lastLine = y + lineHeight >= layoutBox.Height; } else { lastLine = y + lineHeight + lineHeight > layoutBox.Height; } if (lastLine) { noWrap = true; } } // Now we ask the typesetter to break off a line for us. // This also will take into account line feeds embedded in the text. // Example: "This is text \n with a line feed embedded inside it" var count = (int)typesetter.SuggestLineBreak(start, noWrap ? double.MaxValue : layoutBox.Width); var line = typesetter.GetLine(new NSRange(start, count)); // Note: trimming may return a null line i.e. not enough space for any characters var trim = line; switch (format.Trimming) { case StringTrimming.Character: trim = line.GetTruncatedLine(noWrap ? nfloat.MaxValue : layoutBox.Width, CTLineTruncation.End, null); break; case StringTrimming.EllipsisWord: // Fall thru for now case StringTrimming.EllipsisCharacter: using (CTLine ellipsisToken = EllipsisToken(font, format)) { // Trimming when not necessary causes bad results if (line.GetBounds(CTLineBoundsOptions.UseOpticalBounds).Width <= layoutBox.Width) { break; } trim = line.GetTruncatedLine(layoutBox.Width, CTLineTruncation.End, ellipsisToken); //Put back the first letter if we got ellipsis only. if (trim == null || trim.GlyphCount == 1 && trim.GetGlyphRuns()[0].GetGlyphs()[0] == ellipsisToken.GetGlyphRuns()[0].GetGlyphs()[0]) { var plain = attributedString.Value.Substring(0, 1) + "\u2026"; var attributed = buildAttributedString(plain, font, format, lastBrushColor); trim = new CTLine(attributed); } } break; case StringTrimming.EllipsisPath: using (CTLine ellipsisToken = EllipsisToken(font, format)) trim = line.GetTruncatedLine(layoutBox.Width, CTLineTruncation.Middle, ellipsisToken) ?? line; break; } if (trim != null) { lines.Add(trim); } if (line != trim) { line.Dispose(); } start += (int)count; y += (float)lineHeight; } return(lines); } }
void setupTextLayer(string l) { clearLayer(); var singleLetter = l.Length == 1; var fontSize = singleLetter ? letterFontSize : messageFontSize; var font = new CTFont(new CTFontDescriptor(new CTFontDescriptorAttributes { Name = UIFont.SystemFontOfSize(fontSize).Name, Size = (float?)fontSize }), fontSize); var attrStr = new NSAttributedString(l, singleLetter ? letterStringAttributes : messageStringAttributes); var line = new CTLine(attrStr); var runArray = line.GetGlyphRuns(); var letters = new CGPath(); for (int runIndex = 0; runIndex < runArray.Length; runIndex++) { var run = runArray [runIndex]; for (int runGlyphIndex = 0; runGlyphIndex < run.GlyphCount; runGlyphIndex++) { var thisGlyphRange = new NSRange(runGlyphIndex, 1); var glyph = run.GetGlyphs(thisGlyphRange).FirstOrDefault(); var position = run.GetPositions(thisGlyphRange).FirstOrDefault(); var letter = font.GetPathForGlyph(glyph); var t = CGAffineTransform.MakeTranslation(position.X, position.Y); if (letter != null) { letters.AddPath(t, letter); } } } var path = new UIBezierPath(); path.MoveTo(CGPoint.Empty); path.AppendPath(UIBezierPath.FromPath(letters)); var layer = new CAShapeLayer(); layer.Frame = new CGRect(((animationLayer.Bounds.Width - path.Bounds.Width) / 2) - 10, (animationLayer.Bounds.Height - path.Bounds.Height) / 2, path.Bounds.Width, path.Bounds.Height); layer.GeometryFlipped = true; layer.Path = path.CGPath; layer.StrokeColor = UIColor.Blue.CGColor; layer.FillColor = null; layer.LineWidth = 3; layer.LineJoin = CAShapeLayer.JoinBevel; animationLayer?.AddSublayer(layer); pathLayer = layer; }
public override void DrawRect (CGRect dirtyRect) { // Don't draw if we don't have a font or a title. if (Font == null || Title == string.Empty) return; // Initialize the text matrix to a known value CGContext context = NSGraphicsContext.CurrentContext.GraphicsPort; context.TextMatrix = CGAffineTransform.MakeIdentity (); // Draw a white background NSColor.White.Set (); context.FillRect (dirtyRect); CTLine line = new CTLine (AttributedString); int glyphCount = (int)line.GlyphCount; if (glyphCount == 0) return; GlyphArcInfo[] glyphArcInfo = new GlyphArcInfo[glyphCount]; PrepareGlyphArcInfo (line, glyphCount, glyphArcInfo); // Move the origin from the lower left of the view nearer to its center. context.SaveState (); context.TranslateCTM (dirtyRect.GetMidX (), dirtyRect.GetMidY () - Radius / 2); // Stroke the arc in red for verification. context.BeginPath (); context.AddArc (0, 0, Radius, (float)Math.PI, 0, true); context.SetStrokeColor (1, 0, 0, 1); context.StrokePath (); // Rotate the context 90 degrees counterclockwise. context.RotateCTM ((float)PI_2); /* Now for the actual drawing. The angle offset for each glyph relative to the previous glyph has already been calculated; with that information in hand, draw those glyphs overstruck and centered over one another, making sure to rotate the context after each glyph so the glyphs are spread along a semicircular path. */ CGPoint textPosition = new CGPoint (0, Radius); context.TextPosition = textPosition; var runArray = line.GetGlyphRuns (); var runCount = runArray.Count (); var glyphOffset = 0; var runIndex = 0; for (; runIndex < runCount; runIndex++) { var run = runArray [runIndex]; var runGlyphCount = run.GlyphCount; bool drawSubstitutedGlyphsManually = false; CTFont runFont = run.GetAttributes ().Font; // Determine if we need to draw substituted glyphs manually. Do so if the runFont is not // the same as the overall font. var description = NSFontDescriptor.FromNameSize (runFont.FamilyName, runFont.Size); NSFont rrunFont = NSFont.FromDescription (description, runFont.Size); // used for comparison if (DimsSubstitutedGlyphs && Font != rrunFont) { drawSubstitutedGlyphsManually = true; } var runGlyphIndex = 0; for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) { var glyphRange = new NSRange (runGlyphIndex, 1); context.RotateCTM (-(glyphArcInfo [runGlyphIndex + glyphOffset].angle)); // Center this glyph by moving left by half its width. var glyphWidth = glyphArcInfo [runGlyphIndex + glyphOffset].width; var halfGlyphWidth = glyphWidth / 2.0; var positionForThisGlyph = new CGPoint (textPosition.X - (float)halfGlyphWidth, textPosition.Y); // Glyphs are positioned relative to the text position for the line, so offset text position leftwards by this glyph's // width in preparation for the next glyph. textPosition.X -= glyphWidth; CGAffineTransform textMatrix = run.TextMatrix; textMatrix.x0 = positionForThisGlyph.X; textMatrix.y0 = positionForThisGlyph.Y; context.TextMatrix = textMatrix; if (!drawSubstitutedGlyphsManually) { run.Draw (context, glyphRange); } else { // We need to draw the glyphs manually in this case because we are effectively applying a graphics operation by // setting the context fill color. Normally we would use kCTForegroundColorAttributeName, but this does not apply // as we don't know the ranges for the colors in advance, and we wanted demonstrate how to manually draw. var cgFont = runFont.ToCGFont (); var glyph = run.GetGlyphs (glyphRange); var position = run.GetPositions (glyphRange); context.SetFont (cgFont); context.SetFontSize (runFont.Size); context.SetFillColor (0.25f, 0.25f, 0.25f, 1); context.ShowGlyphsAtPositions (glyph, position, 1); } // Draw the glyph bounds if (ShowsGlyphBounds) { var glyphBounds = run.GetImageBounds (context, glyphRange); context.SetStrokeColor (0, 0, 1, 1); context.StrokeRect (glyphBounds); } // Draw the bounding boxes defined by the line metrics if (ShowsLineMetrics) { var lineMetrics = new CGRect (); nfloat ascent = 0; nfloat descent = 0; nfloat leading = 0; run.GetTypographicBounds (glyphRange, out ascent, out descent, out leading); // The glyph is centered around the y-axis lineMetrics.Location = new CGPoint (-(float)halfGlyphWidth, positionForThisGlyph.Y - descent); lineMetrics.Size = new CGSize (glyphWidth, ascent + descent); context.SetStrokeColor (0, 1, 0, 1); context.StrokeRect (lineMetrics); } } glyphOffset += (int)runGlyphCount; } context.RestoreState (); }
/// <summary> /// Draws text along a given line. /// </summary> /// <param name="target"></param> /// <param name="x"></param> /// <param name="y"></param> /// <param name="color"></param> /// <param name="size"></param> /// <param name="text">Text.</param> protected override void DrawLineText(Target2DWrapper <CGContextWrapper> target, double[] x, double[] y, string text, int color, double size, int?haloColor, int?haloRadius, string fontName) { double[] transformed = this.Tranform(x[0], y[0]); float xPixels = (float)transformed[0]; float yPixels = (float)transformed[1]; float textSize = this.ToPixels(size); // set the fill color as the regular text-color. SimpleColor simpleColor = SimpleColor.FromArgb(color); target.Target.CGContext.InterpolationQuality = CGInterpolationQuality.High; target.Target.CGContext.SetAllowsFontSubpixelQuantization(true); target.Target.CGContext.SetAllowsFontSmoothing(true); target.Target.CGContext.SetFillColor(simpleColor.R / 256.0f, simpleColor.G / 256.0f, simpleColor.B / 256.0f, simpleColor.A / 256.0f); if (haloColor.HasValue) // set the stroke color as the halo color. { SimpleColor haloSimpleColor = SimpleColor.FromArgb(haloColor.Value); target.Target.CGContext.SetStrokeColor(haloSimpleColor.R / 256.0f, haloSimpleColor.G / 256.0f, haloSimpleColor.B / 256.0f, haloSimpleColor.A / 256.0f); } if (haloRadius.HasValue) // set the halo radius as line width. { target.Target.CGContext.SetLineWidth(haloRadius.Value); } // get the glyhps/paths from the font. if (string.IsNullOrWhiteSpace(fontName)) { fontName = "Arial"; } CTFont font = new CTFont(fontName, textSize); CTStringAttributes stringAttributes = new CTStringAttributes { ForegroundColorFromContext = true, Font = font }; NSAttributedString attributedString = new NSAttributedString(text, stringAttributes); CTLine line = new CTLine(attributedString); RectangleF textBounds = line.GetBounds(CTLineBoundsOptions.UseOpticalBounds); CTRun[] runs = line.GetGlyphRuns(); var lineLength = Polyline2D.Length(x, y); // set the correct tranformations to draw the resulting paths. target.Target.CGContext.SaveState(); //target.Target.CGContext.TranslateCTM (xPixels, yPixels); //target.Target.CGContext.ConcatCTM (new CGAffineTransform (1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f)); foreach (CTRun run in runs) { ushort[] glyphs = run.GetGlyphs(); PointF[] positions = run.GetPositions(); float[] characterWidths = new float[glyphs.Length]; float previous = 0; float textLength = (float)this.FromPixels(_target, _view, positions [positions.Length - 1].X); if (lineLength > textLength * 1.2f) { for (int idx = 0; idx < characterWidths.Length - 1; idx++) { characterWidths [idx] = (float)this.FromPixels(_target, _view, positions [idx + 1].X - previous); previous = positions [idx + 1].X; } characterWidths [characterWidths.Length - 1] = characterWidths[characterWidths.Length - 2]; float characterHeight = textBounds.Height; this.DrawLineTextSegment(target, x, y, glyphs, color, haloColor, haloRadius, lineLength / 2f, characterWidths, textLength, characterHeight, font); } } target.Target.CGContext.RestoreState(); }