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