使用字体库的文本渲染| .NET

概述

如果您想将文本渲染到图像中的功能,则此处的文章将教您如何使用任何格式的字体进行操作,并在字体库中使用Aspose.font API解决方案支持。该库允许您轻松地将文本转换为图像,并将文本添加到任何图像中。

渲染文字

要在图像中绘制文本,请使用 RenderingUtils 类的 DrawText 方法。本例使用 Verdana 绘制“Hello world”,并将 PNG 流保存为 Text.png。

将 DataDir 设为包含 TTF/verdana.ttf 和 TTF/LatoWeb-Regular.ttf 的目录,将 OutputDir 设为现有的输出目录。示例使用的命名空间为 Aspose.Font, Aspose.Font.Sources, Aspose.Font.Renderers, System, System.IO。在测试项目中,BaseTests 提供这些目录和许可证设置。

 1// Render text to a PNG image
 2
 3string fontPath = Path.Combine(DataDir, "TTF", "verdana.ttf");
 4Font font = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(fontPath)));
 5string text = "Hello world";
 6string outFile = Path.Combine(OutputDir, "Text.png");
 7using (Stream image = RenderingUtils.DrawText(font, text, 18))
 8{
 9    image.Position = 0;
10    using FileStream output = File.Create(outFile);
11    image.CopyTo(output);
12}
Example-DrawText.cs hosted with ❤ by GitHub

实现结果:

用 Verdana 绘制的 Hello world

drawText方法允许您设置线间距并执行自动逐字文本包装。

在下面的代码示例中,我们指定了线间距类型 lines -pacingtype.pixels并将其设置为10,然后将图像的最大宽度设置为450。

在给定范围内无法正确显示的所有文本(在我们的情况下为450)将包裹在新线路上。

本例用 Verdana 重复绘制“Hello world”,使文本超出 450 像素的宽度并自动换行。结果保存为 WrappedText.png:

 1// Render wrapped text with pixel line spacing
 2
 3string fontPath = Path.Combine(DataDir, "TTF", "verdana.ttf");
 4Font font = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(fontPath)));
 5string text = "Hello world Hello world Hello world Hello world Hello world Hello world";
 6string outFile = Path.Combine(OutputDir, "WrappedText.png");
 7using (Stream image = RenderingUtils.DrawText(font, text, 22, RenderingUtils.LineSpacingType.Pixels, 10, 450))
 8{
 9    image.Position = 0;
10    using FileStream output = File.Create(outFile);
11    image.CopyTo(output);
12}

实现结果:

用 Verdana 绘制并自动换行的 Hello world

DrawText() 返回 PNG 流。示例将流的位置重置到开头,复制到文件,然后释放两个流。

drawText()方法的函数家族涵盖标准文本或多行文本输出。但是,在某些情况下,您可能需要文本的自定义输出,例如,当您想以角度或其他方式压缩,伸展,旋转文本时。在这种情况下,您需要根据需要自定义输出。

自定义文本渲染

以下示例在 Windows 上使用 System.Drawing.Common,以 300 DPI 在 960 x 720 像素的画布上绘制字形轮廓。除上述命名空间外,还需导入 Aspose.Font.Glyphs, Aspose.Font.Rendering, Aspose.Font.RenderingPath, System.Drawing, System.Drawing.Drawing2D, System.Drawing.Imaging。

在aspose.font中绘制 glyphs使用 glyphoutlinererer类的方法之一。所有这些超载方法均在 iglyphrenderer接口中声明。

我们必须将字体的引用、字形ID或字形索引以及字形输出坐标作为参数传递给这些方法。为了传输最后一个参数,我们使用了一个由 Aspose.Font TransformationMatrix 对象表示的特殊矩阵。 下面我们将展示如何使用 TransformationMatrix 类型的对象将字形输出坐标传递给 RenderGlyph() 系列方法。

因此,要绘制字形,我们需要创建一个 GlyphOutlineRenderer 类型的对象。但此类对象无法自行绘制字形。它需要外部功能,该功能由 IGlyphOutlinePainter 接口描述。要使用 GlyphOutlineRenderer 对象,需要提供 IGlyphOutlinePainter 接口的实现。

