Поделиться через


Пути и текст в SkiaSharp

Изучение пересечения путей и текста

В современных графических системах текстовые шрифты представляют собой коллекции контуров символов, как правило, определяются квадратичными кривыми Bézier. Следовательно, многие современные графические системы включают в себя объект для преобразования текстовых символов в графический путь.

Вы уже видели, что можно обчеркить контуры текстовых символов, а также заполнить их. Это позволяет отображать эти контуры символов с определенной шириной штриха и даже эффектом пути, как описано в статье "Эффекты пути". Но также можно преобразовать символьную строку в SKPath объект. Это означает, что контуры текста можно использовать для обрезки с помощью методов, описанных в статье "Обрезка с путями и регионами ".

Помимо использования эффекта пути для росчерка контура символов, можно также создавать эффекты пути, основанные на пути, который является производным от строки символов, и вы можете даже объединить два эффекта:

Эффект текстового пути

В предыдущей статье о эффектах пути вы узнали, как GetFillPath метод SKPaint может получить контур штрихового пути. Этот метод также можно использовать с путями, производными от контуров символов.

Наконец, в этой статье демонстрируется еще одно пересечение путей и текста: DrawTextOnPath метод SKCanvas позволяет отображать текстовую строку таким образом, чтобы базовый план текста следил за кривым путем.

Преобразование текста в путь

Метод GetTextPathSKPaint преобразует символьную строку в SKPath объект:

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

y Аргументы x указывают начальную точку базового плана левой части текста. Они играют ту же роль здесь, что и в методе DrawTextSKCanvas. В пути базовый план левой части текста будет иметь координаты (x, y).

Метод GetTextPath переклинается, если вы просто хотите заполнить или обвести результирующий путь. Обычный DrawText метод позволяет сделать это. Этот GetTextPath метод более полезен для других задач, связанных с путями.

Одна из этих задач — вырезка. Страница "Вырезка текста " создает путь вырезки на основе контуров символов слова "CODE". Этот путь растянут до размера страницы, чтобы закрепить растровое изображение, содержащее изображение исходного кода вырезки текста :

Тройной снимок экрана страницы

Конструктор ClippingTextPage класса загружает растровое изображение, которое хранится в качестве внедренного ресурса в папке мультимедиа решения:

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 объект используется исключительно для GetTextPath вызова с помощью текстовой строки "CODE". Затем обработчик измеряет результирующий 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) используется для преобразования амперсанда с TextSize 50 в путь для эффекта 1D-пути. Второй (textPaint) используется для отображения более крупной версии амперсанда с этим эффектом пути. По этой причине для этого второго объекта краски задано Strokeзначение, но StrokeWidth свойство не задано, Style так как это свойство не требуется при использовании эффекта 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 объект для измерения амперсанда с TextSize 50. Затем отрицательные координаты центра этого прямоугольника передаются GetTextPath методу для преобразования текста в путь. Результирующий путь имеет точку (0, 0) в центре символа, которая идеально подходит для эффекта 1D-пути.

Возможно, SKPathEffect объект, созданный в конце конструктора, может быть задан свойством PathEffecttextPaint вместо сохранения в качестве поля. Но это оказалось не работать очень хорошо, потому что оно исказило результаты MeasureText вызова в обработчике 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);
    }
}

Этот MeasureText вызов используется для центра символа на странице. Чтобы избежать проблем, PathEffect свойство присваивается объекту краски после измерения текста, но до его отображения.

Контуры символьных контуров

GetFillPath Обычно метод преобразует один путь в другой путем применения свойств краски, в частности ширины штриха SKPaint и эффекта пути. При использовании без эффектов GetFillPath пути эффективно создает путь, описывающий другой путь. Это было показано на странице "Контур пути" в статье "Эффекты пути".

Вы также можете вызвать GetFillPath путь, возвращенный из GetTextPath , но сначала вы не можете быть полностью уверены, что это будет выглядеть.

Страница "Структура символов " демонстрирует метод. Весь соответствующий код находится в 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:

Снимок экрана: страница

Внимательно посмотрите, и вы увидите перекрытия, где контур пути делает острый угол. Это обычные артефакты этого процесса.

Текст по пути

Текст обычно отображается на горизонтальном базовом уровне. Текст можно повернуть для вертикального или диагонали, но базовый план по-прежнему является прямой линией.

Однако есть времена, когда требуется, чтобы текст выполнялся вдоль кривой. Это цель DrawTextOnPath метода SKCanvas:

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

Текст, указанный в первом аргументе, выполняется по пути, указанному в качестве второго аргумента. Текст можно начать с смещения с начала пути с аргументом hOffset . Обычно путь формирует базовый план текста: по возрастанию текста находятся на одной стороне пути, а нисходящие тексты находятся в другой. Но можно смещать текстовые базовые показатели из пути с помощью аргумента vOffset .

Этот метод не имеет возможности предоставить рекомендации по настройке TextSize свойства SKPaint , чтобы размер текста идеально выполнялся с начала пути к концу. Иногда вы можете определить размер текста самостоятельно. В других случаях необходимо использовать функции измерения пути, описанные в следующей статье о сведениях о пути и перечислении.

Программа циклического текста упаковывает текст вокруг круга. Это легко определить окружность круга, поэтому легко размер текста, чтобы точно соответствовать. Обработчик PaintSurfaceCircularTextPage класса вычисляет радиус круга на основе размера страницы. Этот круг становится 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 настраивается таким образом, чтобы ширина текста соответствовала окружности круга:

Тройной снимок экрана страницы

Сам текст был выбран, чтобы быть несколько циклическим, а также: слово "круг" является как предметом предложения, так и объектом предварительной фразы.