# Inspect DOCX font resolution

Inspect requested and rendered families, then load an explicit Arial to Carlito substitution. The included Latin regular face is an evaluation input, not a complete font collection.

## Run

Requires Node.js 22 or newer. `npm install` then `npm run dev`. Open the localhost URL Vite prints. To check and build the project, run `npm run check` and `npm run build`.

Browser editor: 2.30.0. Direct dependencies are pinned in package.json.

## Try it

The report distinguishes logical and render families. A loaded explicit mapping is a rendering choice, not proof of a faithful font match. Export keeps the logical font name.

The included public DOCX is the original homepage sample. It is not your uploaded document or the current edited demo state. Export a separate DOCX, then reopen it in Word or SuperDoc. Built-in and custom review examples use Suggesting mode. The application form fills text fields directly.

## Files

- package.json
- examples/fonts/main.ts
- examples/fonts/index.html
- examples/shared/browser.ts
- examples/shared/style.css
- vite.config.ts
- tsconfig.json
- FONT-NOTES.md
- public/SuperDoc.docx

## Continue

[Current documentation](https://docs.superdoc.dev/editor/fonts/)
[Try the homepage demo](https://www.superdoc.dev/?doc=ai#editor)
[SuperDoc licensing](https://docs.superdoc.dev/editor/license/)

The shared browser.ts file contains the export button, status messages and CSS imports. Document setup and task logic are in main.ts.

## Complete source

### package.json

```json
{
  "name": "superdoc-fonts",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "engines": {
    "node": ">=22"
  },
  "scripts": {
    "dev": "vite --open /examples/fonts/",
    "build": "vite build",
    "check": "tsc --noEmit"
  },
  "dependencies": {
    "superdoc": "2.30.0",
    "fflate": "0.8.3"
  },
  "devDependencies": {
    "vite": "6.4.1",
    "typescript": "5.8.3"
  }
}
```

### examples/fonts/main.ts

```javascript
import { SuperDoc } from 'superdoc';
import { unzipSync, strFromU8 } from 'fflate';
import { element, enableExport, report, showError } from '../shared/browser';

const output = element('report');
const substitute = element<HTMLButtonElement>('substitute');
const reset = element<HTMLButtonElement>('reset');
const superdoc = new SuperDoc({
  selector: '#editor',
  document: '/SuperDoc.docx',
  ui: { toolbar: { container: '#toolbar', responsiveTo: 'container' } },
  fonts: {
    families: [{ family: 'Carlito', faces: [{ source: '/fonts/Carlito-latin.woff2', weight: 400, style: 'normal' }] }],
  },
  onReady: ({ superdoc: ready }) => {
    enableExport(ready);
    const refresh = () => {
      const rows = ready.fonts.getReport();
      element('raw-report').textContent = JSON.stringify(rows, null, 2);
      output.replaceChildren(...rows.map(font => {
        const item = document.createElement('section');
        item.style.cssText = 'padding:12px 0;border-top:1px solid #d1d5db';
        const title = document.createElement('strong');
        title.textContent = `${font.logicalFamily} → ${font.physicalFamily}`;
        const detail = document.createElement('p');
        detail.textContent = `${font.face?.weight ?? '400'} ${font.face?.style ?? 'normal'} · ${font.loadStatus} · ${font.reason}`;
        const exported = document.createElement('p');
        exported.textContent = `DOCX export: ${font.exportFamily}. API missing flag: ${font.missing ? 'yes' : 'no'}.`;
        item.append(title, detail, exported);
        return item;
      }));
    };
    const stop = ready.fonts.onReport(refresh);
    refresh();
    substitute.disabled = reset.disabled = false;
    const check = element<HTMLButtonElement>('check-export');
    check.disabled = false;
    check.addEventListener('click', async () => {
      check.disabled = true;
      const result = element('export-check');
      result.textContent = 'Checking exported DOCX…';
      try {
        const blob = await ready.export({ exportType: ['docx'], triggerDownload: false });
        const bytes = new Uint8Array(await blob.arrayBuffer());
        const parts = unzipSync(bytes);
        const xml = ['word/document.xml', 'word/styles.xml'].map(name => strFromU8(parts[name])).join('');
        if (!xml.includes('Arial') || xml.includes('Carlito')) throw new Error('Unexpected exported font names. Inspect the Word file.');
        const sha256 = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bytes))).map(n => n.toString(16).padStart(2, '0')).join('');
        result.textContent = 'Export checked: Arial retained. Carlito was not written into document or style font names.';
        result.dataset.sha256 = sha256;
        result.dataset.bytes = String(bytes.byteLength);
        const download = element<HTMLAnchorElement>('checked-download');
        const reader = new FileReader();
        download.href = await new Promise<string>((resolve, reject) => {
          reader.onload = () => resolve(String(reader.result));
          reader.onerror = () => reject(new Error('Could not prepare the checked DOCX download.'));
          reader.readAsDataURL(blob);
        });
        download.hidden = false;
      } catch (error) { console.error('Font export diagnostic', error instanceof Error ? error.stack : String(error)); result.textContent = (error instanceof Error ? error.message : String(error)) || 'Export failed. Inspect the console diagnostic before retrying.'; }
      finally { check.disabled = false; }
    });
    substitute.addEventListener('click', async () => {
      substitute.disabled = true;
      try {
        ready.fonts.map({ Arial: 'Carlito' });
        await ready.fonts.preload(['Arial']);
        refresh();
        report('Carlito is registered for Arial. Check the load status and exported DOCX font name.');
      } catch (error) { showError(error); }
      finally { substitute.disabled = false; }
    });
    reset.addEventListener('click', () => { ready.fonts.unmap('Arial'); refresh(); report('Runtime substitution removed.'); });
    window.addEventListener('pagehide', () => { stop(); ready.destroy(); }, { once: true });
  },
  onContentError: ({ error }) => showError(error),
  onException: ({ error }) => showError(error),
});
```

### examples/fonts/index.html

```html
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Inspect DOCX font resolution | SuperDoc example</title>
<meta name="description" content="Inspect the fonts a Word file requests, load Carlito and compare an explicit rendering substitution.">
<link rel="canonical" href="https://www.superdoc.dev/examples/fonts/index.html">
<link rel="alternate" type="text/markdown" href="https://www.superdoc.dev/examples/fonts.md">
</head><body>
<header><h1>Inspect DOCX font resolution</h1><p>The public sample keeps its Word font names. Inspect the report, then compare an explicit Arial to Carlito rendering substitution. Export retains the logical DOCX family.</p>
<div class="actions"><button id="substitute" type="button" disabled>Use Carlito for Arial</button><button id="reset" type="button" disabled>Remove substitution</button><button id="check-export" type="button" disabled>Check exported font names</button><button id="export" type="button" disabled>Export DOCX</button><output id="status" role="status">Opening document…</output></div>
<p><a href="https://docs.superdoc.dev/editor/fonts/">Current font documentation</a> · <a href="https://www.superdoc.dev/examples/fonts.zip">Download this project</a> · <a href="https://www.superdoc.dev/examples/fonts.md">Read the complete source</a></p></header>
<div id="toolbar"></div><main><aside aria-label="Document font report"><h2>Requested and rendered fonts</h2><p>This report includes declared document families. A loaded substitute does not establish a faithful match.</p><div id="report" aria-live="polite"></div><output id="export-check" role="status"></output><p><a id="checked-download" download="font-checked.docx" hidden>Download checked DOCX</a></p><details><summary>Full font report</summary><pre id="raw-report" style="white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px"></pre></details></aside><div id="editor"></div></main>
<script type="module" src="./main.ts"></script></body></html>```

### examples/shared/browser.ts

```javascript
import type { SuperDoc } from 'superdoc';
import 'superdoc/style.css';
import './style.css';

export function element<T extends HTMLElement>(id: string): T {
  const node = document.getElementById(id);
  if (!node) throw new Error(`Missing element: ${id}`);
  return node as T;
}

const status = element<HTMLOutputElement>('status');
const exportButton = element<HTMLButtonElement>('export');
export function report(message: string) { status.value = message; }
export function showError(error: unknown) {
  report(error instanceof Error ? error.message : String(error));
}

export function enableExport(superdoc: SuperDoc) {
  report('Ready. Edit the document, then export a copy.');
  exportButton.disabled = false;
  exportButton.addEventListener('click', async () => {
    exportButton.disabled = true;
    try {
      await superdoc.export({ exportType: ['docx'], exportedName: 'edited-example', triggerDownload: true });
      report('DOCX exported. Open the saved file to check your changes.');
    } catch (error) { showError(error); }
    finally { exportButton.disabled = false; }
  });
}
```

### examples/shared/style.css

```css
:root { font-family: system-ui, sans-serif; color: #242424; background: #f5f5f5; }
#editor { --sd-caret-color: #242424; }
input, textarea, [contenteditable="true"] { caret-color: #242424; }
* { box-sizing: border-box; }
body { margin: 0; }
header { padding: 1rem; border-bottom: 1px solid #ddd; background: white; }
h1 { font-size: 1.2rem; margin: 0 0 .5rem; }
p { line-height: 1.5; }
button, input { font: inherit; padding: .5rem .7rem; min-height: 44px; }
button { cursor: pointer; }
button:disabled { cursor: wait; }
label { display: grid; gap: .3rem; margin-bottom: .7rem; }
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: .7rem; }
main { display: grid; grid-template-columns: minmax(0, 1fr) 300px; }
main.solo { display: block; }
#editor { height: 76vh; overflow: auto; min-width: 0; }
aside { padding: 1rem; background: white; overflow: auto; max-height: 76vh; }
ul { list-style: none; padding: 0; }
li { border-bottom: 1px solid #ddd; padding: .7rem 0; }
li button { margin: .2rem; }
input { width: 100%; }
@media (max-width: 760px) { main { grid-template-columns: minmax(0, 1fr); } aside { max-height: none; } }
```

### vite.config.ts

```javascript
import { defineConfig } from 'vite';
import { resolve } from 'node:path';
export default defineConfig({
  optimizeDeps: { exclude: ['@superdoc/docx-engine'], include: ['vue'] },
  resolve: { dedupe: ['vue'] },
  build: { rollupOptions: { input: resolve('examples/fonts/index.html') } },
});
```

### tsconfig.json

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": [
      "ES2022",
      "DOM",
      "DOM.Iterable"
    ],
    "strict": true,
    "skipLibCheck": true,
    "noEmit": true,
    "types": []
  },
  "include": [
    "examples/**/*.ts"
  ]
}
```

### FONT-NOTES.md

```javascript
The Carlito regular Latin WOFF2 is an OFL evaluation face from Google Fonts. See public/fonts/OFL-Carlito.txt. Supply every weight, style and script your documents need. Rendering substitution preserves the logical DOCX family; it does not embed a replacement in the exported file.
```

[Download the project](https://www.superdoc.dev/examples/fonts.zip)
[Download the public sample](https://www.superdoc.dev/SuperDoc.docx)
