diff --git a/example/src/Tests/assets/DIN-Medium.ttf b/example/src/Tests/assets/DIN-Medium.ttf new file mode 100644 index 0000000000..a8360c2fd9 Binary files /dev/null and b/example/src/Tests/assets/DIN-Medium.ttf differ diff --git a/example/src/Tests/useAssets.ts b/example/src/Tests/useAssets.ts index d65f78ee01..9d7c660ab1 100644 --- a/example/src/Tests/useAssets.ts +++ b/example/src/Tests/useAssets.ts @@ -35,6 +35,10 @@ export const useAssets = () => { require("./assets/NotoSansSC-Regular.otf"), errorHandler ); + const DinMedium = useTypeface( + require("./assets/DIN-Medium.ttf"), + errorHandler + ); if (error) { throw new Error("Failed to load assets: " + error.message); } @@ -44,6 +48,7 @@ export const useAssets = () => { !NotoColorEmoji || !NotoSansSCRegular || !UberMoveMediumMono || + !DinMedium || !skiaLogoJpeg || !skiaLogoPng || !mask @@ -55,6 +60,7 @@ export const useAssets = () => { NotoColorEmoji, NotoSansSCRegular, UberMoveMediumMono, + DinMedium, oslo, skiaLogoJpeg, skiaLogoPng, diff --git a/package/cpp/api/JsiSkFont.h b/package/cpp/api/JsiSkFont.h index b0dd78e8d6..548f8ed50e 100644 --- a/package/cpp/api/JsiSkFont.h +++ b/package/cpp/api/JsiSkFont.h @@ -60,6 +60,7 @@ class JsiSkFont : public JsiSkWrappingSharedPtrHostObject { return jsiWidths; } + // TODO: deprecate JSI_HOST_FUNCTION(getTextWidth) { auto str = arguments[0].asString(runtime).utf8(runtime); auto numGlyphIDs = getObject()->countText(str.c_str(), str.length(), @@ -85,6 +86,21 @@ class JsiSkFont : public JsiSkWrappingSharedPtrHostObject { return jsi::Value(std::accumulate(widthPtrs.begin(), widthPtrs.end(), 0)); } + JSI_HOST_FUNCTION(measureText) { + auto str = arguments[0].asString(runtime).utf8(runtime); + SkRect bounds; + if (count > 1) { + auto paint = JsiSkPaint::fromValue(runtime, arguments[1]); + getObject()->measureText(str.c_str(), str.length(), SkTextEncoding::kUTF8, + &bounds, paint.get()); + } else { + getObject()->measureText(str.c_str(), str.length(), SkTextEncoding::kUTF8, + &bounds); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared(getContext(), std::move(bounds))); + } + JSI_HOST_FUNCTION(getMetrics) { SkFontMetrics fm; getObject()->getMetrics(&fm); @@ -260,6 +276,7 @@ class JsiSkFont : public JsiSkWrappingSharedPtrHostObject { JSI_EXPORT_FUNC(JsiSkFont, setTypeface), JSI_EXPORT_FUNC(JsiSkFont, getGlyphWidths), JSI_EXPORT_FUNC(JsiSkFont, getTextWidth), + JSI_EXPORT_FUNC(JsiSkFont, measureText), JSI_EXPORT_FUNC(JsiSkFont, dispose)) JsiSkFont(std::shared_ptr context, const SkFont &font) diff --git a/package/src/__tests__/snapshots/text/text-bounds-android.png b/package/src/__tests__/snapshots/text/text-bounds-android.png new file mode 100644 index 0000000000..a6205bf3d3 Binary files /dev/null and b/package/src/__tests__/snapshots/text/text-bounds-android.png differ diff --git a/package/src/__tests__/snapshots/text/text-bounds-ios.png b/package/src/__tests__/snapshots/text/text-bounds-ios.png new file mode 100644 index 0000000000..752cd78119 Binary files /dev/null and b/package/src/__tests__/snapshots/text/text-bounds-ios.png differ diff --git a/package/src/__tests__/snapshots/text/text-path-bug-android.png b/package/src/__tests__/snapshots/text/text-path-bug-android.png new file mode 100644 index 0000000000..6626c0f645 Binary files /dev/null and b/package/src/__tests__/snapshots/text/text-path-bug-android.png differ diff --git a/package/src/renderer/__tests__/e2e/Text.spec.tsx b/package/src/renderer/__tests__/e2e/Text.spec.tsx index dd2acde113..3f537aac63 100644 --- a/package/src/renderer/__tests__/e2e/Text.spec.tsx +++ b/package/src/renderer/__tests__/e2e/Text.spec.tsx @@ -5,7 +5,7 @@ import { itRunsCIAndNodeOnly, itRunsE2eOnly, } from "../../../__tests__/setup"; -import { Fill, Group, Text, TextPath } from "../../components"; +import { Fill, Group, Rect, Text, TextPath } from "../../components"; import { fonts, importSkia, surface } from "../setup"; describe("Text", () => { @@ -30,6 +30,44 @@ describe("Text", () => { ); expect(result).toBe(64); }); + itRunsE2eOnly("Should calculate text width correctly", async () => { + const font = fonts.DinMedium; + const result = await surface.eval( + (_Skia, ctx) => { + return ctx.font.measureText( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do" + ).width; + }, + { font } + ); + expect(result).toBeLessThan(1000); + }); + + itRunsE2eOnly("should draw the text bound measure correctly", async () => { + const font = fonts.DinMedium; + const bounds = await surface.eval( + (_Skia, ctx) => { + return ctx.font.measureText("Lorem ipsum"); + }, + { font } + ); + const image = await surface.draw( + <> + + + + + + + ); + checkImage(image, `snapshots/text/text-bounds-${surface.OS}.png`); + }); // We test it only on Android and iOS now because there might be no default font on Web itRunsE2eOnly("The font property is optional", async () => { diff --git a/package/src/renderer/__tests__/setup.tsx b/package/src/renderer/__tests__/setup.tsx index 38f1027973..541c8142bc 100644 --- a/package/src/renderer/__tests__/setup.tsx +++ b/package/src/renderer/__tests__/setup.tsx @@ -42,6 +42,7 @@ export let fonts: { NotoColorEmoji: SkFont; NotoSansSCRegular: SkFont; UberMoveMediumMono: SkFont; + DinMedium: SkFont; }; beforeAll(async () => { @@ -67,6 +68,7 @@ beforeAll(async () => { "skia/__tests__/assets/UberMove-Medium_mono.ttf", fontSize ); + const DinMedium = loadFont("skia/__tests__/assets/DIN-Medium.ttf", fontSize); const oslo = loadImage("skia/__tests__/assets/oslo.jpg"); const skiaLogoPng = loadImage("skia/__tests__/assets/skia_logo.png"); const skiaLogoJpeg = loadImage("skia/__tests__/assets/skia_logo_jpeg.jpg"); @@ -77,6 +79,7 @@ beforeAll(async () => { NotoColorEmoji, NotoSansSCRegular, UberMoveMediumMono, + DinMedium, }; assets.set(mask, "mask"); assets.set(oslo, "oslo"); @@ -84,6 +87,7 @@ beforeAll(async () => { assets.set(NotoColorEmoji, "NotoColorEmoji"); assets.set(NotoSansSCRegular, "NotoSansSCRegular"); assets.set(UberMoveMediumMono, "UberMoveMediumMono"); + assets.set(DinMedium, "DinMedium"); assets.set(skiaLogoPng, "skiaLogoPng"); assets.set(skiaLogoJpeg, "skiaLogoJpeg"); }); diff --git a/package/src/skia/__tests__/assets/DIN-Medium.ttf b/package/src/skia/__tests__/assets/DIN-Medium.ttf new file mode 100644 index 0000000000..a8360c2fd9 Binary files /dev/null and b/package/src/skia/__tests__/assets/DIN-Medium.ttf differ diff --git a/package/src/skia/types/Font/Font.ts b/package/src/skia/types/Font/Font.ts index 52e2569fe9..6340b9ae79 100644 --- a/package/src/skia/types/Font/Font.ts +++ b/package/src/skia/types/Font/Font.ts @@ -12,10 +12,20 @@ export interface FontMetrics { } export interface SkFont extends SkJSIInstance<"Font"> { + /** + * Returns the advance width of text. + * The advance is the normal distance to move before drawing additional text. + * Returns the bounding box of text + * @param text + * @param paint + */ + measureText(text: string, paint?: SkPaint): SkRect; + /** * Retrieves the total width of the provided text * @param text * @param paint + * @deprecated Use measureText or getGlyphWidths instead */ getTextWidth(text: string, paint?: SkPaint): number; diff --git a/package/src/skia/web/JsiSkFont.ts b/package/src/skia/web/JsiSkFont.ts index 399ebc153d..c9f2468b7e 100644 --- a/package/src/skia/web/JsiSkFont.ts +++ b/package/src/skia/web/JsiSkFont.ts @@ -6,10 +6,11 @@ import type { SkFont, SkPaint, SkPoint, + SkRect, SkTypeface, } from "../types"; -import { HostObject, ckEnum } from "./Host"; +import { HostObject, NotImplementedOnRNWeb, ckEnum } from "./Host"; import { JsiSkPaint } from "./JsiSkPaint"; import { JsiSkPoint } from "./JsiSkPoint"; import { JsiSkRect } from "./JsiSkRect"; @@ -20,6 +21,10 @@ export class JsiSkFont extends HostObject implements SkFont { super(CanvasKit, ref, "Font"); } + measureText(_text: string, _paint?: SkPaint | undefined): SkRect { + throw new NotImplementedOnRNWeb(); + } + dispose = () => { this.ref.delete(); };