Export guarantees
What each export guarantees for international text, and which route to take where the grid does not.
@workspace/active-grid 11.0.0Status unknowncontent differs from the recorded release 11.0.0baseline ce081dc3
This guide is the package's docs/, included
unchanged from the package as it is in Foundation. Links to the package's
internal design documents keep their text.
What each export of @workspace/ guarantees for international
text, who owns that guarantee, and which route a consumer takes when the grid
does not guarantee what it needs. Decided in ADR-055; the PDF fonts, the
workbook direction and the error model are ADR-054; the formats are
export.. active-charts carries the same tiers in its own
words for its own formats (its SVG and PNG exports have no grid
counterpart).
The tiers#
| Tier | Owner | Formats | Script guarantee | Limitation |
|---|---|---|---|---|
| L0 | package | CSV, clipboard TSV, JSON, XLSX values | every script, including supplementary-plane characters and ZWJ sequences: the logical Unicode text, no direction or isolation controls; XLSX numbers and dates stay typed | data only: no font. An XLSX also carries its sheet direction (Workbook.), never a font or a style |
| L1 | package + browser | the live grid | what the browser shapes (joining, reordering, marks, bidi) with the fonts the page loads | a glyph no loaded font has is the browser's fallback |
| L2 | spreadsheet app | the XLSX view | an rtl grid's workbook opens right to left | how the sheet looks (fonts, shaping) is the spreadsheet application's |
| L3a | package | Latin, Greek, Cyrillic, Armenian, Georgian, Han (BMP), Kana, Hangul (precomposed), each cell fully covered by one embedded TrueType family; WinAnsi without a set | refused with a code, never drawn broken: complex and right-to-left scripts, combining marks, joiners and selectors, emoji, supplementary-plane characters, uncovered cells | |
| L3b-raster | host | a PDF the host builds with an image of the grid | everything the host's browser shapes | the table is a picture: its text is not selectable or searchable |
| L3b-print | host | a PDF the host's browser prints | everything the host's browser shapes, as selectable text | a host recipe, not a package route; valid only as far as the host has verified it with its own browser and fonts |
No tier promises more than it says.
Known gap (ROADMAP R-64). In presentation mode (always for the clipboard,
and CSV, JSON or XLSX with {mode: 'presentation'}), a number, percentage or
amount formatted for some locales still carries the bidi marks Intl inserts
(for example U+200F in a he- amount, U+061C in an ar- number). Data
mode, the default, is L0 as stated.
Per script#
| Script or data | Live (L1) | CSV, TSV, JSON (L0) | XLSX (L0 values, rtl view) | PDF, no set | PDF, host set that covers it |
|---|---|---|---|---|---|
| Latin | browser | package | package | Helvetica (WinAnsi) | drawn (L3a) |
| Cyrillic, Greek | browser | package | package | 'EXPORT_ | drawn (L3a) |
| Han, Kana, Hangul (BMP) | browser | package | package | 'EXPORT_ | drawn (L3a) |
| Arabic, Hebrew | browser | package | package | 'EXPORT_ | 'EXPORT_; host L3b |
| Devanagari and other Indic | browser | package | package | 'EXPORT_ | 'EXPORT_; host L3b |
| Combining mark in the text | browser | package | package | 'EXPORT_ | 'EXPORT_ (normalizable says whether NFC would help) |
| Emoji, ZWJ sequences | browser | package | package | 'EXPORT_ | 'EXPORT_; host L3b |
| Supplementary plane | browser | package | package | 'EXPORT_ | 'EXPORT_; host L3b |
| A script no family covers | browser | package | package | 'EXPORT_ | 'EXPORT_ |
One refused cell refuses the whole PDF: a table is one document. Thai, Lao, Khmer, Myanmar, Tibetan, Mongolian, Syriac, Thaana and N'Ko are refused like Arabic. A refusal names one code point and, for a script, its name; it never carries the cell text.
Which route#
| The consumer needs | Route |
|---|---|
| the data, for a program or a spreadsheet | CSV, TSV or JSON (L0); XLSX for typed cells and the sheet direction |
| a PDF with selectable text in a supported script | PDF (L3a) with an export font set that covers every cell |
| a PDF with Arabic, Hebrew, Indic, emoji or other shaped text | a host route: L3b-raster (image in a PDF) or L3b-print (browser print) |
The grid never hands over to a host route by itself: it refuses, and the
host decides. A host that knows its data is complex-script can leave PDF out
of export..
Errors#
A Grid code separates four kinds of failure; none carries the
exported text (at most one code point and a script name, the host's family
name, the detected font format, the missing module, the families tried).
| Kind | Codes | What the host does |
|---|---|---|
| a refusal of the text (L3a) | 'EXPORT_, 'EXPORT_, 'EXPORT_ | supply or extend fonts, or take a host route |
| a font set the export cannot use | 'EXPORT_, 'EXPORT_, 'EXPORT_ | fix the configuration |
| a font that did not arrive | 'EXPORT_ (the host loader's error is the cause) | retry; the next export calls the loader again |
| the export itself could not run | 'EXPORT_ (missing) | install the module, or offer no such export |
The codes and their meaning are active-charts' where the failure is the same; the charts add two of their own (a capture that failed, a CSV without rows).
Configuring export fonts in a host#
The host owns its fonts. A typical enterprise set is a corporate Latin family first and a CJK family as its fallback, with faces loaded only when a PDF needs them:
const load = (path: string) => () => fetch(path, {signal: AbortSignal.timeout(15_000)}).then((response) => { if (!response.ok) throw new Error(`font ${response.status}`); return response.arrayBuffer(); });
const fonts: GridExportFonts = { families: [ { family: 'Corporate Sans', faces: [ {source: load('/fonts/corporate-sans-regular.ttf')}, {source: load('/fonts/corporate-sans-bold.ttf'), weight: 700}, {source: load('/fonts/corporate-sans-italic.ttf'), style: 'italic'}, ], }, { family: 'Example CJK Sans', faces: [{source: load('/fonts/example-cjk-sans-regular.ttf')}], }, ],};
<GridDefaults export={{fonts}}>{children}</GridDefaults>;The PDF needs static TrueType (
glyf) faces; WOFF, WOFF2, CFF-flavoured OpenType and variable fonts cannot be embedded.The header row and group rows are bold: give a bold face, or they draw in the regular one.
Keep the object stable (a module constant): each face object is loaded once, and concurrent exports share the load.
The same object serves active-charts as its export font set.
Host routes (L3b)#
These are recipes for a host. The grid provides neither.
L3b-raster: a browser image in a PDF#
Render the grid in a browser or headless browser that has the fonts the cells need.
Wait for
document.and for the rows to be loaded.fonts. ready Take a screenshot of the grid element at a scale for print (for example 2x device pixels).
Place the image in the host's PDF.
The browser shapes every script it has fonts for. The cells are pixels: they cannot be selected, searched or read by assistive technology from the PDF, and a table longer than a page needs the host to paginate the capture.
L3b-print: the browser prints a PDF#
Launch a browser the host controls (for example headless Chromium) with the fonts installed or loaded.
Render a page with the table (the live grid, or a print layout the host builds from the same rows).
Wait for
document..fonts. ready Print with the browser's PDF mechanism (in Chromium,
page.).pdf() Verify before relying on it: extract the text from the PDF and render it back to an image, for each script the host serves.
The browser shapes the text and writes it as selectable text with a Unicode mapping. This was verified once in a research harness (Chromium 153 on Windows with the operating system's fonts): Arabic, Hebrew and Devanagari came back from the PDF in logical order, and the rendered PDF showed joined Arabic, right-to-left Hebrew and formed Devanagari conjuncts. That is evidence the recipe works, not a guarantee for another browser version, operating system or font set. A virtualised grid draws only its visible rows, so a print route renders a layout of all rows, not the scrolled viewport.