下面您可以看到此接口的一个简单实现。 让我们创建 GlyphOutlinePainter 类,它需要一个 System.Drawing.Drawing2D.GraphicsPath 类型的对象来实现图形绘制目标。

下面是接口的实现。请将后面展示的 CustomDrawText() 重载和 FontWidthToImageWidth() 方法添加为 GlyphOutlinePainter 类的成员。示例项目将这些成员放在同一个类的第二个分部声明中。

 1// Implement a glyph outline painter
 2
 3public partial class GlyphOutlinePainter : IGlyphOutlinePainter
 4{
 5    private readonly GraphicsPath path;
 6    private PointF currentPoint;
 7
 8    public GlyphOutlinePainter(GraphicsPath path)
 9    {
10        this.path = path;
11    }
12
13    public void MoveTo(MoveTo moveTo)
14    {
15        path.CloseFigure();
16        currentPoint = new PointF((float)moveTo.X, (float)moveTo.Y);
17    }
18
19    public void LineTo(LineTo lineTo)
20    {
21        var next = new PointF((float)lineTo.X, (float)lineTo.Y);
22        path.AddLine(currentPoint, next);
23        currentPoint = next;
24    }
25
26    public void CurveTo(CurveTo curveTo)
27    {
28        var next = new PointF((float)curveTo.X3, (float)curveTo.Y3);
29        path.AddBezier(currentPoint, new PointF((float)curveTo.X1, (float)curveTo.Y1),
30            new PointF((float)curveTo.X2, (float)curveTo.Y2), next);
31        currentPoint = next;
32    }
33
34    public void ClosePath()
35    {
36        path.CloseFigure();
37    }
38}

在 IGlyphOutlinePainter 实现之后,我们可以使用其类型的对象来渲染单个字形,方法是将它们传递给 GlyphOutlineRenderer 并调用 RenderGlyph() 系列的相应重载方法。

为了渲染一串文本,我们需要获取每个字符的 glyphId,然后使用 GlyphOutlineRenderer 类型的对象调用 RenderGlyph() 系列中的某个方法,并传递相应字形的坐标。

让我们来看一个使用 Aspose.Font 库输出一行文本的示例。它将以 CustomDrawText() 方法的形式构建,该方法接受以下参数:CustomDrawText(string text, IFont font, double fontSize, Brush backgroundBrush, Brush textBrush, string outFile)。

创建customDrawText()方法,该方法将指定的文本绘制到 system.drawing.bitmap对象中,并保存在光盘上的结果位图。

这将包括以下步骤:

此策略的辅助步骤

第一个重载创建并释放位图。第二个重载接受由调用方拥有的位图,支持间距、坐标、叠加绘制和垂直输出。请将两个重载都添加到 GlyphOutlinePainter:

 1// Render text with spacing, coordinates, overlays and vertical layout
 2
 3public static void CustomDrawText(string text, IFont font, double fontSize,
 4    Brush backgroundBrush, Brush textBrush, string outFile)
 5{
 6    using var bitmap = new Bitmap(960, 720);
 7    CustomDrawText(text, font, fontSize, backgroundBrush, textBrush, outFile, bitmap);
 8}
 9
