Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci-superdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
- 'packages/document-api/**'
- 'packages/collaboration-yjs/**'
- 'packages/docx-evidence-contracts/**'
- 'packages/fonts/**'
- 'shared/**'
- 'tests/**'
- 'scripts/**'
Expand Down Expand Up @@ -131,6 +132,13 @@ jobs:
# Local equivalent: `pnpm check:public:superdoc` (with build).
run: pnpm check:public:superdoc --skip-build

- name: Font curation list drift check
# Fails if the committed packages/fonts/src/bundled-families.ts no longer matches the
# font-system curation set, so a font-offerings change that is not regenerated cannot merge
# stale (guards the Verdana-bug class). Runs here because the job's path filter covers both
# the font offerings (shared/**) and the fonts package (packages/fonts/**).
run: pnpm --filter @superdoc-dev/fonts run check:families

unit-tests:
needs: build
runs-on: ubuntu-latest
Expand Down
94 changes: 94 additions & 0 deletions .github/workflows/release-fonts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Auto-releases on push to main (@next).
# Stable releases are orchestrated centrally by release-stable.yml so that
# every stable release shares one concurrency slot and one git push lane.
name: 📦 Release fonts

on:
push:
branches:
- main
paths:
- 'packages/fonts/**'
- 'shared/**'
- 'pnpm-workspace.yaml'
- '!**/*.md'
workflow_dispatch:

permissions:
contents: write
packages: write

concurrency:
# Stable releases share the `release-stable` group so @semantic-release/git
# pushes to `stable` serialize across workflows; per-workflow groups would
# let releases race on `git push origin stable`. queue: max keeps GitHub
# from dropping older pending stable releases when a stable push touches
# multiple wrapper packages; default queue: single only allows one pending.
# queue: max requires cancel-in-progress: false (cannot be combined with true).
group: ${{ github.ref_name == 'stable' && 'release-stable' || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: false
queue: max

jobs:
release:
# Stable publishes must go through release-stable.yml.
if: ${{ github.event_name != 'workflow_dispatch' || github.ref_name != 'stable' }}
runs-on: ubuntu-24.04
steps:
- name: Generate token
id: generate_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.generate_token.outputs.token }}

- name: Refresh branch head
# Queued release runs may start against a stale checkout (queue: max
# plus cancel-in-progress: false). Refresh to the current branch head
# so @semantic-release/git pushes fast-forward; semantic-release no-ops
# if no new commits were added since the previous queued run released.
run: |
git fetch origin "${{ github.ref_name }}" --tags
git checkout -B "${{ github.ref_name }}" "origin/${{ github.ref_name }}"

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: pnpm
registry-url: 'https://registry.npmjs.org'

- name: Verify 0.1.0 bootstrap
run: |
if ! git ls-remote --exit-code --tags origin "fonts-v0.1.0" >/dev/null 2>&1; then
echo "::error::Missing fonts-v0.1.0 tag. Publish @superdoc-dev/fonts@0.1.0 and push fonts-v0.1.0 before automated fonts releases run."
exit 1
fi
if ! npm view @superdoc-dev/fonts@0.1.0 version >/dev/null 2>&1; then
echo "::error::@superdoc-dev/fonts@0.1.0 is not published. Publish it before automated fonts releases run."
exit 1
fi

- uses: oven-sh/setup-bun@v2

- name: Install dependencies
run: pnpm install

- name: Build fonts package
run: pnpm --filter @superdoc-dev/fonts build

