The Text class is the central class for drawing text in Graphics Mill. A single Text object holds a text string, its formatting, and one or more text frames that define where the text goes. Different kinds of frame place the text differently: at a single point, inside a shape, along a curved path, and so on. If the Text.Frames collection contains more than one frame, the text flows through them one by one: when a frame is full, the rest of the text continues in the next frame.
Here is a complete example. It creates a text, gives it a font, places it with a single frame, and draws it:
using (var bitmap = new Bitmap(400, 120, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
// A frame tells the text where to go. At least one frame is required.
var frame = new PointTextFrame()
{
Point = new System.Drawing.PointF(20, 75)
};
var text = new Text()
{
String = "Hello, world!",
CharStyle = new CharStyle("Arial", 36)
};
text.Frames.Add(frame);
graphics.DrawText(text);
bitmap.Save(@"Images\Output\HelloWorld.png");
}
Text is always drawn through the Graphics.DrawText(Text) method, whatever frames it uses. If you are not familiar with Graphics, read the Graphics. Drawing Images and Geometric Shapes article first.
This topic covers the following:
A text frame is one of the classes derived from TextFrame. This topic covers the three frames you will use most often:
Add one or more frames to Text.Frames. When a frame cannot hold the whole string, the rest of the text is not clipped or dropped. It continues in the next frame of the collection, in the order the frames were added, until either the text or the frames run out. This is what makes multi-column layouts simple to build:
using (var bitmap = new Bitmap(460, 300, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var column1 = new Path();
column1.DrawRectangle(10, 10, 210, 280);
var column2 = new Path();
column2.DrawRectangle(240, 10, 210, 280);
var firstFrame = new ShapeTextFrame()
{
Shape = column1
};
var secondFrame = new ShapeTextFrame()
{
Shape = column2
};
var flowExplanation =
TextFlowSummary + " " +
"Once a frame is full, the remaining text does not disappear or get clipped. " +
"It simply carries on inside the next frame in the collection, in the order " +
"the frames were added, until either the text or the frames run out. " +
"This is what makes multi-column layouts straightforward. Define each column " +
"once as its own frame, then let a single text object fill them in sequence. " +
"Editing the text later reflows every frame automatically.";
var text = new Text()
{
String = flowExplanation,
CharStyle = new CharStyle("Arial", 17)
};
// Frames are filled in order: the overflow from the first frame continues in the second.
text.Frames.Add(firstFrame);
text.Frames.Add(secondFrame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), column1);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), column2);
graphics.DrawText(text);
bitmap.Save(@"Images\Output\TextFlowBetweenFrames.png");
}
PointTextFrame is the simplest frame. It has an anchor PointTextFrame.Point and a PointTextFrame.TextOrientation that switches between horizontal and vertical text. The frame has no bounds, so the text is never wrapped or fitted. It grows from the anchor point, and only an explicit line break starts a new line:
using (var bitmap = new Bitmap(400, 130, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var frame = new PointTextFrame()
{
Point = new System.Drawing.PointF(30, 60)
};
var text = new Text()
{
String = "Point text\nwith two lines",
CharStyle = new CharStyle("Arial", 24)
};
text.Frames.Add(frame);
graphics.DrawEllipse(new Pen(RgbColor.IndianRed, 1), 27, 57, 6, 6);
graphics.DrawText(text);
bitmap.Save(@"Images\Output\PointTextFrame.png");
}
ShapeTextFrame bounds the text by a ShapeTextFrame.Shape. The shape is any closed Path, not just a rectangle. This frame has the richest set of properties, because it has to decide how the text and the shape adapt to each other.
The samples in this section call a small local helper named DrawCaption. It only draws the gray label under each box and is not part of the text API.
ShapeTextFrame.AutoSizeMode resizes the shape to fit the text. ShapeTextFrame.AutoSizeAnchor selects which corner or edge stays in place while the rest moves. The resized outline is available through the frame's AutoSizedShape property, but only after the text has been drawn or measured:
using (var bitmap = new Bitmap(500, 300, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var modes = new[]
{
AutoSizeMode.Off,
AutoSizeMode.WidthOnly,
AutoSizeMode.HeightOnly,
AutoSizeMode.WidthHeight
};
for (int i = 0; i < modes.Length; i++)
{
float offsetX = 20 + (i % 2) * 240;
float offsetY = 20 + (i / 2) * 140;
var shape = new Path();
shape.DrawRectangle(offsetX, offsetY, 200, 60);
var frame = new ShapeTextFrame()
{
Shape = shape,
AutoSizeMode = modes[i]
};
var text = new Text()
{
String = "Sample text",
CharStyle = new CharStyle("Arial", 20)
};
text.Frames.Add(frame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), shape);
graphics.DrawText(text);
// The resized outline is available only after the text has been drawn or measured.
if (frame.AutoSizedShape != null)
graphics.DrawPath(new Pen(RgbColor.IndianRed, 1), frame.AutoSizedShape);
DrawCaption(graphics, modes[i].ToString(), new System.Drawing.PointF(offsetX, offsetY + 85));
}
bitmap.Save(@"Images\Output\ShapeTextFrameAutoSize.png");
}
ShapeTextFrame.CopyfittingMode does the opposite. The shape
stays fixed and the text is adjusted to fit it. There are six modes. The plain modes keep the original glyph proportions,
while the WithScale modes are allowed to change the aspect ratio of the glyphs. In the sample
below each pair of modes uses the same text and the same starting char style, so that the difference between the two modes
of a pair is what you actually see:
using (var bitmap = new Bitmap(500, 540, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
// Each pair of modes shares the same text and the same initial char style,
// so that the difference between the two modes is what you actually see.
const string tooLong = "This paragraph is far longer than the box that has to hold it, " +
"so with copyfitting off its last lines simply run past the bottom edge.";
const string tooWide = "Extra wide headline";
const string tooShort = "Short";
var samples = new[]
{
Tuple.Create(CopyfittingMode.Off, tooLong, 20f),
Tuple.Create(CopyfittingMode.FitToBox, tooLong, 20f),
Tuple.Create(CopyfittingMode.FitToWidth, tooWide, 30f),
Tuple.Create(CopyfittingMode.FitToWidthWithScale, tooWide, 30f),
Tuple.Create(CopyfittingMode.Fill, tooShort, 11f),
Tuple.Create(CopyfittingMode.FillWithScale, tooShort, 11f)
};
for (int i = 0; i < samples.Length; i++)
{
float offsetX = 20 + (i % 2) * 240;
float offsetY = 20 + (i / 2) * 170;
var shape = new Path();
shape.DrawRectangle(offsetX, offsetY, 200, 110);
var frame = new ShapeTextFrame()
{
Shape = shape,
CopyfittingMode = samples[i].Item1
};
var text = new Text()
{
String = samples[i].Item2,
CharStyle = new CharStyle("Arial", samples[i].Item3)
};
text.Frames.Add(frame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), shape);
graphics.DrawText(text);
DrawCaption(graphics, samples[i].Item1.ToString(), new System.Drawing.PointF(offsetX, offsetY + 135));
}
bitmap.Save(@"Images\Output\ShapeTextFrameCopyfitting.png");
}
Off and FitToBox share text that overflows the box. With
Off the last lines run past the bottom edge; FitToBox makes the
whole text fit inside.FitToWidth and FitToWidthWithScale share a headline wider than the
box, and both make it fit the width. The first keeps the original glyph proportions, the second may change their
aspect ratio.Fill and FillWithScale share a short string, which both modes
enlarge to fill the box. Again, the first preserves the glyph proportions and the second may change them.ShapeTextFrame.VerticalAlignment positions the text block within the height of the shape:
using (var bitmap = new Bitmap(640, 290, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var alignments = new[]
{
VerticalAlignment.Top,
VerticalAlignment.Center,
VerticalAlignment.Bottom,
VerticalAlignment.Justify
};
for (int i = 0; i < alignments.Length; i++)
{
float offsetX = 20 + i * 155;
var shape = new Path();
shape.DrawRectangle(offsetX, 20, 135, 220);
var frame = new ShapeTextFrame()
{
Shape = shape,
VerticalAlignment = alignments[i]
};
var text = new Text()
{
String = "One two three four",
CharStyle = new CharStyle("Arial", 28)
};
text.Frames.Add(frame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), shape);
graphics.DrawText(text);
DrawCaption(graphics, alignments[i].ToString(), new System.Drawing.PointF(offsetX, 262));
}
bitmap.Save(@"Images\Output\ShapeTextFrameVerticalAlignment.png");
}
ShapeTextFrame.FirstBaselineOffset selects the font metric
that positions the first baseline below the top of the frame.
ShapeTextFrame.FirstBaselineMinOffset sets the smallest
allowed distance between the top of the frame and that baseline. The sample below sets it to
0, so each metric shows its own natural result. The Fixed value ignores
the font metrics and puts the baseline exactly
ShapeTextFrame.FirstBaselineMinOffset below the top:
using (var bitmap = new Bitmap(520, 320, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var offsets = new[]
{
FirstBaselineOffset.Ascent,
FirstBaselineOffset.CapHeight,
FirstBaselineOffset.Leading,
FirstBaselineOffset.xHeight,
FirstBaselineOffset.EmBox,
FirstBaselineOffset.Fixed
};
for (int i = 0; i < offsets.Length; i++)
{
float offsetX = 20 + (i % 3) * 165;
float offsetY = 20 + (i / 3) * 145;
var shape = new Path();
shape.DrawRectangle(offsetX, offsetY, 145, 90);
var frame = new ShapeTextFrame()
{
Shape = shape,
FirstBaselineOffset = offsets[i],
FirstBaselineMinOffset = 0
};
var text = new Text()
{
String = "Hxg",
CharStyle = new CharStyle("Arial", 30)
};
text.Frames.Add(frame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), shape);
graphics.DrawText(text);
DrawCaption(graphics, offsets[i].ToString(), new System.Drawing.PointF(offsetX, offsetY + 115));
}
bitmap.Save(@"Images\Output\ShapeTextFrameFirstBaseline.png");
}
ShapeTextFrame.TextOrientation switches the frame between horizontal and vertical text. In vertical mode the glyphs stack downwards and the lines run from right to left, which is the usual layout for East Asian scripts. Vertical text needs a font that contains the required glyphs, so the sample looks up an installed Korean font instead of assuming one:
using (var bitmap = new Bitmap(460, 280, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
// "The weather is clear today" in Korean.
const string korean = "\uC624\uB298\uC740 \uB0A0\uC528\uAC00 \uB9D1\uC2B5\uB2C8\uB2E4";
var koreanFont = FindKoreanFont();
var orientations = new[]
{
TextOrientation.Horizontal,
TextOrientation.Vertical
};
for (int i = 0; i < orientations.Length; i++)
{
float offsetX = 20 + i * 230;
var shape = new Path();
shape.DrawRectangle(offsetX, 20, 210, 200);
var frame = new ShapeTextFrame()
{
Shape = shape,
TextOrientation = orientations[i]
};
var text = new Text()
{
String = korean,
CharStyle = new CharStyle(koreanFont, 26)
};
text.Frames.Add(frame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), shape);
graphics.DrawText(text);
DrawCaption(graphics, orientations[i].ToString(), new System.Drawing.PointF(offsetX, 242));
}
bitmap.Save(@"Images\Output\ShapeTextFrameOrientation.png");
}
PathTextFrame flows the text along a PathTextFrame.Baseline path. Read the Working with Paths article if you are not familiar with Path. PathTextFrame.PathAlignment selects which part of the glyphs sits on the path:
using (var bitmap = new Bitmap(500, 260, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var baseline = new Path();
baseline.MoveTo(20, 200);
baseline.CurveTo(180, 20, 320, 20, 480, 200);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), baseline);
var alignments = new[]
{
Tuple.Create(PathTextAlignment.Ascender, RgbColor.IndianRed),
Tuple.Create(PathTextAlignment.Center, RgbColor.SeaGreen),
Tuple.Create(PathTextAlignment.Baseline, RgbColor.Black),
Tuple.Create(PathTextAlignment.Descender, RgbColor.SteelBlue)
};
// Each label takes its own quarter of the path so the four alignments can be compared side by side.
for (int i = 0; i < alignments.Length; i++)
{
var frame = new PathTextFrame()
{
Baseline = baseline,
PathAlignment = alignments[i].Item1,
Start = i * 0.25f,
End = i * 0.25f + 0.25f
};
var text = new Text()
{
String = alignments[i].Item1.ToString(),
CharStyle = new CharStyle("Arial", 18),
Brush = new SolidBrush(alignments[i].Item2)
};
text.Frames.Add(frame);
graphics.DrawText(text);
}
bitmap.Save(@"Images\Output\PathTextFrameAlignment.png");
}
PathTextFrame.Start and
PathTextFrame.End limit the text to a part of the path.
Both are fractions of the path length, from 0 to 1.
PathTextFrame.Flip mirrors the text to the other side of
the path:
using (var bitmap = new Bitmap(500, 320, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
// Start and End restrict the text to a portion of the path, given as a fraction of its length.
var topBaseline = new Path();
topBaseline.MoveTo(20, 100);
topBaseline.CurveTo(180, 20, 320, 20, 480, 100);
var trimmedFrame = new PathTextFrame()
{
Baseline = topBaseline,
Start = 0.25f,
End = 0.85f
};
var trimmedText = new Text()
{
String = "This part only",
CharStyle = new CharStyle("Arial", 20)
};
trimmedText.Frames.Add(trimmedFrame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), topBaseline);
graphics.DrawText(trimmedText);
DrawCaption(graphics, "Start = 0.25, End = 0.85", new System.Drawing.PointF(20, 130));
// Flip mirrors the text to the other side of the baseline.
var bottomBaseline = new Path();
bottomBaseline.MoveTo(20, 260);
bottomBaseline.CurveTo(180, 340, 320, 340, 480, 260);
var flippedFrame = new PathTextFrame()
{
Baseline = bottomBaseline,
Flip = true
};
var flippedText = new Text()
{
String = "Flip = true",
CharStyle = new CharStyle("Arial", 20)
};
flippedText.Frames.Add(flippedFrame);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), bottomBaseline);
graphics.DrawText(flippedText);
bitmap.Save(@"Images\Output\PathTextFrameStartEndFlip.png");
}
Several more properties tune the fit numerically. PathTextFrame.Spacing adds space between characters. PathTextFrame.VerticalOffset moves the text away from the path. PathTextFrame.Stretch stretches glyphs so they follow sharp curves more closely. PathTextFrame.AutoExtend lets the text run past the end of the path instead of stopping there. PathTextFrame.CopyfittingMode works as it does for ShapeTextFrame, fitting the text to the part of the path between PathTextFrame.Start and PathTextFrame.End.
Besides the frames, a Text object needs a Text.String and a Text.CharStyle. Text.CharStyle and Text.ParagraphStyle hold the default character and paragraph settings for the whole string. Individual parts of the string can override these defaults with the XML markup syntax written inside the text string itself. The Formatted Text article describes that markup and both style objects in detail.
The rest of this section covers the properties that belong to the Text object itself. Colors come from Text.Brush and Text.Pen, and Text.Transform applies a geometric transform to the result.
The Text.Kernings property tunes the spacing between
individual glyph pairs. It takes one value per gap between glyphs, so a string of N characters
takes N - 1 values, measured in 1/1000 em. A positive value opens up a pair, a negative value
tightens it. This is separate from the tracking that
Text.CharStyle applies to the whole string:
using (var bitmap = new Bitmap(500, 140, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var frame = new PointTextFrame()
{
Point = new System.Drawing.PointF(20, 90)
};
var text = new Text()
{
String = "Kerning",
CharStyle = new CharStyle("Arial", 60),
// One value per gap between glyphs, in 1/1000 em.
// Positive values open up the pair, negative values tighten it.
Kernings = new float[] { 250, 120, 0, -60, -120, -180 }
};
text.Frames.Add(frame);
graphics.DrawText(text);
bitmap.Save(@"Images\Output\TextKernings.png");
}
The Text.WrappingPaths collection holds obstacles that the text flows around. They apply to the text as a whole, in every frame. Each Path in the collection is one obstacle:
using (var bitmap = new Bitmap(400, 260, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var container = new Path();
container.DrawRectangle(10, 10, 380, 240);
var obstacle = new Path();
obstacle.DrawEllipse(140, 80, 110, 110);
var frame = new ShapeTextFrame()
{
Shape = container
};
var text = new Text()
{
String = TextFlowSummary,
CharStyle = new CharStyle("Arial", 24)
};
text.Frames.Add(frame);
text.WrappingPaths.Add(obstacle);
graphics.FillPath(new SolidBrush(new RgbColor(255, 224, 224)), obstacle);
graphics.DrawText(text);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), container);
graphics.DrawPath(new Pen(RgbColor.IndianRed, 1), obstacle);
bitmap.Save(@"Images\Output\TextWrappingSinglePath.png");
}
Add several paths to wrap the text around several obstacles at once:
using (var bitmap = new Bitmap(400, 260, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var container = new Path();
container.DrawRectangle(10, 10, 380, 240);
var obstacle1 = new Path();
obstacle1.DrawEllipse(30, 30, 100, 100);
var obstacle2 = new Path();
obstacle2.DrawEllipse(250, 130, 120, 90);
var frame = new ShapeTextFrame()
{
Shape = container
};
var text = new Text()
{
String = TextFlowSummary + " " + TextFlowSummary,
CharStyle = new CharStyle("Arial", 17)
};
text.Frames.Add(frame);
// Every path added here is a separate obstacle.
text.WrappingPaths.Add(obstacle1);
text.WrappingPaths.Add(obstacle2);
graphics.FillPath(new SolidBrush(new RgbColor(255, 224, 224)), obstacle1);
graphics.FillPath(new SolidBrush(new RgbColor(255, 224, 224)), obstacle2);
graphics.DrawText(text);
graphics.DrawPath(new Pen(RgbColor.Gray, 1), container);
graphics.DrawPath(new Pen(RgbColor.IndianRed, 1), obstacle1);
graphics.DrawPath(new Pen(RgbColor.IndianRed, 1), obstacle2);
bitmap.Save(@"Images\Output\TextWrappingMultiplePaths.png");
}
Set the Text.Effect property to make the text glow or cast a shadow. The Effects.Glow and Effects.Shadow classes and all their parameters are described in the Text Effects section of the Drawing Simple Text Objects article:
using (var bitmap = new Bitmap(500, 140, PixelFormat.Format24bppRgb, new RgbColor(60, 60, 70)))
using (var graphics = bitmap.GetGraphics())
{
var glow = new Aurigma.GraphicsMill.Drawing.Effects.Glow()
{
Color = RgbColor.Cyan,
Size = 8,
Opacity = 0.9f
};
var frame = new PointTextFrame()
{
Point = new System.Drawing.PointF(20, 100)
};
var text = new Text()
{
String = "Glowing text",
CharStyle = new CharStyle("Arial", 65),
Brush = new SolidBrush(RgbColor.White),
Effect = glow
};
text.Frames.Add(frame);
graphics.DrawText(text);
bitmap.Save(@"Images\Output\TextGlowEffect.png");
}
The Text.GetBlackBox(FontRegistry, Single, Single) method returns the smallest rectangle that bounds the text at its current size and position. The Getting Black Box section of the Fonts and Measuring Text article explains the concept. Text.FitTo(RectangleF, FontRegistry, Single, Single) goes further and adjusts the text so that its black box matches a target rectangle. Both methods take a FontRegistry and the target DPI, because font metrics depend on them:
using (var bitmap = new Bitmap(500, 260, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
var pen = new Pen(RgbColor.IndianRed, 1);
var frame = new PointTextFrame()
{
Point = new System.Drawing.PointF(20, 80)
};
var text = new Text()
{
String = "Fit to box",
CharStyle = new CharStyle("Arial", 60)
};
text.Frames.Add(frame);
// Before: measure the black box of the text at its natural size.
var blackBox = text.GetBlackBox(graphics.FontRegistry, graphics.DpiX, graphics.DpiY);
graphics.DrawText(text);
graphics.DrawRectangle(pen, blackBox);
DrawCaption(graphics, "GetBlackBox: natural size", new System.Drawing.PointF(20, 105));
// After: FitTo adjusts the same text so that it matches a target rectangle.
var targetRect = new System.Drawing.RectangleF(20, 160, 460, 70);
text.FitTo(targetRect, graphics.FontRegistry, graphics.DpiX, graphics.DpiY);
graphics.DrawText(text);
graphics.DrawRectangle(pen, targetRect);
DrawCaption(graphics, "FitTo: fitted into the rectangle above", new System.Drawing.PointF(20, 245));
bitmap.Save(@"Images\Output\TextBlackBoxAndFitTo.png");
}
Every char style refers to a font by its postscript name, and Graphics resolves that name against its own Graphics.FontRegistry. If the font is not in that registry, drawing the text or calling Text.GetBlackBox(FontRegistry, Single, Single) or Text.FitTo(RectangleF, FontRegistry, Single, Single) on it fails. The Creating Fonts section of the Fonts and Measuring Text article covers the fundamentals: the FontRegistry.Installed registry of system fonts, FontRegistry.GetFontStyles(String), and adding a font from a file to a CustomFontRegistry via CustomFontRegistry.Add(String). This section covers what that article does not.
CustomFontRegistry can also load a font from a stream via CustomFontRegistry.Add(Stream). This is useful when the font comes from a database, an archive, or any other source that is not a plain file on disk. Both overloads return the postscript name of the font, which you then use in the char style.
If a font might be missing, you have two options instead of letting the exception propagate. The FontRegistry.FallbackFonts collection lists postscript names to fall back to, in order, whenever a requested font cannot be found. Alternatively, subscribe to the CustomFontRegistry.FontMissing event and load the missing font on demand, for example from a remote font service. The FontMissingEventArgs passed to the handler carries the requested FontMissingEventArgs.FontName and the FontMissingEventArgs.FontRegistry to add it to. Both are covered in more detail elsewhere; the sample below only shows adding a font from a file.
Remember to assign your custom registry to Graphics.FontRegistry before drawing. Graphics looks up fonts only in its own registry, so a font you added elsewhere stays invisible to it until you do.
The following snippet starts from the installed fonts and adds one more from a file:
using (var bitmap = new Bitmap(500, 140, PixelFormat.Format24bppRgb, RgbColor.White))
using (var graphics = bitmap.GetGraphics())
{
// Start from the installed system fonts, then add a font from a file.
// Add(Stream) works the same way when the font comes from a database, an archive, and so on.
using (var fontRegistry = new CustomFontRegistry(FontRegistry.Installed))
{
var psName = fontRegistry.Add(@"C:\Windows\Fonts\comic.ttf");
// Graphics resolves font names only against its own FontRegistry,
// so assign the custom registry before drawing.
graphics.FontRegistry = fontRegistry;
var frame = new PointTextFrame()
{
Point = new System.Drawing.PointF(20, 80)
};
var text = new Text()
{
String = "Loaded from a file",
CharStyle = new CharStyle(psName, 34)
};
text.Frames.Add(frame);
graphics.DrawText(text);
}
bitmap.Save(@"Images\Output\CustomFontRegistry.png");
}
Graphics Mill also provides PlainText, BoundedText, PathText, and DoublePathText. They are shorthand for the common cases: each one creates a text object with a single frame of the matching kind, without you having to build that frame yourself. They are convenient when a text needs no flow between frames and no frame-level tuning.
Use the Text class described in this topic when you need several frames, auto-sizing, copyfitting, or the other frame properties. See the Drawing Simple Text Objects article for the simplified classes.