SkiaSharp'ta Yollar ve Metin

Yolların ve metnin kesişimini keşfetme

Modern grafik sistemlerinde metin yazı tipleri genellikle ikinci dereceden Bézier eğrileri tarafından tanımlanan karakter ana hatlarından oluşan koleksiyonlardır. Sonuç olarak, birçok modern grafik sistemi metin karakterlerini grafik yoluna dönüştürmek için bir özellik içerir.

Metin karakterlerinin ana hatlarını konturlayabileceğinizi ve doldurabileceğinizi zaten gördünüz. Bu, yol efektleri makalesinde açıklandığı gibi bu karakter ana hatlarını belirli bir vuruş genişliği ve hatta yol efektiyle görüntülemenizi sağlar. Ancak bir karakter dizesini nesneye SKPath dönüştürmek de mümkündür. Bu, metin ana hatlarının Yollar ve Bölgeler ile Kırpma makalesinde açıklanan tekniklerle kırpma için kullanılabileceğini gösterir.

Karakter ana hattına vuruş yapmak için yol efekti kullanmanın yanı sıra, karakter dizesinden türetilen bir yolu temel alan yol efektleri de oluşturabilir ve hatta iki efekti birleştirebilirsiniz:

Metin Yolu Efekti

Yol Efektleri ile ilgili önceki makalede, yönteminin GetFillPathSKPaint vuruşlu bir yolun ana hattını nasıl edinebileceğini gördünüz. Bu yöntemi karakter ana hatlarından türetilen yollarla da kullanabilirsiniz.

Son olarak, bu makalede yollar ve metnin başka bir kesişimi gösterilmektedir: DrawTextOnPath yöntemi SKCanvas , metnin taban çizgisinin eğri bir yolu izlemesi için bir metin dizesi görüntülemenizi sağlar.

Metinden Yola Dönüştürme

GetTextPath yöntemi SKPaint bir karakter dizesini nesneye SKPath dönüştürür:

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

x ve y bağımsız değişkenleri, metnin sol tarafının taban çizgisinin başlangıç noktasını gösterir. Burada yönteminde DrawTextSKCanvasolduğu gibi aynı rolü oynarlar. Yol içinde, metnin sol tarafının taban çizgisi koordinatlara (x, y) sahip olur.

Yalnızca sonuçta elde edilen GetTextPath yolu doldurmak veya konturlamak istiyorsanız, yöntem fazlalıktır. Normal DrawText yöntem bunu yapmanızı sağlar. GetTextPath yöntemi, yollar içeren diğer görevler için daha kullanışlıdır.

Bu görevlerden biri kırpmadır. Kırpma Metni sayfası, "CODE" sözcüğünün karakter ana hatlarını temel alan bir kırpma yolu oluşturur. Bu yol, Kırpma Metni kaynak kodunun görüntüsünü içeren bir bit eşlemi kırpmak için sayfanın boyutuna kadar uzatılır:

Kırpma Metni sayfasının üç kez ekran görüntüsü

Sınıf oluşturucu, ClippingTextPage çözümün Media klasöründe katıştırılmış kaynak olarak depolanan bit eşlemi yükler:

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

İşleyici, PaintSurface metne uygun bir SKPaint nesne oluşturarak başlar. Typeface özelliği ve TextSizeolarak ayarlanır, ancak bu uygulama TextSize için özellik tamamen rastgeledir. Ayrıca ayar olmadığına Style da dikkat edin.

TextSizeStyle Ve özellik ayarları gerekli değildir çünkü bu SKPaint nesne yalnızca "CODE" metin dizesi kullanılarak çağrı için GetTextPath kullanılır. İşleyici daha sonra sonuçta SKPath elde edilen nesneyi ölçer ve ortalayıp sayfanın boyutuna ölçeklendirmek için üç dönüşüm uygular. Yol daha sonra kırpma yolu olarak ayarlanabilir:

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

Kırpma yolu ayarlandıktan sonra bit eşlem görüntülenebilir ve karakter ana hatlarına kırpılır. En boy oranını korurken sayfayı AspectFill doldurmak için bir dikdörtgen hesaplayan yönteminin SKRect kullanıldığına dikkat edin.

Metin Yolu Efekti sayfası, 1B yol efekti oluşturmak için tek bir ve işareti karakterini yola dönüştürür. Daha sonra bu yol efektine sahip bir paint nesnesi, aynı karakterin daha büyük bir sürümünün ana hattını vuruş yapmak için kullanılır:

Metin Yolu Efekti sayfasının üçlü ekran görüntüsü

Sınıfındaki işin TextPathEffectPath büyük bir kısmı alanlarda ve oluşturucuda gerçekleşir. Alan olarak tanımlanan iki SKPaint nesne iki farklı amaç için kullanılır: İlk (adlandırılmış textPathPaint) ve işareti TextSize 50 olan ve işaretini 1B yol efekti için bir yola dönüştürmek için kullanılır. İkinci (textPaint), ve işaretin daha büyük sürümünü bu yol etkisiyle görüntülemek için kullanılır. Bu nedenle, Style bu ikinci paint nesnesinin öğesi olarak ayarlanır Stroke, ancak StrokeWidth 1B yol efekti kullanılırken bu özellik gerekli olmadığından özelliği ayarlanmaz:

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

