static CTLine GetLabel(string label, CGColor foregroundColor, out CGSize bounds) { // I seriously have no idea what this is other than 8388608/1024/1024=8 -abock const int undocumentedKennethConstantOnlyHeUnderstands = 8388608; var typesetter = new CTTypesetter( new NSAttributedString( label, new CTStringAttributes { Font = new CTFont(LabelFontBold.FontName, LabelFontBold.PointSize), ForegroundColor = foregroundColor }.Dictionary ) ); var lineRange = new NSRange(0, typesetter.SuggestLineBreak(0, undocumentedKennethConstantOnlyHeUnderstands)); var line = typesetter.GetLine(lineRange); nfloat ascent; nfloat descent; nfloat leading; var lineWidth = line.GetTypographicBounds(out ascent, out descent, out leading); bounds = new CGSize( // +1 matches best to CTFramesetter's behavior (nfloat)Math.Ceiling(ascent + descent + leading + 1), (nfloat)lineWidth ); return(line); }
public SizeF MeasureString(string textg, Font font, RectangleF rect) { // As per documentation // https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_text/dq_text.html#//apple_ref/doc/uid/TP30001066-CH213-TPXREF101 // // * Note * Not sure if we should save off the graphic state, set context transform to identity // and restore state to do the measurement. Something to be looked into. // context.TextPosition = rect.Location; // var startPos = context.TextPosition; // context.SelectFont ( font.nativeFont.PostScriptName, // font.SizeInPoints, // CGTextEncoding.MacRoman); // context.SetTextDrawingMode(CGTextDrawingMode.Invisible); // // context.SetCharacterSpacing(1); // var textMatrix = CGAffineTransform.MakeScale(1f,-1f); // context.TextMatrix = textMatrix; // // context.ShowTextAtPoint(rect.X, rect.Y, textg, textg.Length); // var endPos = context.TextPosition; // // var measure = new SizeF(endPos.X - startPos.X, font.nativeFont.CapHeightMetric); var atts = buildAttributedString(textg, font); var measure = SizeF.Empty; // Calculate the lines int start = 0; int length = (int)atts.Length; var typesetter = new CTTypesetter(atts); while (start < length) { int count = (int)typesetter.SuggestLineBreak(start, 8388608); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat leading; var lineWidth = line.GetTypographicBounds(out ascent, out descent, out leading); measure.Height += (float)Math.Ceiling(ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior if (lineWidth > measure.Width) { measure.Width = (float)lineWidth; } line.Dispose(); start += count; } return(measure); }
public static XIR.Image RemoteRepresentation(this NSAttributedString attributedString) { var typesetter = new CTTypesetter(attributedString); var measure = CGSize.Empty; var count = typesetter.SuggestLineBreak(0, 8388608); var line = typesetter.GetLine(new NSRange(0, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat leading; var lineWidth = line.GetTypographicBounds(out ascent, out descent, out leading); measure.Height += (float)Math.Ceiling(ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior measure.Width = (float)lineWidth; var width = (int)measure.Width > 0 ? (int)measure.Width : 200; var height = (int)measure.Height > 0 ? (int)measure.Height : 200; var bytesPerRow = width * 4; using (var context = new CGBitmapContext( IntPtr.Zero, width, height, 8, bytesPerRow, CGColorSpace.CreateDeviceRGB(), CGImageAlphaInfo.PremultipliedFirst)) { context.ConcatCTM(context.GetCTM().Invert()); var matrix = new CGAffineTransform( 1, 0, 0, -1, 0, height); context.ConcatCTM(matrix); var textMatrix = new CGAffineTransform( 1, 0, 0, -1, 0, ascent); context.TextMatrix = textMatrix; line.Draw(context); line.Dispose(); return(RemoteRepresentation(context)); } }
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); // Note: trimming may return a null line i.e. not enough space for any characters var line = typesetter.GetLine(new NSRange(start, count)); switch (format.Trimming) { case StringTrimming.Character: using (var oldLine = line) line = line.GetTruncatedLine(noWrap ? nfloat.MaxValue : layoutBox.Width, CTLineTruncation.End, null); break; case StringTrimming.EllipsisWord: // Fall thru for now case StringTrimming.EllipsisCharacter: using (var oldLine = line) using (CTLine ellipsisToken = EllipsisToken(font, format)) line = line.GetTruncatedLine(layoutBox.Width, CTLineTruncation.End, ellipsisToken); break; case StringTrimming.EllipsisPath: using (var oldLine = line) using (CTLine ellipsisToken = EllipsisToken(font, format)) line = line.GetTruncatedLine(layoutBox.Width, CTLineTruncation.Middle, ellipsisToken); break; } lines.Add(line); start += (int)count; y += (float)lineHeight; } return(lines); } }
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 = (int)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 = (int)typesetter.SuggestLineBreak(start, 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(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 = (int)typesetter.SuggestLineBreak(start, boundsWidth); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat leading; double lineWidth = line.GetTypographicBounds(out ascent, out descent, out leading); if (!layoutAvailable) { insetBounds.Width = (float)lineWidth; insetBounds.Height = (float)(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).ToPointF(); elementPoints[1] = textMatrix.TransformPoint(pathElement.Point2).ToPointF(); elementPoints[2] = textMatrix.TransformPoint(pathElement.Point3).ToPointF(); //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(); } }
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; if (format.LineAlignment != StringAlignment.Near) { insetBounds.Size = MeasureString(s, font); } } PointF textPosition = new PointF(insetBounds.X, insetBounds.Y); float boundsWidth = insetBounds.Width; // Calculate the lines int start = 0; int length = (int)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 = (int)typesetter.SuggestLineBreak(start, 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(ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose(); start += count; } start = 0; } // 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 = (int)typesetter.SuggestLineBreak(start, 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); 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 = (int)typesetter.SuggestLineBreak(start, boundsWidth); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat 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) { if (layoutAvailable) { textMatrix.Translate(penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height / 2) - (baselineOffset / 2))); } else { textMatrix.Translate(penFlushness + textPosition.X, textPosition.Y - ((insetBounds.Height / 2) - (baselineOffset / 2))); } } if (format.LineAlignment == StringAlignment.Far) { if (layoutAvailable) { textMatrix.Translate(penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height) - (baselineOffset))); } else { 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(); }
public override void Draw (RectangleF rect) { // Initialize a graphics context and set the text matrix to a known value var context = UIGraphics.GetCurrentContext (); context.TextMatrix = CGAffineTransform.MakeScale (1f, -1f); var attrString = new NSAttributedString ("This is a long string. You will only see half"); var typesetter = new CTTypesetter (attrString); // Find a break for line from the beginning of the string to the given width int start = 0; var width = Frame.Width / 2; Console.WriteLine ("# ManualBreak: width={0}", width); width = 125; var count = typesetter.SuggestLineBreak (start, width); Console.WriteLine ("# ManualBreak: count={0}", count); // use the character count (to the break) to create the line: using (var line = typesetter.GetLine (new NSRange (start, count))) { // Get the offset needed to center the line: var flush = 0.5f; // centered var penOffset = line.GetPenOffsetForFlush (flush, Frame.Width); Console.WriteLine ("# ManualBreak: pen offset={0}", penOffset); // Move the given text drawing position by the calculated offset and draw var curPosition = context.TextPosition; Console.WriteLine ("# ManualBreak: curPosition.X={0}", curPosition.X); context.TextPosition = new PointF ((float) penOffset, base.Center.Y); line.Draw (context); // move the index beyond the line break; why? start += count; } }
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; if (format.LineAlignment != StringAlignment.Near) { insetBounds.Size = MeasureString (s, font); } } PointF textPosition = new PointF(insetBounds.X, insetBounds.Y); float boundsWidth = insetBounds.Width; // Calculate the lines int start = 0; int length = (int)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 = (int)typesetter.SuggestLineBreak (start, 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 (ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior line.Dispose (); start += count; } start = 0; } // 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 = (int)typesetter.SuggestLineBreak (start, 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); 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 = (int)typesetter.SuggestLineBreak(start, boundsWidth); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat 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) { if (layoutAvailable) textMatrix.Translate (penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height / 2) - (baselineOffset / 2))); else textMatrix.Translate (penFlushness + textPosition.X, textPosition.Y - ((insetBounds.Height / 2) - (baselineOffset / 2))); } if (format.LineAlignment == StringAlignment.Far) { if (layoutAvailable) textMatrix.Translate (penFlushness + textPosition.X, textPosition.Y + ((insetBounds.Height) - (baselineOffset))); else 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(); }
public SizeF MeasureString(string textg, Font font, RectangleF rect) { // As per documentation // https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_text/dq_text.html#//apple_ref/doc/uid/TP30001066-CH213-TPXREF101 // // * Note * Not sure if we should save off the graphic state, set context transform to identity // and restore state to do the measurement. Something to be looked into. // context.TextPosition = rect.Location; // var startPos = context.TextPosition; // context.SelectFont ( font.nativeFont.PostScriptName, // font.SizeInPoints, // CGTextEncoding.MacRoman); // context.SetTextDrawingMode(CGTextDrawingMode.Invisible); // // context.SetCharacterSpacing(1); // var textMatrix = CGAffineTransform.MakeScale(1f,-1f); // context.TextMatrix = textMatrix; // // context.ShowTextAtPoint(rect.X, rect.Y, textg, textg.Length); // var endPos = context.TextPosition; // // var measure = new SizeF(endPos.X - startPos.X, font.nativeFont.CapHeightMetric); var atts = buildAttributedString(textg, font); var measure = SizeF.Empty; // Calculate the lines int start = 0; int length = (int)atts.Length; var typesetter = new CTTypesetter(atts); while (start < length) { int count = (int)typesetter.SuggestLineBreak (start, 8388608); var line = typesetter.GetLine (new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat leading; var lineWidth = line.GetTypographicBounds (out ascent, out descent, out leading); measure.Height += (float)Math.Ceiling (ascent + descent + leading + 1); // +1 matches best to CTFramesetter's behavior if (lineWidth > measure.Width) measure.Width = (float)lineWidth; line.Dispose (); start += count; } return measure; }
internal static void NativeDrawString(CGBitmapContext bitmapContext, string s, CTFont font, CCColor4B brush, RectangleF 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 RectangleF insetBounds = layoutRectangle; 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 (vertical != CCVerticalTextAlignment.Top) { 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; 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(); } }
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); } }
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; } // 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, 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; var typesetter = new CTTypesetter(attributedString); 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 = CGAffineTransform.MakeIdentity(); // flip us over or things just do not look good textMatrix.Scale(1, -1); context.TextMatrix = textMatrix; // move us to our graphics baseline var textViewPos = textPosition; textViewPos.Y += (float)Math.Floor(ascent - 1); // take into account our justification textViewPos.X += penFlushness; // setup our text position context.TextPosition = textViewPos; // 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 = (int)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 = (int)typesetter.SuggestLineBreak (start, 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 (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 = (int)typesetter.SuggestLineBreak(start, boundsWidth); var line = typesetter.GetLine(new NSRange(start, count)); // Create and initialize some values from the bounds. nfloat ascent; nfloat descent; nfloat leading; double lineWidth = line.GetTypographicBounds(out ascent, out descent, out leading); if (!layoutAvailable) { insetBounds.Width = (float)lineWidth; insetBounds.Height = (float)(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).ToPointF (); elementPoints[1] = textMatrix.TransformPoint(pathElement.Point2).ToPointF (); elementPoints[2] = textMatrix.TransformPoint(pathElement.Point3).ToPointF (); //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(); } }