Отрисовка текста используя библиотеку шрифтов | .NET

Обзор

Если вы когда-нибудь хотели иметь возможность рендеринга текста в изображение, эта статья научит вас, как это сделать со шрифтами любого формата, поддерживаемыми библиотекой шрифтов, с использованием решения Aspose.Font API. Библиотека позволяет легко конвертировать текст в изображения, а также добавлять текст к любому изображению.

Отрисовка текста

Для вывода текста в изображение используйте метод DrawText класса RenderingUtils. Пример выводит «Hello world» шрифтом Verdana и сохраняет 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

Результат выполнения:

Текст Hello world шрифтом Verdana

Метод DrawText позволяет вам установить межстрочный интервал и выполнить автоматический пословный перенос текста.

В приведенном ниже примере кода мы указали тип межстрочного интервала LineSpacingType.Pixels и установили для него значение 10, а максимальную ширину изображения установили на 450.

Весь текст, который не может быть корректно отображен в заданном диапазоне (в нашем случае это 450), будет перенесен на новую строку.

Пример повторяет «Hello world» шрифтом Verdana, чтобы текст превысил ширину 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}

Результат выполнения:

Текст Hello world шрифтом Verdana с переносом строк

DrawText() возвращает PNG-поток. Примеры переводят его позицию в начало, копируют данные в файл и освобождают оба потока.

Семейство функций метода DrawText() охватывает стандартный текстовый или многострочный текстовый вывод. Но в некоторых случаях вам может понадобиться настраиваемый вывод текста, например, когда вы хотите сжать, растянуть, повернуть текст под углом или что-то еще. В этом случае вам необходимо настроить вывод в соответствии с вашими потребностями.

Пользовательский Отрисовка текста

Следующие примеры рисуют контуры глифов с помощью System.Drawing.Common в Windows на холсте 960 x 720 при 300 DPI. Помимо указанных выше пространств имён подключите Aspose.Font.Glyphs, Aspose.Font.Rendering, Aspose.Font.RenderingPath, System.Drawing, System.Drawing.Drawing2D, System.Drawing.Imaging.

Для рисования glyphs в Aspose.Font используется один из методов RenderGlyph() класса GlyphOutlineRenderer. Все эти перегруженные методы объявлены в интерфейсе IGlyphRenderer.

В качестве параметра мы должны передать этим методам ссылку на шрифт, glyphId или индекс глифа, а также выходные координаты глифа. Для передачи последнего параметра используется специальная матрица, представленная объектом 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(текстовая строка, шрифт IFont, двойной размер шрифта, Brush backgroundBrush, Brush textBrush, строка 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}

Пример выводит «Hello world» шрифтом Verdana чёрным на белом и шрифтом 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);

Результат выполнения:

Verdana с коэффициентом интервала 1.25 Verdana с коэффициентом интервала 1.25

Отрисовка текста по координатам

Положение текста задают 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; владельцем изображения остаётся вызывающий код.

Пример создаёт OverlayBase.png в OutputDir, затем открывает его и добавляет серую тень и коричневую аннотацию. Результаты сохраняются отдельно в 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 пикселей ниже.

Пример выводит «Top down» шрифтом Verdana при X=400 и сохраняет 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 масштабирует их сумму.