- name: Release
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
LINEAR_TOKEN: ${{ secrets.LINEAR_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
working-directory: packages/fonts
run: pnpx semantic-release
7 changes: 7 additions & 0 deletions .github/workflows/release-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ jobs:
packages-dir: ${{ steps.stage_recovered_python.outputs.main_dir }}
skip-existing: true

# build-python-sdk.mjs requires packages/sdk/tools/catalog.json, a
# generated (gitignored) artifact. `pnpm run build` does not produce it,
# so generate the SDK tool catalog/clients before the Python build.
- name: Generate SDK artifacts (tool catalog)
if: steps.stable_release.outputs.sdk_release_present == 'true'
run: pnpm run generate:all

- name: Build and verify Python SDK
if: steps.stable_release.outputs.sdk_release_present == 'true'
run: node packages/sdk/scripts/build-python-sdk.mjs
Expand Down
4 changes: 1 addition & 3 deletions apps/docs/advanced/headless-toolbar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,6 @@ Snapshot values match the format you pass to `execute()`. What you read is what

For a font dropdown that also includes fonts used by the active document, use `useSuperDocFontOptions()` from `superdoc/ui/react` or `ui.fonts` from `superdoc/ui`.

`DEFAULT_FONT_FAMILY_OPTIONS` mirrors the built-in picker list. It can include bundled font choices beyond the core defaults; use the font report when you need per-font rendering details.

For font family, font size, and other commands that apply to selected text, prefer a button menu or popover that prevents `mousedown` from moving focus out of the editor. Native selects can visually clear the editor selection while their menu is open.

<CodeGroup>
Expand Down Expand Up @@ -361,7 +359,7 @@ const {

| Constant | Contents |
|----------|----------|
| `DEFAULT_FONT_FAMILY_OPTIONS` | Built-in picker list. Includes strict defaults plus explicitly advertised bundled fallback choices. |
| `DEFAULT_FONT_FAMILY_OPTIONS` | Conservative no-pack baseline: one font per CSS generic (Arial, Times New Roman, Courier New). Configure the pack for the full list, or use `useSuperDocFontOptions()` / `ui.fonts`. |
| `DEFAULT_FONT_SIZE_OPTIONS` | 8pt through 96pt (14 sizes) |
| `DEFAULT_TEXT_ALIGN_OPTIONS` | left, center, right, justify |
| `DEFAULT_LINE_HEIGHT_OPTIONS` | 1.00, 1.15, 1.50, 2.00, 2.50, 3.00 |
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/editor/custom-ui/toolbar-and-commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function FontSizePicker() {

## Font family picker

Use `useSuperDocFontOptions()` for a custom font dropdown. It returns the built-in defaults plus fonts used by the active document, sorted alphabetically.
Use `useSuperDocFontOptions()` for a custom font dropdown. It returns the fonts SuperDoc can render plus fonts used by the active document, sorted alphabetically. Without a configured font pack that is the conservative baseline; configure the pack (`@superdoc-dev/fonts`, or `fonts.assetBaseUrl`) and it returns the full set, minus anything curated with `createSuperDocFonts`.

`label` is what you show. `value` is what you pass to the `font-family` command. `previewFamily` is only for rendering the option row.

Expand Down
126 changes: 120 additions & 6 deletions apps/docs/getting-started/fonts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,136 @@ keywords: 'fonts, font loading, calibri, cambria, aptos, font fallback, custom f

SuperDoc keeps the font name from the Word document. When SuperDoc ships an approved fallback for that font, it renders with the fallback but keeps the original name for export. When no fallback is available, load the real font in your app.

## Load fonts in your app
## Bundled fallback fonts

Fonts are your host page's responsibility. `@font-face`, a hosted stylesheet, or a font CDN: anything the browser can resolve.
A Word document asks for fonts like Calibri, Cambria, and Times New Roman. Most are proprietary, or not installed on every machine. SuperDoc renders them with reviewed open substitutes that match the metrics: Carlito for Calibri, Liberation Serif for Times New Roman, and more. The original name is kept for export.

Without the pack, the toolbar lists one widely available font per CSS generic: Arial for sans-serif, Times New Roman for serif, Courier New for monospace. Each is applied with that generic as a fallback, so it renders acceptably even where the exact font is absent - a readable floor, not an exact-typography guarantee. Wire the pack and the toolbar lists the full reviewed set, and SuperDoc renders the substitutes everywhere. Either way, the Word font name is kept for export.

These substitutes are real `.woff2` files the browser fetches from a URL. The `superdoc` core does not ship them; the optional `@superdoc-dev/fonts` package does. Pick one path below.

### Recommended: the `@superdoc-dev/fonts` package

Install the optional pack and pass it. Your bundler (Vite, Webpack, Next, Nuxt) emits the files and rewrites the URLs. No copy step. No path config.

```bash
npm install @superdoc-dev/fonts
```

```js
import { SuperDoc } from 'superdoc';
import { superdocFonts } from '@superdoc-dev/fonts';

new SuperDoc({
selector: '#editor',
document: 'contract.docx',
fonts: superdocFonts,
});
```

### Choose which bundled fonts

By default the pack enables every reviewed font. To narrow it, use `createSuperDocFonts` and name the families. Think in Word names (`Calibri`, not the substitute `Carlito`).

Drop a few:

```js
import { createSuperDocFonts } from '@superdoc-dev/fonts';

new SuperDoc({
selector: '#editor',
document: 'contract.docx',
fonts: createSuperDocFonts({ exclude: ['Cooper Black', 'Brush Script MT'] }),
});
```

Or allow only a set:

```js
fonts: createSuperDocFonts({ include: ['Calibri', 'Cambria', 'Arial'] }),
```

Curation changes the toolbar list and which families SuperDoc substitutes. It does not touch your own licensed fonts (see [Load your own fonts](#load-your-own-fonts)). A curated-out family a document still uses keeps its Word name for export and renders with a system font.

### Alternative: host the files yourself

Serve the `.woff2` from your own path or a CDN, then point SuperDoc at them. The files ship in the `@superdoc-dev/fonts` package at `node_modules/@superdoc-dev/fonts/assets/`; copy them into your served folder.

```js
new SuperDoc({
selector: '#editor',
document: 'contract.docx',
fonts: { assetBaseUrl: '/fonts/' },
});
```

Use `fonts.resolveAssetUrl` instead for signed or versioned URLs.

### CDN script build

The `superdoc` CDN build ships no fonts: by default the toolbar shows the baseline and documents render with system fonts. To load the reviewed pack, add the `@superdoc-dev/fonts` browser build and pass the `SuperDocFonts` global.

```html
<script src="https://cdn.jsdelivr.net/npm/superdoc/dist/superdoc.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@superdoc-dev/fonts/dist/superdoc-fonts.min.js"></script>
<script>
new SuperDoc({
selector: '#editor',
document: 'contract.docx',
fonts: SuperDocFonts.superdocFonts, // or SuperDocFonts.createSuperDocFonts({ exclude: [...] })
});
</script>
```

From a public CDN (jsDelivr or unpkg) the faces resolve automatically, relative to `superdoc-fonts.min.js`. If you self-host it, keep the package's `dist/` and `assets/` folders in their relative layout.

### Skipping the pack

The pack is optional. Skip it if you load your own fonts for every family, or only need fonts the user's OS already has. With no pack configured the toolbar shows the baseline and documents render with system fonts, quietly. If you do wire the pack but the `.woff2` cannot be fetched, SuperDoc logs a one-time warning that names the fix; until then text falls back to the original font name, so it can look unchanged on a machine that lacks it.

## Load your own fonts

For a brand font, a licensed font, or any family SuperDoc does not ship a fallback for, load the real file yourself. Use `@font-face`, a hosted stylesheet, or a font CDN. Anything the browser can resolve.

```css
@font-face {
font-family: 'Calibri';
src: url('/fonts/Carlito-Regular.woff2') format('woff2');
font-family: 'Inter';
src: url('/fonts/Inter-Regular.woff2') format('woff2');
font-display: swap;
}
```

For custom or licensed fonts, load the real file yourself. SuperDoc's built-in fallbacks cover only the fonts it ships and verifies.
SuperDoc's built-in fallbacks cover only the fonts it ships and verifies. For everything else, the real file is yours to provide.

To register a font through SuperDoc instead of CSS, pass it in `fonts.families` (which composes with `@superdoc-dev/fonts`). Give one file per face so bold and italic resolve correctly:

```js
new SuperDoc({
selector: '#editor',
document: 'contract.docx',
fonts: {
families: [
{
family: 'Brand Sans',
faces: [
{ source: '/fonts/BrandSans-Regular.woff2' },
{ source: '/fonts/BrandSans-Italic.woff2', style: 'italic' },
{ source: '/fonts/BrandSans-Bold.woff2', weight: 700 },
{ source: '/fonts/BrandSans-BoldItalic.woff2', weight: 700, style: 'italic' },
],
},
],
},
});
```

SuperDoc passes each `source` URL to the browser; it does not read the font file, so register one file per weight and style you use. `weight` defaults to 400 and `style` to `normal`. Use WOFF2 for the widest browser support.

Registering a family makes it render wherever a document uses it. It does not add the font to the toolbar; to offer it there, list it in `modules.toolbar.fonts` (see [Toolbar font list](#toolbar-font-list)).

## Toolbar font list

The built-in toolbar lists SuperDoc's defaults plus fonts used by the active document, sorted alphabetically. If you pass `modules.toolbar.fonts`, that custom list replaces the default list.
The built-in toolbar lists the fonts SuperDoc can render, plus the fonts the active document uses, sorted alphabetically. With no pack configured that is the baseline of one font per CSS generic (Arial, Times New Roman, Courier New); with the pack it is the full reviewed set, minus anything you curated out with `createSuperDocFonts`. If you pass `modules.toolbar.fonts`, that custom list replaces it entirely.

Each custom entry is a `{ label, key }` pair where `key` is the CSS `font-family` value:

Expand Down Expand Up @@ -61,6 +174,7 @@ editor.doc.format.apply({

- **Font name preserved, browser falls back.** SuperDoc keeps the DOCX font name. If no bundled fallback or loaded real font exists, the browser chooses its own fallback.
- **Custom toolbar list hides document fonts.** Passing `modules.toolbar.fonts` replaces the built-in list. Include every option you want users to pick.
- **Not every bundled family ships every weight and style.** A few substitutes are a single face. For a bold or italic run the substitute lacks, SuperDoc renders the faces it has and leaves the missing ones to the browser's fallback rather than synthesizing a face, so spacing stays predictable.
- **Office font licensing.** Calibri, Cambria, and Aptos are licensed Microsoft fonts. Self-hosting the real files requires a license.

## Where to next
Expand Down
4 changes: 3 additions & 1 deletion apps/docs/getting-started/frameworks/angular.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ Requires Angular 17.2+. `viewChild()` and `input()` are stable from Angular 19;
## Install

```bash
npm install superdoc
npm install superdoc @superdoc-dev/fonts
```

## Basic setup

```typescript
import { Component, ElementRef, viewChild, AfterViewInit, inject, DestroyRef } from '@angular/core';
import { SuperDoc } from 'superdoc';
import { superdocFonts } from '@superdoc-dev/fonts';
import 'superdoc/style.css';

@Component({
Expand All @@ -38,6 +39,7 @@ export class EditorComponent implements AfterViewInit {
selector: this.editorRef().nativeElement,
document: 'contract.docx',
documentMode: 'editing',
fonts: superdocFonts,
});
}
}
Expand Down
4 changes: 3 additions & 1 deletion apps/docs/getting-started/frameworks/laravel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ SuperDoc works with Laravel + Blade + Vite. Laravel serves the Blade template wi
```bash
composer install
npm install
npm install superdoc @superdoc-dev/fonts
```

## Vite config
Expand Down Expand Up @@ -55,9 +56,10 @@ Create a Blade view that loads the Vite-bundled script and mounts the editor:

```js resources/js/app.js
import { SuperDoc } from 'superdoc';
import { superdocFonts } from '@superdoc-dev/fonts';
import 'superdoc/style.css';

let superdoc = new SuperDoc({ selector: '#editor' });
let superdoc = new SuperDoc({ selector: '#editor', fonts: superdocFonts });

document.getElementById('file-input').addEventListener('change', (e) => {
const file = e.target.files[0];
Expand Down
4 changes: 3 additions & 1 deletion apps/docs/getting-started/frameworks/nextjs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SuperDoc works seamlessly with Next.js. The recommended approach is using `@supe
The React wrapper is the simplest way to integrate SuperDoc with Next.js:

```bash
npm install @superdoc-dev/react
npm install @superdoc-dev/react @superdoc-dev/fonts
```

### App Router (Next.js 13+)
Expand All @@ -20,12 +20,14 @@ npm install @superdoc-dev/react
'use client';

import { SuperDocEditor } from '@superdoc-dev/react';
import { superdocFonts } from '@superdoc-dev/fonts';
import '@superdoc-dev/react/style.css';

export default function EditorPage() {
return (
<SuperDocEditor
document="/sample.docx"
fonts={superdocFonts}
documentMode="editing"
onReady={() => console.log('Editor ready!')}
style={{ height: '100vh' }}
Expand Down
Loading
Loading