From 1a333d9853ad6e6cf46f3c242f4b7bbe967b6544 Mon Sep 17 00:00:00 2001 From: "otoneko." Date: Sat, 24 Oct 2026 23:44:01 +0900 Subject: [PATCH] fix: support @napi-rs/canvas 1.0.12+ and require ^1.0.13 With font families now quoted, @napi-rs/canvas 1.0.13 renders the whole visual gallery and every catalogued, Google Fonts and self-registered family without tofu, with layout and line breaks unchanged. The one visible difference is a fix: variable fonts such as Noto Sans JP and Noto Sans SC now honour their weight axis, so regular text is no longer drawn at the thin default instance and markdown bold is real bold. Require ^1.0.13 so every install renders the same way, run CI against it, and add a real-canvas test that fails if ctx.font rejects a font shorthand built by fontString(). --- package-lock.json | 111 +++++++++++++++++++++----------------- package.json | 2 +- src/render/layout.test.ts | 19 +++++++ src/render/textStyle.ts | 2 +- 4 files changed, 84 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22dc508..b19d602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@makeitaquote/utils": "^1.2.1", - "@napi-rs/canvas": ">=1.0.8 <1.0.12", + "@napi-rs/canvas": "^1.0.13", "@twemoji/parser": "^17.0.2", "budoux": "^0.9.1", "cleye": "^2.6.0", @@ -316,9 +316,9 @@ "license": "MIT AND CC-BY-4.0 AND Unicode-3.0" }, "node_modules/@napi-rs/canvas": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.8.tgz", - "integrity": "sha512-/SaLcvlqGWdm0HSCWMgHu7cjJiQXfP8/mOY+6dUyV9flQz7sPBBZ+ed2zYtoukojPmxOaL7bm+d/G4GeWWoN7g==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.13.tgz", + "integrity": "sha512-b87ugZpQ5ViwqVBkjervxpwa0GmJPsKHLBNoZN6GpjWeHzTbDUGryriq+2DYW2XbB+xCqSIINQysC0/I87nPbw==", "license": "MIT", "workspaces": [ "e2e/*" @@ -331,23 +331,23 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "1.0.8", - "@napi-rs/canvas-darwin-arm64": "1.0.8", - "@napi-rs/canvas-darwin-x64": "1.0.8", - "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.8", - "@napi-rs/canvas-linux-arm64-gnu": "1.0.8", - "@napi-rs/canvas-linux-arm64-musl": "1.0.8", - "@napi-rs/canvas-linux-riscv64-gnu": "1.0.8", - "@napi-rs/canvas-linux-x64-gnu": "1.0.8", - "@napi-rs/canvas-linux-x64-musl": "1.0.8", - "@napi-rs/canvas-win32-arm64-msvc": "1.0.8", - "@napi-rs/canvas-win32-x64-msvc": "1.0.8" + "@napi-rs/canvas-android-arm64": "1.0.13", + "@napi-rs/canvas-darwin-arm64": "1.0.13", + "@napi-rs/canvas-darwin-x64": "1.0.13", + "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.13", + "@napi-rs/canvas-linux-arm64-gnu": "1.0.13", + "@napi-rs/canvas-linux-arm64-musl": "1.0.13", + "@napi-rs/canvas-linux-riscv64-gnu": "1.0.13", + "@napi-rs/canvas-linux-x64-gnu": "1.0.13", + "@napi-rs/canvas-linux-x64-musl": "1.0.13", + "@napi-rs/canvas-win32-arm64-msvc": "1.0.13", + "@napi-rs/canvas-win32-x64-msvc": "1.0.13" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.8.tgz", - "integrity": "sha512-5+nkh8i3gt6lqS/d2jTZ1xAn6tdgtB4Lf1mW6T0Qm5/rXNwBuV1sAEyLEWan5o9gJPU/GuvHR3rvSeZ+FaGrbw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.13.tgz", + "integrity": "sha512-trn7AaDzQ+yeeA0ezhkC7cB1ZDPtp4UYk0EvB5F8qtYL9jEVWtVrU30WOpI52DszoTxaTsCgf5PP4ZVaOlOIHQ==", "cpu": [ "arm64" ], @@ -365,9 +365,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.8.tgz", - "integrity": "sha512-7jQ47gi+fZ7KJmfc/5rNyy1CYw/cu4kZ0KPIYbo9UUgSdW0bKQJpt+WihEor6s4Lyp7+xc3a+3HeyXmAEbbnPg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.13.tgz", + "integrity": "sha512-6Y2djEetL8copxfNK6hg7oig/xqTwOchv19hBOm+zBNdKc/02Z3kjZN5wrLzv/l5O04/bAQS6I/AlQpwBlXgPg==", "cpu": [ "arm64" ], @@ -385,9 +385,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.8.tgz", - "integrity": "sha512-rRjDMZs9pIRKGxgijwezplKc1RnJsqUokrA9h88bbTkqQ+7ePj0ZN4ZnZDy8Vu0tXs7KRlI2tQLaK4mx9QlxHg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.13.tgz", + "integrity": "sha512-OZoPwwu4TMMg8GTCFBrg8UFExwQTU/55i3uTC/izDat6PHE+ro9dimyQoOboFf91+NY7Pnn70ScR8f8I8+eZXw==", "cpu": [ "x64" ], @@ -405,9 +405,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.8.tgz", - "integrity": "sha512-jGcCd+8ra6Q61xKqZeiItujTpp9a9eRLcQ0jW6qYNku+WpupqOPFPY0SrsuSnXFviJwkpKYT9p7QrB4lsf3LNQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.13.tgz", + "integrity": "sha512-dZSYaKROuswU2eUWG5aBKndNVe68elo1YX93xtRax7q658tj8rYE+HwduovWJgs6qwv1r1/CLR60nDeBbyjvyg==", "cpu": [ "arm" ], @@ -425,12 +425,15 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.8.tgz", - "integrity": "sha512-od6I2Y7kU7i1SwZYG2EKW8rWz6JiedtPpko4WEe1DDsiikrfaotVBCRaUTM5/yeZKaZ92EatoAS+5xG+6uJlYA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.13.tgz", + "integrity": "sha512-W6FzYE8ADHI93In1vi80KBL6btDtAMuj50Ch5ptkWS8xuntyzbR4h5JDUmXoakxZn2yMInksudTEqVSWoFqFGg==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -445,12 +448,15 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.8.tgz", - "integrity": "sha512-yYkPbJDJiWj6N0gASA3CAvRypZmVpJnxU0DQg3aBhneLDQde9TPLKADsQkobNoJUtTT/lj46aWpzT48PDb3Qcg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.13.tgz", + "integrity": "sha512-+6fQ+rR49hkAa4voh2stLIwQ+4t+gmoseM8Bc53/DlMXb73IKymDPWK9A/NtWeqTW4ijWppYuyuxS2hPI8tzNA==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -465,12 +471,15 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.8.tgz", - "integrity": "sha512-PB00MSKAp4VwK/xwe6duKxRKmH8UH4GIl1pqHSbxng0jnU9Dr7FwaDypDiqwNFZ774N+8G7mJLGuLtg9NTcQsg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.13.tgz", + "integrity": "sha512-17XEMO3t9RW/TtHtSFf2AkaSWuCWdzbPeidz7zrOFawHxoLSJCnpTI1hN5aUvrZNnkNNmt/vZVJCh0ryUaik3A==", "cpu": [ "riscv64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -485,12 +494,15 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.8.tgz", - "integrity": "sha512-TWM2XWJoitLiIPCvgJh7SriC+L/T9qkYCVzC66AidsZy0QP1hkKzBzVwshCdcA3q6fIn3yE0ISbq4lMJSy8jFw==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.13.tgz", + "integrity": "sha512-lc48IwknVHBsNzXn9Xs5OkUsRX38Ml3Z1aOybQ51mMEzZckm8+4A0qROlyYHEoBPzr1kL3IU8ZUAqDb7wtdX8w==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -505,12 +517,15 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.8.tgz", - "integrity": "sha512-hb20MxKXXb5IB7AAwN8UHz9WRsa2HmdZfjsDCzjElwJoeV1aotVEwFU4FrFQcYQVzsJQLeaCc/2Qdt/0Q72mMg==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.13.tgz", + "integrity": "sha512-bGY1r26S2bs8UNFJcPWcCSNdCFbKep5PATsvtgtC9A5u6SRgr2/jJE/RlhHL5m9ZpixNDhVkLY/5h16Kx5V9YA==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -525,9 +540,9 @@ } }, "node_modules/@napi-rs/canvas-win32-arm64-msvc": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.8.tgz", - "integrity": "sha512-WwPN08IXE4SkL+FhJyPz/iFnycMAUkbphFIT4cmKLlvbSU0Zfn1R7BGJ3Hqky1S89QUYc0Q4IOScXb/42Re9wQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.13.tgz", + "integrity": "sha512-2vRPBZoaIvJrUkDhZTE2Hn0YrnpJRCWVM3EJACoBKjuWI6WgPxGtUoRMsxRviCsyks8bKTSFdyw14CellnG2qA==", "cpu": [ "arm64" ], @@ -545,9 +560,9 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.8.tgz", - "integrity": "sha512-XkrVqKb+pxyba7kjy2LJvABFVBTE0DNpEl7MrG4OYUmaWarrXH+t54z/Czj2YxCKtizYTV4mg6phNm3x24qjhQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.13.tgz", + "integrity": "sha512-DUlW3lZPz63kYsf8mxgyyZuevhlH0T3/BGP9l+NazhB0F1+gL6D4hqZdnhZpw24DlWmTpATDcyYSZlJOi+DdfQ==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 3d03d07..e455fc6 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ }, "dependencies": { "@makeitaquote/utils": "^1.2.1", - "@napi-rs/canvas": ">=1.0.8 <1.0.12", + "@napi-rs/canvas": "^1.0.13", "@twemoji/parser": "^17.0.2", "budoux": "^0.9.1", "cleye": "^2.6.0", diff --git a/src/render/layout.test.ts b/src/render/layout.test.ts index 54327c1..ec36640 100644 --- a/src/render/layout.test.ts +++ b/src/render/layout.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { defineTheme } from '../theme/resolve' +import { createCanvas } from './canvasFactory' import { autoArea, computeLayout, cssFontFamily, fontString, gradientLine } from './layout' describe('computeLayout · side', () => { @@ -196,3 +197,21 @@ describe('cssFontFamily', () => { expect(cssFontFamily(' Noto Sans JP ,, sans-serif ')).toBe('"Noto Sans JP", sans-serif') }) }) + +describe('fontString on a real canvas', () => { + // A ctx.font value the parser rejects is silently ignored, so read it back: any family name a user or the catalogue can supply must stick. + it.each([ + 'M PLUS Rounded 1c, Noto Sans JP, sans-serif', + 'M PLUS 1p', + 'DotGothic16', + "O'Brien Sans", + '2nd Custom Font', + 'sans-serif', + ])('is accepted by ctx.font for %s', (family) => { + const ctx = createCanvas(1, 1).getContext('2d') + ctx.font = '7px serif' + ctx.font = fontString('bold', 32, family, true) + expect(ctx.font).not.toBe('7px serif') + expect(ctx.font).toContain('32px') + }) +}) diff --git a/src/render/textStyle.ts b/src/render/textStyle.ts index c47c5ac..40aea6d 100644 --- a/src/render/textStyle.ts +++ b/src/render/textStyle.ts @@ -24,7 +24,7 @@ export function resolvedWeight(base: FontWeight, bold: boolean | undefined): Fon /** * Families whose bold is faked, keyed by family name. * - * Variable fonts registered through `GlobalFonts` — including the Noto Sans JP this package downloads — expose only their default instance to Skia, so `ctx.font = 'bold …'` measures and draws identically to regular. Detected once per family by measuring both. + * A family registered with only its regular face — common for fonts registered at runtime — has no bold to switch to, so `ctx.font = 'bold …'` measures and draws identically to regular. Detected once per family by measuring both. (Variable fonts such as the Noto Sans JP this package downloads honour their weight axis since `@napi-rs/canvas` 1.0.12, so they detect as real bold.) */ const syntheticFamilies = new Map()