From c177c15c8f6271b9b0b03fb7d8abea9481f06855 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Tue, 22 Apr 2025 01:04:49 +0200 Subject: [PATCH 1/3] feat: Add TextMetrics to API --- build/Build.LibAlphaSkia.cs | 4 +- lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs | 10 +- lib/dotnet/AlphaSkia/AlphaSkiaTextMetrics.cs | 73 ++++++ lib/dotnet/AlphaSkia/AlphaSkiaTextStyle.cs | 2 +- lib/dotnet/AlphaSkia/GlobalUsings.cs | 3 +- lib/dotnet/AlphaSkia/NativeMethods.cs | 56 +++- .../alphaTab_alphaSkia_AlphaSkiaCanvas.h | 6 +- .../alphaTab_alphaSkia_AlphaSkiaTextMetrics.h | 117 +++++++++ lib/java/jni/src/AlphaSkiaCanvas.cpp | 40 ++- lib/java/jni/src/AlphaSkiaTextMetrics.cpp | 124 +++++++++ lib/java/jni/src/AlphaSkiaTextStyle.cpp | 6 +- .../alphaTab/alphaSkia/AlphaSkiaCanvas.java | 10 +- .../alphaSkia/AlphaSkiaTextMetrics.java | 75 ++++++ lib/node/addon/addon.cpp | 242 ++++++++++++++++-- lib/node/alphaskia/src/AlphaSkiaCanvas.ts | 13 +- .../alphaskia/src/AlphaSkiaTextMetrics.ts | 100 ++++++++ lib/node/alphaskia/src/addon.ts | 17 +- wrapper/include/AlphaSkiaCanvas.h | 48 +++- wrapper/include/alphaskia.h | 33 ++- wrapper/src/AlphaSkiaCanvas.cpp | 28 +- wrapper/src/alphaskia_canvas.cpp | 11 +- wrapper/src/alphaskia_text_metrics.cpp | 61 +++++ ...textstyle.cpp => alphaskia_text_style.cpp} | 26 +- 23 files changed, 1008 insertions(+), 97 deletions(-) create mode 100644 lib/dotnet/AlphaSkia/AlphaSkiaTextMetrics.cs create mode 100644 lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaTextMetrics.h create mode 100644 lib/java/jni/src/AlphaSkiaTextMetrics.cpp create mode 100644 lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaTextMetrics.java create mode 100644 lib/node/alphaskia/src/AlphaSkiaTextMetrics.ts create mode 100644 wrapper/src/alphaskia_text_metrics.cpp rename wrapper/src/{alphaskia_textstyle.cpp => alphaskia_text_style.cpp} (72%) diff --git a/build/Build.LibAlphaSkia.cs b/build/Build.LibAlphaSkia.cs index fbc0273..b2801e7 100644 --- a/build/Build.LibAlphaSkia.cs +++ b/build/Build.LibAlphaSkia.cs @@ -85,7 +85,8 @@ partial class Build "../../wrapper/src/alphaskia_canvas.cpp", "../../wrapper/src/alphaskia_image.cpp", "../../wrapper/src/alphaskia_typeface.cpp", - "../../wrapper/src/alphaskia_textstyle.cpp", + "../../wrapper/src/alphaskia_text_style.cpp", + "../../wrapper/src/alphaskia_text_metrics.cpp", "../../wrapper/src/alphaskia_data.cpp", "../../wrapper/src/alphaskia_string.cpp" ] @@ -158,6 +159,7 @@ partial class Build "../../lib/java/jni/src/AlphaSkiaData.cpp", "../../lib/java/jni/src/AlphaSkiaImage.cpp", "../../lib/java/jni/src/AlphaSkiaTextStyle.cpp", + "../../lib/java/jni/src/AlphaSkiaTextMetrics.cpp", "../../lib/java/jni/src/AlphaSkiaTypeface.cpp" ] } diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs b/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs index 3d214e1..154b8f4 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaCanvas.cs @@ -296,16 +296,18 @@ public void FillText(string text, AlphaSkiaTextStyle textStyle, float fontSize, } /// - /// Measures the given text. + /// Returns a object that contains information about the measured text (such as its width, for example). /// /// The text to measure. /// The text style to use for measuring the text. /// The font size to use when measuring the text. - /// The horizontal width of the text when it would be drawn. - public float MeasureText(string text, AlphaSkiaTextStyle textStyle, float fontSize) + /// How to align the text at the given position horizontally. + /// How to align the text at the given position vertically. + /// The text metrics. + public AlphaSkiaTextMetrics MeasureText(string text, AlphaSkiaTextStyle textStyle, float fontSize, AlphaSkiaTextAlign textAlign, AlphaSkiaTextBaseline baseline) { CheckDisposed(); - return NativeMethods.alphaskia_canvas_measure_text(Handle, text, text.Length, textStyle.Handle, fontSize); + return new AlphaSkiaTextMetrics(NativeMethods.alphaskia_canvas_measure_text(Handle, text, text.Length, textStyle.Handle, fontSize, textAlign, baseline)); } /// diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaTextMetrics.cs b/lib/dotnet/AlphaSkia/AlphaSkiaTextMetrics.cs new file mode 100644 index 0000000..9044ec9 --- /dev/null +++ b/lib/dotnet/AlphaSkia/AlphaSkiaTextMetrics.cs @@ -0,0 +1,73 @@ +namespace AlphaSkia; + +/// +/// The TextMetrics interface represents the dimensions of a piece of text in the canvas; +/// A TextMetrics instance can be retrieved using the method. +/// +public sealed class AlphaSkiaTextMetrics : AlphaSkiaNative +{ + private AlphaSkiaTextMetrics(IntPtr handle) + : base(handle, NativeMethods.alphaskia_text_metrics_free) + { + } + + /// + /// Returns the width of a segment of inline text in pixels. It takes into account the current font of the context. + /// + public float Width => NativeMethods.alphaskia_text_metrics_get_width(Handle); + + /// + /// Distance parallel to the baseline from the alignment point given by the textAlign parameter to the left side of the bounding rectangle of the given text, in pixels; positive numbers indicating a distance going left from the given alignment point. + /// + public float ActualBoundingBoxLeft => NativeMethods.alphaskia_text_metrics_get_actual_bounding_box_left(Handle); + + /// + /// Returns the distance from the alignment point given by the textAlign parameter to the right side of the bounding rectangle of the given text, in pixels. The distance is measured parallel to the baseline. + /// + public float ActualBoundingBoxRight => NativeMethods.alphaskia_text_metrics_get_actual_bounding_box_right(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the highest bounding rectangle of all the fonts used to render the text, in pixels. + /// + public float FontBoundingBoxAscent => NativeMethods.alphaskia_text_metrics_get_font_bounding_box_ascent(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the bounding rectangle of all the fonts used to render the text, in pixels. + /// + public float FontBoundingBoxDescent => NativeMethods.alphaskia_text_metrics_get_font_bounding_box_descent(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the bounding rectangle used to render the text, in pixels. + /// + public float ActualBoundingBoxAscent => NativeMethods.alphaskia_text_metrics_get_actual_bounding_box_ascent(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the bounding rectangle used to render the text, in pixels. + /// + public float ActualBoundingBoxDescent => NativeMethods.alphaskia_text_metrics_get_actual_bounding_box_descent(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the em square in the line box, in pixels. + /// + public float EmHeightAscent => NativeMethods.alphaskia_text_metrics_get_em_height_ascent(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the em square in the line box, in pixels. + /// + public float EmHeightDescent => NativeMethods.alphaskia_text_metrics_get_em_height_descent(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the hanging baseline of the line box, in pixels. + /// + public float HangingBaseline => NativeMethods.alphaskia_text_metrics_get_hanging_baseline(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the alphabetic baseline of the line box, in pixels. + /// + public float AlphabeticBaseline => NativeMethods.alphaskia_text_metrics_get_hanging_baseline(Handle); + + /// + /// Returns the distance from the horizontal line indicated by the textBaseline parameter to the ideographic baseline of the line box, in CSS pixels. + /// + public float IdeographicBaseline => NativeMethods.alphaskia_text_metrics_get_ideographic_baseline(Handle); +} \ No newline at end of file diff --git a/lib/dotnet/AlphaSkia/AlphaSkiaTextStyle.cs b/lib/dotnet/AlphaSkia/AlphaSkiaTextStyle.cs index aca3875..05f1ae5 100644 --- a/lib/dotnet/AlphaSkia/AlphaSkiaTextStyle.cs +++ b/lib/dotnet/AlphaSkia/AlphaSkiaTextStyle.cs @@ -33,7 +33,7 @@ public sealed class AlphaSkiaTextStyle : AlphaSkiaNative /// The font weight typefaces should have. /// Whether typefaces should be italic. public AlphaSkiaTextStyle(string[] fontFamilies, ushort weight, bool isItalic) - : base(NativeMethods.alphaskia_textstyle_new((byte)fontFamilies.Length, fontFamilies, weight, isItalic ? (byte)1 : (byte)0), NativeMethods.alphaskia_textstyle_free) + : base(NativeMethods.alphaskia_text_style_new((byte)fontFamilies.Length, fontFamilies, weight, isItalic ? (byte)1 : (byte)0), NativeMethods.alphaskia_text_style_free) { FontFamilies = fontFamilies; Weight = weight; diff --git a/lib/dotnet/AlphaSkia/GlobalUsings.cs b/lib/dotnet/AlphaSkia/GlobalUsings.cs index 6ff5c50..db08941 100644 --- a/lib/dotnet/AlphaSkia/GlobalUsings.cs +++ b/lib/dotnet/AlphaSkia/GlobalUsings.cs @@ -3,4 +3,5 @@ global using alphaskia_canvas_t = System.IntPtr; global using alphaskia_data_t = System.IntPtr; global using alphaskia_string_t = System.IntPtr; -global using alphaskia_textstyle_t = System.IntPtr; \ No newline at end of file +global using alphaskia_text_style_t = System.IntPtr; +global using alphaskia_text_metrics_t = System.IntPtr; \ No newline at end of file diff --git a/lib/dotnet/AlphaSkia/NativeMethods.cs b/lib/dotnet/AlphaSkia/NativeMethods.cs index 9c89ce3..4dde57e 100644 --- a/lib/dotnet/AlphaSkia/NativeMethods.cs +++ b/lib/dotnet/AlphaSkia/NativeMethods.cs @@ -49,17 +49,17 @@ internal static class NativeMethods public static extern void alphaskia_typeface_free(alphaskia_typeface_t type_face); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] - public static extern alphaskia_textstyle_t alphaskia_textstyle_new(byte family_name_count, string[] family_names, ushort weight, byte italic); + public static extern alphaskia_text_style_t alphaskia_text_style_new(byte family_name_count, string[] family_names, ushort weight, byte italic); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] - public static extern byte alphaskia_textstyle_get_family_name_count(alphaskia_textstyle_t textstyle); + public static extern byte alphaskia_text_style_get_family_name_count(alphaskia_text_style_t text_style); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] - public static extern alphaskia_string_t alphaskia_textstyle_get_family_name(alphaskia_textstyle_t textstyle, byte index); + public static extern alphaskia_string_t alphaskia_text_style_get_family_name(alphaskia_text_style_t text_style, byte index); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] - public static extern ushort alphaskia_textstyle_get_weight(alphaskia_textstyle_t textstyle); + public static extern ushort alphaskia_text_style_get_weight(alphaskia_text_style_t text_style); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] - public static extern byte alphaskia_textstyle_is_italic(alphaskia_textstyle_t textstyle); + public static extern byte alphaskia_text_style_is_italic(alphaskia_text_style_t text_style); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] - public static extern void alphaskia_textstyle_free(alphaskia_textstyle_t textstyle); + public static extern void alphaskia_text_style_free(alphaskia_text_style_t text_style); [DllImport(AlphaSkiaNativeLibName)] public static extern alphaskia_typeface_t alphaskia_typeface_make_from_name( @@ -168,7 +168,7 @@ public static extern void alphaskia_canvas_fill_text(alphaskia_canvas_t canvas, [MarshalAs(UnmanagedType.LPWStr)] string text, int text_length, - alphaskia_textstyle_t textstyle, float font_size, float x, float y, AlphaSkiaTextAlign text_align, + alphaskia_text_style_t text_style, float font_size, float x, float y, AlphaSkiaTextAlign text_align, AlphaSkiaTextBaseline baseline); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] @@ -176,7 +176,8 @@ public static extern float alphaskia_canvas_measure_text(alphaskia_canvas_t canv [MarshalAs(UnmanagedType.LPWStr)] string text, int text_length, - alphaskia_textstyle_t textstyle, float font_size); + alphaskia_text_style_t text_style, float font_size, AlphaSkiaTextAlign text_align, + AlphaSkiaTextBaseline baseline); [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] public static extern void alphaskia_canvas_begin_rotate(alphaskia_canvas_t canvas, float center_x, float center_y, @@ -188,4 +189,43 @@ public static extern void alphaskia_canvas_begin_rotate(alphaskia_canvas_t canva [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] public static extern void alphaskia_canvas_draw_image(alphaskia_canvas_t canvas, alphaskia_image_t image, float x, float y, float w, float h); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_width(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_actual_bounding_box_left(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_actual_bounding_box_right(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_font_bounding_box_ascent(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_font_bounding_box_descent(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_actual_bounding_box_ascent(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_actual_bounding_box_descent(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_em_height_ascent(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_em_height_descent(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_hanging_baseline(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_alphabetic_baseline(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern float alphaskia_text_metrics_get_ideographic_baseline(alphaskia_text_metrics_t text_metrics); + + [DllImport(AlphaSkiaNativeLibName, CallingConvention = CallingConvention.Cdecl)] + public static extern void alphaskia_text_metrics_free(alphaskia_text_metrics_t text_metrics); } \ No newline at end of file diff --git a/lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaCanvas.h b/lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaCanvas.h index 386c56c..42fd39b 100644 --- a/lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaCanvas.h +++ b/lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaCanvas.h @@ -178,10 +178,10 @@ JNIEXPORT void JNICALL Java_alphaTab_alphaSkia_AlphaSkiaCanvas_fillText /* * Class: alphaTab_alphaSkia_AlphaSkiaCanvas * Method: measureText - * Signature: (Ljava/lang/String;LalphaTab/alphaSkia/AlphaSkiaTextStyle;F)F + * Signature: (Ljava/lang/String;LalphaTab/alphaSkia/AlphaSkiaTextStyle;FLalphaTab/alphaSkia/AlphaSkiaTextAlign;LalphaTab/alphaSkia/AlphaSkiaTextBaseline;)LalphaTab/alphaSkia/AlphaSkiaTextMetrics; */ -JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaCanvas_measureText - (JNIEnv *, jobject, jstring, jobject, jfloat); +JNIEXPORT jobject JNICALL Java_alphaTab_alphaSkia_AlphaSkiaCanvas_measureText + (JNIEnv *, jobject, jstring, jobject, jfloat, jobject, jobject); /* * Class: alphaTab_alphaSkia_AlphaSkiaCanvas diff --git a/lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaTextMetrics.h b/lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaTextMetrics.h new file mode 100644 index 0000000..dab2d24 --- /dev/null +++ b/lib/java/jni/include/alphaTab_alphaSkia_AlphaSkiaTextMetrics.h @@ -0,0 +1,117 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class alphaTab_alphaSkia_AlphaSkiaTextMetrics */ + +#ifndef _Included_alphaTab_alphaSkia_AlphaSkiaTextMetrics +#define _Included_alphaTab_alphaSkia_AlphaSkiaTextMetrics +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getWidth + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getWidth + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getActualBoundingBoxLeft + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxLeft + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getActualBoundingBoxRight + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxRight + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getFontBoundingBoxAscent + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getFontBoundingBoxAscent + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getFontBoundingBoxDescent + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getFontBoundingBoxDescent + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getActualBoundingBoxAscent + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxAscent + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getActualBoundingBoxDescent + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxDescent + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getEmHeightAscent + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getEmHeightAscent + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getEmHeightDescent + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getEmHeightDescent + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getHangingBaseline + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getHangingBaseline + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getAlphabeticBaseline + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getAlphabeticBaseline + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: getIdeographicBaseline + * Signature: ()F + */ +JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getIdeographicBaseline + (JNIEnv *, jobject); + +/* + * Class: alphaTab_alphaSkia_AlphaSkiaTextMetrics + * Method: close + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_close + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/lib/java/jni/src/AlphaSkiaCanvas.cpp b/lib/java/jni/src/AlphaSkiaCanvas.cpp index 481f395..9590a1a 100644 --- a/lib/java/jni/src/AlphaSkiaCanvas.cpp +++ b/lib/java/jni/src/AlphaSkiaCanvas.cpp @@ -172,7 +172,7 @@ extern "C" const jchar *nativeStr = env->GetStringChars(str, nullptr); - alphaskia_textstyle_t nativeTextStyle = reinterpret_cast(get_handle(env, typeface)); + alphaskia_text_style_t nativeTextStyle = reinterpret_cast(get_handle(env, typeface)); CHECK_HANDLE(nativeTextStyle) jmethodID textAlignGetValue = env->GetMethodID(env->GetObjectClass(text_align), "getValue", "()I"); @@ -204,20 +204,46 @@ extern "C" env->ReleaseStringChars(str, nativeStr); } - JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaCanvas_measureText(JNIEnv *env, jobject instance, jstring str, jobject typeface, jfloat font_size) + + JNIEXPORT jobject JNICALL Java_alphaTab_alphaSkia_AlphaSkiaCanvas_measureText(JNIEnv *env, jobject instance, jstring str, jobject typeface, jfloat font_size, jobject text_align, jobject baseline) { alphaskia_canvas_t canvas = reinterpret_cast(get_handle(env, instance)); - CHECK_HANDLE_RETURN(canvas, 0.0f) + CHECK_HANDLE_RETURN(canvas, nullptr) const jchar *nativeStr = env->GetStringChars(str, nullptr); - alphaskia_textstyle_t nativeTextStyle = reinterpret_cast(get_handle(env, typeface)); - CHECK_HANDLE_RETURN(nativeTextStyle, 0.0f) + alphaskia_text_style_t nativeTextStyle = reinterpret_cast(get_handle(env, typeface)); + CHECK_HANDLE_RETURN(nativeTextStyle, nullptr) - float width = alphaskia_canvas_measure_text(canvas, reinterpret_cast(nativeStr), static_cast(env->GetStringLength(str)), nativeTextStyle, static_cast(font_size)); + jmethodID textAlignGetValue = env->GetMethodID(env->GetObjectClass(text_align), "getValue", "()I"); + if (!textAlignGetValue) + { + if (!env->ExceptionCheck()) + { + env->ThrowNew(env->FindClass("java/lang/IllegalStateException"), "Could not find 'int getValue' on text align"); + } + return; + } + + jmethodID baselineGetValue = env->GetMethodID(env->GetObjectClass(baseline), "getValue", "()I"); + if (!baselineGetValue) + { + if (!env->ExceptionCheck()) + { + env->ThrowNew(env->FindClass("java/lang/IllegalStateException"), "Could not find 'int getValue' on text baseline"); + } + return; + } + + alphaskia_text_align_t nativeTextAlign = static_cast(env->CallIntMethod(text_align, textAlignGetValue)); + alphaskia_text_baseline_t nativeBaseline = static_cast(env->CallIntMethod(baseline, baselineGetValue)); + + auto text_metrics = alphaskia_canvas_measure_text(canvas, reinterpret_cast(nativeStr), static_cast(env->GetStringLength(str)), nativeTextStyle, static_cast(font_size), nativeTextAlign, nativeBaseline); env->ReleaseStringChars(str, nativeStr); - return static_cast(width); + jclass cls = env->FindClass("alphaTab/alphaSkia/AlphaSkiaTextMetrics"); + jmethodID ctor = env->GetMethodID(cls, "", "(J)V"); + return env->NewObject(cls, ctor, static_cast(reinterpret_cast(text_metrics))); } JNIEXPORT void JNICALL Java_alphaTab_alphaSkia_AlphaSkiaCanvas_beginRotate(JNIEnv *env, jobject instance, jfloat x, jfloat y, jfloat angle) diff --git a/lib/java/jni/src/AlphaSkiaTextMetrics.cpp b/lib/java/jni/src/AlphaSkiaTextMetrics.cpp new file mode 100644 index 0000000..1ebde69 --- /dev/null +++ b/lib/java/jni/src/AlphaSkiaTextMetrics.cpp @@ -0,0 +1,124 @@ +#include "../include/alphaTab_alphaSkia_AlphaSkiaTextMetrics.h" +#include "../include/JniHelper.h" +#include "../../../../wrapper/include/alphaskia.h" + +extern "C" +{ + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getWidth(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_width(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxLeft(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_actual_bounding_box_left(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxRight(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_actual_bounding_box_right(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getFontBoundingBoxAscent(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_font_bounding_box_ascent(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getFontBoundingBoxDescent(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_font_bounding_box_descent(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxAscent(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_actual_bounding_box_ascent(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getActualBoundingBoxDescent(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_actual_bounding_box_descent(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getEmHeightAscent(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_em_height_ascent(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getEmHeightDescent(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_em_height_descent(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getHangingBaseline(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_hanging_baseline(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getAlphabeticBaseline(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_alphabetic_baseline(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT jfloat JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_getIdeographicBaseline(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE_RETURN(handle, static_cast(0)) + + uint16_t value = alphaskia_text_metrics_get_ideographic_baseline(reinterpret_cast(handle)); + return static_cast(value); + } + + JNIEXPORT void JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextMetrics_close(JNIEnv *env, jobject instance) + { + jlong handle = get_handle(env, instance); + CHECK_HANDLE(handle) + + alphaskia_text_metrics_t data = reinterpret_cast(handle); + alphaskia_text_metrics_free(data); + set_handle(env, instance, 0); + } +} \ No newline at end of file diff --git a/lib/java/jni/src/AlphaSkiaTextStyle.cpp b/lib/java/jni/src/AlphaSkiaTextStyle.cpp index 6ebfe0f..e56c105 100644 --- a/lib/java/jni/src/AlphaSkiaTextStyle.cpp +++ b/lib/java/jni/src/AlphaSkiaTextStyle.cpp @@ -17,7 +17,7 @@ extern "C" nativeFamilyNames[i] = env->GetStringUTFChars(familyName, nullptr); } - alphaskia_textstyle_t canvas = alphaskia_textstyle_new( + alphaskia_text_style_t canvas = alphaskia_text_style_new( familyNameLength, &nativeFamilyNames[0], static_cast(weight), @@ -33,10 +33,10 @@ extern "C" JNIEXPORT void JNICALL Java_alphaTab_alphaSkia_AlphaSkiaTextStyle_close(JNIEnv *env, jobject instance) { - alphaskia_textstyle_t textStyle = reinterpret_cast(get_handle(env, instance)); + alphaskia_text_style_t textStyle = reinterpret_cast(get_handle(env, instance)); CHECK_HANDLE(textStyle) - alphaskia_textstyle_free(textStyle); + alphaskia_text_style_free(textStyle); set_handle(env, instance, 0); } } \ No newline at end of file diff --git a/lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaCanvas.java b/lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaCanvas.java index 829a21b..cc77717 100644 --- a/lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaCanvas.java +++ b/lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaCanvas.java @@ -218,14 +218,18 @@ public native void fillText(String text, AlphaSkiaTextStyle textStyle, float fon AlphaSkiaTextBaseline baseline); /** - * Measures the given text. + * Returns a {@see AlphaSkiaTextMetrics} object that contains information about the measured text (such as its width, for example). * * @param text The text to measure. * @param textStyle The text style to use for measuring the text. * @param fontSize The font size to use when measuring the text. - * @return The horizontal width of the text when it would be drawn. + * @param textAlign How to align the text at the given position horizontally. + * @param baseline How to align the text at the given position vertically. + * @return The text metrics. */ - public native float measureText(String text, AlphaSkiaTextStyle textStyle, float fontSize); + public native AlphaSkiaTextMetrics measureText(String text, AlphaSkiaTextStyle textStyle, float fontSize, + AlphaSkiaTextAlign textAlign, + AlphaSkiaTextBaseline baseline); /** * Rotates the canvas allowing angled drawing diff --git a/lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaTextMetrics.java b/lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaTextMetrics.java new file mode 100644 index 0000000..8ce6c30 --- /dev/null +++ b/lib/java/main/src/main/java/alphaTab/alphaSkia/AlphaSkiaTextMetrics.java @@ -0,0 +1,75 @@ +package alphaTab.alphaSkia; + +/** + * The TextMetrics interface represents the dimensions of a piece of text in the canvas; + * A TextMetrics instance can be retrieved using the {@see AlphaSkiaCanvas.measureText} method. + */ +public class AlphaSkiaTextMetrics extends AlphaSkiaNative { + + /** + * Returns the width of a segment of inline text in pixels. It takes into account the current font of the context. + */ + public native float getWidth(); + + /** + * Distance parallel to the baseline from the alignment point given by the textAlign parameter to the left side of the bounding rectangle of the given text, in pixels; positive numbers indicating a distance going left from the given alignment point. + */ + public native float getActualBoundingBoxLeft(); + + /** + * Returns the distance from the alignment point given by the textAlign parameter to the right side of the bounding rectangle of the given text, in pixels. The distance is measured parallel to the baseline. + */ + public native float getActualBoundingBoxRight(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the highest bounding rectangle of all the fonts used to render the text, in pixels. + */ + public native float getFontBoundingBoxAscent(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the bounding rectangle of all the fonts used to render the text, in pixels. + */ + public native float getFontBoundingBoxDescent(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the bounding rectangle used to render the text, in pixels. + */ + public native float getActualBoundingBoxAscent(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the bounding rectangle used to render the text, in pixels. + */ + public native float getActualBoundingBoxDescent(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the em square in the line box, in pixels. + */ + public native float getEmHeightAscent(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the em square in the line box, in pixels. + */ + public native float getEmHeightDescent(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the hanging baseline of the line box, in pixels. + */ + public native float getHangingBaseline(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the alphabetic baseline of the line box, in pixels. + */ + public native float getAlphabeticBaseline(); + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the ideographic baseline of the line box, in CSS pixels. + */ + public native float getIdeographicBaseline(); + + AlphaSkiaTextMetrics(long handle) { + super(handle); + } + + @Override + public native void close(); +} \ No newline at end of file diff --git a/lib/node/addon/addon.cpp b/lib/node/addon/addon.cpp index 106ae0a..bfa146e 100644 --- a/lib/node/addon/addon.cpp +++ b/lib/node/addon/addon.cpp @@ -21,7 +21,8 @@ static const napi_type_tag alphaskia_data_t_tag = {0x6a960ece6a0c4caf, 0xad61688 static const napi_type_tag alphaskia_typeface_t_tag = {0x0068df0314224b96, 0x8048b968915995f1}; static const napi_type_tag alphaskia_image_t_tag = {0x9372c0f8e8de466f, 0x96a04b6ee0a9394b}; static const napi_type_tag alphaskia_canvas_t_tag = {0xaa77c76772a34052, 0x88ac80d4dc474395}; -static const napi_type_tag alphaskia_textstyle_t_tag = {0x8f2bc41a57024cf4, 0x8b5e1cd26ded5245}; +static const napi_type_tag alphaskia_text_style_t_tag = {0x8f2bc41a57024cf4, 0x8b5e1cd26ded5245}; +static const napi_type_tag alphaskia_text_metrics_t_tag = {0x85fbe8d4da634b51, 0x970a89efa0ef13ff}; #define RETURN_UNDEFINED() \ { \ @@ -345,7 +346,7 @@ static napi_value node_alphaskia_typeface_is_italic(napi_env env, napi_callback_ return node_is_italic; } -static napi_value node_alphaskia_textstyle_new(napi_env env, napi_callback_info info) +static napi_value node_alphaskia_text_style_new(napi_env env, napi_callback_info info) { napi_status status(napi_ok); @@ -395,19 +396,203 @@ static napi_value node_alphaskia_textstyle_new(napi_env env, napi_callback_info GET_ARGUMENT_INT32(1, weight); GET_ARGUMENT_BOOL(2, italic); - alphaskia_textstyle_t textstyle = alphaskia_textstyle_new(static_cast(familyNamesLength), &familyNamesRaw[0], static_cast(weight), italic ? 1 : 0); - WRAP_HANDLE(alphaskia_textstyle_t, wrapped, textstyle); + alphaskia_text_style_t text_style = alphaskia_text_style_new(static_cast(familyNamesLength), &familyNamesRaw[0], static_cast(weight), italic ? 1 : 0); + WRAP_HANDLE(alphaskia_text_style_t, wrapped, text_style); return wrapped; } -static napi_value node_alphaskia_textstyle_free(napi_env env, napi_callback_info info) +static napi_value node_alphaskia_text_style_free(napi_env env, napi_callback_info info) { napi_status status(napi_ok); CHECK_ARGS(1); - GET_ARGUMENT_HANDLE(0, alphaskia_textstyle_t, textstyle); + GET_ARGUMENT_HANDLE(0, alphaskia_text_style_t, text_style); + + alphaskia_text_style_free(text_style); + void *old; + status = napi_remove_wrap(env, node_argv[0], &old); + ASSERT_STATUS(); + + RETURN_UNDEFINED(); +} + +static napi_value node_alphaskia_text_metrics_get_width(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_width(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_actual_bounding_box_left(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_actual_bounding_box_left(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_actual_bounding_box_right(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_actual_bounding_box_right(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_font_bounding_box_ascent(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_font_bounding_box_ascent(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_font_bounding_box_descent(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_font_bounding_box_descent(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_actual_bounding_box_ascent(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_actual_bounding_box_ascent(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_actual_bounding_box_descent(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_actual_bounding_box_descent(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_em_height_ascent(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_em_height_ascent(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_em_height_descent(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_em_height_descent(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_hanging_baseline(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_hanging_baseline(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_alphabetic_baseline(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_alphabetic_baseline(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_get_ideographic_baseline(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + float value = alphaskia_text_metrics_get_ideographic_baseline(text_metrics); + napi_value node_value; + status = napi_create_double(env, static_cast(value), &node_value); + ASSERT_STATUS(); + return node_value; +} + +static napi_value node_alphaskia_text_metrics_free(napi_env env, napi_callback_info info) +{ + napi_status status(napi_ok); + + CHECK_ARGS(1); + GET_ARGUMENT_HANDLE(0, alphaskia_text_metrics_t, text_metrics); + + alphaskia_text_metrics_free(text_metrics); - alphaskia_textstyle_free(textstyle); void *old; status = napi_remove_wrap(env, node_argv[0], &old); ASSERT_STATUS(); @@ -843,14 +1028,14 @@ static napi_value node_alphaskia_canvas_fill_text(napi_env env, napi_callback_in CHECK_ARGS(8); GET_ARGUMENT_HANDLE(0, alphaskia_canvas_t, canvas); GET_ARGUMENT_UTF16_STRING(1, text); - GET_ARGUMENT_HANDLE(2, alphaskia_textstyle_t, textstyle); + GET_ARGUMENT_HANDLE(2, alphaskia_text_style_t, text_style); GET_ARGUMENT_FLOAT(3, font_size); GET_ARGUMENT_FLOAT(4, x); GET_ARGUMENT_FLOAT(5, y); GET_ARGUMENT_INT32(6, text_align); GET_ARGUMENT_INT32(7, baseline); - alphaskia_canvas_fill_text(canvas, text.c_str(), text.length(), textstyle, font_size, x, y, static_cast(text_align), static_cast(baseline)); + alphaskia_canvas_fill_text(canvas, text.c_str(), text.length(), text_style, font_size, x, y, static_cast(text_align), static_cast(baseline)); RETURN_UNDEFINED(); } @@ -862,15 +1047,14 @@ static napi_value node_alphaskia_canvas_measure_text(napi_env env, napi_callback CHECK_ARGS(4); GET_ARGUMENT_HANDLE(0, alphaskia_canvas_t, canvas); GET_ARGUMENT_UTF16_STRING(1, text); - GET_ARGUMENT_HANDLE(2, alphaskia_textstyle_t, textstyle); + GET_ARGUMENT_HANDLE(2, alphaskia_text_style_t, text_style); GET_ARGUMENT_FLOAT(3, font_size); + GET_ARGUMENT_INT32(4, text_align); + GET_ARGUMENT_INT32(5, baseline); - float width = alphaskia_canvas_measure_text(canvas, text.c_str(), text.length(), textstyle, font_size); - napi_value width_node; - status = napi_create_double(env, width, &width_node); - ASSERT_STATUS(); - - return width_node; + auto text_metrics = alphaskia_canvas_measure_text(canvas, text.c_str(), text.length(), text_style, font_size, static_cast(text_align), static_cast(baseline)); + WRAP_HANDLE(alphaskia_text_metrics_t, wrapped, text_metrics); + return wrapped; } static napi_value node_alphaskia_canvas_begin_rotate(napi_env env, napi_callback_info info) @@ -928,12 +1112,26 @@ napi_value init(napi_env env, napi_value exports) EXPORT_NODE_FUNCTION(alphaskia_typeface_get_weight); EXPORT_NODE_FUNCTION(alphaskia_typeface_is_italic); - EXPORT_NODE_FUNCTION(alphaskia_textstyle_new); - // EXPORT_NODE_FUNCTION(alphaskia_textstyle_get_family_name_count); - // EXPORT_NODE_FUNCTION(alphaskia_textstyle_get_family_name); - // EXPORT_NODE_FUNCTION(alphaskia_textstyle_get_weight); - // EXPORT_NODE_FUNCTION(alphaskia_textstyle_is_italic); - EXPORT_NODE_FUNCTION(alphaskia_textstyle_free); + EXPORT_NODE_FUNCTION(alphaskia_text_style_new); + // EXPORT_NODE_FUNCTION(alphaskia_text_style_get_family_name_count); + // EXPORT_NODE_FUNCTION(alphaskia_text_style_get_family_name); + // EXPORT_NODE_FUNCTION(alphaskia_text_style_get_weight); + // EXPORT_NODE_FUNCTION(alphaskia_text_style_is_italic); + EXPORT_NODE_FUNCTION(alphaskia_text_style_free); + + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_width); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_actual_bounding_box_left); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_actual_bounding_box_right); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_font_bounding_box_ascent); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_font_bounding_box_descent); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_actual_bounding_box_ascent); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_actual_bounding_box_descent); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_em_height_ascent); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_em_height_descent); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_hanging_baseline); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_alphabetic_baseline); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_get_ideographic_baseline); + EXPORT_NODE_FUNCTION(alphaskia_text_metrics_free); EXPORT_NODE_FUNCTION(alphaskia_image_get_width); EXPORT_NODE_FUNCTION(alphaskia_image_get_height); diff --git a/lib/node/alphaskia/src/AlphaSkiaCanvas.ts b/lib/node/alphaskia/src/AlphaSkiaCanvas.ts index d37a262..8d1e66c 100644 --- a/lib/node/alphaskia/src/AlphaSkiaCanvas.ts +++ b/lib/node/alphaskia/src/AlphaSkiaCanvas.ts @@ -1,5 +1,6 @@ import { AlphaSkiaImage } from './AlphaSkiaImage'; import { AlphaSkiaNative } from './AlphaSkiaNative'; +import { AlphaSkiaTextMetrics } from './AlphaSkiaTextMetrics'; import { AlphaSkiaTextStyle } from './AlphaSkiaTextStyle'; import { AlphaSkiaTextBaseline, AlphaSkiaCanvasHandle, AlphaSkiaColorType, AlphaSkiaTextAlign, loadAddon } from './addon'; @@ -38,7 +39,7 @@ export class AlphaSkiaCanvas extends AlphaSkiaNative { public static switchToFreeTypeFonts(): void { loadAddon().alphaskia_switch_to_freetype_fonts(); } - + /** * Gets the color to use for drawing operations in the native canvas. * See also {@link rgbaToColor} @@ -287,16 +288,18 @@ export class AlphaSkiaCanvas extends AlphaSkiaNative { } /** - * Measures the given text. + * Returns a {@link AlphaSkiaTextMetrics} object that contains information about the measured text (such as its width, for example). * * @param text The text to measure. * @param typeface The typeface to use for drawing the text. * @param fontSize The font size to use when drawing the text. - * @return The horizontal width of the text when it would be drawn. + * @param textAlign How to align the text at the given position horizontally. + * @param baseline How to align the text at the given position vertically. + * @return The text metrics. */ - public measureText(text: string, textStyle: AlphaSkiaTextStyle, fontSize: number): number { + public measureText(text: string, textStyle: AlphaSkiaTextStyle, fontSize: number, textAlign: AlphaSkiaTextAlign, baseline: AlphaSkiaTextBaseline): AlphaSkiaTextMetrics { this.checkDisposed(); - return loadAddon().alphaskia_canvas_measure_text(this.handle!, text, textStyle.handle!, fontSize); + return new AlphaSkiaTextMetrics(loadAddon().alphaskia_canvas_measure_text(this.handle!, text, textStyle.handle!, fontSize, textAlign as number, baseline as number)); } /** diff --git a/lib/node/alphaskia/src/AlphaSkiaTextMetrics.ts b/lib/node/alphaskia/src/AlphaSkiaTextMetrics.ts new file mode 100644 index 0000000..302f6bd --- /dev/null +++ b/lib/node/alphaskia/src/AlphaSkiaTextMetrics.ts @@ -0,0 +1,100 @@ +import { AlphaSkiaNative } from './AlphaSkiaNative'; +import { AlphaSkiaTextMetricsHandle, loadAddon } from './addon'; + +/** + * Represents a typeface to draw text. + */ +export class AlphaSkiaTextMetrics extends AlphaSkiaNative { + + + /** + * Returns the width of a segment of inline text in pixels. It takes into account the current font of the context. + */ + public get width(): number { + return loadAddon().alphaskia_text_metrics_get_width(this.handle!); + } + + /** + * Distance parallel to the baseline from the alignment point given by the textAlign parameter to the left side of the bounding rectangle of the given text, in pixels; positive numbers indicating a distance going left from the given alignment point. + */ + public get actualBoundingBoxLeft(): number { + return loadAddon().alphaskia_text_metrics_get_actual_bounding_box_left(this.handle!); + } + + /** + * Returns the distance from the alignment point given by the textAlign parameter to the right side of the bounding rectangle of the given text, in pixels. The distance is measured parallel to the baseline. + */ + public get actualBoundingBoxRight(): number { + return loadAddon().alphaskia_text_metrics_get_actual_bounding_box_right(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the highest bounding rectangle of all the fonts used to render the text, in pixels. + */ + public get fontBoundingBoxAscent(): number { + return loadAddon().alphaskia_text_metrics_get_font_bounding_box_ascent(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the bounding rectangle of all the fonts used to render the text, in pixels. + */ + public get fontBoundingBoxDescent(): number { + return loadAddon().alphaskia_text_metrics_get_font_bounding_box_descent(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the bounding rectangle used to render the text, in pixels. + */ + public get actualBoundingBoxAscent(): number { + return loadAddon().alphaskia_text_metrics_get_actual_bounding_box_ascent(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the bounding rectangle used to render the text, in pixels. + */ + public get actualBoundingBoxDescent(): number { + return loadAddon().alphaskia_text_metrics_get_actual_bounding_box_descent(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the top of the em square in the line box, in pixels. + */ + public get emHeightAscent(): number { + return loadAddon().alphaskia_text_metrics_get_em_height_ascent(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the bottom of the em square in the line box, in pixels. + */ + public get emHeightDescent(): number { + return loadAddon().alphaskia_text_metrics_get_em_height_descent(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the hanging baseline of the line box, in pixels. + */ + public get hangingBaseline(): number { + return loadAddon().alphaskia_text_metrics_get_hanging_baseline(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the alphabetic baseline of the line box, in pixels. + */ + public get alphabeticBaseline(): number { + return loadAddon().alphaskia_text_metrics_get_alphabetic_baseline(this.handle!); + } + + /** + * Returns the distance from the horizontal line indicated by the textBaseline parameter to the ideographic baseline of the line box, in CSS pixels. + */ + public get ideographicBaseline(): number { + return loadAddon().alphaskia_text_metrics_get_ideographic_baseline(this.handle!); + } + + /** + * @internal + */ + public constructor(handle: AlphaSkiaTextMetricsHandle) { + super(handle, loadAddon().alphaskia_text_metrics_free); + } +} \ No newline at end of file diff --git a/lib/node/alphaskia/src/addon.ts b/lib/node/alphaskia/src/addon.ts index b01ec9d..14fca06 100644 --- a/lib/node/alphaskia/src/addon.ts +++ b/lib/node/alphaskia/src/addon.ts @@ -7,6 +7,7 @@ export interface AlphaSkiaTypefaceHandle { } export interface AlphaSkiaImageHandle { } export interface AlphaSkiaCanvasHandle { } export interface AlphaSkiaTextStyleHandle { } +export interface AlphaSkiaTextMetricsHandle { } /** * Lists all text alignments which can be used to draw text. @@ -126,9 +127,23 @@ export interface AlphaSkiaNodeAddon { alphaskia_canvas_draw_image(canvas: AlphaSkiaCanvasHandle, image: AlphaSkiaImageHandle, x: number, y: number, w: number, h: number): void; alphaskia_canvas_fill_text(canvas: AlphaSkiaCanvasHandle, text: string, textStyle: AlphaSkiaTextStyleHandle, font_size: number, x: number, y: number, text_align: AlphaSkiaTextAlign, baseline: AlphaSkiaTextBaseline): void; - alphaskia_canvas_measure_text(canvas: AlphaSkiaCanvasHandle, text: string, textStyle: AlphaSkiaTextStyleHandle, font_size: number): number; + alphaskia_canvas_measure_text(canvas: AlphaSkiaCanvasHandle, text: string, textStyle: AlphaSkiaTextStyleHandle, font_size: number, text_align: AlphaSkiaTextAlign, baseline: AlphaSkiaTextBaseline): number; alphaskia_canvas_begin_rotate(canvas: AlphaSkiaCanvasHandle, center_x: number, center_y: number, angle: number): void; alphaskia_canvas_end_rotate(canvas: AlphaSkiaCanvasHandle): void; + + alphaskia_text_metrics_get_width(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_actual_bounding_box_left(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_actual_bounding_box_right(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_font_bounding_box_ascent(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_font_bounding_box_descent(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_actual_bounding_box_ascent(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_actual_bounding_box_descent(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_em_height_ascent(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_em_height_descent(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_hanging_baseline(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_alphabetic_baseline(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_get_ideographic_baseline(text_metrics: AlphaSkiaTextMetricsHandle): number; + alphaskia_text_metrics_free(text_metrics: AlphaSkiaTextMetricsHandle): void; } const require = createRequire(import.meta.url); diff --git a/wrapper/include/AlphaSkiaCanvas.h b/wrapper/include/AlphaSkiaCanvas.h index f5c6486..3d5aa3b 100644 --- a/wrapper/include/AlphaSkiaCanvas.h +++ b/wrapper/include/AlphaSkiaCanvas.h @@ -12,6 +12,48 @@ #include +class AlphaSkiaTextMetrics +{ +public: + AlphaSkiaTextMetrics( + float width, + float actual_bounding_box_left, + float actual_bounding_box_right, + float font_bounding_box_ascent, + float font_bounding_box_descent, + float em_height_ascent, + float em_height_descent, + float hanging_baseline, + float alphabetic_baseline, + float ideographic_baseline) + : width_(width), actual_bounding_box_left_(actual_bounding_box_left), actual_bounding_box_right_(actual_bounding_box_right), font_bounding_box_ascent_(font_bounding_box_ascent), font_bounding_box_descent_(font_bounding_box_descent), em_height_ascent_(em_height_ascent), em_height_descent_(em_height_descent), hanging_baseline_(hanging_baseline), alphabetic_baseline_(alphabetic_baseline), ideographic_baseline_(ideographic_baseline) + { + } + + float get_width() const { return width_; } + float get_actual_bounding_box_left() const { return actual_bounding_box_left_; } + float get_actual_bounding_box_right() const { return actual_bounding_box_right_; } + float get_font_bounding_box_ascent() const { return font_bounding_box_ascent_; } + float get_font_bounding_box_descent() const { return font_bounding_box_descent_; } + float get_em_height_ascent() const { return em_height_ascent_; } + float get_em_height_descent() const { return em_height_descent_; } + float get_hanging_baseline() const { return hanging_baseline_; } + float get_alphabetic_baseline() const { return alphabetic_baseline_; } + float get_ideographic_baseline() const { return ideographic_baseline_; } + +private: + float width_; + float actual_bounding_box_left_; + float actual_bounding_box_right_; + float font_bounding_box_ascent_; + float font_bounding_box_descent_; + float em_height_ascent_; + float em_height_descent_; + float hanging_baseline_; + float alphabetic_baseline_; + float ideographic_baseline_; +}; + class AlphaSkiaTextStyle { public: @@ -72,14 +114,14 @@ class AlphaSkiaCanvas void stroke(); void draw_image(sk_sp image, float x, float y, float w, float h); - void fill_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &textstyle, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline); - float measure_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &textstyle, float font_size); + void fill_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline); + AlphaSkiaTextMetrics* measure_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline); void begin_rotate(float center_x, float center_y, float angle); void end_rotate(); private: SkPaint create_paint(); - std::unique_ptr build_paragraph(const char16_t *text, int text_length, const AlphaSkiaTextStyle &textstyle, float font_size, alphaskia_text_align_t text_align); + std::unique_ptr build_paragraph(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align); static float get_font_baseline(const SkFont &font, alphaskia_text_baseline_t baseline); SkColor color_; diff --git a/wrapper/include/alphaskia.h b/wrapper/include/alphaskia.h index a7d3e87..3b1a78a 100644 --- a/wrapper/include/alphaskia.h +++ b/wrapper/include/alphaskia.h @@ -43,13 +43,13 @@ extern "C" AS_API uint16_t alphaskia_typeface_get_weight(alphaskia_typeface_t typeface); AS_API uint8_t alphaskia_typeface_is_italic(alphaskia_typeface_t typeface); - typedef AS_API void *alphaskia_textstyle_t; - AS_API alphaskia_textstyle_t alphaskia_textstyle_new(uint8_t family_name_count, const char** family_names, uint16_t weight, uint8_t italic); - AS_API uint8_t alphaskia_textstyle_get_family_name_count(alphaskia_textstyle_t textstyle); - AS_API alphaskia_string_t alphaskia_textstyle_get_family_name(alphaskia_textstyle_t textstyle, uint8_t index); - AS_API uint16_t alphaskia_textstyle_get_weight(alphaskia_textstyle_t textstyle); - AS_API uint8_t alphaskia_textstyle_is_italic(alphaskia_textstyle_t textstyle); - AS_API void alphaskia_textstyle_free(alphaskia_textstyle_t textstyle); + typedef AS_API void *alphaskia_text_style_t; + AS_API alphaskia_text_style_t alphaskia_text_style_new(uint8_t family_name_count, const char** family_names, uint16_t weight, uint8_t italic); + AS_API uint8_t alphaskia_text_style_get_family_name_count(alphaskia_text_style_t text_style); + AS_API alphaskia_string_t alphaskia_text_style_get_family_name(alphaskia_text_style_t text_style, uint8_t index); + AS_API uint16_t alphaskia_text_style_get_weight(alphaskia_text_style_t text_style); + AS_API uint8_t alphaskia_text_style_is_italic(alphaskia_text_style_t text_style); + AS_API void alphaskia_text_style_free(alphaskia_text_style_t text_style); typedef AS_API void *alphaskia_image_t; AS_API int32_t alphaskia_image_get_width(alphaskia_image_t image); @@ -60,6 +60,21 @@ extern "C" AS_API alphaskia_image_t alphaskia_image_from_pixels(int32_t width, int32_t height, const uint8_t *pixels); AS_API void alphaskia_image_free(alphaskia_image_t image); + typedef AS_API void *alphaskia_text_metrics_t; + AS_API float alphaskia_text_metrics_get_width(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_actual_bounding_box_left(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_actual_bounding_box_right(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_font_bounding_box_ascent(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_font_bounding_box_descent(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_actual_bounding_box_ascent(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_actual_bounding_box_descent(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_em_height_ascent(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_em_height_descent(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_hanging_baseline(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_alphabetic_baseline(alphaskia_text_metrics_t text_metrics); + AS_API float alphaskia_text_metrics_get_ideographic_baseline(alphaskia_text_metrics_t text_metrics); + AS_API void alphaskia_text_metrics_free(alphaskia_text_metrics_t text_metrics); + typedef AS_API void *alphaskia_canvas_t; AS_API alphaskia_canvas_t alphaskia_canvas_new(); AS_API void alphaskia_canvas_free(alphaskia_canvas_t canvas); @@ -100,9 +115,9 @@ extern "C" alphaskia_text_baseline_middle = 2, alphaskia_text_baseline_bottom = 3 } alphaskia_text_baseline_t; - AS_API void alphaskia_canvas_fill_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_textstyle_t textstyle, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline); + AS_API void alphaskia_canvas_fill_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_text_style_t text_style, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline); - AS_API float alphaskia_canvas_measure_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_textstyle_t textstyle, float font_size); + AS_API alphaskia_text_metrics_t alphaskia_canvas_measure_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_text_style_t text_style, float font_size, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline); AS_API void alphaskia_canvas_begin_rotate(alphaskia_canvas_t canvas, float center_x, float center_y, float angle); AS_API void alphaskia_canvas_end_rotate(alphaskia_canvas_t canvas); } \ No newline at end of file diff --git a/wrapper/src/AlphaSkiaCanvas.cpp b/wrapper/src/AlphaSkiaCanvas.cpp index 519f58f..da59364 100644 --- a/wrapper/src/AlphaSkiaCanvas.cpp +++ b/wrapper/src/AlphaSkiaCanvas.cpp @@ -154,7 +154,7 @@ void AlphaSkiaCanvas::stroke() path_.reset(); } -std::unique_ptr AlphaSkiaCanvas::build_paragraph(const char16_t *text, int text_length, const AlphaSkiaTextStyle &textstyle, float font_size, alphaskia_text_align_t text_align) +std::unique_ptr AlphaSkiaCanvas::build_paragraph(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align) { skia::textlayout::TextStyle style; @@ -162,8 +162,8 @@ std::unique_ptr AlphaSkiaCanvas::build_paragraph(co foregroundColor.setColor(color_); style.setForegroundColor(foregroundColor); - style.setFontFamilies(std::vector(textstyle.get_family_names())); - style.setFontStyle(textstyle.get_font_style()); + style.setFontFamilies(std::vector(text_style.get_family_names())); + style.setFontStyle(text_style.get_font_style()); style.setFontSize(font_size); skia::textlayout::ParagraphStyle paraStyle; @@ -189,9 +189,9 @@ std::unique_ptr AlphaSkiaCanvas::build_paragraph(co return builder->Build(); } -void AlphaSkiaCanvas::fill_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &textstyle, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) +void AlphaSkiaCanvas::fill_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) { - auto paragraph(build_paragraph(text, text_length, textstyle, font_size, text_align)); + auto paragraph(build_paragraph(text, text_length, text_style, font_size, text_align)); // layout with enough space for our text to definitely fit const float layoutWidth = surface_->width() * 2; @@ -217,13 +217,25 @@ void AlphaSkiaCanvas::fill_text(const char16_t *text, int text_length, const Alp paragraph->paint(surface_->getCanvas(), x, y); } -float AlphaSkiaCanvas::measure_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &textstyle, float font_size) +AlphaSkiaTextMetrics* AlphaSkiaCanvas::measure_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) { - auto paragraph(build_paragraph(text, text_length, textstyle, font_size, alphaskia_text_align_t::alphaskia_text_align_left)); + auto paragraph(build_paragraph(text, text_length, text_style, font_size, alphaskia_text_align_t::alphaskia_text_align_left)); paragraph->layout(10000); - return static_cast(paragraph->getMaxIntrinsicWidth()); + return new AlphaSkiaTextMetrics( + static_cast(paragraph->getMaxIntrinsicWidth()), + // TODO + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ); } void AlphaSkiaCanvas::begin_rotate(float center_x, float center_y, float angle) diff --git a/wrapper/src/alphaskia_canvas.cpp b/wrapper/src/alphaskia_canvas.cpp index cbb0df3..78e7938 100644 --- a/wrapper/src/alphaskia_canvas.cpp +++ b/wrapper/src/alphaskia_canvas.cpp @@ -130,16 +130,17 @@ extern "C" reinterpret_cast(canvas)->stroke(); } - AS_API void alphaskia_canvas_fill_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_textstyle_t textstyle, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) + AS_API void alphaskia_canvas_fill_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_text_style_t text_style, float font_size, float x, float y, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) { - AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(textstyle); + AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(text_style); reinterpret_cast(canvas)->fill_text(text, text_length, *skTextstyle, font_size, x, y, text_align, baseline); } - AS_API float alphaskia_canvas_measure_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_textstyle_t textstyle, float font_size) + AS_API alphaskia_text_metrics_t alphaskia_canvas_measure_text(alphaskia_canvas_t canvas, const char16_t *text, int text_length, alphaskia_text_style_t text_style, float font_size, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) { - AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(textstyle); - return reinterpret_cast(canvas)->measure_text(text, text_length, *skTextstyle, font_size); + AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(text_style); + auto text_metrics = reinterpret_cast(canvas)->measure_text(text, text_length, *skTextstyle, font_size, text_align, baseline); + return reinterpret_cast(text_metrics); } AS_API void alphaskia_canvas_begin_rotate(alphaskia_canvas_t canvas, float center_x, float center_y, float angle) diff --git a/wrapper/src/alphaskia_text_metrics.cpp b/wrapper/src/alphaskia_text_metrics.cpp new file mode 100644 index 0000000..00be095 --- /dev/null +++ b/wrapper/src/alphaskia_text_metrics.cpp @@ -0,0 +1,61 @@ +#include "../include/alphaskia.h" +#include "../include/AlphaSkiaCanvas.h" + +extern "C" +{ + AS_API float alphaskia_text_metrics_get_width(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_width(); + } + + AS_API float alphaskia_text_metrics_get_actual_bounding_box_left(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_actual_bounding_box_left(); + } + + AS_API float alphaskia_text_metrics_get_actual_bounding_box_right(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_actual_bounding_box_right(); + } + + AS_API float alphaskia_text_metrics_get_font_bounding_box_ascent(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_font_bounding_box_ascent(); + } + + AS_API float alphaskia_text_metrics_get_font_bounding_box_descent(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_font_bounding_box_descent(); + } + + AS_API float alphaskia_text_metrics_get_em_height_ascent(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_em_height_ascent(); + } + + AS_API float alphaskia_text_metrics_get_em_height_descent(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_em_height_descent(); + } + + AS_API float alphaskia_text_metrics_get_hanging_baseline(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_hanging_baseline(); + } + + AS_API float alphaskia_text_metrics_get_alphabetic_baseline(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_alphabetic_baseline(); + } + + AS_API float alphaskia_text_metrics_get_ideographic_baseline(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_ideographic_baseline(); + } + + AS_API void alphaskia_text_metrics_free(alphaskia_text_metrics_t text_metrics) + { + auto alphaSkiaTextMetrics = reinterpret_cast(text_metrics); + delete text_metrics; + } +} \ No newline at end of file diff --git a/wrapper/src/alphaskia_textstyle.cpp b/wrapper/src/alphaskia_text_style.cpp similarity index 72% rename from wrapper/src/alphaskia_textstyle.cpp rename to wrapper/src/alphaskia_text_style.cpp index 487face..b58190b 100644 --- a/wrapper/src/alphaskia_textstyle.cpp +++ b/wrapper/src/alphaskia_text_style.cpp @@ -3,7 +3,7 @@ extern "C" { - AS_API alphaskia_textstyle_t alphaskia_textstyle_new(uint8_t family_name_count, const char **family_names, uint16_t weight, uint8_t italic) + AS_API alphaskia_text_style_t alphaskia_text_style_new(uint8_t family_name_count, const char **family_names, uint16_t weight, uint8_t italic) { SkFontStyle::Weight skWeight; switch (weight) @@ -36,18 +36,18 @@ extern "C" skTextStyle->get_family_names().emplace_back(SkString(family_names[i])); } - return reinterpret_cast(skTextStyle); + return reinterpret_cast(skTextStyle); } - AS_API uint8_t alphaskia_textstyle_get_family_name_count(alphaskia_textstyle_t textstyle) + AS_API uint8_t alphaskia_text_style_get_family_name_count(alphaskia_text_style_t text_style) { - AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(textstyle); + AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(text_style); return static_cast(skTextstyle->get_family_names().size()); } - AS_API alphaskia_string_t alphaskia_textstyle_get_family_name(alphaskia_textstyle_t textstyle, uint8_t index) + AS_API alphaskia_string_t alphaskia_text_style_get_family_name(alphaskia_text_style_t text_style, uint8_t index) { - AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(textstyle); + AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(text_style); if (index >= skTextstyle->get_family_names().size()) { @@ -58,26 +58,26 @@ extern "C" return reinterpret_cast(skFamilyName); } - AS_API uint16_t alphaskia_textstyle_get_weight(alphaskia_textstyle_t textstyle) + AS_API uint16_t alphaskia_text_style_get_weight(alphaskia_text_style_t text_style) { - AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(textstyle); + AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(text_style); return static_cast(skTextstyle->get_font_style().weight()); } - AS_API uint8_t alphaskia_textstyle_is_italic(alphaskia_textstyle_t textstyle) + AS_API uint8_t alphaskia_text_style_is_italic(alphaskia_text_style_t text_style) { - AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(textstyle); + AlphaSkiaTextStyle *skTextstyle = reinterpret_cast(text_style); return skTextstyle->get_font_style().slant() == SkFontStyle::Slant::kItalic_Slant ? 1 : 0; } - AS_API void alphaskia_textstyle_free(alphaskia_textstyle_t textstyle) + AS_API void alphaskia_text_style_free(alphaskia_text_style_t text_style) { - if (!textstyle) + if (!text_style) { return; } - AlphaSkiaTextStyle *skTextStyle = reinterpret_cast(textstyle); + AlphaSkiaTextStyle *skTextStyle = reinterpret_cast(text_style); delete skTextStyle; } } \ No newline at end of file From 315b936951392c10e2806684ce7d50d733e4c757 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Tue, 22 Apr 2025 01:52:29 +0200 Subject: [PATCH 2/3] feat: base implementation for measureText --- test/native/src/main.cpp | 51 +++++- wrapper/include/AlphaSkiaCanvas.h | 8 +- wrapper/src/AlphaSkiaCanvas.cpp | 243 +++++++++++++++---------- wrapper/src/alphaskia_text_metrics.cpp | 10 + 4 files changed, 213 insertions(+), 99 deletions(-) diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index e2518b8..b7f971b 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -84,8 +84,8 @@ bool write_data_to_file_and_free(alphaskia_data_t data, std::string path) return true; } - -void empty_image_test() { +void empty_image_test() +{ // https://github.com/CoderLine/alphaSkia/issues/53 auto canvas = alphaskia_canvas_new(); alphaskia_canvas_begin_render(canvas, 0, 0, 1); @@ -97,6 +97,47 @@ void empty_image_test() { alphaskia_canvas_free(canvas); } +void measure_test() +{ + // https://github.com/CoderLine/alphaSkia/issues/53 + auto canvas = alphaskia_canvas_new(); + + std::vector familyNames({"Noto Sans"}); + + auto text_style = alphaskia_text_style_new( + familyNames.size(), + const_cast(familyNames.data()), + 400, + 0); + + auto text_metrics = alphaskia_canvas_measure_text( + canvas, + u"Hello World", + 11, + text_style, + 18, + alphaskia_text_align_center, + alphaskia_text_baseline_alphabetic); + + std::cout << "Hello World (Noto Sans 18px) Text Metrics" << std::endl; + std::cout << " get_width: " << alphaskia_text_metrics_get_width(text_metrics) << std::endl; + std::cout << " get_actual_bounding_box_left: " << alphaskia_text_metrics_get_actual_bounding_box_left(text_metrics) << std::endl; + std::cout << " get_actual_bounding_box_right: " << alphaskia_text_metrics_get_actual_bounding_box_right(text_metrics) << std::endl; + std::cout << " get_font_bounding_box_ascent: " << alphaskia_text_metrics_get_font_bounding_box_ascent(text_metrics) << std::endl; + std::cout << " get_font_bounding_box_descent: " << alphaskia_text_metrics_get_font_bounding_box_descent(text_metrics) << std::endl; + std::cout << " get_actual_bounding_box_ascent: " << alphaskia_text_metrics_get_actual_bounding_box_ascent(text_metrics) << std::endl; + std::cout << " get_actual_bounding_box_descent: " << alphaskia_text_metrics_get_actual_bounding_box_descent(text_metrics) << std::endl; + std::cout << " get_em_height_ascent: " << alphaskia_text_metrics_get_em_height_ascent(text_metrics) << std::endl; + std::cout << " get_em_height_descent: " << alphaskia_text_metrics_get_em_height_descent(text_metrics) << std::endl; + std::cout << " get_hanging_baseline: " << alphaskia_text_metrics_get_hanging_baseline(text_metrics) << std::endl; + std::cout << " get_alphabetic_baseline: " << alphaskia_text_metrics_get_alphabetic_baseline(text_metrics) << std::endl; + std::cout << " get_ideographic_baseline: " << alphaskia_text_metrics_get_ideographic_baseline(text_metrics) << std::endl; + + alphaskia_text_metrics_free(text_metrics); + alphaskia_text_style_free(text_style); + + alphaskia_canvas_free(canvas); +} int main(int argc, char **argv) { @@ -124,6 +165,10 @@ int main(int argc, char **argv) empty_image_test(); std::cout << "Image worked" << std::endl; + std::cout << "Measure Test" << std::endl; + measure_test(); + std::cout << "Measure Test Done" << std::endl; + // Load all fonts for rendering std::cout << "Loading fonts" << std::endl; std::filesystem::path test_data_path = repository_root / "test" / "test-data"; @@ -132,7 +177,7 @@ int main(int argc, char **argv) auto music_typeface_weight = alphaskia_typeface_get_weight(music_typeface); auto music_typeface_italic = alphaskia_typeface_is_italic(music_typeface); auto music_typeface_name_raw = alphaskia_string_get_utf8(music_typeface_name); - music_text_style = alphaskia_textstyle_new(1, &music_typeface_name_raw, music_typeface_weight, music_typeface_italic); + music_text_style = alphaskia_text_style_new(1, &music_typeface_name_raw, music_typeface_weight, music_typeface_italic); alphaskia_string_free(music_typeface_name); alphaskia_load_typeface((test_data_path / "font" / "noto-sans" / "NotoSans-Regular.otf").generic_string()); diff --git a/wrapper/include/AlphaSkiaCanvas.h b/wrapper/include/AlphaSkiaCanvas.h index 3d5aa3b..f71dbdc 100644 --- a/wrapper/include/AlphaSkiaCanvas.h +++ b/wrapper/include/AlphaSkiaCanvas.h @@ -21,12 +21,14 @@ class AlphaSkiaTextMetrics float actual_bounding_box_right, float font_bounding_box_ascent, float font_bounding_box_descent, + float actual_bounding_box_ascent, + float actual_bounding_box_descent, float em_height_ascent, float em_height_descent, float hanging_baseline, float alphabetic_baseline, float ideographic_baseline) - : width_(width), actual_bounding_box_left_(actual_bounding_box_left), actual_bounding_box_right_(actual_bounding_box_right), font_bounding_box_ascent_(font_bounding_box_ascent), font_bounding_box_descent_(font_bounding_box_descent), em_height_ascent_(em_height_ascent), em_height_descent_(em_height_descent), hanging_baseline_(hanging_baseline), alphabetic_baseline_(alphabetic_baseline), ideographic_baseline_(ideographic_baseline) + : width_(width), actual_bounding_box_left_(actual_bounding_box_left), actual_bounding_box_right_(actual_bounding_box_right), font_bounding_box_ascent_(font_bounding_box_ascent), font_bounding_box_descent_(font_bounding_box_descent), actual_bounding_box_ascent_(actual_bounding_box_ascent), actual_bounding_box_descent_(actual_bounding_box_descent), em_height_ascent_(em_height_ascent), em_height_descent_(em_height_descent), hanging_baseline_(hanging_baseline), alphabetic_baseline_(alphabetic_baseline), ideographic_baseline_(ideographic_baseline) { } @@ -35,6 +37,8 @@ class AlphaSkiaTextMetrics float get_actual_bounding_box_right() const { return actual_bounding_box_right_; } float get_font_bounding_box_ascent() const { return font_bounding_box_ascent_; } float get_font_bounding_box_descent() const { return font_bounding_box_descent_; } + float get_actual_bounding_box_ascent() const { return actual_bounding_box_ascent_; } + float get_actual_bounding_box_descent() const { return actual_bounding_box_descent_; } float get_em_height_ascent() const { return em_height_ascent_; } float get_em_height_descent() const { return em_height_descent_; } float get_hanging_baseline() const { return hanging_baseline_; } @@ -47,6 +51,8 @@ class AlphaSkiaTextMetrics float actual_bounding_box_right_; float font_bounding_box_ascent_; float font_bounding_box_descent_; + float actual_bounding_box_ascent_; + float actual_bounding_box_descent_; float em_height_ascent_; float em_height_descent_; float hanging_baseline_; diff --git a/wrapper/src/AlphaSkiaCanvas.cpp b/wrapper/src/AlphaSkiaCanvas.cpp index da59364..f5b4ee9 100644 --- a/wrapper/src/AlphaSkiaCanvas.cpp +++ b/wrapper/src/AlphaSkiaCanvas.cpp @@ -13,12 +13,95 @@ #include "../../externals/skia/include/core/SkRefCnt.h" #include "../../externals/skia/modules/skparagraph/include/FontCollection.h" #include "../../externals/skia/modules/skparagraph/include/ParagraphBuilder.h" +#include "../../externals/skia/modules/skparagraph/src/ParagraphImpl.h" #include #include #include #include +#define kHangingAsPercentOfAscent 80 + +float float_ascent(const SkFontMetrics &metrics) +{ + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/font_metrics.h;l=49?q=FloatAscent&ss=chromium%2Fchromium%2Fsrc + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=131;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/font_metrics.cc;l=112;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1 + return SkScalarRoundToScalar(-metrics.fAscent); +} + +float float_descent(const SkFontMetrics &metrics) +{ + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/font_metrics.cc;l=112;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1 + return SkScalarRoundToScalar(metrics.fDescent); +} + +std::pair typo_ascender_and_descender(SkTypeface *typeface) +{ + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=388;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d?q=TypoAscenderAndDescender&sq=&ss=chromium%2Fchromium%2Fsrc + uint8_t buffer[4]; + size_t size = typeface->getTableData(SkSetFourByteTag('O', 'S', '/', '2'), 68, + sizeof(buffer), buffer); + if (size == sizeof(buffer)) + { + return std::make_pair( + (int16_t)((buffer[0] << 8) | buffer[1]), + -(int16_t)((buffer[2] << 8) | buffer[3])); + } + + return std::make_pair(0, 0); +} + +const uint32_t layoutUnitFractionalBits_ = 6; +const int fixedPointDenominator_ = 1 << layoutUnitFractionalBits_; + +int float_to_layout_unit(float value) +{ + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/geometry/layout_unit.h;l=147;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=FromFloatRound&ss=chromium%2Fchromium%2Fsrc + return static_cast(roundf(value * fixedPointDenominator_)); +} + +float layout_unit_to_float(int value) +{ + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/geometry/layout_unit.h;l=147;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=FromFloatRound&sq=&ss=chromium%2Fchromium%2Fsrc return static_cast(roundf(value * kFixedPointDenominator)) + return static_cast(value) / fixedPointDenominator_; +} + +bool try_set_normalized_typo_ascent_and_descent(float em_height, float typo_ascent, float typo_descent, int &ascent, int &descent) +{ + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=422;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=NormalizedTypoAscentAndDescent&ss=chromium%2Fchromium%2Fsrc + const float height = typo_ascent + typo_descent; + if (height <= 0 || typo_ascent < 0 || typo_descent > height) + { + return false; + } + + ascent = float_to_layout_unit(typo_ascent * em_height / height); + descent = float_to_layout_unit(em_height) - ascent; + return true; +} + +void normalized_typo_ascent_and_descent(const SkFont &font, int &ascent, int &descent) +{ + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=366;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=NormalizedTypoAscentAndDescent&ss=chromium%2Fchromium%2Fsrc + SkTypeface *typeface = font.getTypeface(); + auto [typo_ascender, typo_descender] = typo_ascender_and_descender(typeface); + + if (typo_ascender > 0 && + try_set_normalized_typo_ascent_and_descent(font.getSize(), typo_ascender, typo_descender, ascent, descent)) + { + return; + } + + // As the last resort, compute em height metrics from our ascent/descent. + SkFontMetrics metrics; + font.getMetrics(&metrics); + if (try_set_normalized_typo_ascent_and_descent(font.getSize(), float_ascent(metrics), float_descent(metrics), ascent, descent)) + { + return; + } +} + AlphaSkiaCanvas::AlphaSkiaCanvas() : color_(SK_ColorWHITE), line_width_(1.0f) { @@ -217,25 +300,76 @@ void AlphaSkiaCanvas::fill_text(const char16_t *text, int text_length, const Alp paragraph->paint(surface_->getCanvas(), x, y); } -AlphaSkiaTextMetrics* AlphaSkiaCanvas::measure_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) +AlphaSkiaTextMetrics *AlphaSkiaCanvas::measure_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) { auto paragraph(build_paragraph(text, text_length, text_style, font_size, alphaskia_text_align_t::alphaskia_text_align_left)); - paragraph->layout(10000); + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc;l=1290 + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/canvas/text_metrics.cc + + // the original HTML5 canvas doesn't really support newlines, we simply calculate everything with the + // first line + + skia::textlayout::ParagraphImpl *paragraphImpl = reinterpret_cast(paragraph.get()); + auto &line0 = paragraphImpl->lines()[0]; + + float width = static_cast(paragraph->getMaxIntrinsicWidth()); + + auto text_align_dx_ = 0.0f; + if (text_align == alphaskia_text_align_t::alphaskia_text_align_center) + { + text_align_dx_ = width / 2.0f; + } + else if (text_align == alphaskia_text_align_t::alphaskia_text_align_right) + { + text_align_dx_ = width; + } + else + { + text_align_dx_ = 0; + } + + auto lineOffset = line0.offset(); + float actual_bounding_box_left = -lineOffset.fX + text_align_dx_; + float actual_bounding_box_right = (lineOffset.fX + line0.width()) - text_align_dx_; + + SkFont font = paragraph->getFontAt(0); + + SkFontMetrics font_metrics; + font.getMetrics(&font_metrics); + const float ascent = float_ascent(font_metrics); + const float descent = float_descent(font_metrics); + const float baseline_y = get_font_baseline(font, baseline); + + float font_bounding_box_ascent = ascent - baseline_y; + float font_bounding_box_descent = descent + baseline_y; + float actual_bounding_box_ascent = -lineOffset.fY - baseline_y; + float actual_bounding_box_descent = (lineOffset.fY + line0.height()) + baseline_y; + + int normalizedAscent = 0; + int normalizedDescent = 0; + normalized_typo_ascent_and_descent(font, normalizedAscent, normalizedDescent); + float em_height_ascent = normalizedAscent - baseline_y; + float em_height_descent = normalizedDescent + baseline_y; + // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/canvas/text_metrics.cc;l=174;drc=1d737975521c1d4191937c2c659bd78d9f1681f4;bpv=0;bpt=1 + float hanging_baseline = ascent * kHangingAsPercentOfAscent / 100.0f - baseline_y; + float alphabetic_baseline = line0.alphabeticBaseline(); + float ideographic_baseline = line0.ideographicBaseline(); + return new AlphaSkiaTextMetrics( - static_cast(paragraph->getMaxIntrinsicWidth()), - // TODO - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ); + width, + actual_bounding_box_left, + actual_bounding_box_right, + font_bounding_box_ascent, + font_bounding_box_descent, + actual_bounding_box_ascent, + actual_bounding_box_descent, + em_height_ascent, + em_height_descent, + hanging_baseline, + alphabetic_baseline, + ideographic_baseline); } void AlphaSkiaCanvas::begin_rotate(float center_x, float center_y, float angle) @@ -256,86 +390,6 @@ void AlphaSkiaCanvas::draw_image(sk_sp image, float x, float y, float w surface_->getCanvas()->drawImageRect(image, SkRect::MakeXYWH(x, y, w, h), sampling); } -float float_ascent(const SkFontMetrics &metrics) -{ - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/font_metrics.h;l=49?q=FloatAscent&ss=chromium%2Fchromium%2Fsrc - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=131;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/font_metrics.cc;l=112;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1 - return SkScalarRoundToScalar(-metrics.fAscent); -} - -float float_descent(const SkFontMetrics &metrics) -{ - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/font_metrics.cc;l=112;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1 - return SkScalarRoundToScalar(metrics.fDescent); -} - -std::pair typo_ascender_and_descender(SkTypeface *typeface) -{ - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=388;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d?q=TypoAscenderAndDescender&sq=&ss=chromium%2Fchromium%2Fsrc - uint8_t buffer[4]; - size_t size = typeface->getTableData(SkSetFourByteTag('O', 'S', '/', '2'), 68, - sizeof(buffer), buffer); - if (size == sizeof(buffer)) - { - return std::make_pair( - (int16_t)((buffer[0] << 8) | buffer[1]), - -(int16_t)((buffer[2] << 8) | buffer[3])); - } - - return std::make_pair(0, 0); -} - -const uint32_t layoutUnitFractionalBits_ = 6; -const int fixedPointDenominator_ = 1 << layoutUnitFractionalBits_; - -int float_to_layout_unit(float value) -{ - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/geometry/layout_unit.h;l=147;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=FromFloatRound&ss=chromium%2Fchromium%2Fsrc - return static_cast(roundf(value * fixedPointDenominator_)); -} - -float layout_unit_to_float(int value) -{ - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/geometry/layout_unit.h;l=147;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=FromFloatRound&sq=&ss=chromium%2Fchromium%2Fsrc return static_cast(roundf(value * kFixedPointDenominator)) - return static_cast(value) / fixedPointDenominator_; -} - -bool try_set_normalized_typo_ascent_and_descent(float em_height, float typo_ascent, float typo_descent, int &ascent, int &descent) -{ - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=422;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=NormalizedTypoAscentAndDescent&ss=chromium%2Fchromium%2Fsrc - const float height = typo_ascent + typo_descent; - if (height <= 0 || typo_ascent < 0 || typo_descent > height) - { - return false; - } - - ascent = float_to_layout_unit(typo_ascent * em_height / height); - descent = float_to_layout_unit(em_height) - ascent; - return true; -} - -void normalized_typo_ascent_and_descent(const SkFont &font, int &ascent, int &descent) -{ - // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/fonts/simple_font_data.cc;l=366;drc=5a2e12875a8fe207bfe6f0febc782b6297788b6d;bpv=1;bpt=1?q=NormalizedTypoAscentAndDescent&ss=chromium%2Fchromium%2Fsrc - SkTypeface *typeface = font.getTypeface(); - auto [typo_ascender, typo_descender] = typo_ascender_and_descender(typeface); - - if (typo_ascender > 0 && - try_set_normalized_typo_ascent_and_descent(font.getSize(), typo_ascender, typo_descender, ascent, descent)) - { - return; - } - - // As the last resort, compute em height metrics from our ascent/descent. - SkFontMetrics metrics; - font.getMetrics(&metrics); - if (try_set_normalized_typo_ascent_and_descent(font.getSize(), float_ascent(metrics), float_descent(metrics), ascent, descent)) - { - return; - } -} - float AlphaSkiaCanvas::get_font_baseline(const SkFont &font, alphaskia_text_baseline_t baseline) { // https://github.com/chromium/chromium/blob/99314be8152e688bafbbf9a615536bdbb289ea87/third_party/blink/renderer/core/html/canvas/text_metrics.cc#L14 @@ -352,7 +406,6 @@ float AlphaSkiaCanvas::get_font_baseline(const SkFont &font, alphaskia_text_base baselineOffset = 0; break; case alphaskia_text_baseline_top: // kHangingTextBaseline -#define kHangingAsPercentOfAscent 80 baselineOffset = float_ascent(metrics) * kHangingAsPercentOfAscent / 100.0f; break; case alphaskia_text_baseline_middle: // kMiddleTextBaseline diff --git a/wrapper/src/alphaskia_text_metrics.cpp b/wrapper/src/alphaskia_text_metrics.cpp index 00be095..53557df 100644 --- a/wrapper/src/alphaskia_text_metrics.cpp +++ b/wrapper/src/alphaskia_text_metrics.cpp @@ -28,6 +28,16 @@ extern "C" return reinterpret_cast(text_metrics)->get_font_bounding_box_descent(); } + AS_API float alphaskia_text_metrics_get_actual_bounding_box_ascent(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_actual_bounding_box_ascent(); + } + + AS_API float alphaskia_text_metrics_get_actual_bounding_box_descent(alphaskia_text_metrics_t text_metrics) + { + return reinterpret_cast(text_metrics)->get_actual_bounding_box_descent(); + } + AS_API float alphaskia_text_metrics_get_em_height_ascent(alphaskia_text_metrics_t text_metrics) { return reinterpret_cast(text_metrics)->get_em_height_ascent(); From f80efb6bed2c622a5c256175e25eaf668a3f525c Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Tue, 22 Apr 2025 02:16:55 +0200 Subject: [PATCH 3/3] test: prepare for correct text measurement --- test/native/include/AlphaSkiaTestBridge.h | 6 ++-- test/native/src/AlphaSkiaTestBridge.cpp | 22 +++++++----- test/native/src/main.cpp | 41 +++++++++++++++++------ wrapper/include/AlphaSkiaCanvas.h | 2 +- wrapper/src/AlphaSkiaCanvas.cpp | 34 +++++++++++-------- wrapper/src/alphaskia_text_metrics.cpp | 2 +- 6 files changed, 69 insertions(+), 38 deletions(-) diff --git a/test/native/include/AlphaSkiaTestBridge.h b/test/native/include/AlphaSkiaTestBridge.h index 6a59534..6f64f49 100644 --- a/test/native/include/AlphaSkiaTestBridge.h +++ b/test/native/include/AlphaSkiaTestBridge.h @@ -15,14 +15,14 @@ typedef std::function render_function_t; extern alphaskia_text_align_t text_align; extern alphaskia_text_baseline_t text_baseline; -extern alphaskia_textstyle_t text_style; -extern alphaskia_textstyle_t music_text_style; +extern alphaskia_text_style_t text_style; +extern alphaskia_text_style_t music_text_style; extern float music_font_size; extern float font_size; extern float render_scale; extern int32_t total_width; extern int32_t total_height; -alphaskia_textstyle_t alphaskia_get_text_style(const std::vector& family_names, uint16_t weight, bool is_italic); +alphaskia_text_style_t alphaskia_get_text_style(const std::vector& family_names, uint16_t weight, bool is_italic); alphaskia_typeface_t alphaskia_load_typeface(std::string file_path); void read_file(std::string file_path, std::vector &data); \ No newline at end of file diff --git a/test/native/src/AlphaSkiaTestBridge.cpp b/test/native/src/AlphaSkiaTestBridge.cpp index 0d3a6ae..887fc68 100644 --- a/test/native/src/AlphaSkiaTestBridge.cpp +++ b/test/native/src/AlphaSkiaTestBridge.cpp @@ -8,15 +8,15 @@ alphaskia_text_align_t text_align = alphaskia_text_align_left; alphaskia_text_baseline_t text_baseline = alphaskia_text_baseline_top; -alphaskia_textstyle_t text_style = nullptr; -alphaskia_textstyle_t music_text_style = nullptr; +alphaskia_text_style_t text_style = nullptr; +alphaskia_text_style_t music_text_style = nullptr; float music_font_size = 34; float render_scale = 1; float font_size = 12.0f; -static std::map custom_text_styles; +static std::map custom_text_styles; -std::string custom_textstyle_key(const std::vector &family_names, uint16_t weight, bool is_italic) +std::string custom_text_style_key(const std::vector &family_names, uint16_t weight, bool is_italic) { std::stringstream s; for (auto &name : family_names) @@ -29,13 +29,13 @@ std::string custom_textstyle_key(const std::vector &family_names, return s.str(); } -alphaskia_textstyle_t alphaskia_get_text_style(const std::vector &family_names, uint16_t weight, bool is_italic) +alphaskia_text_style_t alphaskia_get_text_style(const std::vector &family_names, uint16_t weight, bool is_italic) { - auto key = custom_textstyle_key(family_names, weight, is_italic); + auto key = custom_text_style_key(family_names, weight, is_italic); auto it = custom_text_styles.find(key); if (it == custom_text_styles.end()) { - alphaskia_textstyle_t new_text_style = alphaskia_textstyle_new( + alphaskia_text_style_t new_text_style = alphaskia_text_style_new( static_cast(family_names.size()), const_cast(&family_names[0]), weight, @@ -83,10 +83,16 @@ alphaskia_typeface_t alphaskia_load_typeface(std::string file_path) std::cerr << "Could not create typeface from data " << file_path << std::endl; return nullptr; } + + alphaskia_string_t family_name = alphaskia_typeface_get_family_name(typeface); + std::cout << "Typeface " - << alphaskia_typeface_get_family_name(typeface) + << alphaskia_string_get_utf8(typeface) << "weight: " << alphaskia_typeface_get_weight(typeface) << " italic: " << (alphaskia_typeface_is_italic(typeface) ? "yes" : "no") << " loaded" << std::endl; + + alphaskia_string_free(family_name); + return typeface; } \ No newline at end of file diff --git a/test/native/src/main.cpp b/test/native/src/main.cpp index b7f971b..9661f6f 100644 --- a/test/native/src/main.cpp +++ b/test/native/src/main.cpp @@ -116,22 +116,41 @@ void measure_test() 11, text_style, 18, - alphaskia_text_align_center, + alphaskia_text_align_left, alphaskia_text_baseline_alphabetic); + + // in chrome we get: + // + // x = new OffscreenCanvas(100, 100) + // ctx = x.getContext('2d') + // ctx.font = '18px "Noto Sans"' + // ctx.measureText('Hello World') + // + // Output: + // actualBoundingBoxAscent: 13 + // actualBoundingBoxDescent: 0 + // actualBoundingBoxLeft: 1 + // actualBoundingBoxRight: 90.033203125 + // alphabeticBaseline: -0 + // fontBoundingBoxAscent: 16 + // fontBoundingBoxDescent: 4 + // hangingBaseline: 12.800000190734863 + // ideographicBaseline: -4 + // width: 89.033203125 std::cout << "Hello World (Noto Sans 18px) Text Metrics" << std::endl; - std::cout << " get_width: " << alphaskia_text_metrics_get_width(text_metrics) << std::endl; + std::cout << " get_actual_bounding_box_ascent: " << alphaskia_text_metrics_get_actual_bounding_box_ascent(text_metrics) << std::endl; + std::cout << " get_actual_bounding_box_descent: " << alphaskia_text_metrics_get_actual_bounding_box_descent(text_metrics) << std::endl; std::cout << " get_actual_bounding_box_left: " << alphaskia_text_metrics_get_actual_bounding_box_left(text_metrics) << std::endl; std::cout << " get_actual_bounding_box_right: " << alphaskia_text_metrics_get_actual_bounding_box_right(text_metrics) << std::endl; + std::cout << " get_alphabetic_baseline: " << alphaskia_text_metrics_get_alphabetic_baseline(text_metrics) << std::endl; std::cout << " get_font_bounding_box_ascent: " << alphaskia_text_metrics_get_font_bounding_box_ascent(text_metrics) << std::endl; std::cout << " get_font_bounding_box_descent: " << alphaskia_text_metrics_get_font_bounding_box_descent(text_metrics) << std::endl; - std::cout << " get_actual_bounding_box_ascent: " << alphaskia_text_metrics_get_actual_bounding_box_ascent(text_metrics) << std::endl; - std::cout << " get_actual_bounding_box_descent: " << alphaskia_text_metrics_get_actual_bounding_box_descent(text_metrics) << std::endl; + std::cout << " get_ideographic_baseline: " << alphaskia_text_metrics_get_ideographic_baseline(text_metrics) << std::endl; + std::cout << " get_hanging_baseline: " << alphaskia_text_metrics_get_hanging_baseline(text_metrics) << std::endl; + std::cout << " get_width: " << alphaskia_text_metrics_get_width(text_metrics) << std::endl; std::cout << " get_em_height_ascent: " << alphaskia_text_metrics_get_em_height_ascent(text_metrics) << std::endl; std::cout << " get_em_height_descent: " << alphaskia_text_metrics_get_em_height_descent(text_metrics) << std::endl; - std::cout << " get_hanging_baseline: " << alphaskia_text_metrics_get_hanging_baseline(text_metrics) << std::endl; - std::cout << " get_alphabetic_baseline: " << alphaskia_text_metrics_get_alphabetic_baseline(text_metrics) << std::endl; - std::cout << " get_ideographic_baseline: " << alphaskia_text_metrics_get_ideographic_baseline(text_metrics) << std::endl; alphaskia_text_metrics_free(text_metrics); alphaskia_text_style_free(text_style); @@ -165,10 +184,6 @@ int main(int argc, char **argv) empty_image_test(); std::cout << "Image worked" << std::endl; - std::cout << "Measure Test" << std::endl; - measure_test(); - std::cout << "Measure Test Done" << std::endl; - // Load all fonts for rendering std::cout << "Loading fonts" << std::endl; std::filesystem::path test_data_path = repository_root / "test" / "test-data"; @@ -194,6 +209,10 @@ int main(int argc, char **argv) std::cout << "Fonts loaded" << std::endl; + std::cout << "Measure Test" << std::endl; + measure_test(); + std::cout << "Measure Test Done" << std::endl; + // render full image std::cout << "Rendering image" << std::endl; alphaskia_image_t actual_image = render_full_image(); diff --git a/wrapper/include/AlphaSkiaCanvas.h b/wrapper/include/AlphaSkiaCanvas.h index f71dbdc..98bc798 100644 --- a/wrapper/include/AlphaSkiaCanvas.h +++ b/wrapper/include/AlphaSkiaCanvas.h @@ -128,7 +128,7 @@ class AlphaSkiaCanvas private: SkPaint create_paint(); std::unique_ptr build_paragraph(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align); - static float get_font_baseline(const SkFont &font, alphaskia_text_baseline_t baseline); + static float get_font_baseline(const SkFont &font, alphaskia_text_baseline_t baseline, bool correctSkParagraphBaseline); SkColor color_; float line_width_; diff --git a/wrapper/src/AlphaSkiaCanvas.cpp b/wrapper/src/AlphaSkiaCanvas.cpp index f5b4ee9..b7f2e99 100644 --- a/wrapper/src/AlphaSkiaCanvas.cpp +++ b/wrapper/src/AlphaSkiaCanvas.cpp @@ -281,7 +281,7 @@ void AlphaSkiaCanvas::fill_text(const char16_t *text, int text_length, const Alp paragraph->layout(surface_->width() * 2); // NOTE: SkParagraph has no support for font/line specific baselines, first font is better than nothing - y += get_font_baseline(paragraph->getFontAt(0), baseline); + y += get_font_baseline(paragraph->getFontAt(0), baseline, true); switch (text_align) { @@ -303,7 +303,8 @@ void AlphaSkiaCanvas::fill_text(const char16_t *text, int text_length, const Alp AlphaSkiaTextMetrics *AlphaSkiaCanvas::measure_text(const char16_t *text, int text_length, const AlphaSkiaTextStyle &text_style, float font_size, alphaskia_text_align_t text_align, alphaskia_text_baseline_t baseline) { auto paragraph(build_paragraph(text, text_length, text_style, font_size, alphaskia_text_align_t::alphaskia_text_align_left)); - paragraph->layout(10000); + const float layoutWidth = 10000; + paragraph->layout(layoutWidth); // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc;l=1290 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/canvas/text_metrics.cc @@ -317,17 +318,20 @@ AlphaSkiaTextMetrics *AlphaSkiaCanvas::measure_text(const char16_t *text, int te float width = static_cast(paragraph->getMaxIntrinsicWidth()); auto text_align_dx_ = 0.0f; - if (text_align == alphaskia_text_align_t::alphaskia_text_align_center) + switch (text_align) { + case alphaskia_text_align_left: + // doesn't matter + break; + case alphaskia_text_align_center: text_align_dx_ = width / 2.0f; - } - else if (text_align == alphaskia_text_align_t::alphaskia_text_align_right) - { + text_align_dx_ -= layoutWidth / 2; + break; + case alphaskia_text_align_right: + // text is aligned at layoutWidth, shift it left text_align_dx_ = width; - } - else - { - text_align_dx_ = 0; + text_align_dx_ -= layoutWidth; + break; } auto lineOffset = line0.offset(); @@ -340,7 +344,7 @@ AlphaSkiaTextMetrics *AlphaSkiaCanvas::measure_text(const char16_t *text, int te font.getMetrics(&font_metrics); const float ascent = float_ascent(font_metrics); const float descent = float_descent(font_metrics); - const float baseline_y = get_font_baseline(font, baseline); + const float baseline_y = get_font_baseline(font, baseline, true); float font_bounding_box_ascent = ascent - baseline_y; float font_bounding_box_descent = descent + baseline_y; @@ -390,7 +394,7 @@ void AlphaSkiaCanvas::draw_image(sk_sp image, float x, float y, float w surface_->getCanvas()->drawImageRect(image, SkRect::MakeXYWH(x, y, w, h), sampling); } -float AlphaSkiaCanvas::get_font_baseline(const SkFont &font, alphaskia_text_baseline_t baseline) +float AlphaSkiaCanvas::get_font_baseline(const SkFont &font, alphaskia_text_baseline_t baseline, bool correctSkParagraphBaseline) { // https://github.com/chromium/chromium/blob/99314be8152e688bafbbf9a615536bdbb289ea87/third_party/blink/renderer/core/html/canvas/text_metrics.cc#L14 SkFontMetrics metrics; @@ -424,8 +428,10 @@ float AlphaSkiaCanvas::get_font_baseline(const SkFont &font, alphaskia_text_base // SkParagraph defines its baseline() as (fLeading / 2 - fAscent) // see: Run.h -> InternalLineMetrics::baseline() // we reset this here - const float skParagraphBaseline = metrics.fLeading / 2 + float_ascent(metrics); - baselineOffset -= skParagraphBaseline; + if(correctSkParagraphBaseline) { + const float skParagraphBaseline = metrics.fLeading / 2 + float_ascent(metrics); + baselineOffset -= skParagraphBaseline; + } return baselineOffset; } \ No newline at end of file diff --git a/wrapper/src/alphaskia_text_metrics.cpp b/wrapper/src/alphaskia_text_metrics.cpp index 53557df..94f9585 100644 --- a/wrapper/src/alphaskia_text_metrics.cpp +++ b/wrapper/src/alphaskia_text_metrics.cpp @@ -66,6 +66,6 @@ extern "C" AS_API void alphaskia_text_metrics_free(alphaskia_text_metrics_t text_metrics) { auto alphaSkiaTextMetrics = reinterpret_cast(text_metrics); - delete text_metrics; + delete alphaSkiaTextMetrics; } } \ No newline at end of file