Візуалізація тексту за допомогою бібліотеки шрифтів | .NET

Огляд

Якщо ви коли-небудь хотіли мати функцію відтворення тексту в зображенні, ця стаття навчить вас, як це робити зі шрифтами будь-якого формату, які підтримуються бібліотекою шрифтів за допомогою Aspose.Font API Solution. Бібліотека дозволяє легко перетворювати текст на зображення, а також додавати текст до будь-якого зображення.

Візуалізація тексту

Для виведення тексту в зображення використовуйте метод 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.

Для малювання гліфів у Aspose.Font використовується один із методів RenderGlyph() класу GlyphOutlineRenderer. Усі ці перевантажені методи оголошено в інтерфейсі 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}

Приклад виводить «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 масштабує їхню суму.