Partilhar via


Caminhos e texto no SkiaSharp

Explore a interseção de caminhos e texto

Em sistemas gráficos modernos, fontes de texto são coleções de contornos de caracteres, geralmente definidos por curvas de Bézier quadráticas. Consequentemente, muitos sistemas gráficos modernos incluem uma facilidade para converter caracteres de texto em um caminho gráfico.

Você já viu que pode traçar os contornos dos caracteres de texto, bem como preenchê-los. Isso permite que você exiba esses contornos de caracteres com uma largura de traçado específica e até mesmo um efeito de caminho, conforme descrito no artigo Efeitos de caminho. Mas também é possível converter uma cadeia de caracteres em um SKPath objeto. Isso significa que os contornos de texto podem ser usados para recorte com técnicas que foram descritas no artigo Recorte com caminhos e regiões .

Além de usar um efeito de caminho para traçar um contorno de caractere, você também pode criar efeitos de caminho baseados em um caminho derivado de uma cadeia de caracteres e até mesmo combinar os dois efeitos:

Efeito de caminho de texto

No artigo anterior sobre Efeitos de Caminho, você viu como o método de SKPaint pode obter um contorno GetFillPath de um caminho traçado. Você também pode usar esse método com caminhos derivados de contornos de caracteres.

Finalmente, este artigo demonstra outra interseção de caminhos e texto: O DrawTextOnPath método de permite que você exiba uma cadeia de caracteres de SKCanvas texto para que a linha de base do texto siga um caminho curvo.

Conversão de texto em caminho

O GetTextPath método de converte uma cadeia de SKPaint caracteres em um SKPath objeto:

public SKPath GetTextPath (String text, Single x, Single y)

Os x argumentos e y indicam o ponto de partida da linha de base do lado esquerdo do texto. Eles desempenham o mesmo papel aqui como no DrawText método de SKCanvas. Dentro do caminho, a linha de base do lado esquerdo do texto terá as coordenadas (x, y).

O GetTextPath método é exagerado se você quiser apenas preencher ou traçar o caminho resultante. O método normal DrawText permite que você faça isso. O GetTextPath método é mais útil para outras tarefas que envolvem caminhos.

Uma dessas tarefas é o clipping. A página Texto de recorte cria um caminho de recorte com base nos contornos de caracteres da palavra "CODE". Esse caminho é estendido até o tamanho da página para recortar um bitmap que contém uma imagem do código-fonte do Clipping Text :

Captura de tela tripla da página Texto de recorte

O ClippingTextPage construtor de classe carrega o bitmap armazenado como um recurso incorporado na pasta Media da solução:

public class ClippingTextPage : ContentPage
{
    SKBitmap bitmap;

    public ClippingTextPage()
    {
        Title = "Clipping Text";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        string resourceID = "SkiaSharpFormsDemos.Media.PageOfCode.png";
        Assembly assembly = GetType().GetTypeInfo().Assembly;

        using (Stream stream = assembly.GetManifestResourceStream(resourceID))
        {
            bitmap = SKBitmap.Decode(stream);
        }
    }
    ...
}

O PaintSurface manipulador começa criando um SKPaint objeto adequado para texto. A Typeface propriedade é definida, bem como o TextSize, embora para esta aplicação em particular a TextSize propriedade seja puramente arbitrária. Observe também que não há nenhuma Style configuração.

As TextSize configurações de propriedade e Style não são necessárias porque esse SKPaint objeto é usado somente para a GetTextPath chamada usando a cadeia de texto "CODE". Em seguida, o manipulador mede o objeto resultante SKPath e aplica três transformações para centralizá-lo e dimensioná-lo para o tamanho da página. O caminho pode então ser definido como o caminho de recorte:

public class ClippingTextPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear(SKColors.Blue);

        using (SKPaint paint = new SKPaint())
        {
            paint.Typeface = SKTypeface.FromFamilyName(null, SKTypefaceStyle.Bold);
            paint.TextSize = 10;

            using (SKPath textPath = paint.GetTextPath("CODE", 0, 0))
            {
                // Set transform to center and enlarge clip path to window height
                SKRect bounds;
                textPath.GetTightBounds(out bounds);

                canvas.Translate(info.Width / 2, info.Height / 2);
                canvas.Scale(info.Width / bounds.Width, info.Height / bounds.Height);
                canvas.Translate(-bounds.MidX, -bounds.MidY);

                // Set the clip path
                canvas.ClipPath(textPath);
            }
        }

        // Reset transforms
        canvas.ResetMatrix();

        // Display bitmap to fill window but maintain aspect ratio
        SKRect rect = new SKRect(0, 0, info.Width, info.Height);
        canvas.DrawBitmap(bitmap,
            rect.AspectFill(new SKSize(bitmap.Width, bitmap.Height)));
    }
}

