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:
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 :
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:
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:
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:
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.