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(); }
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 (); }