// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform.Linux.Services;
using SkiaSharp;
namespace Microsoft.Maui.Platform.Linux.Rendering;
///
/// Shared text rendering utilities extracted from SkiaEntry, SkiaEditor, and SkiaLabel
/// to eliminate code duplication for common text rendering operations.
///
public static class TextRenderingHelper
{
///
/// Draws text with font fallback for emoji, CJK, and other scripts.
/// Uses FontFallbackManager to shape text across multiple typefaces when needed.
///
public static void DrawTextWithFallback(SKCanvas canvas, string text, float x, float y, SKPaint paint, SKTypeface preferredTypeface, float fontSize)
{
if (string.IsNullOrEmpty(text))
{
return;
}
// Use FontFallbackManager for mixed-script text
var runs = FontFallbackManager.Instance.ShapeTextWithFallback(text, preferredTypeface);
if (runs.Count <= 1)
{
// Single run or no fallback needed - draw directly
canvas.DrawText(text, x, y, paint);
return;
}
// Multiple runs with different fonts
float currentX = x;
foreach (var run in runs)
{
using var runFont = new SKFont(run.Typeface, fontSize);
using var runPaint = new SKPaint(runFont)
{
Color = paint.Color,
IsAntialias = true
};
canvas.DrawText(run.Text, currentX, y, runPaint);
currentX += runPaint.MeasureText(run.Text);
}
}
///
/// Draws underline for IME pre-edit (composition) text.
/// Renders a dashed underline beneath the pre-edit text region.
///
public static void DrawPreEditUnderline(SKCanvas canvas, SKPaint paint, string displayText, int cursorPosition, string preEditText, float x, float y)
{
// Calculate pre-edit text position
var textToCursor = displayText.Substring(0, Math.Min(cursorPosition, displayText.Length));
var preEditStartX = x + paint.MeasureText(textToCursor);
var preEditEndX = preEditStartX + paint.MeasureText(preEditText);
// Draw dotted underline to indicate composition
using var underlinePaint = new SKPaint
{
Color = paint.Color,
StrokeWidth = 1,
IsAntialias = true,
PathEffect = SKPathEffect.CreateDash(new float[] { 3, 2 }, 0)
};
var underlineY = y + 2;
canvas.DrawLine(preEditStartX, underlineY, preEditEndX, underlineY, underlinePaint);
}
///
/// Converts a MAUI Color to SkiaSharp SKColor for rendering.
/// Returns the specified default color when the input color is null.
///
public static SKColor ToSKColor(Color? color, SKColor defaultColor = default)
{
if (color == null) return defaultColor;
return color.ToSKColor();
}
///
/// Converts FontAttributes to the corresponding SKFontStyle.
///
public static SKFontStyle GetFontStyle(FontAttributes attributes)
{
bool isBold = attributes.HasFlag(FontAttributes.Bold);
bool isItalic = attributes.HasFlag(FontAttributes.Italic);
if (isBold && isItalic)
return SKFontStyle.BoldItalic;
if (isBold)
return SKFontStyle.Bold;
if (isItalic)
return SKFontStyle.Italic;
return SKFontStyle.Normal;
}
///
/// Gets the effective font family, returning "Sans" as the platform default when empty.
///
public static string GetEffectiveFontFamily(string? fontFamily)
{
return string.IsNullOrEmpty(fontFamily) ? "Sans" : fontFamily;
}
}