diff --git a/THIRD_PARTY_LICENSES.md b/THIRD_PARTY_LICENSES.md index a4e0607997..93389c5d5e 100644 --- a/THIRD_PARTY_LICENSES.md +++ b/THIRD_PARTY_LICENSES.md @@ -29,11 +29,12 @@ files. SPDX license expression for this bundled font set: `OFL-1.1 AND Apache-2.0 AND (AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817) AND (GPL-2.0-only WITH Font-exception-2.0) AND LicenseRef-GUST-Font-License-1.0`. -C059 and Liberation Sans Narrow are copyleft fonts with document-embedding -exceptions. The exceptions mean embedding the fonts into a rendered document does -not impose AGPL/GPL terms on that document. Distributing the font binaries -themselves carries the corresponding-source obligation, satisfied by the -unmodified upstream release pointers recorded in `shared/font-system/assets/LICENSES.md`. +C059, URW Gothic, and Liberation Sans Narrow are copyleft fonts with +document-embedding exceptions. The exceptions mean embedding the fonts into a +rendered document does not impose AGPL/GPL terms on that document. Distributing +the font binaries themselves carries the corresponding-source obligation, +satisfied by the unmodified upstream release pointers recorded in +`shared/font-system/assets/LICENSES.md`. ### Components @@ -48,12 +49,14 @@ unmodified upstream release pointers recorded in `shared/font-system/assets/LICE | Caprasimo | Cooper Black | OFL-1.1 | none | 1.001 | | Archivo Black | Arial Black | OFL-1.1 | none | 1.006 | | C059 | Century, Century Schoolbook | AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817 | none | 1.00 | +| URW Gothic | Century Gothic | AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817 | none | 1.00 | | Bacasime Antique | Baskerville Old Face | OFL-1.1 | "Playfair" | 2.000 | | Oregano Italic | Brush Script MT | OFL-1.1 | "Oregano Italic" | 1.000 | | Gelasio | Georgia | OFL-1.1 | none | 1.008 | | Cardo | Garamond | OFL-1.1 | none | Regular 1.0451; Bold 1.0011; Italic 0.991 | | Comic Relief | Comic Sans MS | OFL-1.1 | none | 1.200 | | Noto Sans | Tahoma | OFL-1.1 | none | 2.015 | +| Selawik | Segoe UI | OFL-1.1 | "Selawik" | 1.01 | | Noto Sans Mono | Lucida Console | OFL-1.1 | none | 2.014 | | Inconsolata SemiExpanded | Consolas | OFL-1.1 | none | 3.001 | | PT Sans | Trebuchet MS | OFL-1.1 | "PT Sans", "PT Serif", "ParaType" | 2.003W OFL | @@ -69,12 +72,14 @@ unmodified upstream release pointers recorded in `shared/font-system/assets/LICE - **Caprasimo** (OFL-1.1): `Copyright 2023 The Caprasimo Project Authors (https://github.com/docrepair-fonts/caprasimo-fonts).` The v1.001 file declares no OFL Reserved Font Name. - **Archivo Black** (OFL-1.1): `Copyright 2017 The Archivo Black Project Authors (https://github.com/Omnibus-Type/ArchivoBlack).` The v1.006 file declares no OFL Reserved Font Name. - **C059** (AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817): `(URW)++,Copyright 2014 by (URW)++ Design & Development`. These files declare no trademark string and no Reserved Font Name. +- **URW Gothic** (AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817): `(URW)++,Copyright 2014 by (URW)++ Design & Development`. These files declare no trademark string and no Reserved Font Name. - **Bacasime Antique** (OFL-1.1): `Copyright 2023 The Bacasime Antique Project Authors (https://github.com/docrepair-fonts/bacasime-antique-fonts), with Reserved Font Name "Playfair".` Playfair is a trademark of Claus Eggers Sørensen. - **Oregano Italic** (OFL-1.1): `Copyright (c) 2012 by Brian J. Bonislawsky DBA Astigmatic (AOETI) (astigma@astigmatic.com), with Reserved Font Name "Oregano Italic".` Oregano Italic is a trademark of Astigmatic. - **Gelasio** (OFL-1.1): `Copyright 2022 The Gelasio Project Authors (https://github.com/SorkinType/Gelasio).` The v1.008 files declare no OFL Reserved Font Name. - **Cardo** (OFL-1.1): `Copyright (c) 2002-2011, David J. Perry (hospes02@scholarsfonts.net).` These files declare no OFL Reserved Font Name. - **Comic Relief** (OFL-1.1): `Copyright 2013 The Comic Relief Project Authors (https://github.com/loudifier/Comic-Relief).` The v1.200 files declare no OFL Reserved Font Name. - **Noto Sans** (OFL-1.1): `Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic).` Noto is a trademark of Google LLC. The v2.015 files declare no OFL Reserved Font Name. +- **Selawik** (OFL-1.1): `© 2015 Microsoft Corporation (www.microsoft.com), with Reserved Font Name "Selawik". Selawik is a trademark of Microsoft Corporation in the United States and/or other countries.` Selawik is a trademark of the Microsoft group of companies. - **Noto Sans Mono** (OFL-1.1): `Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic).` Noto is a trademark of Google LLC. The v2.014 files declare no OFL Reserved Font Name. - **Inconsolata SemiExpanded** (OFL-1.1): `Copyright 2006 The Inconsolata Project Authors (https://github.com/cyrealtype/Inconsolata).` These v3.001 files declare no OFL Reserved Font Name. - **PT Sans** (OFL-1.1): `Copyright (c) 2010, ParaType Ltd. (http://www.paratype.com/public), with Reserved Font Names "PT Sans", "PT Serif" and "ParaType".` @@ -90,8 +95,8 @@ and Noto Sans Mono ship as static WOFF2 instances generated from upstream variable TrueType sources at the regular and bold weights; Noto Sans and Noto Sans Mono are also instanced at normal width. -Verified for this ship set: 49 / 59 faces are static-source conversions with a -WOFF2 `name` table byte-identical to their source font, and 10 / 59 faces are +Verified for this ship set: 55 / 65 faces are static-source conversions with a +WOFF2 `name` table byte-identical to their source font, and 10 / 65 faces are static variable-font instances with no subsetting. All metrics are preserved. Under OFL FAQ 2.2.1 the static-source conversions are not Modified Versions and retain the original font names. For Caladea, this also serves as the Apache-2.0 diff --git a/packages/super-editor/src/editors/v1/components/toolbar/constants.test.js b/packages/super-editor/src/editors/v1/components/toolbar/constants.test.js index a3c14b4f02..e44af345a1 100644 --- a/packages/super-editor/src/editors/v1/components/toolbar/constants.test.js +++ b/packages/super-editor/src/editors/v1/components/toolbar/constants.test.js @@ -12,6 +12,7 @@ describe('TOOLBAR_FONTS (built-in font dropdown, derived from the font-offering 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -20,6 +21,7 @@ describe('TOOLBAR_FONTS (built-in font dropdown, derived from the font-offering 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', @@ -29,7 +31,7 @@ describe('TOOLBAR_FONTS (built-in font dropdown, derived from the font-offering it('does not leak non-advertised fonts into the default dropdown', () => { const labels = new Set(TOOLBAR_FONTS.map((f) => f.label)); - for (const name of ['Aptos', 'Cambria', 'Calibri Light', 'Century Schoolbook']) { + for (const name of ['Aptos', 'Cambria', 'Calibri Light', 'Century Schoolbook', 'Arial MT', 'Courier', 'Times']) { expect(labels.has(name)).toBe(false); } }); @@ -87,6 +89,7 @@ describe('composeToolbarFontOptions (document fonts unioned with the bundled def 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -95,6 +98,7 @@ describe('composeToolbarFontOptions (document fonts unioned with the bundled def 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', diff --git a/packages/super-editor/src/headless-toolbar/constants.test.ts b/packages/super-editor/src/headless-toolbar/constants.test.ts index 51fa16c5d0..21c6d104e8 100644 --- a/packages/super-editor/src/headless-toolbar/constants.test.ts +++ b/packages/super-editor/src/headless-toolbar/constants.test.ts @@ -12,6 +12,7 @@ describe('DEFAULT_FONT_FAMILY_OPTIONS (headless default font options, derived fr { label: 'Brush Script MT', value: 'Brush Script MT, serif' }, { label: 'Calibri', value: 'Calibri, sans-serif' }, { label: 'Century', value: 'Century, serif' }, + { label: 'Century Gothic', value: 'Century Gothic, sans-serif' }, { label: 'Comic Sans MS', value: 'Comic Sans MS, sans-serif' }, { label: 'Cooper Black', value: 'Cooper Black, serif' }, { label: 'Courier New', value: 'Courier New, monospace' }, @@ -20,6 +21,7 @@ describe('DEFAULT_FONT_FAMILY_OPTIONS (headless default font options, derived fr { label: 'Gill Sans MT Condensed', value: 'Gill Sans MT Condensed, sans-serif' }, { label: 'Helvetica', value: 'Helvetica, sans-serif' }, { label: 'Lucida Console', value: 'Lucida Console, monospace' }, + { label: 'Segoe UI', value: 'Segoe UI, sans-serif' }, { label: 'Tahoma', value: 'Tahoma, sans-serif' }, { label: 'Times New Roman', value: 'Times New Roman, serif' }, { label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' }, @@ -33,5 +35,8 @@ describe('DEFAULT_FONT_FAMILY_OPTIONS (headless default font options, derived fr expect(labels.has('Cambria')).toBe(false); expect(labels.has('Calibri Light')).toBe(false); expect(labels.has('Century Schoolbook')).toBe(false); + expect(labels.has('Arial MT')).toBe(false); + expect(labels.has('Courier')).toBe(false); + expect(labels.has('Times')).toBe(false); }); }); diff --git a/packages/super-editor/src/ui/create-super-doc-ui.test.ts b/packages/super-editor/src/ui/create-super-doc-ui.test.ts index 2268699ff2..f7ea11ea7d 100644 --- a/packages/super-editor/src/ui/create-super-doc-ui.test.ts +++ b/packages/super-editor/src/ui/create-super-doc-ui.test.ts @@ -160,6 +160,7 @@ describe('createSuperDocUI', () => { 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -168,6 +169,7 @@ describe('createSuperDocUI', () => { 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', @@ -221,6 +223,7 @@ describe('createSuperDocUI', () => { 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -229,6 +232,7 @@ describe('createSuperDocUI', () => { 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', @@ -244,6 +248,7 @@ describe('createSuperDocUI', () => { 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -252,6 +257,7 @@ describe('createSuperDocUI', () => { 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', diff --git a/packages/super-editor/src/ui/react/hooks.test.tsx b/packages/super-editor/src/ui/react/hooks.test.tsx index fc33e6a9e5..faba8450f1 100644 --- a/packages/super-editor/src/ui/react/hooks.test.tsx +++ b/packages/super-editor/src/ui/react/hooks.test.tsx @@ -193,6 +193,7 @@ describe('domain hooks', () => { 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -201,6 +202,7 @@ describe('domain hooks', () => { 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dcb83acca..86544ebd30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1041,7 +1041,7 @@ importers: dependencies: '@hocuspocus/provider': specifier: latest - version: 4.1.0(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31) + version: 4.1.1(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31) '@hookform/resolvers': specifier: ^3.9.1 version: 3.10.0(react-hook-form@7.72.0(react@18.2.0)) @@ -2415,7 +2415,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: specifier: 3.5.32 version: 3.5.32(typescript@5.9.3) @@ -2440,7 +2440,7 @@ importers: version: 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) concurrently: specifier: 'catalog:' version: 9.2.1 @@ -2464,7 +2464,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) packages/document-api: {} @@ -3318,8 +3318,8 @@ importers: shared/font-system: dependencies: '@docfonts/fallbacks': - specifier: 0.15.0 - version: 0.15.0 + specifier: 0.16.0 + version: 0.16.0 devDependencies: '@types/node': specifier: 'catalog:' @@ -4814,8 +4814,8 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} - '@docfonts/fallbacks@0.15.0': - resolution: {integrity: sha512-RC64SFSbOgGKyXuo2vtplC3W5qdb8lVF31efsgS/ZleEgY78HOE2voaJ9e9u3JpcNODQQcFO7t4OkdoMnWVxpg==} + '@docfonts/fallbacks@0.16.0': + resolution: {integrity: sha512-nFvGPHT7b5iww3AfWwob3NhwAM23/uptskuqJR2PD6EU5k573Dn12Kd3YqN/vHHw5RsKkstnJl5AgEpL9oACkA==} '@dxup/nuxt@0.4.0': resolution: {integrity: sha512-28LDotpr9G2knUse3cQYsOo6NJq5yhABv4ByRVRYJUmzf9Q31DI7rpRek4POlKy1aAcYyKgu5J2616pyqLohYg==} @@ -5677,8 +5677,8 @@ packages: '@hocuspocus/common@2.15.3': resolution: {integrity: sha512-Rzh1HF0a2o/tf90A3w2XNdXd9Ym3aQzMDfD3lAUONCX9B9QOdqdyiORrj6M25QEaJrEIbXFy8LtAFcL0wRdWzA==} - '@hocuspocus/common@4.1.0': - resolution: {integrity: sha512-SOBbu0GcBMbLo7IYRDZC6gvEcoATbEFIC5KqzvLanC6dZZLkv91pYEBli+Exs/G71ELL3iUjSwnaf+gksxcjFA==} + '@hocuspocus/common@4.1.1': + resolution: {integrity: sha512-7jvCti57eYp6Yf7MHzPI4QI75o4zHg4G2f2HC3jbAdpTb+aKxAEPpnDxxpl2Lj4XzyDcisZfwB0Rvf7lbPXAog==} '@hocuspocus/provider@2.15.3': resolution: {integrity: sha512-oadN05m+KL4ylNKVo5YspNG4MXkT2Y+FUFzrgigpQeTjQibkPUwCNmUnkUxMgrGRgxb+O0lJCfirFIJMxedctA==} @@ -5686,8 +5686,8 @@ packages: y-protocols: ^1.0.6 yjs: ^13.6.8 - '@hocuspocus/provider@4.1.0': - resolution: {integrity: sha512-K80V2yX4AMdpqgjIkexJioyI2Oq8pu8hjdFqY0INXKSpH8TQv9NMaDzJmlrZMYmUeKyRO2t7VhGyFK9jZQPnNw==} + '@hocuspocus/provider@4.1.1': + resolution: {integrity: sha512-LSbr50a1+1yoyO7tiWBkBxZZQzSRkW6xQRfEx6phNhQDzn5E4lL24mvfkUIyVFrr+TzPDGMFz5CBx6cXSaFYzw==} peerDependencies: y-protocols: ^1.0.6 yjs: ^13.6.8 @@ -6563,6 +6563,7 @@ packages: '@microsoft/teamsapp-cli@3.0.2': resolution: {integrity: sha512-AowuJwrrUxeF9Bq/frxuy9YZjK/ECk3pi0UBXl3CQLZ4XNWfgWatiFi/UWpyHDLccFs+0Za3nNYATFvgsxEFwQ==} engines: {node: '>=12'} + deprecated: This package is deprecated and supported Node.js version is 18-22. Please use @microsoft/m365agentstoolkit-cli instead. hasBin: true '@microsoft/teamsfx-api@0.23.1': @@ -25633,7 +25634,7 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} - '@docfonts/fallbacks@0.15.0': {} + '@docfonts/fallbacks@0.16.0': {} '@dxup/nuxt@0.4.0(magicast@0.5.2)(typescript@5.9.3)': dependencies: @@ -26294,7 +26295,7 @@ snapshots: dependencies: lib0: 0.2.117 - '@hocuspocus/common@4.1.0': + '@hocuspocus/common@4.1.1': dependencies: lib0: 0.2.117 @@ -26322,9 +26323,9 @@ snapshots: - bufferutil - utf-8-validate - '@hocuspocus/provider@4.1.0(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31)': + '@hocuspocus/provider@4.1.1(y-protocols@1.0.7(yjs@13.6.31))(yjs@13.6.31)': dependencies: - '@hocuspocus/common': 4.1.0 + '@hocuspocus/common': 4.1.1 '@lifeomic/attempt': 3.1.0 lib0: 0.2.117 y-protocols: 1.0.7(yjs@13.6.31) @@ -33646,6 +33647,25 @@ snapshots: vite: rolldown-vite@7.3.1(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.6.0)(esbuild@0.27.7)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vue: 3.5.32(typescript@5.9.3) + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.12 + debug: 4.4.3(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.4)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/debug@4.1.13)(@types/node@22.19.2)(esbuild@0.27.7)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.3))(less@4.4.2)(sass@1.97.3)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@ampproject/remapping': 2.3.0 diff --git a/shared/font-system/assets/LICENSES.md b/shared/font-system/assets/LICENSES.md index e4dfd29067..681c1cb87f 100644 --- a/shared/font-system/assets/LICENSES.md +++ b/shared/font-system/assets/LICENSES.md @@ -12,7 +12,8 @@ Google Fonts provenance on 2026-06-09. C059 and Liberation Sans Narrow were added from the legal-reviewed asset package on 2026-06-09. Archivo Black, PT Sans Narrow, and Inconsolata SemiExpanded were added from DocFonts' Google Fonts provenance on 2026-06-10. TeX Gyre Bonum was added from CTAN TeX Gyre -provenance on 2026-06-10._ +provenance on 2026-06-10. URW Gothic and Selawik were added from DocFonts +corpus provenance on 2026-06-10._ ## Scope (read first) @@ -25,13 +26,13 @@ obligations below are stated to the broadest redistribution case. Lighter delivery models are covered too. **No edit to this file is required if the delivery model changes.** -Two families are copyleft: **C059** (AGPL-3.0) and **Liberation Sans Narrow** -(GPL-2.0). Their PostScript/PDF font exceptions mean embedding a font into a -rendered document does not impose AGPL/GPL terms on that document. Distributing -the **font binaries themselves** carries the AGPL/GPL corresponding-source -obligation; SuperDoc satisfies it by pointing to the exact unmodified upstream -release recorded in the table below. These fonts are not relicensed under -SuperDoc's AGPLv3 or commercial terms. +Three families are copyleft: **C059** and **URW Gothic** (AGPL-3.0) and +**Liberation Sans Narrow** (GPL-2.0). Their PostScript/PDF font exceptions mean +embedding a font into a rendered document does not impose AGPL/GPL terms on that +document. Distributing the **font binaries themselves** carries the AGPL/GPL +corresponding-source obligation; SuperDoc satisfies it by pointing to the exact +unmodified upstream release recorded in the table below. These fonts are not +relicensed under SuperDoc's AGPLv3 or commercial terms. These notices, `OFL.txt`, `Apache-2.0.txt`, `AGPL-3.0.txt`, `GPL-2.0.txt`, and `GUST-Font-License-1.0.txt` are a single unit. Distribute them together with the @@ -53,12 +54,14 @@ Machine-readable asset metadata: `font-assets.manifest.json`. | Caprasimo | Cooper Black | OFL-1.1 | none | 1.001 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/caprasimo | | Archivo Black | Arial Black | OFL-1.1 | none | 1.006 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/archivoblack | | C059 | Century, Century Schoolbook | AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817 | none | 1.00 | github.com/ArtifexSoftware/urw-base35-fonts (unmodified) | +| URW Gothic | Century Gothic | AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817 | none | 1.00 | github.com/ArtifexSoftware/urw-base35-fonts (unmodified) | | Bacasime Antique | Baskerville Old Face | OFL-1.1 | "Playfair" | 2.000 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/bacasimeantique | | Oregano Italic | Brush Script MT | OFL-1.1 | "Oregano Italic" | 1.000 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/oregano | | Gelasio | Georgia | OFL-1.1 | none | 1.008 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/gelasio | | Cardo | Garamond | OFL-1.1 | none | Regular 1.0451; Bold 1.0011; Italic 0.991 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/cardo | | Comic Relief | Comic Sans MS | OFL-1.1 | none | 1.200 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/comicrelief | | Noto Sans | Tahoma | OFL-1.1 | none | 2.015 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/notosans | +| Selawik | Segoe UI | OFL-1.1 | "Selawik" | 1.01 | github.com/microsoft/Selawik release 1.01 | | Noto Sans Mono | Lucida Console | OFL-1.1 | none | 2.014 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/notosansmono | | Inconsolata SemiExpanded | Consolas | OFL-1.1 | none | 3.001 | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/inconsolata/static | | PT Sans | Trebuchet MS | OFL-1.1 | "PT Sans", "PT Serif", "ParaType" | 2.003W OFL | google/fonts c89741abbf4eeabce432c3ed2fd7dc28b022701e, ofl/ptsans | @@ -133,6 +136,14 @@ No OFL Reserved Font Name is declared in this file. No trademark string and no Reserved Font Name are declared in these files. +**URW Gothic** - AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817 + +```text +(URW)++,Copyright 2014 by (URW)++ Design & Development +``` + +No trademark string and no Reserved Font Name are declared in these files. + **Bacasime Antique** - OFL-1.1 ```text @@ -186,6 +197,14 @@ License description: This Font Software is licensed under the SIL Open Font Lice No OFL Reserved Font Name is declared in these files. +**Selawik** - OFL-1.1 + +```text +© 2015 Microsoft Corporation (www.microsoft.com), with Reserved Font Name "Selawik". Selawik is a trademark of Microsoft Corporation in the United States and/or other countries. +Trademark: Selawik is a trademark of the Microsoft group of companies. +License description: This Font Software is licensed under the SIL Open Font License, Version 1.1. +``` + **Noto Sans Mono** - OFL-1.1 ```text @@ -248,11 +267,11 @@ same statement satisfies the Apache-2.0 section 4(b) "modified files" notice. Caladea carries **no upstream `NOTICE` file**, so Apache-2.0 section 4(d) adds nothing. -For C059 under AGPL-3.0 and Liberation Sans Narrow under GPL-2.0, the same -format-only WOFF2 conversion applies, with no design, metric, glyph, `cmap`, or -`name`-table change. The corresponding-source obligation is satisfied by the -exact upstream releases recorded in the Families table; the unmodified upstream -OTF/TTF files are the preferred form for modification. +For C059 and URW Gothic under AGPL-3.0 and Liberation Sans Narrow under +GPL-2.0, the same format-only WOFF2 conversion applies, with no design, metric, +glyph, `cmap`, or `name`-table change. The corresponding-source obligation is +satisfied by the exact upstream releases recorded in the Families table; the +unmodified upstream OTF/TTF files are the preferred form for modification. For TeX Gyre Bonum under the GUST Font License, the same format-only WOFF2 conversion applies, with no design, metric, glyph, `cmap`, or `name`-table @@ -260,10 +279,10 @@ change. ## Verification evidence (current ship set) -- **49 / 59 WOFF2 faces:** static-source WOFF2 flavor, WOFF2 metadata block +- **55 / 65 WOFF2 faces:** static-source WOFF2 flavor, WOFF2 metadata block omitted, `name` table byte-identical to the source font, identical glyph count and `cmap`. -- **10 / 59 WOFF2 faces:** static variable-font instances for Gelasio, Noto +- **10 / 65 WOFF2 faces:** static variable-font instances for Gelasio, Noto Sans, and Noto Sans Mono, generated at regular and bold weights with no subsetting. - **Metrics:** `unitsPerEm`, `hhea` ascent/descent, selected OS/2 metrics, @@ -273,18 +292,18 @@ change. ## Full license texts - OFL-1.1 (Carlito, Liberation, Caprasimo, Archivo Black, Bacasime Antique, - Oregano Italic, Gelasio, Cardo, Comic Relief, Noto Sans, Noto Sans Mono, - Inconsolata SemiExpanded, PT Sans, PT Sans Narrow): `OFL.txt` in this + Oregano Italic, Gelasio, Cardo, Comic Relief, Noto Sans, Selawik, Noto Sans + Mono, Inconsolata SemiExpanded, PT Sans, PT Sans Narrow): `OFL.txt` in this directory. The copyright notices above are also stacked at the top of that file. - Apache-2.0 (Caladea): `Apache-2.0.txt` in this directory. -- AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817 (C059): `AGPL-3.0.txt` in - this directory. +- AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817 (C059, URW Gothic): + `AGPL-3.0.txt` in this directory. - GPL-2.0-only WITH Font-exception-2.0 (Liberation Sans Narrow): `GPL-2.0.txt` in this directory. - LicenseRef-GUST-Font-License-1.0 (TeX Gyre Bonum): `GUST-Font-License-1.0.txt` in this directory. SuperDoc does not relicense these fonts. They remain under their own OFL-1.1 / -Apache-2.0 / AGPL-3.0 / GPL-2.0 terms regardless of the license under which -SuperDoc itself is offered (AGPLv3 community build or commercial). +Apache-2.0 / AGPL-3.0 / GPL-2.0 / GUST terms regardless of the license under +which SuperDoc itself is offered (AGPLv3 community build or commercial). diff --git a/shared/font-system/assets/OFL.txt b/shared/font-system/assets/OFL.txt index 5fe63f68e6..4d71199f06 100644 --- a/shared/font-system/assets/OFL.txt +++ b/shared/font-system/assets/OFL.txt @@ -3,8 +3,8 @@ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 This is the license text covering the OFL-1.1 fonts bundled in this directory (Carlito, Liberation Sans, Liberation Serif, Liberation Mono, Caprasimo, Bacasime Antique, Oregano Italic, Gelasio, Cardo, Comic Relief, Noto Sans, -Noto Sans Mono, Inconsolata SemiExpanded, PT Sans, Archivo Black, PT Sans -Narrow). Copyright notices and any Reserved Font Name statements for those fonts +Selawik, Noto Sans Mono, Inconsolata SemiExpanded, PT Sans, Archivo Black, PT +Sans Narrow). Copyright notices and any Reserved Font Name statements for those fonts are reproduced verbatim below, taken from the fonts' own name tables, so they travel with this license text. LICENSES.md restates them for convenience. Official OFL text: https://openfontlicense.org/open-font-license-official-text/ @@ -71,6 +71,12 @@ Noto Sans: Licensed under the SIL Open Font License, Version 1.1. (These v2.015 files declare no OFL Reserved Font Name.) +Selawik: + © 2015 Microsoft Corporation (www.microsoft.com), with Reserved Font Name + "Selawik". Selawik is a trademark of Microsoft Corporation in the United + States and/or other countries. + Licensed under the SIL Open Font License, Version 1.1. + Noto Sans Mono: Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic) diff --git a/shared/font-system/assets/Selawik-Bold.woff2 b/shared/font-system/assets/Selawik-Bold.woff2 new file mode 100644 index 0000000000..eb32115bdb Binary files /dev/null and b/shared/font-system/assets/Selawik-Bold.woff2 differ diff --git a/shared/font-system/assets/Selawik-Regular.woff2 b/shared/font-system/assets/Selawik-Regular.woff2 new file mode 100644 index 0000000000..6b7e55770e Binary files /dev/null and b/shared/font-system/assets/Selawik-Regular.woff2 differ diff --git a/shared/font-system/assets/URWGothic-Book.woff2 b/shared/font-system/assets/URWGothic-Book.woff2 new file mode 100644 index 0000000000..e89f7042ce Binary files /dev/null and b/shared/font-system/assets/URWGothic-Book.woff2 differ diff --git a/shared/font-system/assets/URWGothic-BookOblique.woff2 b/shared/font-system/assets/URWGothic-BookOblique.woff2 new file mode 100644 index 0000000000..4eed9dce59 Binary files /dev/null and b/shared/font-system/assets/URWGothic-BookOblique.woff2 differ diff --git a/shared/font-system/assets/URWGothic-Demi.woff2 b/shared/font-system/assets/URWGothic-Demi.woff2 new file mode 100644 index 0000000000..b52faf97a9 Binary files /dev/null and b/shared/font-system/assets/URWGothic-Demi.woff2 differ diff --git a/shared/font-system/assets/URWGothic-DemiOblique.woff2 b/shared/font-system/assets/URWGothic-DemiOblique.woff2 new file mode 100644 index 0000000000..8a9bd6d7bf Binary files /dev/null and b/shared/font-system/assets/URWGothic-DemiOblique.woff2 differ diff --git a/shared/font-system/assets/font-assets.manifest.json b/shared/font-system/assets/font-assets.manifest.json index b0ea754d83..37abc11a02 100644 --- a/shared/font-system/assets/font-assets.manifest.json +++ b/shared/font-system/assets/font-assets.manifest.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "spdxExpression": "OFL-1.1 AND Apache-2.0 AND (AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817) AND (GPL-2.0-only WITH Font-exception-2.0) AND LicenseRef-GUST-Font-License-1.0", - "generatedFrom": "Bundled font legal review, 2026-06-03; Caprasimo added from DocFonts Google Fonts OFL provenance, 2026-06-08; OFL fallback batch added from DocFonts Google Fonts provenance, 2026-06-09; Bacasime Antique, Oregano Italic, and Noto Sans Mono added from DocFonts Google Fonts provenance, 2026-06-09; C059 and Liberation Sans Narrow added from legal-reviewed asset package, 2026-06-09; Arial Black and PT Sans Narrow added from DocFonts Google Fonts provenance, 2026-06-10; TeX Gyre Bonum added from CTAN TeX Gyre provenance, 2026-06-10; Inconsolata SemiExpanded added from DocFonts Google Fonts provenance, 2026-06-10", + "generatedFrom": "Bundled font legal review, 2026-06-03; Caprasimo added from DocFonts Google Fonts OFL provenance, 2026-06-08; OFL fallback batch added from DocFonts Google Fonts provenance, 2026-06-09; Bacasime Antique, Oregano Italic, and Noto Sans Mono added from DocFonts Google Fonts provenance, 2026-06-09; C059 and Liberation Sans Narrow added from legal-reviewed asset package, 2026-06-09; Arial Black and PT Sans Narrow added from DocFonts Google Fonts provenance, 2026-06-10; TeX Gyre Bonum added from CTAN TeX Gyre provenance, 2026-06-10; Inconsolata SemiExpanded added from DocFonts Google Fonts provenance, 2026-06-10; URW Gothic and Selawik added from DocFonts corpus provenance, 2026-06-10", "families": [ { "family": "Carlito", @@ -309,6 +309,61 @@ } ] }, + { + "family": "URW Gothic", + "replaces": ["Century Gothic"], + "license": "AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817", + "licenseFiles": ["AGPL-3.0.txt"], + "version": "Version 1.00", + "upstreamSource": "github.com/ArtifexSoftware/urw-base35-fonts (URW Gothic, unmodified); Corresponding Source per AGPL-3.0 sec. 6(d)", + "reservedFontName": null, + "copyrightNotice": "(URW)++,Copyright 2014 by (URW)++ Design & Development", + "trademarkNotice": null, + "sourceFiles": [ + { + "file": "URWGothic-Book.otf", + "sha256": "04318316cee29950805110c9c8949eea189ef5575132881f4d4d7e03e5299903" + }, + { + "file": "URWGothic-Demi.otf", + "sha256": "5b009410cf5231dcb1e45b155c1afedcfc63d82042fd8c414d0dd7705c9fbbae" + }, + { + "file": "URWGothic-BookOblique.otf", + "sha256": "ec4e947734950fd344918e560bc8ad0c5d8b17d0ab4853988824f13b9f77e53f" + }, + { + "file": "URWGothic-DemiOblique.otf", + "sha256": "d57abaa6d40718cfbf735ad50080d0b095e7194e451992e02786a7c0113db35c" + } + ], + "faces": [ + { + "file": "URWGothic-Book.woff2", + "weight": "normal", + "style": "normal", + "sha256": "27afe1c2d046a74797ff436c7162c9c64d7ed888af550b20ce6d36bc8cd69258" + }, + { + "file": "URWGothic-Demi.woff2", + "weight": "bold", + "style": "normal", + "sha256": "9258ca6eae59d3df23a4f34af9abd806b15b962ac11b546a9bafac733c3e492a" + }, + { + "file": "URWGothic-BookOblique.woff2", + "weight": "normal", + "style": "italic", + "sha256": "c843b8fe2b56948c366335b8bff1c3180b0c9bf2ffa7e21903458432d39ff806" + }, + { + "file": "URWGothic-DemiOblique.woff2", + "weight": "bold", + "style": "italic", + "sha256": "fb292fed0e0d8005c99826be1967c278f7ad2bea0d31735e16f3ccb2b2ed51e8" + } + ] + }, { "family": "Bacasime Antique", "replaces": ["Baskerville Old Face"], @@ -535,6 +590,41 @@ } ] }, + { + "family": "Selawik", + "replaces": ["Segoe UI"], + "license": "OFL-1.1", + "licenseFiles": ["OFL.txt"], + "version": "1.01", + "upstreamSource": "github.com/microsoft/Selawik release 1.01", + "reservedFontName": "Selawik", + "copyrightNotice": "© 2015 Microsoft Corporation (www.microsoft.com), with Reserved Font Name \"Selawik\". Selawik is a trademark of Microsoft Corporation in the United States and/or other countries.", + "trademarkNotice": "Selawik is a trademark of the Microsoft group of companies.", + "sourceFiles": [ + { + "file": "selawk.ttf", + "sha256": "e9d98518d8ac2817782a9a382430463a2e0793ea68350b695bb727d9a830ee1c" + }, + { + "file": "selawkb.ttf", + "sha256": "f0db5e174a90e0956ad7d2844bdca1d5e6da92ec65b2c04e57ba9b180668c904" + } + ], + "faces": [ + { + "file": "Selawik-Regular.woff2", + "weight": "normal", + "style": "normal", + "sha256": "12fe84efc16ae0d3196ccf0f6a84bcc916a05c6fc1a9d5ff9d710a384c525a93" + }, + { + "file": "Selawik-Bold.woff2", + "weight": "bold", + "style": "normal", + "sha256": "946fc97e8d2fec1d4b183b5e8653e506bd4d045b529d797a7e3d56cd6a7ee744" + } + ] + }, { "family": "Noto Sans Mono", "replaces": ["Lucida Console"], diff --git a/shared/font-system/package.json b/shared/font-system/package.json index 7974aadccf..1fb0f0c77d 100644 --- a/shared/font-system/package.json +++ b/shared/font-system/package.json @@ -33,6 +33,6 @@ "vitest": "catalog:" }, "dependencies": { - "@docfonts/fallbacks": "0.15.0" + "@docfonts/fallbacks": "0.16.0" } } diff --git a/shared/font-system/src/bundled-manifest.ts b/shared/font-system/src/bundled-manifest.ts index b36b6989f7..d37570cd41 100644 --- a/shared/font-system/src/bundled-manifest.ts +++ b/shared/font-system/src/bundled-manifest.ts @@ -77,6 +77,12 @@ export const BUNDLED_MANIFEST: readonly BundledFamilyManifest[] = Object.freeze( { weight: 'normal', style: 'italic', file: 'C059-Italic.woff2' }, { weight: 'bold', style: 'italic', file: 'C059-BdIta.woff2' }, ]), + familyWithFaces('URW Gothic', 'AGPL-3.0-only WITH PS-or-PDF-font-exception-20170817', [ + { weight: 'normal', style: 'normal', file: 'URWGothic-Book.woff2' }, + { weight: 'bold', style: 'normal', file: 'URWGothic-Demi.woff2' }, + { weight: 'normal', style: 'italic', file: 'URWGothic-BookOblique.woff2' }, + { weight: 'bold', style: 'italic', file: 'URWGothic-DemiOblique.woff2' }, + ]), familyWithFaces('Bacasime Antique', 'OFL-1.1', [ { weight: 'normal', style: 'normal', file: 'BacasimeAntique-Regular.woff2' }, ]), @@ -94,6 +100,10 @@ export const BUNDLED_MANIFEST: readonly BundledFamilyManifest[] = Object.freeze( { weight: 'bold', style: 'normal', file: 'ComicRelief-Bold.woff2' }, ]), family('Noto Sans', 'NotoSans', 'OFL-1.1'), + familyWithFaces('Selawik', 'OFL-1.1', [ + { weight: 'normal', style: 'normal', file: 'Selawik-Regular.woff2' }, + { weight: 'bold', style: 'normal', file: 'Selawik-Bold.woff2' }, + ]), familyWithFaces('Noto Sans Mono', 'OFL-1.1', [ { weight: 'normal', style: 'normal', file: 'NotoSansMono-Regular.woff2' }, { weight: 'bold', style: 'normal', file: 'NotoSansMono-Bold.woff2' }, diff --git a/shared/font-system/src/bundled.test.ts b/shared/font-system/src/bundled.test.ts index f1bf145778..f4e6b4bef8 100644 --- a/shared/font-system/src/bundled.test.ts +++ b/shared/font-system/src/bundled.test.ts @@ -35,6 +35,7 @@ describe('installBundledSubstitutes URL resolution', () => { expect(reg.sourcesFor('Liberation Sans')).toContain('url(/fonts/LiberationSans-Bold.woff2)'); expect(reg.sourcesFor('Liberation Sans Narrow')).toContain('url(/fonts/LiberationSansNarrow-BoldItalic.woff2)'); expect(reg.sourcesFor('C059')).toContain('url(/fonts/C059-BdIta.woff2)'); + expect(reg.sourcesFor('URW Gothic')).toContain('url(/fonts/URWGothic-DemiOblique.woff2)'); expect(reg.sourcesFor('Caprasimo')).toEqual(['url(/fonts/Caprasimo-Regular.woff2)']); expect(reg.sourcesFor('Archivo Black')).toEqual(['url(/fonts/ArchivoBlack-Regular.woff2)']); expect(reg.sourcesFor('Bacasime Antique')).toEqual(['url(/fonts/BacasimeAntique-Regular.woff2)']); @@ -49,6 +50,7 @@ describe('installBundledSubstitutes URL resolution', () => { 'url(/fonts/ComicRelief-Regular.woff2)', 'url(/fonts/ComicRelief-Bold.woff2)', ]); + expect(reg.sourcesFor('Selawik')).toEqual(['url(/fonts/Selawik-Regular.woff2)', 'url(/fonts/Selawik-Bold.woff2)']); expect(reg.sourcesFor('Noto Sans Mono')).toEqual([ 'url(/fonts/NotoSansMono-Regular.woff2)', 'url(/fonts/NotoSansMono-Bold.woff2)', diff --git a/shared/font-system/src/document-font-options.test.ts b/shared/font-system/src/document-font-options.test.ts index 49f186ce62..6e2cb57d8f 100644 --- a/shared/font-system/src/document-font-options.test.ts +++ b/shared/font-system/src/document-font-options.test.ts @@ -135,6 +135,7 @@ describe('buildFontFamilyOptions (custom UI font picker rows)', () => { 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -143,6 +144,7 @@ describe('buildFontFamilyOptions (custom UI font picker rows)', () => { 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', diff --git a/shared/font-system/src/font-offerings.test.ts b/shared/font-system/src/font-offerings.test.ts index 81e024ed81..4239f2276a 100644 --- a/shared/font-system/src/font-offerings.test.ts +++ b/shared/font-system/src/font-offerings.test.ts @@ -19,6 +19,7 @@ const EXPECTED_BUILT_IN_TOOLBAR = [ 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -27,6 +28,7 @@ const EXPECTED_BUILT_IN_TOOLBAR = [ 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', @@ -35,13 +37,15 @@ const EXPECTED_BUILT_IN_TOOLBAR = [ /** * Must NOT appear as DEFAULT options yet. Aptos has no clone, Cambria/Georgia/Cooper Black/ - * Baskerville Old Face/Arial Black/Arial Narrow/Century/Century Schoolbook/ITC Bookman are qualified, and - * Calibri Light/Tahoma/Trebuchet MS/Garamond/Comic Sans MS/Brush Script MT/Gill Sans MT Condensed/ - * Lucida Console/Consolas/Verdana are category fallbacks. - * Some may be explicit built-in picker choices, but none should become silent strict defaults. + * Baskerville Old Face/Arial Black/Arial Narrow/Century/Century Gothic/Century Schoolbook/ITC + * Bookman are qualified/category rows, Arial MT/Courier/Times are supported aliases, and Calibri + * Light/Tahoma/Trebuchet MS/Garamond/Comic Sans MS/Brush Script MT/Gill Sans MT Condensed/Lucida + * Console/Consolas/Verdana/Segoe UI are category fallbacks. Some may be explicit built-in picker + * choices, but none should become silent strict defaults. */ const NOT_DEFAULT_YET = [ 'Aptos', + 'Arial MT', 'Georgia', 'Cambria', 'Cooper Black', @@ -50,9 +54,12 @@ const NOT_DEFAULT_YET = [ 'Baskerville Old Face', 'Bookman Old Style', 'Arial Narrow', + 'Century Gothic', 'Century', 'Century Schoolbook', + 'Courier', 'Tahoma', + 'Times', 'Trebuchet MS', 'Garamond', 'Comic Sans MS', @@ -62,6 +69,7 @@ const NOT_DEFAULT_YET = [ 'Consolas', 'ITC Bookman', 'Verdana', + 'Segoe UI', ]; describe('font offerings', () => { @@ -127,6 +135,12 @@ describe('font offerings', () => { bundled: true, physicalFamily: 'C059', }); + expect(byName('Century Gothic')).toMatchObject({ + offering: 'category_fallback', + verdict: 'visual_only', + bundled: true, + physicalFamily: 'URW Gothic', + }); expect(byName('Century Schoolbook')).toMatchObject({ offering: 'qualified', verdict: 'visual_only', @@ -175,6 +189,26 @@ describe('font offerings', () => { bundled: true, physicalFamily: 'Noto Sans', }); + expect(byName('Segoe UI')).toMatchObject({ + offering: 'category_fallback', + bundled: true, + physicalFamily: 'Selawik', + }); + expect(byName('Arial MT')).toMatchObject({ + offering: 'supported_alias', + bundled: true, + physicalFamily: 'Liberation Sans', + }); + expect(byName('Times')).toMatchObject({ + offering: 'supported_alias', + bundled: true, + physicalFamily: 'Liberation Serif', + }); + expect(byName('Courier')).toMatchObject({ + offering: 'supported_alias', + bundled: true, + physicalFamily: 'Liberation Mono', + }); }); it('getDefaultFontFamilyOptions returns logical label + logical stack', () => { @@ -187,6 +221,7 @@ describe('font offerings', () => { { label: 'Brush Script MT', value: 'Brush Script MT, serif' }, { label: 'Calibri', value: 'Calibri, sans-serif' }, { label: 'Century', value: 'Century, serif' }, + { label: 'Century Gothic', value: 'Century Gothic, sans-serif' }, { label: 'Comic Sans MS', value: 'Comic Sans MS, sans-serif' }, { label: 'Cooper Black', value: 'Cooper Black, serif' }, { label: 'Courier New', value: 'Courier New, monospace' }, @@ -195,6 +230,7 @@ describe('font offerings', () => { { label: 'Gill Sans MT Condensed', value: 'Gill Sans MT Condensed, sans-serif' }, { label: 'Helvetica', value: 'Helvetica, sans-serif' }, { label: 'Lucida Console', value: 'Lucida Console, monospace' }, + { label: 'Segoe UI', value: 'Segoe UI, sans-serif' }, { label: 'Tahoma', value: 'Tahoma, sans-serif' }, { label: 'Times New Roman', value: 'Times New Roman, serif' }, { label: 'Trebuchet MS', value: 'Trebuchet MS, sans-serif' }, diff --git a/shared/font-system/src/font-offerings.ts b/shared/font-system/src/font-offerings.ts index ef9a58bba1..b0da75d2c8 100644 --- a/shared/font-system/src/font-offerings.ts +++ b/shared/font-system/src/font-offerings.ts @@ -26,6 +26,7 @@ export type OfferingClass = | 'default' // metric_safe + bundled: safe to advertise as a normal default toolbar option | 'qualified' // bundled and renderable, but with fidelity caveats (visual_only / near_metric), e.g. Georgia | 'category_fallback' // a usable family fallback, not a faithful clone, e.g. Calibri Light -> Carlito + | 'supported_alias' // renderable duplicate name, but not a built-in picker choice, e.g. Arial MT -> Arial | 'requires_asset' // a candidate exists, but SuperDoc does not bundle its asset yet, e.g. Arial Rounded MT Bold | 'customer_supplied' // no open substitute; the real font must come from the customer, e.g. Aptos | 'preserve_only'; // keep the name, never a default option, e.g. Cambria Math @@ -51,6 +52,7 @@ export interface FontOffering { } const BUNDLED_FAMILIES: ReadonlySet = new Set(BUNDLED_MANIFEST.map((f) => f.family)); +const SUPPORTED_ALIAS_FAMILIES: ReadonlySet = new Set(['Arial MT', 'Courier', 'Times']); const ADVERTISED_BUILT_IN_TOOLBAR_FAMILIES: ReadonlySet = new Set([ 'Arial Black', 'Arial Narrow', @@ -58,12 +60,14 @@ const ADVERTISED_BUILT_IN_TOOLBAR_FAMILIES: ReadonlySet = new Set([ 'Bookman Old Style', 'Brush Script MT', 'Century', + 'Century Gothic', 'Cooper Black', 'Comic Sans MS', 'Garamond', 'Georgia', 'Gill Sans MT Condensed', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Trebuchet MS', 'Verdana', @@ -71,6 +75,7 @@ const ADVERTISED_BUILT_IN_TOOLBAR_FAMILIES: ReadonlySet = new Set([ /** Classify one evidence row by its policy action, verdict, and whether its target is bundled. */ function classifyOffering( + logicalFamily: string, policyAction: (typeof SUBSTITUTION_EVIDENCE)[number]['policyAction'], verdict: SubstituteVerdict, physicalFamily: string | null, @@ -78,6 +83,7 @@ function classifyOffering( ): OfferingClass { if (policyAction === 'preserve_only') return 'preserve_only'; if (policyAction === 'customer_supplied' || physicalFamily == null) return 'customer_supplied'; + if (bundled && SUPPORTED_ALIAS_FAMILIES.has(logicalFamily)) return 'supported_alias'; if (policyAction === 'category_fallback') return 'category_fallback'; // policyAction === 'substitute' from here. if (!bundled) return 'requires_asset'; // a clone exists but SuperDoc does not ship it yet @@ -91,7 +97,7 @@ function deriveOfferings(): readonly FontOffering[] { logicalFamily: row.logicalFamily, physicalFamily: row.physicalFamily, generic: row.generic, - offering: classifyOffering(row.policyAction, row.verdict, row.physicalFamily, bundled), + offering: classifyOffering(row.logicalFamily, row.policyAction, row.verdict, row.physicalFamily, bundled), bundled, verdict: row.verdict, evidenceId: row.evidenceId, @@ -108,11 +114,14 @@ function compareLogicalFamily(a: FontOffering, b: FontOffering): number { } /** - * The metric-safe, bundled-backed offerings safe to treat as clean defaults, sorted by logical family. - * Excludes qualified rows (Arial Black, Arial Narrow, Cambria, Century, Century Schoolbook, - * Cooper Black, Georgia, Baskerville Old Face, Bookman Old Style, ITC Bookman), category fallbacks - * (Calibri Light, Tahoma, Trebuchet MS, Garamond, Comic Sans MS, Brush Script MT, - * Gill Sans MT Condensed, Lucida Console, Consolas, Verdana), and not-yet-bundled candidates. + * The metric-safe, bundled-backed offerings safe to treat as clean defaults, sorted by logical + * family. Excludes redundant aliases even when they are renderable; those still resolve for + * documents through the resolver. + * Excludes qualified rows (Arial Black, Arial Narrow, Cambria, Century, Century Gothic, + * Century Schoolbook, Cooper Black, Georgia, Baskerville Old Face, Bookman Old Style, ITC Bookman), + * category fallbacks (Calibri Light, Tahoma, Trebuchet MS, Garamond, Comic Sans MS, Brush Script MT, + * Gill Sans MT Condensed, Lucida Console, Consolas, Verdana, Segoe UI), supported aliases + * (Arial MT, Courier, Times), and not-yet-bundled candidates. */ export function getDefaultFontOfferings(): FontOffering[] { return FONT_OFFERINGS.filter((o) => o.offering === 'default').sort(compareLogicalFamily); diff --git a/shared/font-system/src/report.test.ts b/shared/font-system/src/report.test.ts index 3ba880c997..7a40948a75 100644 --- a/shared/font-system/src/report.test.ts +++ b/shared/font-system/src/report.test.ts @@ -83,6 +83,7 @@ describe('buildFontReport', () => { 'Liberation Mono', 'Caprasimo', 'Archivo Black', + 'URW Gothic', 'Bacasime Antique', 'TeX Gyre Bonum', 'Oregano Italic', @@ -90,6 +91,7 @@ describe('buildFontReport', () => { 'Inconsolata SemiExpanded', 'Noto Sans', 'Noto Sans Mono', + 'Selawik', 'PT Sans Narrow', ]) { reg.statuses.set(family, 'loaded'); @@ -100,6 +102,7 @@ describe('buildFontReport', () => { 'Cambria', 'Arial', 'Arial Black', + 'Century Gothic', 'Times New Roman', 'Courier New', 'Cooper Black', @@ -108,6 +111,7 @@ describe('buildFontReport', () => { 'ITC Bookman', 'Brush Script MT', 'Georgia', + 'Segoe UI', 'Consolas', 'Lucida Console', 'Tahoma', @@ -122,6 +126,7 @@ describe('buildFontReport', () => { 'Caladea', 'Liberation Sans', 'Archivo Black', + 'URW Gothic', 'Liberation Serif', 'Liberation Mono', 'Caprasimo', @@ -130,6 +135,7 @@ describe('buildFontReport', () => { 'TeX Gyre Bonum', 'Oregano Italic', 'Gelasio', + 'Selawik', 'Inconsolata SemiExpanded', 'Noto Sans Mono', 'Noto Sans', @@ -156,6 +162,14 @@ describe('buildFontReport', () => { reason: 'bundled_substitute', missing: false, }); + expect(report.find((r) => r.logicalFamily === 'Century Gothic')).toMatchObject({ + reason: 'category_fallback', + missing: true, + }); + expect(report.find((r) => r.logicalFamily === 'Segoe UI')).toMatchObject({ + reason: 'category_fallback', + missing: true, + }); expect(report.find((r) => r.logicalFamily === 'Brush Script MT')).toMatchObject({ reason: 'category_fallback', missing: true, diff --git a/shared/font-system/src/resolver.test.ts b/shared/font-system/src/resolver.test.ts index 8f12385c4d..646da950c0 100644 --- a/shared/font-system/src/resolver.test.ts +++ b/shared/font-system/src/resolver.test.ts @@ -12,12 +12,16 @@ describe('font resolver', () => { expect(resolvePhysicalFamily('Calibri')).toBe('Carlito'); expect(resolvePhysicalFamily('Cambria')).toBe('Caladea'); expect(resolvePhysicalFamily('Arial')).toBe('Liberation Sans'); + expect(resolvePhysicalFamily('Arial MT')).toBe('Liberation Sans'); expect(resolvePhysicalFamily('Arial Black')).toBe('Archivo Black'); + expect(resolvePhysicalFamily('Times')).toBe('Liberation Serif'); expect(resolvePhysicalFamily('Times New Roman')).toBe('Liberation Serif'); + expect(resolvePhysicalFamily('Courier')).toBe('Liberation Mono'); expect(resolvePhysicalFamily('Courier New')).toBe('Liberation Mono'); expect(resolvePhysicalFamily('Helvetica')).toBe('Liberation Sans'); expect(resolvePhysicalFamily('Arial Narrow')).toBe('Liberation Sans Narrow'); expect(resolvePhysicalFamily('Century')).toBe('C059'); + expect(resolvePhysicalFamily('Century Gothic')).toBe('URW Gothic'); expect(resolvePhysicalFamily('Century Schoolbook')).toBe('C059'); expect(resolvePhysicalFamily('Cooper Black')).toBe('Caprasimo'); expect(resolvePhysicalFamily('Baskerville Old Face')).toBe('Bacasime Antique'); @@ -29,6 +33,7 @@ describe('font resolver', () => { expect(resolvePhysicalFamily('Consolas')).toBe('Inconsolata SemiExpanded'); expect(resolvePhysicalFamily('Comic Sans MS')).toBe('Comic Relief'); expect(resolvePhysicalFamily('Lucida Console')).toBe('Noto Sans Mono'); + expect(resolvePhysicalFamily('Segoe UI')).toBe('Selawik'); expect(resolvePhysicalFamily('Tahoma')).toBe('Noto Sans'); expect(resolvePhysicalFamily('Trebuchet MS')).toBe('PT Sans'); expect(resolvePhysicalFamily('Verdana')).toBe('Noto Sans'); @@ -93,21 +98,46 @@ describe('font resolver', () => { physicalFamily: 'Archivo Black', reason: 'bundled_substitute', }); + expect(resolveFontFamily('Arial MT')).toEqual({ + logicalFamily: 'Arial MT', + physicalFamily: 'Liberation Sans', + reason: 'bundled_substitute', + }); expect(resolveFontFamily('Century')).toEqual({ logicalFamily: 'Century', physicalFamily: 'C059', reason: 'bundled_substitute', }); + expect(resolveFontFamily('Century Gothic')).toEqual({ + logicalFamily: 'Century Gothic', + physicalFamily: 'URW Gothic', + reason: 'category_fallback', + }); expect(resolveFontFamily('Century Schoolbook')).toEqual({ logicalFamily: 'Century Schoolbook', physicalFamily: 'C059', reason: 'bundled_substitute', }); + expect(resolveFontFamily('Times')).toEqual({ + logicalFamily: 'Times', + physicalFamily: 'Liberation Serif', + reason: 'bundled_substitute', + }); + expect(resolveFontFamily('Courier')).toEqual({ + logicalFamily: 'Courier', + physicalFamily: 'Liberation Mono', + reason: 'category_fallback', + }); expect(resolveFontFamily('Tahoma')).toEqual({ logicalFamily: 'Tahoma', physicalFamily: 'Noto Sans', reason: 'category_fallback', }); + expect(resolveFontFamily('Segoe UI')).toEqual({ + logicalFamily: 'Segoe UI', + physicalFamily: 'Selawik', + reason: 'category_fallback', + }); expect(resolveFontFamily('Brush Script MT')).toEqual({ logicalFamily: 'Brush Script MT', physicalFamily: 'Oregano Italic', diff --git a/shared/font-system/src/substitution-evidence.test.ts b/shared/font-system/src/substitution-evidence.test.ts index f69ecd41df..a5fd979dab 100644 --- a/shared/font-system/src/substitution-evidence.test.ts +++ b/shared/font-system/src/substitution-evidence.test.ts @@ -12,8 +12,10 @@ const EXPECTED_SUBSTITUTES: ReadonlyArray resolver derivation', () => { verdict: 'visual_only', faces: { regular: true, bold: true, italic: true, boldItalic: true }, }); + expect(SUBSTITUTION_EVIDENCE.find((r) => r.evidenceId === 'century-gothic')).toMatchObject({ + logicalFamily: 'Century Gothic', + physicalFamily: 'URW Gothic', + policyAction: 'category_fallback', + verdict: 'visual_only', + faces: { regular: true, bold: true, italic: true, boldItalic: true }, + advance: { basis: 'latin_text', meanDelta: 0.0013, maxDelta: 0.1662 }, + }); + expect(SUBSTITUTION_EVIDENCE.find((r) => r.evidenceId === 'segoe-ui')).toMatchObject({ + logicalFamily: 'Segoe UI', + physicalFamily: 'Selawik', + policyAction: 'category_fallback', + faces: { regular: true, bold: true, italic: false, boldItalic: false }, + faceSources: { + italic: { kind: 'synthetic', from: 'regular' }, + boldItalic: { kind: 'synthetic', from: 'bold' }, + }, + }); + expect(SUBSTITUTION_EVIDENCE.find((r) => r.evidenceId === 'arial-mt')).toMatchObject({ + logicalFamily: 'Arial MT', + physicalFamily: 'Liberation Sans', + policyAction: 'substitute', + verdict: 'metric_safe', + advance: { basis: 'latin_text', meanDelta: 0, maxDelta: 0 }, + }); + expect(SUBSTITUTION_EVIDENCE.find((r) => r.evidenceId === 'times')).toMatchObject({ + logicalFamily: 'Times', + physicalFamily: 'Liberation Serif', + policyAction: 'substitute', + verdict: 'visual_only', + faceVerdicts: { + regular: 'metric_safe', + bold: 'metric_safe', + italic: 'metric_safe', + boldItalic: 'visual_only', + }, + }); expect(resolveFontFamily('Baskerville Old Face').reason).toBe('bundled_substitute'); expect(resolveFontFamily('Brush Script MT').reason).toBe('category_fallback'); expect(resolveFontFamily('Lucida Console').reason).toBe('category_fallback'); expect(resolveFontFamily('Consolas').reason).toBe('category_fallback'); expect(resolveFontFamily('Gill Sans MT Condensed').reason).toBe('category_fallback'); expect(resolveFontFamily('Verdana').reason).toBe('category_fallback'); + expect(resolveFontFamily('Century Gothic').reason).toBe('category_fallback'); + expect(resolveFontFamily('Segoe UI').reason).toBe('category_fallback'); }); }); diff --git a/shared/font-system/src/substitution-evidence.ts b/shared/font-system/src/substitution-evidence.ts index c1a0b26f2a..40346e5529 100644 --- a/shared/font-system/src/substitution-evidence.ts +++ b/shared/font-system/src/substitution-evidence.ts @@ -53,7 +53,7 @@ export type FaceSlot = 'regular' | 'bold' | 'italic' | 'boldItalic'; /** Advance-width divergence vs the proprietary oracle, as fractions (0 = identical advances). */ export interface AdvanceDelta { /** sample/model used for the advance comparison, e.g. Latin glyph advances vs monospace cell width. */ - basis: 'latin_full' | 'latin_text' | 'monospace_cell'; + basis: 'latin_full' | 'latin_text' | 'monospace_cell' | 'cjk_jp_text'; meanDelta: number; /** the worst-case delta, not the mean, is what gates line-break fidelity. */ maxDelta: number; diff --git a/tests/behavior/tests/toolbar/font-dropdown-document-options.spec.ts b/tests/behavior/tests/toolbar/font-dropdown-document-options.spec.ts index a4a01aa85f..52946ad67e 100644 --- a/tests/behavior/tests/toolbar/font-dropdown-document-options.spec.ts +++ b/tests/behavior/tests/toolbar/font-dropdown-document-options.spec.ts @@ -14,6 +14,7 @@ const DEFAULT_FONT_LABELS = [ 'Brush Script MT', 'Calibri', 'Century', + 'Century Gothic', 'Comic Sans MS', 'Cooper Black', 'Courier New', @@ -22,6 +23,7 @@ const DEFAULT_FONT_LABELS = [ 'Gill Sans MT Condensed', 'Helvetica', 'Lucida Console', + 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', @@ -108,7 +110,7 @@ test('font dropdown opens immediately with the built-in font list and an enabled const labels = await fontOptionLabels(superdoc); expect(labels).toEqual(DEFAULT_FONT_LABELS); - for (const absent of ['Aptos', 'Cambria', 'Calibri Light', 'Century Schoolbook']) { + for (const absent of ['Aptos', 'Cambria', 'Calibri Light', 'Century Schoolbook', 'Arial MT', 'Courier', 'Times']) { expect(labels).not.toContain(absent); } });