Depois que o caminho de recorte estiver definido, o bitmap poderá ser exibido e será cortado nos contornos de caracteres. Observe o uso do método SKRect que calcula AspectFill um retângulo para preencher a página, preservando a proporção.

A página Efeito de Caminho de Texto converte um único caractere e comercial em um caminho para criar um efeito de caminho 1D. Um objeto de pintura com esse efeito de caminho é usado para traçar o contorno de uma versão maior desse mesmo caractere:

Captura de tela tripla da página Efeito de Caminho de Texto

Grande parte do trabalho na TextPathEffectPath classe ocorre nos campos e construtor. Os dois SKPaint objetos definidos como campos são usados para duas finalidades diferentes: O primeiro (nomeado textPathPaint) é usado para converter o e comercial com um TextSize de 50 em um caminho para o efeito de caminho 1D. O segundo (textPaint) é usado para exibir a versão maior do e comercial com esse efeito de caminho. Por esse motivo, o Style desse segundo objeto de pintura é definido como Stroke, mas a StrokeWidth propriedade não é definida porque essa propriedade não é necessária ao usar um efeito de caminho 1D:

public class TextPathEffectPage : ContentPage
{
    const string character = "@";
    const float littleSize = 50;

    SKPathEffect pathEffect;

    SKPaint textPathPaint = new SKPaint
    {
        TextSize = littleSize
    };

    SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Black
    };

    public TextPathEffectPage()
    {
        Title = "Text Path Effect";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;

        // Get the bounds of textPathPaint
        SKRect textPathPaintBounds = new SKRect();
        textPathPaint.MeasureText(character, ref textPathPaintBounds);

        // Create textPath centered around (0, 0)
        SKPath textPath = textPathPaint.GetTextPath(character,
                                                    -textPathPaintBounds.MidX,
                                                    -textPathPaintBounds.MidY);
        // Create the path effect
        pathEffect = SKPathEffect.Create1DPath(textPath, littleSize, 0,
                                               SKPath1DPathEffectStyle.Translate);
    }
    ...
}

O construtor primeiro usa o textPathPaint objeto para medir o e comercial com um TextSize de 50. Os negativos das coordenadas centrais desse retângulo são então passados para o GetTextPath método para converter o texto em um caminho. O caminho resultante tem o ponto (0, 0) no centro do caractere, o que é ideal para um efeito de caminho 1D.

Você pode pensar que o SKPathEffect objeto criado no final do construtor poderia ser definido como a PathEffect propriedade de em vez de textPaint salvo como um campo. Mas isso acabou não funcionando muito bem porque distorceu os resultados da MeasureText chamada no PaintSurface manipulador:

public class TextPathEffectPage : ContentPage
{
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        // Set textPaint TextSize based on screen size
        textPaint.TextSize = Math.Min(info.Width, info.Height);

        // Do not measure the text with PathEffect set!
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(character, ref textBounds);

        // Coordinates to center text on screen
        float xText = info.Width / 2 - textBounds.MidX;
        float yText = info.Height / 2 - textBounds.MidY;

        // Set the PathEffect property and display text
        textPaint.PathEffect = pathEffect;
        canvas.DrawText(character, xText, yText, textPaint);
    }
}

Essa MeasureText chamada é usada para centralizar o caractere na página. Para evitar problemas, a PathEffect propriedade é definida como o objeto paint depois que o texto é medido, mas antes de ser exibido.

Contornos de contornos de caracteres

Normalmente, o GetFillPath método de converte um caminho em outro aplicando propriedades de pintura, principalmente a largura do traçado e o efeito de SKPaint caminho. Quando usado sem efeitos de caminho, GetFillPath cria efetivamente um caminho que descreve outro caminho. Isso foi demonstrado na página Tocar para destacar o caminho no artigo Efeitos de caminho.

Você também pode chamar GetFillPath o caminho retornado GetTextPath , mas no início você pode não ter certeza de como seria.

A página Contornos da Estrutura de Tópicos de Caracteres demonstra a técnica. Todo o código relevante está no PaintSurface manipulador da CharacterOutlineOutlinesPage classe.