10public static void CustomDrawText(string text, IFont font, double fontSize,
11    Brush backgroundBrush, Brush textBrush, string outFile, Bitmap bitmap,
12    double kerningCoefficient = 1, double coordinateX = 0, double coordinateY = 0, bool topDown = false)
13{
14    const double dpi = 300;
15    double scale = fontSize * dpi / 72;
16    bitmap.SetResolution((float)dpi, (float)dpi);
17    using (Graphics graphics = Graphics.FromImage(bitmap))
18    {
19        // A transparent background preserves an existing image for overlays.
20        graphics.FillRectangle(backgroundBrush, 0, 0, bitmap.Width, bitmap.Height);
21        graphics.SmoothingMode = SmoothingMode.HighQuality;
22        GlyphId? previousGid = null;
23        double glyphXCoordinate = coordinateX;
24        double glyphYCoordinate = coordinateY + scale;
25
26        foreach (char character in text)
27        {
28            GlyphId gid = font.Encoding.DecodeToGid(character);
29            if (gid == null) continue;
30            Glyph glyph = font.GlyphAccessor.GetGlyphById(gid);
31            if (glyph == null) continue;
32
33            if (previousGid != null)
34            {
35                if (topDown)
36                    glyphYCoordinate += scale;
37                else
38                {
39                    double advance = FontWidthToImageWidth(font.Metrics.GetGlyphWidth(previousGid),
40                        glyph.SourceResolution, fontSize, dpi);
41                    double kerning = font.Metrics.GetKerningValue(previousGid, gid)
42                        / (double)glyph.SourceResolution * scale;
43                    // Like the source example, the coefficient scales the whole advance.
44                    glyphXCoordinate += (advance + kerning) * kerningCoefficient;
45                }
46            }
47
48            using var path = new GraphicsPath();
49            var painter = new GlyphOutlinePainter(path);
50            var renderer = new GlyphOutlineRenderer(painter);
51            var matrix = new TransformationMatrix(new double[]
52            {
53                scale, 0, 0, -scale, glyphXCoordinate, glyphYCoordinate
54            });
55            renderer.RenderGlyph(font, gid, matrix);
56            path.FillMode = FillMode.Winding;
57            graphics.FillPath(textBrush, path);
58            previousGid = gid;
59        }
60    }
61    // The caller owns the supplied bitmap and may reuse it for another overlay.
62    bitmap.Save(outFile, ImageFormat.Png);
63}

将以下辅助方法添加到同一个类中,以将字体单位的宽度转换为像素:

1// Convert font units to image pixels
2
3private static double FontWidthToImageWidth(double width, int fontSourceResolution,
4    double fontSize, double dpi = 300)
5{
6    return width / fontSourceResolution * fontSize * dpi / 72;
7}

本例使用 Verdana 以白底黑字绘制“Hello world”,并使用 Lato 以黄底蓝字绘制相同文本。结果保存为 Custom_Verdana.png 和 Custom_Lato.png:

 1// Render two fonts with a custom glyph painter
 2
 3string verdanaPath = Path.Combine(DataDir, "TTF", "verdana.ttf");
 4string latoPath = Path.Combine(DataDir, "TTF", "LatoWeb-Regular.ttf");
 5Font verdana = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(verdanaPath)));
 6Font lato = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(latoPath)));
 7string verdanaOutput = Path.Combine(OutputDir, "Custom_Verdana.png");
 8string latoOutput = Path.Combine(OutputDir, "Custom_Lato.png");
 9GlyphOutlinePainter.CustomDrawText("Hello world", verdana, 24, Brushes.White, Brushes.Black, verdanaOutput);
10GlyphOutlinePainter.CustomDrawText("Hello world", lato, 24, Brushes.Yellow, Brushes.Blue, latoOutput);

实现结果:

Verdana 自定义文本绘制 Verdana

Lato 自定义文本绘制 Lato

凯宁

辅助方法从字体中获取字偶间距。kerningCoefficient: 1.25 将整个水平前进量(前一个字形的宽度加上字偶间距)增大 25%。本例使用 Verdana,并保存为 IncreasedSpacing.png:

1// Increase the horizontal glyph advance
2
3string fontPath = Path.Combine(DataDir, "TTF", "verdana.ttf");
4Font font = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(fontPath)));
5string outFile = Path.Combine(OutputDir, "IncreasedSpacing.png");
6using var bitmap = new Bitmap(960, 720);
7GlyphOutlinePainter.CustomDrawText("Hello world", font, 18, Brushes.White, Brushes.Black,
8    outFile, bitmap, kerningCoefficient: 1.25);

实现结果:

间距系数为 1.25 的 Verdana 间距系数为 1.25 的 Verdana

通过坐标渲染文本

使用 coordinateX 和 coordinateY 设置文本位置。方法在 coordinateY 上加上 fontSize * 300 / 72 像素,得到第一条基线的位置。当字号为 18 点时,coordinateX: 300 和 coordinateY: 225 将其放在 (300, 300)。结果保存为 PositionedText.png:

