Trazados y texto en SkiaSharp
Exploración de la intersección de trazados y texto
En los sistemas de gráficos modernos, las fuentes de texto son colecciones de contornos de caracteres, normalmente definidas por curvas de Bézier cuadráticas. Por lo tanto, muchos sistemas de gráficos modernos incluyen una función para convertir caracteres de texto en un trazado de gráficos.
Ya ha visto que puede trazar los contornos de los caracteres de texto, así como rellenarlos. Esto le permite mostrar estos contornos de caracteres con un ancho de trazo determinado e incluso un efecto de trazado, tal como se describe en el artículo Efectos del trazado. Pero también es posible convertir una cadena de caracteres en un objeto SKPath
. Esto significa que los contornos de texto se pueden usar para recortar con técnicas que se describen en el artículo Recorte con trazados y regiones.
Además de usar un efecto de trazado para trazar un contorno de caracteres, también puede crear efectos de trazado basados en un trazado derivado de una cadena de caracteres, e incluso puede combinar ambos efectos:
En el artículo anterior Efectos del trazado, vio cómo el método GetFillPath
de SKPaint
puede obtener un esquema de un trazado. También puede usar este método con trazados derivados de esquemas de caracteres.
Por último, en este artículo se muestra otra intersección de trazados y texto: el método DrawTextOnPath
de SKCanvas
permite mostrar una cadena de texto para que la línea base del texto siga un trazado curvo.
Conversión de texto a trazado
El método GetTextPath
de SKPaint
convierte una cadena de caracteres en un objeto SKPath
:
public SKPath GetTextPath (String text, Single x, Single y)
Los argumentos x
y y
indican el punto inicial de la línea base del lado izquierdo del texto. Desempeñan el mismo papel que en el método DrawText
de SKCanvas
. Dentro del trazado, la línea base del lado izquierdo del texto tendrá las coordenadas (x, y).
El método GetTextPath
es excesivo si solo desea rellenar o crear el trazado resultante. El método DrawText
normal le permite hacerlo. El método GetTextPath
es más útil para otras tareas que implican trazados.
Una de estas tareas es la de recortar. La página Clipping Text (Recorte de texto) crea un trazado de recorte basado en los contornos de caracteres de la palabra "CODE". Este trazado se extiende al tamaño de la página para recortar un mapa de bits que contenga una imagen del código fuente de Clipping Text:
El constructor de clase ClippingTextPage
carga el mapa de bits que se almacena como un recurso incrustado en la carpeta Media de la solución:
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);
}
}
...
}
El controlador PaintSurface
comienza creando un objeto SKPaint
adecuado para el texto. Se establece la propiedad Typeface
, así como TextSize
, aunque para esta aplicación determinada la propiedad TextSize
es puramente arbitraria. Observe también que no hay ninguna configuración Style
.
La configuración de las propiedades TextSize
y Style
no es necesaria porque este objeto SKPaint
se usa únicamente para la llamada GetTextPath
mediante la cadena de texto "CODE". A continuación, el controlador mide el objeto SKPath
resultante y aplica tres transformaciones para centrarlo y escalarlo al tamaño de la página. A continuación, el trazado se puede establecer como trazado 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)));
}
}
Una vez establecido el trazado de recorte, se puede mostrar el mapa de bits y se recortará en los contornos de caracteres. Observe el uso del método AspectFill
de SKRect
que calcula un rectángulo para rellenar la página mientras conserva la relación de aspecto.
La página Text Path Effect (Efecto del trazado de texto) convierte un solo carácter & en un trazado para crear un efecto de trazado 1D. A continuación, se usa un objeto paint con este efecto de trazado para trazar el contorno de una versión más grande de ese mismo carácter:
Gran parte del trabajo de la clase TextPathEffectPath
se produce en los campos y en el constructor. Los dos objetos SKPaint
definidos como campos se usan con dos propósitos diferentes: el primero (denominado textPathPaint
) sirve para convertir el carácter & con un TextSize
de 50 a un trazado para el efecto de trazado 1D. El segundo (textPaint
) sirve para mostrar la versión más grande del carácter & con ese efecto de trazado. Por ese motivo, el Style
de este segundo objeto paint se establece en Stroke
, pero la propiedad StrokeWidth
no se establece porque no es necesaria cuando se usa un efecto de trazado 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);
}
...
}
El constructor usa primero el objeto textPathPaint
para medir el carácter & con un TextSize
de 50. Los negativos de las coordenadas centrales de ese rectángulo se pasan al método GetTextPath
para convertir el texto en un trazado. El trazado resultante tiene el punto (0, 0) en el centro del carácter, que es ideal para un efecto de trazado 1D.
Es posible que piense que el objeto SKPathEffect
creado al final del constructor podría establecerse en la propiedad PathEffect
de textPaint
, en lugar de guardarse como un campo. Pero esto resultó no funcionar muy bien porque distorsionaba los resultados de la llamada MeasureText
en el controlador PaintSurface
:
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);
}
}
Esa llamada MeasureText
sirve para centrar el carácter en la página. Para evitar problemas, la propiedad PathEffect
se establece en el objeto paint después de medir el texto, pero antes de mostrarse.
Esquemas de contornos de caracteres
Normalmente, el método GetFillPath
de SKPaint
convierte un trazado a otro aplicando propiedades paint, especialmente el ancho del trazo y el efecto de trazado. Cuando se usa sin efectos de trazado, GetFillPath
crea eficazmente un trazado que describe otro trazado. Esto se mostró en la página Tap to Outline the Path (Pulsar para describir el trazado) del artículo Efectos del trazado.
También puede llamar a GetFillPath
en el trazado devuelto desde GetTextPath
, pero al principio es posible que no esté completamente seguro de cómo sería.
En la página Esquemas de contornos de caracteres se muestra la técnica. Todo el código pertinente está en el controlador PaintSurface
de la clase CharacterOutlineOutlinesPage
.
El constructor comienza creando un objeto SKPaint
denominado textPaint
con una propiedad TextSize
basada en el tamaño de la página. Se convierte en un trazado mediante el método GetTextPath
. Los argumentos de coordenadas en GetTextPath
centran eficazmente el trazado en la pantalla:
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);
}
}
}
}
}
A continuación, el controlador PaintSurface
crea otro trazado denominado outlinePath
. Se convierte en el trazado de destino de la llamada a GetFillPath
. La propiedad StrokeWidth
de 25 hace que outlinePath
describa el contorno de un trazado de 25 píxeles trazando los caracteres del texto. A continuación, este trazado se muestra en rojo con un ancho de trazo de 5:
Mire detenidamente y verá superposiciones en las que el contorno del trazado crea una esquina afilada. Estos son artefactos normales de este proceso.
Texto a lo largo de un trazado
Normalmente, el texto se muestra en una línea base horizontal. El texto se puede girar para ejecutarse vertical o diagonalmente, pero la línea base sigue siendo una línea recta.
Sin embargo, hay ocasiones en las que desea que el texto se ejecute a lo largo de una curva. Este es el propósito del método DrawTextOnPath
de SKCanvas
:
public Void DrawTextOnPath (String text, SKPath path, Single hOffset, Single vOffset, SKPaint paint)
El texto especificado en el primer argumento se realiza para ejecutarse a lo largo del trazado especificado como segundo argumento. Puede comenzar el texto en un desplazamiento desde el principio del trazado con el argumento hOffset
. Normalmente, el trazado forma la línea base del texto: los ascendentes de texto están en un lado del trazado y los descendientes, en el otro. Pero puede desplazar la línea base del texto desde el trazado con el argumento vOffset
.
Este método no tiene ninguna facilidad para proporcionar instrucciones sobre cómo establecer la propiedad TextSize
de SKPaint
para que el texto tenga el tamaño perfecto para ejecutarse desde el principio del trazado hasta el final. A veces, puede averiguar el tamaño del texto por su cuenta. En otras ocasiones, deberá usar las funciones de medición de trazado que se describen en el siguiente artículo, Enumeración e información de trazado.
El programa Circular Text (Texto circular) ajusta el texto alrededor de un círculo. Es fácil determinar la circunferencia de un círculo, por lo que es fácil ajustar el tamaño del texto exactamente. El controlador PaintSurface
de la clase CircularTextPage
calcula un radio de un círculo en función del tamaño de la página. Ese círculo se convierte en 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 continuación, se ajusta la propiedad TextSize
de textPaint
para que el ancho del texto coincida con la circunferencia del círculo:
El texto también se ha elegido para que sea circular: la palabra "circle" (círculo) es a la vez sujeto de la frase y objeto de una frase preposicional.