探索路徑和文字的交集
在現代圖形系統中,文字字型是字元外框的集合,通常是由二次方貝塞爾曲線所定義。 因此,許多新式圖形系統包含可將文字字元轉換成圖形路徑的工具。
您已經看到您可以筆劃文字字元的外框,並填滿它們。 這可讓您使用特定的筆劃寬度,甚至是路徑效果一文中所述的路徑效果來顯示這些字元外框。 但您也可以將字元字串轉換成 SKPath 物件。 這表示文字外框可用來裁剪與使用路徑和區域裁剪一文中所述的技術。
除了使用路徑效果來筆劃字元外框之外,您也可以根據衍生自字元字串的路徑來建立路徑效果,甚至可以結合這兩個效果:

在上一篇關於路徑效果的文章中,您已瞭解如何GetFillPathSKPaint取得筆劃路徑的大綱。 您也可以使用此方法搭配衍生自字元外框的路徑。
最後,本文示範路徑和文字的另一個交集: DrawTextOnPath 的 方法 SKCanvas 可讓您顯示文字字串,讓文字的基準遵循曲線路徑。
文字到路徑轉換
將GetTextPath字元字串SKPath轉換成物件的 方法SKPaint:
public SKPath GetTextPath (String text, Single x, Single y)
x和 y 自變數表示文字左側基準的起點。 它們在這裡扮演的角色與 在的方法SKCanvas中DrawText相同。 在路徑內,文字左側的基準會有座標 (x, y)。
如果您只是想要填滿或筆劃結果路徑,則 GetTextPath 方法會過度使用。 一般 DrawText 方法可讓您執行此動作。 此方法 GetTextPath 對涉及路徑的其他工作更有用。
其中一項工作正在裁剪。 [裁剪文字] 頁面會根據 “CODE” 一詞的字元外框來建立裁剪路徑。此路徑會延展至頁面大小,以裁剪包含裁剪文字原始碼影像的點陣圖:
類別建ClippingTextPage構函式會載入儲存為解決方案之 Media 資料夾中內嵌資源的點陣圖:
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);
}
}
...
}
處理程式 PaintSurface 會從建立 SKPaint 適合文字的對象開始。 屬性 Typeface 會設定和 TextSize,雖然對於這個特定應用程式, TextSize 屬性純粹是任意的。 另請注意,沒有 Style 設定。
TextSize和 Style 屬性設定並非必要,因為此SKPaint物件僅用於使用文字字串 「CODE」 的GetTextPath呼叫。 處理程式接著會測量結果 SKPath 物件,並套用三個轉換將它置中,並將其調整為頁面的大小。 然後,可以將路徑設定為裁剪路徑:
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)));
}
}
設定裁剪路徑之後,即可顯示點陣圖,並將它裁剪到字元外框。 請注意,使用 AspectFill 的方法來 SKRect 計算矩形以填滿頁面,同時保留外觀比例。
[ 文字路徑效果 ] 頁面會將單一連字元轉換成路徑,以建立 1D 路徑效果。 然後,使用此路徑效果的繪製物件,用來繪製相同字元較大版本的外框:
類別中的 TextPathEffectPath 大部分工作都發生在欄位和建構函式中。 定義為欄位的兩 SKPaint 個對象用於兩個不同的用途:第一個物件會 textPathPaint用來將 ampers 和 TextSize 50 轉換成 1D 路徑效果的路徑。 第二個 (textPaint) 用來顯示較大型版本的 ampersand 與該路徑效果。 基於這個理由, Style 這個第二個繪製物件的 會設定為 Stroke,但 StrokeWidth 不會設定 屬性,因為使用 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);
}
...
}
建構函式會先使用 textPathPaint 對象來測量 50 的 ampersand TextSize 。 然後,該矩形的中心座標負數會傳遞至 GetTextPath 方法,將文字轉換成路徑。 結果路徑在字元中央有 (0, 0) 點,非常適合 1D 路徑效果。
您可能會認為 SKPathEffect 建構函式結尾建立的物件可以設定為 PathEffect 的屬性 textPaint ,而不是儲存為字段。 但事實證明,這無法正常運作,因為它扭曲了處理程式中PaintSurface呼叫的結果MeasureText:
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);
}
}
該 MeasureText 呼叫用來將頁面上的字元置中。 為了避免問題, PathEffect 屬性會在測量文字之後,但在顯示之前,設定為繪製物件。
字元大綱的大綱
一般而言,套 GetFillPath 用繪製屬性,將一個路徑轉換成另一個路徑的方法 SKPaint ,尤其是筆劃寬度和路徑效果。 在沒有路徑效果的情況下使用時, GetFillPath 可有效地建立概述另一個路徑的路徑。 這已在 [路徑效果] 文章的 [點選至大綱路徑] 頁面中示範。
您也可以在從 GetTextPath 傳回的路徑上呼叫 GetFillPath ,但起初您可能不確定其外觀。
[ 字元大綱大綱 ] 頁面示範這項技術。 所有相關程序代碼都在 PaintSurface 類別的處理程式中 CharacterOutlineOutlinesPage 。
建構函式一 SKPaint 開始會根據頁面的大小,建立名為 textPaint 的對象與 TextSize 屬性。 這會使用 GetTextPath 方法轉換成路徑。 座標自變數,可 GetTextPath 有效地將畫面上的路徑置中:
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);
}
}
}
}
}
處理程序 PaintSurface 接著會建立名為 outlinePath的新路徑。 這會成為呼叫 GetFillPath中的目的地路徑。 StrokeWidth 25 的 屬性會outlinePath描述文字字元的 25 像素寬路徑外框。 然後,此路徑會以紅色顯示,筆觸寬度為 5:
仔細查看,您會看到路徑外框變成尖角的重疊。 這些是此程式的一般成品。
沿著路徑的文字
文字通常會顯示在水平基準上。 文字可以垂直或對角旋轉,但基準仍然是直線。
不過,有時候,您希望文字沿著曲線執行。 這是 方法的DrawTextOnPathSKCanvas用途:
public Void DrawTextOnPath (String text, SKPath path, Single hOffset, Single vOffset, SKPaint paint)
第一個自變數中指定的文字會沿著指定為第二個自變數的路徑執行。 您可以使用 自變數,從路徑 hOffset 開頭的位移開始文字。 路徑通常會形成文字的基準:文字遞增器位於路徑的一邊,而文字下階則位於另一端。 但是,您可以使用 自變數,從路徑 vOffset 位移文字基準。
這個方法沒有提供設定 屬性SKPaint的TextSize指引,讓文字大小從路徑的開頭到結尾完美地執行。 有時候,您可以自行找出該文字大小。 其他時候,您必須使用路徑測量函數,以在下一篇文章中 說明路徑資訊和列舉。
迴圈文字程式會將文字包裝在圓形周圍。 很容易判斷圓形的周長,因此很容易調整文字的大小,以完全符合。 類別 PaintSurface 的 CircularTextPage 處理程式會根據頁面的大小計算圓形的半徑。 該圓形會 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);
}
}
}
}
接著會 TextSize 調整 的 textPaint 屬性,讓文字寬度符合圓形的周長:
文字本身也選擇有點圓形:「圓形」一詞既是句子的主旨,也是片語的物件。


![[字元大綱] 頁面的三重螢幕快照](text-paths-images/characteroutlineoutlines-small.png)