1// Position text at explicit coordinates
2
3string fontPath = Path.Combine(DataDir, "TTF", "verdana.ttf");
4Font font = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(fontPath)));
5string outFile = Path.Combine(OutputDir, "PositionedText.png");
6using var bitmap = new Bitmap(960, 720);
7// At 18 points and 300 DPI the 75-pixel baseline offset makes the baseline Y=300.
8GlyphOutlinePainter.CustomDrawText("Hello world", font, 18, Brushes.White, Brushes.Black,
9    outFile, bitmap, coordinateX: 300, coordinateY: 225);

实现结果:

Verdana,第一条基线位于 X=300、Y=300 Verdana,第一条基线位于 X=300、Y=300

如何将文本添加到图像

要在现有图像上添加文本,请将位图和透明背景画刷传给通用辅助方法。透明画刷会保留现有像素。

上面接受位图的 CustomDrawText() 重载已支持此场景。它在传入的图像上绘制并将其保存为 PNG;位图仍由调用方拥有。

本例先在 OutputDir 中创建 OverlayBase.png,然后打开该图像,添加灰色阴影和棕色注释。结果分别保存为 Shadow.png 和 ShadowAnnotated.png,无需外部输入图像:

 1// Add a shadow and annotation to an existing image
 2
 3string fontPath = Path.Combine(DataDir, "TTF", "verdana.ttf");
 4Font font = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(fontPath)));
 5string inputImagePath = Path.Combine(OutputDir, "OverlayBase.png");
 6GlyphOutlinePainter.CustomDrawText("Hello world", font, 17, Brushes.White, Brushes.Black, inputImagePath);
 7using var bitmap = new Bitmap(inputImagePath);
 8string shadowFile = Path.Combine(OutputDir, "Shadow.png");
 9GlyphOutlinePainter.CustomDrawText("Hello world", font, 17, Brushes.Transparent, Brushes.Gray,
10    shadowFile, bitmap, coordinateX: -3);
11string annotatedFile = Path.Combine(OutputDir, "ShadowAnnotated.png");
12GlyphOutlinePainter.CustomDrawText("<= Shadow effect", font, 17, Brushes.Transparent, Brushes.Brown,
13    annotatedFile, bitmap, coordinateX: 400);

实现结果:

带阴影的 Verdana 带阴影的 Verdana

带阴影和注释的 Verdana。注释在宽 960 像素画布的右边缘被截断。 带阴影和注释的 Verdana。注释在宽 960 像素画布的右边缘被截断。

从上到下输出文本

同一个重载可通过 topDown: true 实现从上到下的输出。它将第一个字形放在初始基线上,然后将每个后续字形向下移动 fontSize * 300 / 72 像素。

本例在 X=400 处使用 Verdana 绘制“Top down”,并保存为 TopDown.png。当 coordinateY 为零且字号为 18 点时,第一条基线位于 Y=75 像素:

1// Render text vertically from top to bottom
2
3string fontPath = Path.Combine(DataDir, "TTF", "verdana.ttf");
4Font font = Font.Open(new FontDefinition(FontType.TTF, "ttf", new FileSystemStreamSource(fontPath)));
5string outFile = Path.Combine(OutputDir, "TopDown.png");
6using var bitmap = new Bitmap(960, 720);
7GlyphOutlinePainter.CustomDrawText("Top down", font, 18, Brushes.White, Brushes.Black,
8    outFile, bitmap, coordinateX: 400, topDown: true);
Example-RenderTopDown.cs hosted with ❤ by GitHub

实现结果:

Verdana,从上到下绘制 Verdana,从上到下绘制

附加信息

辅助方法使用 300 DPI。scale = fontSize * dpi / 72 将字号从点转换为像素。glyphXCoordinate 和 glyphYCoordinate 指定字形基线的位置。在水平模式下,下一个前进量包含前一个字形的宽度和字体提供的字偶间距;kerningCoefficient 对两者之和进行缩放。