O construtor começa criando um SKPaint objeto nomeado textPaint com uma TextSize propriedade com base no tamanho da página. Isso é convertido em um caminho usando o GetTextPath método. Os argumentos de coordenadas para GetTextPath centralizar efetivamente o caminho na tela:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint textPaint = new SKPaint())
    {
        // Set Style for the character outlines
        textPaint.Style = SKPaintStyle.Stroke;

        // Set TextSize based on screen size
        textPaint.TextSize = Math.Min(info.Width, info.Height);

        // Measure the text
        SKRect textBounds = new SKRect();
        textPaint.MeasureText("@", ref textBounds);

        // Coordinates to center text on screen
        float xText = info.Width / 2 - textBounds.MidX;
        float yText = info.Height / 2 - textBounds.MidY;

        // Get the path for the character outlines
        using (SKPath textPath = textPaint.GetTextPath("@", xText, yText))
        {
            // Create a new path for the outlines of the path
            using (SKPath outlinePath = new SKPath())
            {
                // Convert the path to the outlines of the stroked path
                textPaint.StrokeWidth = 25;
                textPaint.GetFillPath(textPath, outlinePath);

                // Stroke that new path
                using (SKPaint outlinePaint = new SKPaint())
                {
                    outlinePaint.Style = SKPaintStyle.Stroke;
                    outlinePaint.StrokeWidth = 5;
                    outlinePaint.Color = SKColors.Red;

                    canvas.DrawPath(outlinePath, outlinePaint);
                }
            }
        }
    }
}

Em seguida, o PaintSurface manipulador cria um novo caminho chamado outlinePath. Isso se torna o caminho de destino na chamada para GetFillPath. A StrokeWidth propriedade de 25 faz com que outlinePath descreva a estrutura de tópicos de um caminho de 25 pixels de largura acariciando os caracteres de texto. Esse caminho é exibido em vermelho com uma largura de traçado de 5:

Captura de tela tripla da página Contornos de Contorno de Caracteres

Olhe atentamente e você verá sobreposições onde o contorno do caminho faz um canto nítido. São artefatos normais desse processo.

Texto ao longo de um caminho

O texto é normalmente exibido em uma linha de base horizontal. O texto pode ser girado para ser executado vertical ou diagonalmente, mas a linha de base ainda é uma linha reta.

Há momentos, no entanto, em que você deseja que o texto seja executado ao longo de uma curva. Este é o objetivo do DrawTextOnPath método de SKCanvas:

public Void DrawTextOnPath (String text, SKPath path, Single hOffset, Single vOffset, SKPaint paint)

O texto especificado no primeiro argumento é feito para ser executado ao longo do caminho especificado como o segundo argumento. Você pode começar o texto em um deslocamento do início do caminho com o hOffset argumento. Normalmente, o caminho forma a linha de base do texto: os ascendentes de texto estão de um lado do caminho e os descendentes de texto estão do outro. Mas você pode deslocar a linha de base de texto do caminho com o vOffset argumento.

Esse método não tem nenhuma facilidade para fornecer orientação sobre como definir a TextSize propriedade de para tornar o texto dimensionado SKPaint perfeitamente para ser executado do início ao fim do caminho. Às vezes, você pode descobrir esse tamanho de texto por conta própria. Outras vezes, você precisará usar funções de medição de caminho a serem descritas no próximo artigo sobre informações e enumeração de caminho.

O programa Texto Circular quebra o texto em torno de um círculo. É fácil determinar a circunferência de um círculo, por isso é fácil dimensionar o texto para se ajustar exatamente. O PaintSurface manipulador da CircularTextPage classe calcula um raio de um círculo com base no tamanho da página. Esse círculo torna-se circularPath:

public class CircularTextPage : ContentPage
{
    const string text = "xt in a circle that shapes the te";
    ...
    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPath circularPath = new SKPath())
        {
            float radius = 0.35f * Math.Min(info.Width, info.Height);
            circularPath.AddCircle(info.Width / 2, info.Height / 2, radius);

            using (SKPaint textPaint = new SKPaint())
            {
                textPaint.TextSize = 100;
                float textWidth = textPaint.MeasureText(text);
                textPaint.TextSize *= 2 * 3.14f * radius / textWidth;

                canvas.DrawTextOnPath(text, circularPath, 0, 0, textPaint);
            }
        }
    }
}

A TextSize propriedade de é então ajustada para que a largura do textPaint texto corresponda à circunferência do círculo:

Captura de tela tripla da página Texto Circular

O texto em si foi escolhido para ser um pouco circular também: a palavra "círculo" é tanto o sujeito da frase quanto o objeto de uma frase preposicional.