Renderização de texto usando a biblioteca de fontes | .NET

Visão geral

Se você alguma vez quis ter o recurso de renderizar um texto em uma imagem, o artigo aqui ensinará como fazê -lo com fontes de qualquer formato, suportado pela Biblioteca de fontes usando a solução API ASPOPE.FONT. A biblioteca permite converter o texto facilmente em imagens e adicionar texto a qualquer imagem.

Renderizando texto

Utilize o método DrawText da classe RenderingUtils para desenhar texto numa imagem. O exemplo apresenta «Hello world» em Verdana e guarda o fluxo PNG em Text.png.

Defina DataDir como a pasta que contém TTF/verdana.ttf e TTF/LatoWeb-Regular.ttf, e OutputDir como uma pasta de saída existente. Os exemplos utilizam os espaços de nomes Aspose.Font, Aspose.Font.Sources, Aspose.Font.Renderers, System, System.IO. No projeto de testes, BaseTests fornece as pastas e a configuração da licença.

 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

Resultado da implementação:

Hello world em Verdana

O método DrawText permite definir o espaçamento da linha e executar o embrulho de texto de palavra por palavra automática.

No exemplo do código abaixo, especificamos o tipo de espaçamento de linha linespacingType.pixels e o definimos como 10, e definimos a largura máxima da imagem como 450.

Todo o texto que não pode ser exibido corretamente no intervalo especificado (no nosso caso é 450) será envolvido em uma nova linha.

O exemplo repete «Hello world» em Verdana para ultrapassar a largura de 450 píxeis e distribuir o texto por várias linhas. O resultado é guardado em 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}

Resultado da implementação:

Texto Hello world em Verdana com quebra de linha

DrawText() devolve um fluxo PNG. Os exemplos repõem a sua posição no início, copiam-no para um ficheiro e libertam ambos os fluxos.

A família de funções para o método DrawText() abrange texto padrão ou saída de texto multilina. Mas, em alguns casos, você pode precisar de uma saída personalizada do texto, por exemplo, quando deseja comprimir, esticar, girar o texto em um ângulo ou outra coisa. Nesse caso, você precisa personalizar a saída de acordo com suas necessidades.

Renderização de texto personalizada

Os exemplos seguintes desenham contornos de glifos com System.Drawing.Common no Windows, numa área de 960 x 720 píxeis a 300 DPI. Importe também os espaços de nomes Aspose.Font.Glyphs, Aspose.Font.Rendering, Aspose.Font.RenderingPath, System.Drawing, System.Drawing.Drawing2D, System.Drawing.Imaging.

Para desenhar glifos dentro de Aspose.font, usa um dos métodos RenderGlyph() da classe GlyphOutlineRenderer. Todos esses métodos sobrecarregados são declarados na interface IGlyphRenderer.

Como parâmetro, devemos passar para esses métodos uma referência à fonte, glifídeo ou índice de glifos e as coordenadas de saída do glifo. Para transferir o último parâmetro, uma matriz especial representada pelo objeto Aspose.font transformationMatrix é usada. Abaixo, mostramos como usar objetos da transformationMatrix tipo para passar as coordenadas de saída do glifo para os métodos da família RenderGlyph().

Portanto, para desenhar um glifo, precisamos criar um objeto do tipo GlyphOutlineRenderer. Mas esse objeto não pode desenhar um glifo por conta própria. Requer funcionalidade externa, descrita pela interface IGlyphOutlinePainter. Para usar o objeto GlyphOutlineRenderer, deve ser fornecido com uma implementação do IGlyphOutlinePainter. Abaixo, você pode ver uma implementação simples desta interface. Vamos criar a classeglyphoutlinepainterque requer um objeto de System.drawing.drawing2d.graphicsPath Tipo para objetivos de desenho gráfico.

A implementação da interface é apresentada abaixo. Adicione as sobrecargas de CustomDrawText() e o método FontWidthToImageWidth() apresentados a seguir à classe GlyphOutlinePainter. No projeto de exemplos, estes membros estão numa segunda declaração parcial da mesma classe.

 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}

Após a implementação IGlyphOutlinePainter, podemos usar objetos de seu tipo para renderizar um glifo individual, passando -os para o GlyphOutlineRenderer e chamando os métodos sobrecarregados correspondentes da família RenderGlyph().

