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