diff --git a/packages/fallbacks/fallbacks.test.ts b/packages/fallbacks/fallbacks.test.ts index 19af57c..b53c014 100644 --- a/packages/fallbacks/fallbacks.test.ts +++ b/packages/fallbacks/fallbacks.test.ts @@ -127,11 +127,14 @@ describe("createFallbackMap", () => { const map = createFallbackMap({ canRenderFamily }); expect(Object.keys(map).sort()).toEqual([ "arial", + "arial mt", "calibri", "calibri light", "cambria", + "courier", "courier new", "helvetica", + "times", "times new roman", ]); expect(map.helvetica.substituteFamily).toBe("Liberation Sans"); @@ -500,6 +503,57 @@ describe("selected visual fallback rows", () => { lineBreakSafe: false, }); }); + + test("Japanese CJK rows expose measured regular faces and synthetic italics", () => { + expect( + SUBSTITUTION_EVIDENCE.find((row) => row.evidenceId === "yu-mincho") + ?.advance?.basis, + ).toBe("cjk_jp_text"); + expect( + getRenderableFallbackForFace("Yu Mincho", "regular", renderAll), + ).toMatchObject({ + substituteFamily: "BIZ UDMincho", + verdict: "visual_only", + lineBreakSafe: false, + }); + expect( + getRenderableFallbackForFace("Yu Mincho", "bold", renderAll), + ).toMatchObject({ + substituteFamily: "BIZ UDMincho", + verdict: "visual_only", + lineBreakSafe: false, + }); + expect( + getRenderableFallbackForFace("Yu Mincho", "italic", renderAll), + ).toMatchObject({ + substituteFamily: "BIZ UDMincho", + verdict: "visual_only", + lineBreakSafe: false, + faceSource: { kind: "synthetic", from: "regular" }, + }); + + expect( + getRenderableFallbackForFace("Yu Gothic", "bold", renderAll), + ).toMatchObject({ + substituteFamily: "BIZ UDGothic", + verdict: "visual_only", + lineBreakSafe: false, + }); + expect( + getRenderableFallbackForFace("MS Gothic", "bold", renderAll), + ).toMatchObject({ + substituteFamily: "BIZ UDGothic", + verdict: "visual_only", + lineBreakSafe: false, + }); + expect( + getRenderableFallbackForFace("MS Gothic", "regular", renderAll), + ).toMatchObject({ + substituteFamily: "BIZ UDGothic", + verdict: "cell_width_only", + lineBreakSafe: true, + }); + }); }); describe("candidate license metadata", () => { @@ -549,7 +603,12 @@ describe("generic CSS family metadata", () => { describe("advance measurement basis", () => { test("every measured row states which sample/model produced its deltas", () => { - const BASES = new Set(["latin_full", "latin_text", "monospace_cell"]); + const BASES = new Set([ + "latin_full", + "latin_text", + "monospace_cell", + "cjk_jp_text", + ]); for (const row of SUBSTITUTION_EVIDENCE) { if (!row.advance) continue; expect( diff --git a/packages/fallbacks/records.json b/packages/fallbacks/records.json index 77708a0..079fdc3 100644 --- a/packages/fallbacks/records.json +++ b/packages/fallbacks/records.json @@ -105,6 +105,64 @@ }, "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "arial-mt", + "generic": "sans-serif", + "logicalFamily": "Arial MT", + "physicalFamily": "Liberation Sans", + "verdict": "metric_safe", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "pass" + }, + "policyAction": "substitute", + "measurementRefs": [ + "arial-mt_regular__liberation-sans#regular#w400#analytic_advance#2026-06-10", + "arial-mt_bold__liberation-sans#bold#w700#analytic_advance#2026-06-10", + "arial-mt_italic__liberation-sans#italic#w400#analytic_advance#2026-06-10", + "arial-mt_boldItalic__liberation-sans#boldItalic#w700#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "latin_text", + "meanDelta": 0, + "maxDelta": 0 + }, + "candidateLicense": "OFL-1.1" + }, + { + "evidenceId": "arial-unicode-ms", + "generic": "sans-serif", + "logicalFamily": "Arial Unicode MS", + "physicalFamily": null, + "verdict": "customer_supplied", + "faces": { + "regular": false, + "bold": false, + "italic": false, + "boldItalic": false + }, + "gates": { + "static": "not_run", + "metric": "not_run", + "layout": "not_run", + "ship": "not_run" + }, + "policyAction": "customer_supplied", + "measurementRefs": [ + "arial-unicode-ms#broad_unicode_no_open_substitute#2026-06-10" + ], + "exportRule": "preserve_original_name", + "candidateLicense": null + }, { "evidenceId": "times-new-roman", "generic": "serif", @@ -135,6 +193,53 @@ }, "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "times", + "generic": "serif", + "logicalFamily": "Times", + "physicalFamily": "Liberation Serif", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "pass" + }, + "policyAction": "substitute", + "measurementRefs": [ + "times_regular__liberation-serif#regular#w400#analytic_advance#2026-06-10", + "times_bold__liberation-serif#bold#w700#analytic_advance#2026-06-10", + "times_italic__liberation-serif#italic#w400#analytic_advance#2026-06-10", + "times_boldItalic__liberation-serif#boldItalic#w700#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "latin_text", + "meanDelta": 0.00098877, + "maxDelta": 0.1171875 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "metric_safe", + "bold": "metric_safe", + "italic": "metric_safe", + "boldItalic": "visual_only" + }, + "glyphExceptions": [ + { + "slot": "boldItalic", + "codepoint": 239, + "advanceDelta": 0.1172, + "note": "Times Bold Italic vs Liberation Serif Bold Italic: small i diaeresis (U+00EF) advance differs ~11.7%; lines containing it can reflow. Regular, bold, and italic faces are metric_safe on the text sample." + } + ] + }, { "evidenceId": "courier-new", "generic": "monospace", @@ -165,6 +270,68 @@ }, "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "courier", + "generic": "monospace", + "logicalFamily": "Courier", + "physicalFamily": "Liberation Mono", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "pass" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "courier__liberation-mono#monospace_cell#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "monospace_cell", + "meanDelta": 0.0016, + "maxDelta": 0.2002 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + }, + "glyphExceptions": [ + { + "slot": "regular", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Regular vs Liberation Mono Regular: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + }, + { + "slot": "bold", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Bold vs Liberation Mono Bold: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + }, + { + "slot": "italic", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Italic vs Liberation Mono Italic: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + }, + { + "slot": "boldItalic", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Bold Italic vs Liberation Mono Bold Italic: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + } + ] + }, { "evidenceId": "georgia", "generic": "serif", @@ -495,6 +662,54 @@ } ] }, + { + "evidenceId": "century-gothic", + "generic": "sans-serif", + "logicalFamily": "Century Gothic", + "physicalFamily": "URW Gothic", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "century-gothic_regular__urw-gothic#regular#w400#analytic_advance#2026-06-10", + "century-gothic_bold__urw-gothic#bold#w700#analytic_advance#2026-06-10", + "century-gothic_italic__urw-gothic#italic#w400#analytic_advance#2026-06-10", + "century-gothic_boldItalic__urw-gothic#boldItalic#w700#analytic_advance#2026-06-10", + "century-gothic__urw-gothic#visual_review#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "latin_text", + "meanDelta": 0.0013, + "maxDelta": 0.1662 + }, + "candidateLicense": "AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + }, + "glyphExceptions": [ + { + "slot": "regular", + "codepoint": 35, + "advanceDelta": 0.1662, + "note": "Century Gothic Regular vs URW Gothic Book: number sign (U+0023) advance differs ~16.6%; caret (U+005E) and plus-minus (U+00B1) also exceed the direct threshold. Body text is close, but not line-break safe." + } + ] + }, { "evidenceId": "garamond", "generic": "serif", @@ -619,6 +834,47 @@ "exportRule": "preserve_original_name", "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "segoe-ui", + "generic": "sans-serif", + "logicalFamily": "Segoe UI", + "physicalFamily": "Selawik", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "segoe-ui__selawik#coverage_limited_advance_probe#2026-06-10" + ], + "exportRule": "preserve_original_name", + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, { "evidenceId": "trebuchet-ms", "generic": "sans-serif", @@ -836,6 +1092,191 @@ "boldItalic": "visual_only" } }, + { + "evidenceId": "yu-mincho", + "generic": "serif", + "logicalFamily": "Yu Mincho", + "physicalFamily": "BIZ UDMincho", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "yu-mincho_regular__biz-udmincho#regular#w400#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0.0425, + "maxDelta": 0.4829 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, + { + "evidenceId": "ms-mincho", + "generic": "serif", + "logicalFamily": "MS Mincho", + "physicalFamily": "BIZ UDMincho", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "ms-mincho_regular__biz-udmincho#regular#w400#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0, + "maxDelta": 0 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "cell_width_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, + { + "evidenceId": "ms-gothic", + "generic": "sans-serif", + "logicalFamily": "MS Gothic", + "physicalFamily": "BIZ UDGothic", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "ms-gothic_regular__biz-udgothic#regular#w400#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0, + "maxDelta": 0 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "cell_width_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, + { + "evidenceId": "yu-gothic", + "generic": "sans-serif", + "logicalFamily": "Yu Gothic", + "physicalFamily": "BIZ UDGothic", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "yu-gothic_regular__biz-udgothic#regular#w400#cjk_jp_text#analytic_advance#2026-06-10", + "yu-gothic_bold__biz-udgothic#bold#w700#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0.0415, + "maxDelta": 0.4521 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, { "evidenceId": "aptos-display", "generic": "sans-serif", diff --git a/packages/fallbacks/src/data.ts b/packages/fallbacks/src/data.ts index c6a88af..b38287b 100644 --- a/packages/fallbacks/src/data.ts +++ b/packages/fallbacks/src/data.ts @@ -110,6 +110,64 @@ export const SUBSTITUTION_EVIDENCE: readonly SubstitutionEvidence[] = [ }, "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "arial-mt", + "generic": "sans-serif", + "logicalFamily": "Arial MT", + "physicalFamily": "Liberation Sans", + "verdict": "metric_safe", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "pass" + }, + "policyAction": "substitute", + "measurementRefs": [ + "arial-mt_regular__liberation-sans#regular#w400#analytic_advance#2026-06-10", + "arial-mt_bold__liberation-sans#bold#w700#analytic_advance#2026-06-10", + "arial-mt_italic__liberation-sans#italic#w400#analytic_advance#2026-06-10", + "arial-mt_boldItalic__liberation-sans#boldItalic#w700#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "latin_text", + "meanDelta": 0, + "maxDelta": 0 + }, + "candidateLicense": "OFL-1.1" + }, + { + "evidenceId": "arial-unicode-ms", + "generic": "sans-serif", + "logicalFamily": "Arial Unicode MS", + "physicalFamily": null, + "verdict": "customer_supplied", + "faces": { + "regular": false, + "bold": false, + "italic": false, + "boldItalic": false + }, + "gates": { + "static": "not_run", + "metric": "not_run", + "layout": "not_run", + "ship": "not_run" + }, + "policyAction": "customer_supplied", + "measurementRefs": [ + "arial-unicode-ms#broad_unicode_no_open_substitute#2026-06-10" + ], + "exportRule": "preserve_original_name", + "candidateLicense": null + }, { "evidenceId": "times-new-roman", "generic": "serif", @@ -140,6 +198,53 @@ export const SUBSTITUTION_EVIDENCE: readonly SubstitutionEvidence[] = [ }, "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "times", + "generic": "serif", + "logicalFamily": "Times", + "physicalFamily": "Liberation Serif", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "pass" + }, + "policyAction": "substitute", + "measurementRefs": [ + "times_regular__liberation-serif#regular#w400#analytic_advance#2026-06-10", + "times_bold__liberation-serif#bold#w700#analytic_advance#2026-06-10", + "times_italic__liberation-serif#italic#w400#analytic_advance#2026-06-10", + "times_boldItalic__liberation-serif#boldItalic#w700#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "latin_text", + "meanDelta": 0.00098877, + "maxDelta": 0.1171875 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "metric_safe", + "bold": "metric_safe", + "italic": "metric_safe", + "boldItalic": "visual_only" + }, + "glyphExceptions": [ + { + "slot": "boldItalic", + "codepoint": 239, + "advanceDelta": 0.1172, + "note": "Times Bold Italic vs Liberation Serif Bold Italic: small i diaeresis (U+00EF) advance differs ~11.7%; lines containing it can reflow. Regular, bold, and italic faces are metric_safe on the text sample." + } + ] + }, { "evidenceId": "courier-new", "generic": "monospace", @@ -170,6 +275,68 @@ export const SUBSTITUTION_EVIDENCE: readonly SubstitutionEvidence[] = [ }, "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "courier", + "generic": "monospace", + "logicalFamily": "Courier", + "physicalFamily": "Liberation Mono", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "pass" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "courier__liberation-mono#monospace_cell#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "monospace_cell", + "meanDelta": 0.0016, + "maxDelta": 0.2002 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + }, + "glyphExceptions": [ + { + "slot": "regular", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Regular vs Liberation Mono Regular: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + }, + { + "slot": "bold", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Bold vs Liberation Mono Bold: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + }, + { + "slot": "italic", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Italic vs Liberation Mono Italic: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + }, + { + "slot": "boldItalic", + "codepoint": 176, + "advanceDelta": 0.2002, + "note": "Courier Bold Italic vs Liberation Mono Bold Italic: degree sign (U+00B0) advance differs ~20%; plus-minus (U+00B1) and division sign (U+00F7) also exceed the direct threshold." + } + ] + }, { "evidenceId": "georgia", "generic": "serif", @@ -504,6 +671,54 @@ export const SUBSTITUTION_EVIDENCE: readonly SubstitutionEvidence[] = [ } ] }, + { + "evidenceId": "century-gothic", + "generic": "sans-serif", + "logicalFamily": "Century Gothic", + "physicalFamily": "URW Gothic", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": true, + "boldItalic": true + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "century-gothic_regular__urw-gothic#regular#w400#analytic_advance#2026-06-10", + "century-gothic_bold__urw-gothic#bold#w700#analytic_advance#2026-06-10", + "century-gothic_italic__urw-gothic#italic#w400#analytic_advance#2026-06-10", + "century-gothic_boldItalic__urw-gothic#boldItalic#w700#analytic_advance#2026-06-10", + "century-gothic__urw-gothic#visual_review#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "latin_text", + "meanDelta": 0.0013, + "maxDelta": 0.1662 + }, + "candidateLicense": "AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + }, + "glyphExceptions": [ + { + "slot": "regular", + "codepoint": 35, + "advanceDelta": 0.1662, + "note": "Century Gothic Regular vs URW Gothic Book: number sign (U+0023) advance differs ~16.6%; caret (U+005E) and plus-minus (U+00B1) also exceed the direct threshold. Body text is close, but not line-break safe." + } + ] + }, { "evidenceId": "garamond", "generic": "serif", @@ -634,6 +849,47 @@ export const SUBSTITUTION_EVIDENCE: readonly SubstitutionEvidence[] = [ "exportRule": "preserve_original_name", "candidateLicense": "OFL-1.1" }, + { + "evidenceId": "segoe-ui", + "generic": "sans-serif", + "logicalFamily": "Segoe UI", + "physicalFamily": "Selawik", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "segoe-ui__selawik#coverage_limited_advance_probe#2026-06-10" + ], + "exportRule": "preserve_original_name", + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, { "evidenceId": "trebuchet-ms", "generic": "sans-serif", @@ -861,6 +1117,191 @@ export const SUBSTITUTION_EVIDENCE: readonly SubstitutionEvidence[] = [ "boldItalic": "visual_only" } }, + { + "evidenceId": "yu-mincho", + "generic": "serif", + "logicalFamily": "Yu Mincho", + "physicalFamily": "BIZ UDMincho", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "yu-mincho_regular__biz-udmincho#regular#w400#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0.0425, + "maxDelta": 0.4829 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, + { + "evidenceId": "ms-mincho", + "generic": "serif", + "logicalFamily": "MS Mincho", + "physicalFamily": "BIZ UDMincho", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "ms-mincho_regular__biz-udmincho#regular#w400#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0, + "maxDelta": 0 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "cell_width_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, + { + "evidenceId": "ms-gothic", + "generic": "sans-serif", + "logicalFamily": "MS Gothic", + "physicalFamily": "BIZ UDGothic", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "pass", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "ms-gothic_regular__biz-udgothic#regular#w400#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0, + "maxDelta": 0 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "cell_width_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, + { + "evidenceId": "yu-gothic", + "generic": "sans-serif", + "logicalFamily": "Yu Gothic", + "physicalFamily": "BIZ UDGothic", + "verdict": "visual_only", + "faces": { + "regular": true, + "bold": true, + "italic": false, + "boldItalic": false + }, + "faceSources": { + "italic": { + "kind": "synthetic", + "from": "regular" + }, + "boldItalic": { + "kind": "synthetic", + "from": "bold" + } + }, + "gates": { + "static": "pass", + "metric": "fail", + "layout": "not_run", + "ship": "fail" + }, + "policyAction": "category_fallback", + "measurementRefs": [ + "yu-gothic_regular__biz-udgothic#regular#w400#cjk_jp_text#analytic_advance#2026-06-10", + "yu-gothic_bold__biz-udgothic#bold#w700#cjk_jp_text#analytic_advance#2026-06-10" + ], + "exportRule": "preserve_original_name", + "advance": { + "basis": "cjk_jp_text", + "meanDelta": 0.0415, + "maxDelta": 0.4521 + }, + "candidateLicense": "OFL-1.1", + "faceVerdicts": { + "regular": "visual_only", + "bold": "visual_only", + "italic": "visual_only", + "boldItalic": "visual_only" + } + }, { "evidenceId": "aptos-display", "generic": "sans-serif", diff --git a/packages/fallbacks/src/fallbacks.ts b/packages/fallbacks/src/fallbacks.ts index 034b51f..d69a81f 100644 --- a/packages/fallbacks/src/fallbacks.ts +++ b/packages/fallbacks/src/fallbacks.ts @@ -31,8 +31,8 @@ export interface FallbackDecisionOptions { /** * Verdicts whose advances preserve line breaks: the proportional metric-grade bands, plus - * cell_width_only (a monospace whose cell width - and therefore every advance - matches). Glyph shapes - * may still differ (read `verdict`); line breaks do not move. + * cell_width_only (a limited width model matches). Glyph shapes may still differ (read `verdict`); + * line breaks do not move. */ const LINE_BREAK_SAFE_VERDICTS: ReadonlySet = new Set([ "metric_safe", diff --git a/packages/fallbacks/src/types.ts b/packages/fallbacks/src/types.ts index 3e1c846..3566f66 100644 --- a/packages/fallbacks/src/types.ts +++ b/packages/fallbacks/src/types.ts @@ -7,7 +7,7 @@ export type Verdict = | "metric_safe" // advances within the DIRECT threshold (weighted-mean <= 0.5%, worst-case <= 1%) | "near_metric" // LIKELY band: weighted-mean <= 1%, worst-case <= 2.5% - near-exact, a few glyphs drift - | "cell_width_only" // monospace cell width matches; glyph shapes do not + | "cell_width_only" // width behavior matches for a limited model; glyph shapes do not | "visual_only" // same visual category, but advances are NOT line-break safe | "customer_supplied" // the real font must come from the customer | "preserve_only" // keep the original name, do not substitute (e.g. math / symbol fonts) @@ -34,14 +34,19 @@ export type FaceSlot = "regular" | "bold" | "italic" | "boldItalic"; export type CssGeneric = "serif" | "sans-serif" | "monospace"; /** Which advance sample/model produced the row's mean and max deltas. */ -export type AdvanceBasis = "latin_full" | "latin_text" | "monospace_cell"; +export type AdvanceBasis = + | "latin_full" + | "latin_text" + | "monospace_cell" + | "cjk_jp_text"; /** Advance-width divergence vs the licensed reference font, as fractions (0 = identical advances). */ export interface AdvanceDelta { /** * Measurement basis for the deltas. `latin_full` uses the reviewed Latin sample including - * punctuation and symbols, `latin_text` uses text-carrying Latin codepoints, and `monospace_cell` is a - * cell-width measurement for monospace rows. + * punctuation and symbols, `latin_text` uses text-carrying Latin codepoints, `monospace_cell` is a + * cell-width measurement for monospace rows, and `cjk_jp_text` uses a Japanese CJK text sample that + * is width-only evidence, not a shape match. */ basis: AdvanceBasis; meanDelta: number; @@ -130,8 +135,8 @@ export interface FontFallback { verdict: Verdict; /** * Coarse "advances preserve line breaks" flag: true for metric_safe, near_metric, or monospace - * cell_width_only (cell width, and so every advance, matches). NOT a claim of a perfect/exact clone - - * near_metric drifts a few glyphs, cell_width_only keeps the advances but not the glyph shapes, and a + * cell_width_only (the width model matches). NOT a claim of a perfect/exact clone - + * near_metric drifts a few glyphs, cell_width_only keeps sampled widths but not glyph shapes, and a * row can roll up to a worse top-level verdict because of one face (see Cambria). Read `verdict` (and * the row's `faceVerdicts`) for the precise tier. */ diff --git a/tools/corpus/app/index.html b/tools/corpus/app/index.html index ffb3bc4..ad83a06 100644 --- a/tools/corpus/app/index.html +++ b/tools/corpus/app/index.html @@ -48,6 +48,7 @@
+