public void DrawString(string s, Font font, Brush brush, RectangleF layoutRectangle, StringFormat format = null) { if (font == null) throw new ArgumentNullException ("font"); if (brush == null) throw new ArgumentNullException ("brush"); if (s == null || s.Length == 0) return; if (format == null) { format = StringFormat.GenericDefault; } // TODO: Take into consideration units // Not sure we need the Save and Restore around this yet. context.SaveState(); // TextMatrix is not part of the Graphics State and Restore var saveMatrix = context.TextMatrix; bool layoutAvailable = true; // context.SelectFont ( font.nativeFont.PostScriptName, // font.SizeInPoints, // CGTextEncoding.MacRoman); // // context.SetCharacterSpacing(1); // context.SetTextDrawingMode(CGTextDrawingMode.Fill); // 5 // // // Setup both the stroke and the fill ? // brush.Setup(this, true); // brush.Setup(this, false); // // var textMatrix = font.nativeFont.Matrix; // // textMatrix.Scale(1,-1); // context.TextMatrix = textMatrix; // // context.ShowTextAtPoint(layoutRectangle.X, // layoutRectangle.Y + font.nativeFont.CapHeightMetric, s); // // // First we call the brush with a fill of false so the brush can setup the stroke color // that the text will be using. // For LinearGradientBrush this will setup a TransparentLayer so the gradient can // be filled at the end. See comments. brush.Setup(this, false); // Stroke // I think we only Fill the text with no Stroke surrounding context.SetTextDrawingMode(CGTextDrawingMode.Fill); var attributedString = buildAttributedString(s, font, format, lastBrushColor); // Work out the geometry RectangleF insetBounds = layoutRectangle; if (insetBounds.Size == SizeF.Empty) { insetBounds.Width = boundingBox.Width; insetBounds.Height = boundingBox.Height; layoutAvailable = false; } PointF textPosition = new PointF(insetBounds.X, insetBounds.Y); float boundsWidth = insetBounds.Width; // Calculate the lines int start = 0; int length = attributedString.Length; float baselineOffset = 0; var typesetter = new CTTypesetter(attributedString); // First we need to calculate the offset for Vertical Alignment if we // are using anything but Top if (layoutAvailable && format.LineAlignment != StringAlignment.Near) { while (start < length) { int count = typesetter.SuggestLineBreak (start, boundsWidth); var line = typesetter.GetLine (new NSRange(start, count)); // Create and initialize some values from the bounds. float ascent; float descent; float leading; line.GetTypographicBounds (out ascent, out descent, out leading); baselineOffset += (float)Math.Ceiling (ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose (); start += count; } //textPosition.Y += baselineOffset; } // If we are drawing vertial direction then we need to rotate our context transform by 90 degrees if ((format.FormatFlags & StringFormatFlags.DirectionVertical) == StringFormatFlags.DirectionVertical) { //textMatrix.Rotate (ConversionHelpers.DegreesToRadians (90)); var verticalOffset = 0.0f; while (start < length) { int count = typesetter.SuggestLineBreak (start, boundsWidth); var line = typesetter.GetLine (new NSRange(start, count)); // Create and initialize some values from the bounds. float ascent; float descent; float leading; line.GetTypographicBounds (out ascent, out descent, out leading); verticalOffset += (float)Math.Ceiling (ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose (); start += count; } context.TranslateCTM (layoutRectangle.X, layoutRectangle.Y); context.RotateCTM (ConversionHelpers.DegreesToRadians (90)); context.TranslateCTM (-layoutRectangle.X, -layoutRectangle.Y); context.TranslateCTM (0, -verticalOffset); start = 0; } start = 0; while (start < length && textPosition.Y < insetBounds.Bottom) { // 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" int count = typesetter.SuggestLineBreak(start, boundsWidth); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. float ascent; float descent; float leading; double lineWidth = line.GetTypographicBounds(out ascent, out descent, out leading); // Calculate the string format if need be var penFlushness = 0.0f; if (format != null) { if (layoutAvailable) { if (format.Alignment == StringAlignment.Far) penFlushness = (float)line.GetPenOffsetForFlush(1.0f, boundsWidth); else if (format.Alignment == StringAlignment.Center) penFlushness = (float)line.GetPenOffsetForFlush(0.5f, boundsWidth); } else { // We were only passed in a point so we need to format based // on the point. if (format.Alignment == StringAlignment.Far) penFlushness -= (float)lineWidth; else if (format.Alignment == StringAlignment.Center) penFlushness -= (float)lineWidth / 2.0f; } } // initialize our Text Matrix or we could get trash in here var textMatrix = new CGAffineTransform ( 1, 0, 0, -1, 0, ascent); if (format.LineAlignment == StringAlignment.Near) textMatrix.Translate (penFlushness + textPosition.X, textPosition.Y); if (format.LineAlignment == StringAlignment.Center) textMatrix.Translate (penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height / 2) - (baselineOffset / 2)) ); if (format.LineAlignment == StringAlignment.Far) textMatrix.Translate(penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height) - (baselineOffset))); context.TextMatrix = textMatrix; // and draw the line line.Draw(context); // Move the index beyond the line break. start += count; textPosition.Y += (float)Math.Ceiling(ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose(); } // Now we call the brush with a fill of true so the brush can do the fill if need be // For LinearGradientBrush this will draw the Gradient and end the TransparentLayer. // See comments. brush.Setup(this, true); // Fill context.TextMatrix = saveMatrix; context.RestoreState(); }
internal void NativeDrawString(string s, Font font, Color brush, RectangleF layoutRectangle, StringFormat stringFormat) { if (font == null) throw new ArgumentNullException ("font"); if (s == null || s.Length == 0) return; var attributedString = buildAttributedString(s, font, brush); // Work out the geometry RectangleF insetBounds = layoutRectangle; bool layoutAvailable = true; if (insetBounds.Size == SizeF.Empty) { insetBounds.Size = new SizeF (8388608, 8388608); layoutAvailable = false; } PointF textPosition = new PointF(insetBounds.X, insetBounds.Y); float boundsWidth = insetBounds.Width; // Calculate the lines int start = 0; int length = attributedString.Length; var typesetter = new CTTypesetter(attributedString); float baselineOffset = 0; // First we need to calculate the offset for Vertical Alignment if we // are using anything but Top if (layoutAvailable && stringFormat.LineAlignment != StringAlignment.Near) { while (start < length) { int count = typesetter.SuggestLineBreak (start, boundsWidth); var line = typesetter.GetLine (new NSRange(start, count)); // Create and initialize some values from the bounds. float ascent; float descent; float leading; line.GetTypographicBounds (out ascent, out descent, out leading); baselineOffset += (float)Math.Ceiling (ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose (); start += count; } } start = 0; while (start < length && textPosition.Y < insetBounds.Bottom) { // 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" int count = typesetter.SuggestLineBreak(start, boundsWidth); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. float ascent; float descent; float leading; double lineWidth = line.GetTypographicBounds(out ascent, out descent, out leading); if (!layoutAvailable) { insetBounds.Width = (float)lineWidth; insetBounds.Height = ascent + descent + leading; } // Calculate the string format if need be var penFlushness = 0.0f; if (stringFormat.Alignment == StringAlignment.Far) penFlushness = (float)line.GetPenOffsetForFlush(1.0f, insetBounds.Width); else if (stringFormat.Alignment == StringAlignment.Center) penFlushness = (float)line.GetPenOffsetForFlush(0.5f, insetBounds.Width); // initialize our Text Matrix or we could get trash in here var textMatrix = new CGAffineTransform ( 1, 0, 0, -1, 0, ascent); if (stringFormat.LineAlignment == StringAlignment.Near) textMatrix.Translate (penFlushness + textPosition.X, textPosition.Y); //insetBounds.Height - textPosition.Y -(float)Math.Floor(ascent - 1)); if (stringFormat.LineAlignment == StringAlignment.Center) textMatrix.Translate (penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height / 2) - (baselineOffset / 2)) ); // -(float)Math.Floor(ascent) if (stringFormat.LineAlignment == StringAlignment.Far) textMatrix.Translate(penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height) - (baselineOffset))); var glyphRuns = line.GetGlyphRuns (); for (int glyphRunIndex = 0; glyphRunIndex < glyphRuns.Length; glyphRunIndex++) { var glyphRun = glyphRuns [glyphRunIndex]; var glyphs = glyphRun.GetGlyphs (); var glyphPositions = glyphRun.GetPositions (); //var textMatrix = glyphRun.TextMatrix; // Create and initialize some values from the bounds. float glyphAscent; float glyphDescent; float glyphLeading; var elementPoints = new PointF[3]; for (int glyphIndex = 0; glyphIndex < glyphs.Length; glyphIndex++) { if (glyphIndex > 0) { textMatrix.x0 += glyphPositions [glyphIndex].X - glyphPositions[glyphIndex - 1].X; textMatrix.y0 += glyphPositions [glyphIndex].Y - glyphPositions[glyphIndex - 1].Y; } var glyphPath = font.nativeFont.GetPathForGlyph (glyphs [glyphIndex]); // glyphPath = null if it is a white space character if (glyphPath != null) { glyphPath.Apply ( delegate (CGPathElement pathElement) { elementPoints[0] = textMatrix.TransformPoint(pathElement.Point1); elementPoints[1] = textMatrix.TransformPoint(pathElement.Point2); elementPoints[2] = textMatrix.TransformPoint(pathElement.Point3); //Console.WriteLine ("Applying {0} - {1}, {2}, {3}", pathElement.Type, elementPoints[0], elementPoints[1], elementPoints[2]); // now add position offsets switch(pathElement.Type) { case CGPathElementType.MoveToPoint: start_new_fig = true; Append(elementPoints[0].X, elementPoints[0].Y,PathPointType.Line,true); break; case CGPathElementType.AddLineToPoint: var lastPoint = points[points.Count - 1]; AppendPoint(lastPoint, PathPointType.Line, false); AppendPoint(elementPoints[0], PathPointType.Line, false); break; case CGPathElementType.AddCurveToPoint: case CGPathElementType.AddQuadCurveToPoint: // This is the only thing I can think of right now for the fonts that // I have tested. See the description of the quadraticToCubic method for // more information // Get the last point var pt1 = points[points.Count - 1]; var pt2 = PointF.Empty; var pt3 = PointF.Empty; var pt4 = elementPoints[1]; GeomUtilities.QuadraticToCubic(pt1, elementPoints[0], elementPoints[1], out pt2, out pt3); Append (pt1.X, pt1.Y, PathPointType.Line, true); AppendBezier (pt2.X, pt2.Y, pt3.X, pt3.Y, pt4.X, pt4.Y); break; case CGPathElementType.CloseSubpath: CloseFigure(); break; } } ); } } } // Move the index beyond the line break. start += count; textPosition.Y += (float)Math.Ceiling(ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose(); } }
internal static void NativeDrawString (CGBitmapContext bitmapContext, string s, CTFont font, CCColor4B brush, CGRect layoutRectangle) { if (font == null) throw new ArgumentNullException ("font"); if (s == null || s.Length == 0) return; bitmapContext.ConcatCTM (bitmapContext.GetCTM().Invert()); // This is not needed here since the color is set in the attributed string. //bitmapContext.SetFillColor(brush.R/255f, brush.G/255f, brush.B/255f, brush.A/255f); // I think we only Fill the text with no Stroke surrounding //bitmapContext.SetTextDrawingMode(CGTextDrawingMode.Fill); var attributedString = buildAttributedString(s, font, brush); // Work out the geometry CGRect insetBounds = layoutRectangle; CGPoint textPosition = new CGPoint(insetBounds.X, insetBounds.Y); float boundsWidth = (float)insetBounds.Width; // Calculate the lines nint start = 0; nint length = attributedString.Length; var typesetter = new CTTypesetter(attributedString); float baselineOffset = 0; // First we need to calculate the offset for Vertical Alignment if we // are using anything but Top if (vertical != CCVerticalTextAlignment.Top) { while (start < length) { nint count = typesetter.SuggestLineBreak((int)start, (double)boundsWidth); var line = typesetter.GetLine (new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat leading; line.GetTypographicBounds(out ascent, out descent, out leading); baselineOffset += (float)Math.Ceiling ((float)(ascent + descent + leading + 1)); // +1 matches best to CTFramesetter's behavior line.Dispose (); start += count; } } start = 0; while (start < length && textPosition.Y < insetBounds.Bottom) { // 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" nint count = typesetter.SuggestLineBreak((int)start, (double)boundsWidth); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat leading; line.GetTypographicBounds(out ascent, out descent, out leading); // Calculate the string format if need be var penFlushness = 0.0f; if (horizontal == CCTextAlignment.Right) penFlushness = (float)line.GetPenOffsetForFlush(1.0f, boundsWidth); else if (horizontal == CCTextAlignment.Center) penFlushness = (float)line.GetPenOffsetForFlush(0.5f, boundsWidth); // initialize our Text Matrix or we could get trash in here var textMatrix = CGAffineTransform.MakeIdentity(); if (vertical == CCVerticalTextAlignment.Top) textMatrix.Translate(penFlushness, insetBounds.Height - textPosition.Y -(float)Math.Floor(ascent - 1)); if (vertical == CCVerticalTextAlignment.Center) textMatrix.Translate(penFlushness, ((insetBounds.Height / 2) + (baselineOffset / 2)) - textPosition.Y -(float)Math.Floor(ascent - 1)); if (vertical == CCVerticalTextAlignment.Bottom) textMatrix.Translate(penFlushness, baselineOffset - textPosition.Y -(float)Math.Floor(ascent - 1)); // Set our matrix bitmapContext.TextMatrix = textMatrix; // and draw the line line.Draw(bitmapContext); // Move the index beyond the line break. start += count; textPosition.Y += (float)Math.Ceiling(ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose(); } }