Para renderizar uma sequência de texto, necessitamos de obter o glyphId para cada caractere e depois, utilizando um objeto do tipo GlyphOutlineRenderer, chamar um dos métodos da família RenderGlyph(), passando as coordenadas do glifo correspondente.

Vamos dar uma olhada em um exemplo de saída de uma linha de texto usando a biblioteca Aspose.font. Ele será construído na forma do método CustomDrawText(), que aceita parâmetros - *CustomDrawText *(texto da string, IFont, fontsize duplo, escova de fundo, pincel textbrush, string outfile).

Crie o método CustomDrawText() que atrai o texto especificado no objeto System.Drawing.bitmap e salva o bitmap resultante no disco.

Isso incluirá as seguintes etapas:

Etapas auxiliares para esta estratégia

A primeira sobrecarga cria e liberta um bitmap. A segunda recebe um bitmap pertencente ao código que chama o método e suporta espaçamento, coordenadas, sobreposições e saída vertical. Adicione ambas as sobrecargas a 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}

Adicione à mesma classe o método auxiliar que converte a largura de unidades da fonte para píxeis:

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}

O exemplo apresenta «Hello world» em Verdana preto sobre branco e em Lato azul sobre amarelo. Guarda Custom_Verdana.png e 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);

Resultado da implementação:

Renderização personalizada do texto em Verdana Verdana

Renderização personalizada do texto em Lato Lato

Kerning

O método obtém o kerning dos pares da fonte. kerningCoefficient: 1.25 aumenta em 25% todo o avanço horizontal (largura do glifo anterior mais o kerning do par). O exemplo utiliza Verdana e guarda 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);

Resultado da implementação:

Verdana com coeficiente de espaçamento 1.25 Verdana com coeficiente de espaçamento 1.25

Renderizar texto por coordenadas

Posicione o texto com coordinateX e coordinateY. Para obter a primeira linha de base, o método adiciona fontSize * 300 / 72 píxeis a coordinateY. A 18 pontos, coordinateX: 300 e coordinateY: 225 colocam-na em (300, 300). O resultado é guardado em 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);

Resultado da implementação:

Verdana, primeira linha de base em X=300 e Y=300 Verdana, primeira linha de base em X=300 e Y=300

Como adicionar o texto a uma imagem

Para adicionar texto a uma imagem existente, passe um bitmap e um pincel de fundo transparente ao método comum. O pincel transparente preserva os píxeis existentes.

A sobrecarga de CustomDrawText() que aceita um bitmap já suporta este cenário. Desenha na imagem fornecida e guarda-a em PNG; o bitmap continua a pertencer ao código que chama o método.

O exemplo cria OverlayBase.png em OutputDir, abre a imagem e adiciona uma sombra cinzenta e uma anotação castanha. Guarda separadamente Shadow.png e ShadowAnnotated.png; não é necessária uma imagem de entrada externa:

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

Resultado da implementação:

Verdana com sombra Verdana com sombra

Verdana com sombra e anotação. A anotação é cortada na margem direita da área de 960 píxeis de largura. Verdana com sombra e anotação. A anotação é cortada na margem direita da área de 960 píxeis de largura.

Emitindo o texto de cima para baixo

A mesma sobrecarga suporta saída vertical com topDown: true. Coloca o primeiro glifo na linha de base inicial e desloca cada glifo seguinte fontSize * 300 / 72 píxeis para baixo.

O exemplo apresenta «Top down» em Verdana em X=400 e guarda TopDown.png. Com coordinateY igual a zero e tamanho de 18 pontos, a primeira linha de base fica em Y=75 píxeis:

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

Resultado da implementação:

Verdana, de cima para baixo Verdana, de cima para baixo

Informações adicionais

O método utiliza 300 DPI. scale = fontSize * dpi / 72 converte o tamanho da fonte de pontos para píxeis. glyphXCoordinate e glyphYCoordinate definem a posição da linha de base do glifo. Na saída horizontal, o avanço seguinte inclui a largura do glifo anterior e o kerning do par obtido da fonte; kerningCoefficient multiplica a sua soma.