Oluşturucu ilk olarak ve işaretini 50 ile TextSize ölçmek için nesnesini kullanırtextPathPaint. Bu dikdörtgenin orta koordinatlarının negatifleri, metni yola dönüştürmek için yöntemine geçirilir GetTextPath . Sonuçta elde edilen yol, karakterin ortasında (0, 0) noktasına sahiptir ve bu da 1B yol efekti için idealdir.

Oluşturucunun SKPathEffect sonunda oluşturulan nesnenin alan olarak kaydedilmek yerine özelliğine PathEffect ayarlanabileceğini textPaint düşünebilirsiniz. Ancak bu, işleyicideki PaintSurface çağrının MeasureText sonuçlarını çarpıttığı için çok iyi çalışmadığı ortaya çıktı:

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

Bu MeasureText çağrı, sayfadaki karakteri ortalamak için kullanılır. Sorunlardan kaçınmak için, PathEffect metin ölçüldükten sonra ancak görüntülenmeden önce özelliği paint nesnesine ayarlanır.

Karakter Ana Hatlarının Ana Hatları

Normalde yöntemi GetFillPathSKPaint , en önemlisi vuruş genişliği ve yol efekti olmak üzere boya özelliklerini uygulayarak bir yolu diğerine dönüştürür. Yol efektleri olmadan kullanıldığında, GetFillPath etkili bir şekilde başka bir yolu ana hatlarıyla gösteren bir yol oluşturur. Bu, Yol Efektleri makalesinin Yol Ana Hatlarına Dokunun sayfasında gösterilmiştir.

Ayrıca, içinden GetTextPath döndürülen yolu da çağırabilirsinizGetFillPath, ancak ilk başta bunun nasıl görüneceğinden tam olarak emin olmayabilirsiniz.

Karakter Ana Hatları sayfası tekniği gösterir. Tüm ilgili kod sınıfının işleyicisindedir PaintSurfaceCharacterOutlineOutlinesPage .

Oluşturucu, sayfanın boyutuna göre bir özellik ile adlı textPaint bir TextSize nesne oluşturarak SKPaint başlar. Bu yöntem kullanılarak GetTextPath bir yola dönüştürülür. Yolu ekranda etkili bir şekilde ortalamak için GetTextPath koordinat bağımsız değişkenleri:

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

İşleyici PaintSurface daha sonra adlı outlinePathyeni bir yol oluşturur. Bu, çağrısında GetFillPathhedef yol olur. StrokeWidth 25 özelliğinin, metin karakterlerini tuşlayan 25 piksel genişliğinde bir yolun ana hattını tanımlamasına neden oluroutlinePath. Bu yol daha sonra 5 vuruş genişliğine sahip kırmızı olarak görüntülenir:

Karakter Ana Hatları sayfasının üçlü ekran görüntüsü

Yakından baktığınızda, yol ana hattının keskin bir köşe yaptığı örtüşmeler görürsünüz. Bunlar bu işlemin normal yapıtlarıdır.

Yol Boyunca Metin

Metin normalde yatay taban çizgisinde görüntülenir. Metin dikey veya çapraz çalışacak şekilde döndürülebilir, ancak taban çizgisi hala düz bir çizgidir.

Ancak, metnin eğri boyunca çalışmasını istediğiniz zamanlar vardır. yönteminin DrawTextOnPathSKCanvasamacı budur:

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

İlk bağımsız değişkende belirtilen metin, ikinci bağımsız değişken olarak belirtilen yol boyunca çalışacak şekilde yapılır. Metni, bağımsız değişkeniyle yolun başından itibaren bir uzaklıkta hOffset başlatabilirsiniz. Normalde yol metnin taban çizgisini oluşturur: Metin artanları yolun bir tarafında, metin alt öğeleri ise diğer tarafındadır. Ancak, bağımsız değişkenle vOffset metin taban çizgisini yoldan kaydırabilirsiniz.

Bu yöntemin, özelliğini ayarlayarak metnin yolun başından sonuna kadar çalışacak şekilde mükemmel boyutlandırılacak şekilde ayarlanması TextSizeSKPaint konusunda rehberlik sağlayacak bir özelliği yoktur. Bazen bu metin boyutunu kendiniz anlayabilirsiniz. Diğer zamanlarda Yol Bilgileri ve Numaralandırma ile ilgili bir sonraki makalede açıklanacak yol ölçme işlevlerini kullanmanız gerekir.

Dairesel Metin programı, metni dairenin çevresinde kaydırıyor. Dairenin çevresini belirlemek kolaydır, bu nedenle metni tam olarak sığacak şekilde boyutlandırmak kolaydır. PaintSurface sınıfının işleyicisiCircularTextPage, sayfanın boyutuna göre bir dairenin yarıçapını hesaplar. Bu daire olur 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);
            }
        }
    }
}

Özelliği TextSizetextPaint daha sonra metin genişliğinin dairenin çevresi ile eşleşmesi için ayarlanır:

Döngüsel Metin sayfasının üç kez ekran görüntüsü

Metnin kendisi de biraz dairesel olacak şekilde seçilmiştir: "daire" sözcüğü hem cümlenin konusu hem de edat tümceciği nesnesidir.