js.documents 1.60.2 → 1.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -404,7 +404,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
404
404
  - **A `DocumentPackage` returned via `onDocument`/`ConversionResult.package` is a snapshot from that one conversion pass, not a live view** — its `layout` correlates with its `content` only as of the exact read+layout that produced it (`document-schema.js`'s own `DocumentPackageSchema` doc comment), so if a caller mutates the returned `content` afterwards, the `layout` sitting alongside it silently goes stale; nothing in this package (or `document-schema.js`) detects or rejects that.
405
405
  - **Building the six cross-format bridges surfaced two real, previously-undiscovered gaps in existing `populateParagraph` write paths, both now fixed.** `buildDocxPackage`'s `populateParagraph` (`src/edit/docx/content.ts`) never wrote a paragraph's own `list` membership back (`ContentParagraph.list`, docx's flat `numId`/`level` model) — only read, never written, since no existing caller had ever round-tripped a list-bearing paragraph through it. `buildOdtPackage`'s `populateParagraph` (`src/edit/odt/content.ts`) never wrote a paragraph's own `styleId` back at all (`readOdtContent`/`readOdfParagraph` in `odf.js` reads it unconditionally from `text:style-name`, but nothing on the write side ever set that attribute). Both are now fixed: `DocxParagraph.list` is set unconditionally alongside `styleId`/`alignment`, matching that function's own existing pattern; `OdtParagraph.styleId` is set conditionally alongside `alignment`, matching odt's own local convention. `buildOdtPackage` additionally gained `appendBlocks`/`appendListRun` (`src/edit/odt/content.ts`) — ODF has no flat per-paragraph list property to set the way docx does, so a run of consecutive `ContentParagraph`s sharing `list.numId` is grouped and written as a real, potentially multi-level `text:list`/`text:list-item` tree via `OdtList`/`OdtListItem`, the structural inverse of `odf.js`'s own list-reading (a fresh `text:list` per `numId` change, one level of nesting per `list.level` step, descending only one level at a time since ODF can only open a nested list from inside an existing item). Both gaps were invisible before this task specifically because nothing had previously round-tripped a list-bearing paragraph or a styled paragraph through `docx ⇄ odt` at all — the PDF-pivot conversions never exercised `buildDocxPackage`/`buildOdtPackage` on content read back from the OTHER format.
406
406
  - **A table shape inside an odp slide does not survive `odpToPptx`.** `buildPptxPackage`'s `appendShape` (`src/edit/pptx/content.ts`) silently drops any non-paragraph block found inside a shape's own text-box loop — a scope choice whose own comment ("PDF-reconstructed shapes never mix kinds") assumed its only caller was the PDF-reconstruction path, where that is true. `odpToPptx` is a second, non-PDF-reconstructed caller for which it is not: a real odp `draw:frame` containing a `table:table` directly (not inside a text box) reads as a `ContentShape` with a `'table'` block, and that block is silently dropped, leaving an empty pptx text box where the table was. Everything else on the same slide — a rotated shape, grouped shapes, an image, speaker notes — survives correctly (see `src/convert/bridges.test.ts`'s own dedicated fidelity-gap test, which proves both halves against the existing `minimalOdpBytes()` fixture). A real, tracked, bounded gap, not a silent one: closing it means teaching `buildPptxPackage`/`buildOdpPackage` to write a real table into a slide shape, a materially larger feature than this bridge's own scope.
407
- - **The `ods ⇄ xlsx` bridge inherits several real, format-boundary fidelity limits from `ooxml.js`'s brand-new `readXlsxContent`/`buildXlsxPackage`, on top of its own pivot-copy design.** xlsx has no `percentage`/`currency` cell type of its own (both are a plain numeric cell plus a number-format style neither this reader nor this writer interprets) — an ods `percentage`/`currency` cell survives the `odsToXlsx` hop with its numeric *value* intact but downgrades to a plain `number` *kind*, permanently (currency's own currency code is dropped outright). xlsx also has only one rare `t="d"` cell type covering BOTH date and time — an ods `time` cell survives as a `date`-kind cell carrying its original value string verbatim, but mislabelled; an ods `date` cell is unaffected (it was already the kind xlsx's own `t="d"` maps onto). A formula (`table:formula`/`<f>`) is carried completely verbatim in both directions — never parsed, translated, or evaluated by either this package's own reader or writer — but a REAL spreadsheet application does evaluate a workbook's own `<f>`/`table:formula` on open: confirmed against genuine LibreOffice 26.2, an ods formula authored in OpenFormula syntax (`of:=[.B2]*2`) becomes a formula ERROR (`Err:510`) when the bridged xlsx is opened in real Calc, even though the formula's own cached value is still present and correctly readable via `readXlsxContent` — going the other way is less fragile in practice only because a genuine xlsx formula (bare Excel A1 syntax, e.g. `B2*2`) happens to still parse under LibreOffice's own more lenient, backward-compatible ODF formula grammar, not because of anything this bridge does differently in either direction. Column widths survive the `odsToXlsx` hop within roughly a pixel of rounding tolerance (see `src/convert/bridges.test.ts`'s own `COLUMN_WIDTH_TOLERANCE_PT`) but are then dropped entirely on the return `xlsxToOds` hop — not a character-width-unit rounding loss, but `buildOdsPackage` not writing `ContentSheetColumn.widthPt` at all, a pre-existing, already-documented gap in that file's own module comment, unrelated to and unfixed by this bridge. A boolean cell written by `buildXlsxPackage` renders as a raw `1`/`0` rather than `TRUE`/`FALSE` when opened in real Excel/Calc, since that writer's own genuinely-minimal `xl/styles.xml` (one default cell format, no boolean-specific number format) has nothing else to apply — the underlying `{ kind: 'boolean', value: true }` is still read back correctly by `readXlsxContent` regardless; this is a real-application *display* gap, not a data-fidelity one. `readXlsxContent`'s own cell.value.kind never produces `'error'` from an odf.js-sourced document at all, for a structural reason rather than a bug: ODF's `office:value-type` enumeration has no `error` member, so `OdsCell.value`'s own write-side choice for a `kind: 'error'` cell is to write it as a genuine, non-empty `office:string-value` carrying the error's own text — an `xlsxToOds` → `odsToXlsx` round trip of a genuine xlsx `t="e"` error cell therefore turns it into a plain `string` cell carrying the identical text; the message survives, the `error` semantic does not.
407
+ - **The `ods ⇄ xlsx` bridge inherits several real, format-boundary fidelity limits from `ooxml.js`'s brand-new `readXlsxContent`/`buildXlsxPackage`, on top of its own pivot-copy design.** xlsx has no `percentage`/`currency` cell type of its own (both are a plain numeric cell plus a number-format style neither this reader nor this writer interprets) — an ods `percentage`/`currency` cell survives the `odsToXlsx` hop with its numeric *value* intact but downgrades to a plain `number` *kind*, permanently (currency's own currency code is dropped outright). xlsx also has only one rare `t="d"` cell type covering BOTH date and time — an ods `time` cell survives as a `date`-kind cell carrying its original value string verbatim, but mislabelled; an ods `date` cell is unaffected (it was already the kind xlsx's own `t="d"` maps onto). A formula (`table:formula`/`<f>`) is carried completely verbatim in both directions — never parsed, translated, or evaluated by either this package's own reader or writer — but a REAL spreadsheet application does evaluate a workbook's own `<f>`/`table:formula` on open: confirmed against genuine LibreOffice 26.2, an ods formula authored in OpenFormula syntax (`of:=[.B2]*2`) becomes a formula ERROR (`Err:510`) when the bridged xlsx is opened in real Calc, even though the formula's own cached value is still present and correctly readable via `readXlsxContent` — going the other way is less fragile in practice only because a genuine xlsx formula (bare Excel A1 syntax, e.g. `B2*2`) happens to still parse under LibreOffice's own more lenient, backward-compatible ODF formula grammar, not because of anything this bridge does differently in either direction. Column widths survive the `odsToXlsx` hop within roughly a pixel of rounding tolerance (see `src/convert/bridges.test.ts`'s own `COLUMN_WIDTH_TOLERANCE_PT`) but are then dropped entirely on the return `xlsxToOds` hop — not a character-width-unit rounding loss, but `buildOdsPackage` not writing `ContentSheetColumn.widthPt` at all, a pre-existing, already-documented gap in that file's own module comment, unrelated to and unfixed by this bridge. A boolean cell written by `buildXlsxPackage` renders as a raw `1`/`0` rather than `TRUE`/`FALSE` when opened in real Excel/Calc, since that writer's own genuinely-minimal `xl/styles.xml` (one default cell format, no boolean-specific number format) has nothing else to apply — the underlying `{ kind: 'boolean', value: true }` is still read back correctly by `readXlsxContent` regardless; this is a real-application *display* gap, not a data-fidelity one. `readXlsxContent`'s own cell.value.kind never produces `'error'` from an odf.js-sourced document at all, for a structural reason rather than a bug, confirmed permanent rather than an open question: ODF's `office:value-type` enumeration simply has no `error` member — verified against real LibreOffice 26.2 output, a genuine `#DIV/0!` formula cell serializes as `office:value-type="string"` with an EMPTY `office:string-value`, the error text surviving only in the cell's own `text:p`/displayText, never in any `office:value-type`-driven wire value. The one place the string `"error"` appears anywhere in the format is LibreOffice's own `calcext:value-type="error"` extension attribute, a private, unstable vendor namespace outside the OASIS ODF 1.3 spec — the identical category of escape hatch `odf.js`'s own `typed/shared/table.ts` already declined for `loext:graphic-properties/@draw:fill-color` over the standard `fo:background-color`, and declined here for the same reason: this package's own convention is OASIS-spec-grounded, not vendor-extension-chasing, and a private namespace a future LibreOffice release can rename or drop is not a foundation to build a public API's data fidelity on. `OdsCell.value`'s own write-side choice for a `kind: 'error'` cell is consequently to write it as a genuine, non-empty `office:string-value` carrying the error's own text — an `xlsxToOds` → `odsToXlsx` round trip of a genuine xlsx `t="e"` error cell therefore turns it into a plain `string` cell carrying the identical text; the message survives, the `error` semantic does not, and no mechanism inside or outside the ODF spec can preserve it. This is a permanent format-boundary limitation, not a gap either `odf.js` or this package could close by implementing something — there is nothing standards-based left to implement.
408
408
  - **`odpToPdf`/`pdfToOdp` needed zero new layout code.** `readOdpContent` (`src/odf/odp/read.ts`) produces the identical `presentation` `ContentDocument` shape `readPptxContent` does, so it feeds `convertPresentationToLayout` unmodified — including the existing hidden-annotation speaker-notes mechanism below, which carries odp's `presentation:notes` through to the PDF with no new notes-handling code at all; `pdfToOdp` reuses `reconstructPresentation` unmodified too, the same architectural bet `pdfToOdt` already proved for `reconstructWordprocessing`. The genuinely new work for the reverse direction was the live-view editor itself (`src/edit/odp/*`) — see Architecture above.
409
409
  - **`OdpShape.rotationDeg` writes a real `draw:transform`, built on `odf.js`'s own transform machinery.** It is the write-side inverse of `odf.js`'s `resolveOdfShapeGeometry` (`typed/shared/transform.ts`), built on that module's own exported `applyOdfTransform` rather than a hand-rolled rotation matrix, so it inherits that module's own empirically-verified rotate/translate composition order and sign convention by construction. Unlike `PptxShape` (see the `colSpan`/`rowSpan` gotcha below, which pptx still has and odp does not), `buildOdpPackage` writes a rotated shape's rotation back correctly — verified both by this package's own tests and by opening a fresh, editor-built `.odp` in actual LibreOffice.
410
410
  - **`readPdf` tracks general vector paths, not just axis-aligned rectangles — pdf-codec's own capability, with a direct consequence for this package's `pdfToOds`/`reconstructDrawing`.** A stroked-and-filled rect, any ellipse, and any plain line each come back from a real PDF round trip as a generic `LayoutPath` rather than their original kind (see pdf-codec's own `interpret.ts` gotcha for the exact fast-path boundary and the ISO 32000-1 operators involved). This is the shared infrastructure both `pdfToOds` and `reconstructDrawing` need; both now use it. A direct, practical consequence for `pdfToOds`: `readPdf` never reconstructs a `'line'` kind item at all, so a gridline written by `sheets.ts`'s own `renderGridlines` always comes back from a real PDF round trip as a generic, single-subpath, single-line-segment, stroke-only `LayoutPath` — `reconstructSpreadsheet`'s own gridline-lattice detection accepts both shapes (a genuine `LayoutLine` item and this stroked-single-segment `LayoutPath` shape) for exactly this reason.
@@ -445,7 +445,7 @@ To run a single test file: `pnpm vitest run src/path/to/file.test.ts`.
445
445
  - **A token element's (`mi`/`mn`/`mo`/`mtext`) own box height comes from the font's nominal design ascent/descent, not a tight per-glyph ink bounding box.** `src/mathml/` never parses glyph outlines itself (no `glyf`/CFF charstring geometry extraction anywhere in this package — pdf-codec's own font parsing doesn't expose per-glyph ink bounds either, see its README), so every token run shares one uniform vertical extent regardless of which characters it actually contains. Accurate enough for box-model layout (spacing, baseline alignment, page placement) but not pixel-tight around an unusually tall or shallow glyph.
446
446
  - **The MathML operator dictionary (`src/mathml/operators.ts`) is a deliberately bounded ~60-entry table, not the MathML3 specification's own multi-thousand-entry, form-dependent (prefix/infix/postfix) one.** It covers arithmetic, relational, set/logic, calculus big-operators, fences, and punctuation — the operators real formulas overwhelmingly use — with one entry per character regardless of which position it appears in, falling back to a single sane infix-shaped default (thick-space spacing, no stretch/largeop/movablelimits) for anything else.
447
447
  - **`mover`/`munder`/`munderover` centre an over/under-script geometrically over the wider of the two boxes, not at the base glyph's own font-declared accent-attachment point (`MathTopAccentAttachment`, which the embedded font's `MathGlyphInfo` subtable DOES carry and this package DOES parse — see the CFF-embedding gotcha above — just not consumed here).** Visually correct for the common case of a single-character base (geometric centre ≈ optical centre for a roughly symmetric glyph); measurably different only for a multi-character or asymmetric base under a genuine `accent="true"` mark. A real, bounded simplification, not a data gap — the metric this would need is already being parsed for a different purpose.
448
- - **Greek `mathvariant` mapping covers the plain alphabet plus nabla (∇) and partial differential (∂), not the OpenType/Unicode Greek "symbol variant" set** (epsilon/theta/kappa/phi/rho/pi symbol glyphs`ϵ`/`ϑ`/`ϰ`/`ϕ`/`ϱ`/`ϖ` styled to e.g. bold). Latin letters, digits, and the two named symbols above are fully covered, generated directly from Unicode's own `UnicodeData.txt` (see `src/mathml/variant.ts`'s own generation note) rather than transcribed by hand.
448
+ - **Greek `mathvariant` mapping covers the plain alphabet, nabla (∇), partial differential (∂), and the six OpenType/Unicode Greek "symbol variant" glyphs** (lunate epsilon/theta/kappa/phi/rho/pi symbols U+03F5/U+03D1/U+03F0/U+03D5/U+03F1/U+03D6 — styled to bold, italic, bold-italic, bold-sans-serif, and sans-serif-bold-italic; Unicode never assigned symbol-variant glyphs for plain sans-serif, script, fraktur, or double-struck). Every entry is generated directly from Unicode's own `UnicodeData.txt` (see `src/mathml/variant.ts`'s own generation note) rather than transcribed by hand.
449
449
  - **Embedded-formula detection inside odt/odp is genuinely new work with no `odf.js`-side equivalent (`readDrawFrameContent` doesn't recognise a `draw:object`-bearing `draw:frame` at all yet — see the `src/odf/` architecture entry above), and each format's own detection carries its own real, bounded scope narrowing.** For **odt** (`src/odf/odt/read.ts`): only a `draw:frame` that is a *direct child of `office:text`* is detected — a formula anchored inline inside a paragraph's own run content, or nested inside a `draw:g` group, is not. Detected formulas are appended to the **end** of the section's own `blocks` array, in the order their frames appear in the document, not interleaved at their true original position among the paragraphs/tables `odf.js`'s own reader already produced — true positional interleaving would need per-element block-count bookkeeping this adapter doesn't have (a `text:list`, for instance, unwraps into many `ContentParagraph` blocks from one raw XML element, so "one raw child = one block" doesn't hold in general). For **odp** (`src/odf/odp/read.ts`): only a top-level `draw:frame` on a `draw:page` with no `draw:g` sibling at all is detected (a slide containing any group is skipped entirely for formula detection, to avoid mismatching a formula onto the wrong shape) — but where it IS detected, position is exact, not appended: `odf.js`'s own `walkDrawShapes` already produces exactly one `ContentShape` per top-level `draw:frame` in document order, so the Nth frame maps precisely onto `shapes[N]`. **ods embedded-formula detection is not implemented at all** — `odf.js`'s `readOds` has no existing floating-drawing/anchor-resolution mechanism (`ContentSheetImage`/`ContentSheet.embeddedObjects` are both already-known, pre-existing unpopulated gaps this task does not newly create — see the `ContentSheetCellSchema` gotcha above for the sibling gap on the write side), so there is no `readDrawFrame`-equivalent entry point to hook a formula-frame scan onto the way odt/odp have; `src/layout/sheets.ts` accordingly has no formula-handling branch at all, with a comment marking why.
450
450
  - **A formula that isn't rendered as real MathML (an odm chapter's own embedded formula, or any formula crossing the `odtToDocx`/`docxToOdt`/`odpToPptx`/`pptxToOdp` bridges) survives only as its own plain-text placeholder** — the formula's StarMath annotation if it had one, or the literal `[formula]` otherwise (see `src/odf/formula/placeholder.ts`). `odmToPdf`'s own per-chapter `readOdtContent` call discards that chapter's own `formulas` map entirely (re-keying every formula's `sourcePath` against the final combined document's own renumbered block indices is a materially larger undertaking than this task's own scope, and `.odm` has no confirmed real-world test fixture to validate it against regardless — see the `odmToPdf` gotcha below). `buildDocxPackage` has no MathML-writing path of its own (OOXML's own math markup, OMML, is a different vocabulary this package does not write), so none of the ten cross-format bridges ever consult a formula's real MathML either, even when bridging between two formats that both, individually, support real formula rendering elsewhere in this package — for the two markdown bridges specifically, there is nothing to consult regardless, since markdown has no formula/embedded-object construct of its own for `readMarkdownContent` to detect in the first place.
451
451
  - **`sourcePath` traces a `LayoutItem` back to the `ContentDocument` node it came from, but only within one read+layout pass.** `ooxml.js`'s `readDocx`/`readPptx` stamp every `ContentRun`/`ContentImageBlock`/`ContentTable`/`ContentShape` with a positional path (`sections[0].blocks[2].runs[1]`, `slides[1].shapes[3].blocks[0]`); `convertWordprocessingToLayout`/`convertPresentationToLayout` copy that same string onto whichever `LayoutText`/`LayoutImage`/`LayoutLink`/`LayoutRect` item(s) it produces, so a positioned PDF-side item can be traced back to its semantic origin. When line-wrapping splits one run's word across a run boundary, every resulting fragment gets its own run's path (not a shared or merged one); when a single run is emergency-split across several lines or pages, every resulting fragment keeps that same one run's path unchanged. A table cell's background `LayoutRect` is attributed to its containing table's own `sourcePath`, since `ContentTableCell` carries none of its own. This is **not** an edit-tracking or incremental-relayout mechanism — the path is only valid against the exact `ContentDocument`/`Package` it was assigned from in that one read; editing the document, re-reading it, or reordering its blocks invalidates every previously-captured path, and nothing here recomputes or diffs paths across two versions of a document.
@@ -129,6 +129,11 @@ var OdsCell = class {
129
129
  require_xml_edit.setAttr(this.node, DATE_VALUE_ATTR, value.value);
130
130
  this.displayText = value.value;
131
131
  break;
132
+ case "dateTime":
133
+ require_xml_edit.setAttr(this.node, VALUE_TYPE_ATTR, "date");
134
+ require_xml_edit.setAttr(this.node, DATE_VALUE_ATTR, value.value);
135
+ this.displayText = value.value;
136
+ break;
132
137
  case "time":
133
138
  require_xml_edit.setAttr(this.node, VALUE_TYPE_ATTR, "time");
134
139
  require_xml_edit.setAttr(this.node, TIME_VALUE_ATTR, value.value);
@@ -128,6 +128,11 @@ var OdsCell = class {
128
128
  setAttr(this.node, DATE_VALUE_ATTR, value.value);
129
129
  this.displayText = value.value;
130
130
  break;
131
+ case "dateTime":
132
+ setAttr(this.node, VALUE_TYPE_ATTR, "date");
133
+ setAttr(this.node, DATE_VALUE_ATTR, value.value);
134
+ this.displayText = value.value;
135
+ break;
131
136
  case "time":
132
137
  setAttr(this.node, VALUE_TYPE_ATTR, "time");
133
138
  setAttr(this.node, TIME_VALUE_ATTR, value.value);
@@ -9,8 +9,8 @@ function buildOdsPackage(content) {
9
9
  const odsSheet = editor.addSheet(sheet.name);
10
10
  odsSheet.printSettings = sheet.printSettings;
11
11
  for (const cell of sheet.cells) appendCell(odsSheet, cell);
12
- for (const column of sheet.columns) odsSheet.setColumnWidth(column.index, column.widthPt);
13
- for (const row of sheet.rows) odsSheet.setRowHeight(row.index, row.heightPt);
12
+ for (const column of sheet.columns) if (column.widthPt !== void 0) odsSheet.setColumnWidth(column.index, column.widthPt);
13
+ for (const row of sheet.rows) if (row.heightPt !== void 0) odsSheet.setRowHeight(row.index, row.heightPt);
14
14
  }
15
15
  return editor.toPackage();
16
16
  }
@@ -8,8 +8,8 @@ function buildOdsPackage(content) {
8
8
  const odsSheet = editor.addSheet(sheet.name);
9
9
  odsSheet.printSettings = sheet.printSettings;
10
10
  for (const cell of sheet.cells) appendCell(odsSheet, cell);
11
- for (const column of sheet.columns) odsSheet.setColumnWidth(column.index, column.widthPt);
12
- for (const row of sheet.rows) odsSheet.setRowHeight(row.index, row.heightPt);
11
+ for (const column of sheet.columns) if (column.widthPt !== void 0) odsSheet.setColumnWidth(column.index, column.widthPt);
12
+ for (const row of sheet.rows) if (row.heightPt !== void 0) odsSheet.setRowHeight(row.index, row.heightPt);
13
13
  }
14
14
  return editor.toPackage();
15
15
  }
@@ -20,6 +20,7 @@ function displayTextFor(value) {
20
20
  case "boolean": return value.value ? "TRUE" : "FALSE";
21
21
  case "date":
22
22
  case "time":
23
+ case "dateTime":
23
24
  case "string":
24
25
  case "error": return value.value;
25
26
  case "empty": return "";
@@ -19,6 +19,7 @@ function displayTextFor(value) {
19
19
  case "boolean": return value.value ? "TRUE" : "FALSE";
20
20
  case "date":
21
21
  case "time":
22
+ case "dateTime":
22
23
  case "string":
23
24
  case "error": return value.value;
24
25
  case "empty": return "";
@@ -53,7 +53,7 @@ function resolveAxis(entries, start, end, defaultSizePt) {
53
53
  let currentHidden = false;
54
54
  for (let index = start; index <= end; index++) {
55
55
  while (pointer < sorted.length && sorted[pointer].index <= index) {
56
- currentSizePt = sorted[pointer].sizePt;
56
+ currentSizePt = sorted[pointer].sizePt ?? defaultSizePt;
57
57
  currentHidden = sorted[pointer].hidden ?? false;
58
58
  pointer++;
59
59
  }
@@ -77,7 +77,7 @@ function computeHeaderGutter(printSettings, range, measurer) {
77
77
  };
78
78
  }
79
79
  function resolveScale(printSettings, availableWidthPt, availableHeightPt, totalContentWidthPt, totalContentHeightPt) {
80
- if (printSettings.scale !== void 0) return Math.max(printSettings.scale / 100, MINIMUM_SCALE);
80
+ if (printSettings.scalePercent !== void 0) return Math.max(printSettings.scalePercent / 100, MINIMUM_SCALE);
81
81
  if (printSettings.fitToPages !== void 0) {
82
82
  const budgetWidthPt = availableWidthPt * printSettings.fitToPages.width;
83
83
  const budgetHeightPt = availableHeightPt * printSettings.fitToPages.height;
@@ -125,7 +125,7 @@ function cellStyledRuns(cell) {
125
125
  }];
126
126
  }
127
127
  function isNumericLikeValue(kind) {
128
- return kind === "number" || kind === "percentage" || kind === "currency" || kind === "date" || kind === "time";
128
+ return kind === "number" || kind === "percentage" || kind === "currency" || kind === "date" || kind === "time" || kind === "dateTime";
129
129
  }
130
130
  function defaultAlignmentForValue(kind) {
131
131
  if (isNumericLikeValue(kind)) return "right";
@@ -54,7 +54,7 @@ function resolveAxis(entries, start, end, defaultSizePt) {
54
54
  let currentHidden = false;
55
55
  for (let index = start; index <= end; index++) {
56
56
  while (pointer < sorted.length && sorted[pointer].index <= index) {
57
- currentSizePt = sorted[pointer].sizePt;
57
+ currentSizePt = sorted[pointer].sizePt ?? defaultSizePt;
58
58
  currentHidden = sorted[pointer].hidden ?? false;
59
59
  pointer++;
60
60
  }
@@ -78,7 +78,7 @@ function computeHeaderGutter(printSettings, range, measurer) {
78
78
  };
79
79
  }
80
80
  function resolveScale(printSettings, availableWidthPt, availableHeightPt, totalContentWidthPt, totalContentHeightPt) {
81
- if (printSettings.scale !== void 0) return Math.max(printSettings.scale / 100, MINIMUM_SCALE);
81
+ if (printSettings.scalePercent !== void 0) return Math.max(printSettings.scalePercent / 100, MINIMUM_SCALE);
82
82
  if (printSettings.fitToPages !== void 0) {
83
83
  const budgetWidthPt = availableWidthPt * printSettings.fitToPages.width;
84
84
  const budgetHeightPt = availableHeightPt * printSettings.fitToPages.height;
@@ -126,7 +126,7 @@ function cellStyledRuns(cell) {
126
126
  }];
127
127
  }
128
128
  function isNumericLikeValue(kind) {
129
- return kind === "number" || kind === "percentage" || kind === "currency" || kind === "date" || kind === "time";
129
+ return kind === "number" || kind === "percentage" || kind === "currency" || kind === "date" || kind === "time" || kind === "dateTime";
130
130
  }
131
131
  function defaultAlignmentForValue(kind) {
132
132
  if (isNumericLikeValue(kind)) return "right";
@@ -1,10 +1,14 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let document_schema_js = require("document-schema.js");
2
3
  let markdown_codec = require("markdown-codec");
3
4
  //#region src/markdown/read.ts
4
5
  function readMarkdownContent(text, options) {
5
6
  const { document } = (0, markdown_codec.readMarkdown)(text, options);
6
7
  if (document.kind !== "wordprocessing") throw new Error("readMarkdown returned a non-wordprocessing ContentDocument");
7
- return document;
8
+ return document_schema_js.ContentDocumentSchema.parse({
9
+ ...document,
10
+ formatVersion: document_schema_js.CONTENT_FORMAT_VERSION
11
+ });
8
12
  }
9
13
  //#endregion
10
14
  exports.readMarkdownContent = readMarkdownContent;
@@ -1,9 +1,13 @@
1
+ import { CONTENT_FORMAT_VERSION, ContentDocumentSchema } from "document-schema.js";
1
2
  import { readMarkdown } from "markdown-codec";
2
3
  //#region src/markdown/read.ts
3
4
  function readMarkdownContent(text, options) {
4
5
  const { document } = readMarkdown(text, options);
5
6
  if (document.kind !== "wordprocessing") throw new Error("readMarkdown returned a non-wordprocessing ContentDocument");
6
- return document;
7
+ return ContentDocumentSchema.parse({
8
+ ...document,
9
+ formatVersion: CONTENT_FORMAT_VERSION
10
+ });
7
11
  }
8
12
  //#endregion
9
13
  export { readMarkdownContent };
@@ -1,8 +1,41 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let markdown_codec = require("markdown-codec");
3
3
  //#region src/markdown/write.ts
4
+ function toLegacyEmbeddedDocument(document) {
5
+ if (document.kind !== "wordprocessing") throw new markdown_codec.MarkdownUnsupportedDocumentKindError(document.kind);
6
+ return toLegacyWordprocessingDocument(document);
7
+ }
8
+ function toLegacyBlock(block) {
9
+ if (block.kind === "table") return {
10
+ ...block,
11
+ rows: block.rows.map((row) => ({
12
+ ...row,
13
+ cells: row.cells.map((cell) => ({
14
+ ...cell,
15
+ blocks: cell.blocks.map(toLegacyBlock)
16
+ }))
17
+ }))
18
+ };
19
+ if (block.kind === "embeddedObject") return {
20
+ ...block,
21
+ document: toLegacyEmbeddedDocument(block.document)
22
+ };
23
+ return block;
24
+ }
25
+ function toLegacyWordprocessingDocument(document) {
26
+ return {
27
+ kind: "wordprocessing",
28
+ formatVersion: 1,
29
+ metadata: document.metadata,
30
+ sections: document.sections.map((section) => ({
31
+ ...section,
32
+ blocks: section.blocks.map(toLegacyBlock)
33
+ }))
34
+ };
35
+ }
4
36
  function buildMarkdownText(document, options) {
5
- return (0, markdown_codec.writeMarkdown)(document, options);
37
+ if (document.kind !== "wordprocessing") throw new markdown_codec.MarkdownUnsupportedDocumentKindError(document.kind);
38
+ return (0, markdown_codec.writeMarkdown)(toLegacyWordprocessingDocument(document), options);
6
39
  }
7
40
  //#endregion
8
41
  exports.buildMarkdownText = buildMarkdownText;
@@ -1,7 +1,40 @@
1
- import { writeMarkdown } from "markdown-codec";
1
+ import { MarkdownUnsupportedDocumentKindError, writeMarkdown } from "markdown-codec";
2
2
  //#region src/markdown/write.ts
3
+ function toLegacyEmbeddedDocument(document) {
4
+ if (document.kind !== "wordprocessing") throw new MarkdownUnsupportedDocumentKindError(document.kind);
5
+ return toLegacyWordprocessingDocument(document);
6
+ }
7
+ function toLegacyBlock(block) {
8
+ if (block.kind === "table") return {
9
+ ...block,
10
+ rows: block.rows.map((row) => ({
11
+ ...row,
12
+ cells: row.cells.map((cell) => ({
13
+ ...cell,
14
+ blocks: cell.blocks.map(toLegacyBlock)
15
+ }))
16
+ }))
17
+ };
18
+ if (block.kind === "embeddedObject") return {
19
+ ...block,
20
+ document: toLegacyEmbeddedDocument(block.document)
21
+ };
22
+ return block;
23
+ }
24
+ function toLegacyWordprocessingDocument(document) {
25
+ return {
26
+ kind: "wordprocessing",
27
+ formatVersion: 1,
28
+ metadata: document.metadata,
29
+ sections: document.sections.map((section) => ({
30
+ ...section,
31
+ blocks: section.blocks.map(toLegacyBlock)
32
+ }))
33
+ };
34
+ }
3
35
  function buildMarkdownText(document, options) {
4
- return writeMarkdown(document, options);
36
+ if (document.kind !== "wordprocessing") throw new MarkdownUnsupportedDocumentKindError(document.kind);
37
+ return writeMarkdown(toLegacyWordprocessingDocument(document), options);
5
38
  }
6
39
  //#endregion
7
40
  export { buildMarkdownText };
@@ -142,16 +142,23 @@ function layoutScripts(base, subscript, superscript, ctx) {
142
142
  items
143
143
  };
144
144
  }
145
- function layoutUnderOver(base, under, over, ctx) {
145
+ function layoutUnderOver(base, under, over, ctx, accentAttachment = {}) {
146
146
  const gapPt = ctx.metrics.stackGapMinPt;
147
147
  const overHeightPt = over === void 0 ? 0 : gapPt + over.heightPt;
148
148
  const underHeightPt = under === void 0 ? 0 : gapPt + under.heightPt;
149
149
  const ascentPt = base.ascentPt + overHeightPt;
150
150
  const descentPt = base.descentPt + underHeightPt;
151
151
  const widthPt = Math.max(base.widthPt, under?.widthPt ?? 0, over?.widthPt ?? 0);
152
- const items = [...require_mathml_compose.placeChild(base, (widthPt - base.widthPt) / 2, 0, ascentPt)];
153
- if (over !== void 0) items.push(...require_mathml_compose.placeChild(over, (widthPt - over.widthPt) / 2, -(base.ascentPt + gapPt + over.descentPt), ascentPt));
154
- if (under !== void 0) items.push(...require_mathml_compose.placeChild(under, (widthPt - under.widthPt) / 2, base.descentPt + gapPt + under.ascentPt, ascentPt));
152
+ const baseXPt = (widthPt - base.widthPt) / 2;
153
+ const items = [...require_mathml_compose.placeChild(base, baseXPt, 0, ascentPt)];
154
+ if (over !== void 0) {
155
+ const overXPt = accentAttachment.overXPt === void 0 ? (widthPt - over.widthPt) / 2 : baseXPt + accentAttachment.overXPt - over.widthPt / 2;
156
+ items.push(...require_mathml_compose.placeChild(over, overXPt, -(base.ascentPt + gapPt + over.descentPt), ascentPt));
157
+ }
158
+ if (under !== void 0) {
159
+ const underXPt = accentAttachment.underXPt === void 0 ? (widthPt - under.widthPt) / 2 : baseXPt + accentAttachment.underXPt - under.widthPt / 2;
160
+ items.push(...require_mathml_compose.placeChild(under, underXPt, base.descentPt + gapPt + under.ascentPt, ascentPt));
161
+ }
155
162
  return {
156
163
  widthPt,
157
164
  ascentPt,
@@ -163,6 +170,26 @@ function layoutUnderOver(base, under, over, ctx) {
163
170
  function isMovableLimitsOperator(element) {
164
171
  return require_mathml_nodes.elementLocalName(element) === "mo" && require_mathml_operators.operatorProperties(require_mathml_nodes.textContent(element).trim()).movablelimits;
165
172
  }
173
+ function resolveTopAccentXPt(baseElement, ctx) {
174
+ const name = require_mathml_nodes.elementLocalName(baseElement);
175
+ let rawText;
176
+ let intrinsicDefault;
177
+ if (name === "mi") {
178
+ rawText = require_mathml_nodes.textContent(baseElement);
179
+ intrinsicDefault = miIntrinsicDefault(rawText);
180
+ } else if (name === "mn" || name === "mtext") {
181
+ rawText = require_mathml_nodes.textContent(baseElement);
182
+ intrinsicDefault = "normal";
183
+ } else if (name === "mo") {
184
+ rawText = require_mathml_nodes.textContent(baseElement).trim();
185
+ intrinsicDefault = "normal";
186
+ } else return;
187
+ const codePoints = [...require_mathml_variant.applyMathVariant(rawText, tokenVariant(baseElement, intrinsicDefault, ctx))];
188
+ if (codePoints.length !== 1) return;
189
+ const codePoint = codePoints[0]?.codePointAt(0);
190
+ if (codePoint === void 0) return;
191
+ return ctx.metrics.glyph(codePoint, ctx.sizePt)?.topAccentXPt;
192
+ }
166
193
  function layoutUnderOverElement(element, kind, ctx) {
167
194
  const children = require_mathml_nodes.elementChildren(element);
168
195
  const baseElement = children[0];
@@ -176,9 +203,16 @@ function layoutUnderOverElement(element, kind, ctx) {
176
203
  }
177
204
  const base = layoutNode(baseElement, ctx);
178
205
  const scriptCtx = scriptContext(ctx, false);
179
- if (kind === "munder") return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), void 0, ctx);
180
- if (kind === "mover") return layoutUnderOver(base, void 0, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), ctx);
181
- return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), children[2] === void 0 ? void 0 : layoutNode(children[2], scriptCtx), ctx);
206
+ const isAccent = require_mathml_nodes.attrValue(element, "accent") === "true";
207
+ const isAccentUnder = require_mathml_nodes.attrValue(element, "accentunder") === "true";
208
+ const topAccentXPt = isAccent || isAccentUnder ? resolveTopAccentXPt(baseElement, ctx) : void 0;
209
+ const accentAttachment = {
210
+ overXPt: isAccent ? topAccentXPt : void 0,
211
+ underXPt: isAccentUnder ? topAccentXPt : void 0
212
+ };
213
+ if (kind === "munder") return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), void 0, ctx, accentAttachment);
214
+ if (kind === "mover") return layoutUnderOver(base, void 0, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), ctx, accentAttachment);
215
+ return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), children[2] === void 0 ? void 0 : layoutNode(children[2], scriptCtx), ctx, accentAttachment);
182
216
  }
183
217
  function layoutFraction(element, ctx) {
184
218
  const children = require_mathml_nodes.elementChildren(element);
@@ -141,16 +141,23 @@ function layoutScripts(base, subscript, superscript, ctx) {
141
141
  items
142
142
  };
143
143
  }
144
- function layoutUnderOver(base, under, over, ctx) {
144
+ function layoutUnderOver(base, under, over, ctx, accentAttachment = {}) {
145
145
  const gapPt = ctx.metrics.stackGapMinPt;
146
146
  const overHeightPt = over === void 0 ? 0 : gapPt + over.heightPt;
147
147
  const underHeightPt = under === void 0 ? 0 : gapPt + under.heightPt;
148
148
  const ascentPt = base.ascentPt + overHeightPt;
149
149
  const descentPt = base.descentPt + underHeightPt;
150
150
  const widthPt = Math.max(base.widthPt, under?.widthPt ?? 0, over?.widthPt ?? 0);
151
- const items = [...placeChild(base, (widthPt - base.widthPt) / 2, 0, ascentPt)];
152
- if (over !== void 0) items.push(...placeChild(over, (widthPt - over.widthPt) / 2, -(base.ascentPt + gapPt + over.descentPt), ascentPt));
153
- if (under !== void 0) items.push(...placeChild(under, (widthPt - under.widthPt) / 2, base.descentPt + gapPt + under.ascentPt, ascentPt));
151
+ const baseXPt = (widthPt - base.widthPt) / 2;
152
+ const items = [...placeChild(base, baseXPt, 0, ascentPt)];
153
+ if (over !== void 0) {
154
+ const overXPt = accentAttachment.overXPt === void 0 ? (widthPt - over.widthPt) / 2 : baseXPt + accentAttachment.overXPt - over.widthPt / 2;
155
+ items.push(...placeChild(over, overXPt, -(base.ascentPt + gapPt + over.descentPt), ascentPt));
156
+ }
157
+ if (under !== void 0) {
158
+ const underXPt = accentAttachment.underXPt === void 0 ? (widthPt - under.widthPt) / 2 : baseXPt + accentAttachment.underXPt - under.widthPt / 2;
159
+ items.push(...placeChild(under, underXPt, base.descentPt + gapPt + under.ascentPt, ascentPt));
160
+ }
154
161
  return {
155
162
  widthPt,
156
163
  ascentPt,
@@ -162,6 +169,26 @@ function layoutUnderOver(base, under, over, ctx) {
162
169
  function isMovableLimitsOperator(element) {
163
170
  return elementLocalName(element) === "mo" && operatorProperties(textContent(element).trim()).movablelimits;
164
171
  }
172
+ function resolveTopAccentXPt(baseElement, ctx) {
173
+ const name = elementLocalName(baseElement);
174
+ let rawText;
175
+ let intrinsicDefault;
176
+ if (name === "mi") {
177
+ rawText = textContent(baseElement);
178
+ intrinsicDefault = miIntrinsicDefault(rawText);
179
+ } else if (name === "mn" || name === "mtext") {
180
+ rawText = textContent(baseElement);
181
+ intrinsicDefault = "normal";
182
+ } else if (name === "mo") {
183
+ rawText = textContent(baseElement).trim();
184
+ intrinsicDefault = "normal";
185
+ } else return;
186
+ const codePoints = [...applyMathVariant(rawText, tokenVariant(baseElement, intrinsicDefault, ctx))];
187
+ if (codePoints.length !== 1) return;
188
+ const codePoint = codePoints[0]?.codePointAt(0);
189
+ if (codePoint === void 0) return;
190
+ return ctx.metrics.glyph(codePoint, ctx.sizePt)?.topAccentXPt;
191
+ }
165
192
  function layoutUnderOverElement(element, kind, ctx) {
166
193
  const children = elementChildren(element);
167
194
  const baseElement = children[0];
@@ -175,9 +202,16 @@ function layoutUnderOverElement(element, kind, ctx) {
175
202
  }
176
203
  const base = layoutNode(baseElement, ctx);
177
204
  const scriptCtx = scriptContext(ctx, false);
178
- if (kind === "munder") return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), void 0, ctx);
179
- if (kind === "mover") return layoutUnderOver(base, void 0, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), ctx);
180
- return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), children[2] === void 0 ? void 0 : layoutNode(children[2], scriptCtx), ctx);
205
+ const isAccent = attrValue(element, "accent") === "true";
206
+ const isAccentUnder = attrValue(element, "accentunder") === "true";
207
+ const topAccentXPt = isAccent || isAccentUnder ? resolveTopAccentXPt(baseElement, ctx) : void 0;
208
+ const accentAttachment = {
209
+ overXPt: isAccent ? topAccentXPt : void 0,
210
+ underXPt: isAccentUnder ? topAccentXPt : void 0
211
+ };
212
+ if (kind === "munder") return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), void 0, ctx, accentAttachment);
213
+ if (kind === "mover") return layoutUnderOver(base, void 0, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), ctx, accentAttachment);
214
+ return layoutUnderOver(base, children[1] === void 0 ? void 0 : layoutNode(children[1], scriptCtx), children[2] === void 0 ? void 0 : layoutNode(children[2], scriptCtx), ctx, accentAttachment);
181
215
  }
182
216
  function layoutFraction(element, ctx) {
183
217
  const children = elementChildren(element);
@@ -824,7 +824,13 @@ const GREEK_VARIANTS = {
824
824
  120538
825
825
  ],
826
826
  nabla: 120513,
827
- partial: 120539
827
+ partial: 120539,
828
+ epsilon: 120540,
829
+ theta: 120541,
830
+ kappa: 120542,
831
+ phi: 120543,
832
+ rho: 120544,
833
+ pi: 120545
828
834
  },
829
835
  italic: {
830
836
  upper: [
@@ -880,7 +886,13 @@ const GREEK_VARIANTS = {
880
886
  120596
881
887
  ],
882
888
  nabla: 120571,
883
- partial: 120597
889
+ partial: 120597,
890
+ epsilon: 120598,
891
+ theta: 120599,
892
+ kappa: 120600,
893
+ phi: 120601,
894
+ rho: 120602,
895
+ pi: 120603
884
896
  },
885
897
  "bold-italic": {
886
898
  upper: [
@@ -936,7 +948,13 @@ const GREEK_VARIANTS = {
936
948
  120654
937
949
  ],
938
950
  nabla: 120629,
939
- partial: 120655
951
+ partial: 120655,
952
+ epsilon: 120656,
953
+ theta: 120657,
954
+ kappa: 120658,
955
+ phi: 120659,
956
+ rho: 120660,
957
+ pi: 120661
940
958
  },
941
959
  "bold-sans-serif": {
942
960
  upper: [
@@ -992,7 +1010,13 @@ const GREEK_VARIANTS = {
992
1010
  120712
993
1011
  ],
994
1012
  nabla: 120687,
995
- partial: 120713
1013
+ partial: 120713,
1014
+ epsilon: 120714,
1015
+ theta: 120715,
1016
+ kappa: 120716,
1017
+ phi: 120717,
1018
+ rho: 120718,
1019
+ pi: 120719
996
1020
  },
997
1021
  "sans-serif-bold-italic": {
998
1022
  upper: [
@@ -1048,9 +1072,23 @@ const GREEK_VARIANTS = {
1048
1072
  120770
1049
1073
  ],
1050
1074
  nabla: 120745,
1051
- partial: 120771
1075
+ partial: 120771,
1076
+ epsilon: 120772,
1077
+ theta: 120773,
1078
+ kappa: 120774,
1079
+ phi: 120775,
1080
+ rho: 120776,
1081
+ pi: 120777
1052
1082
  }
1053
1083
  };
1084
+ const GREEK_SYMBOL_BASES = /* @__PURE__ */ new Map([
1085
+ [1013, "epsilon"],
1086
+ [977, "theta"],
1087
+ [1008, "kappa"],
1088
+ [981, "phi"],
1089
+ [1009, "rho"],
1090
+ [982, "pi"]
1091
+ ]);
1054
1092
  const DIGIT_VARIANTS = {
1055
1093
  bold: [
1056
1094
  120782,
@@ -1124,6 +1162,11 @@ function mapMathVariant(codePoint, variant) {
1124
1162
  if (greekLowerIndex !== -1) return GREEK_VARIANTS[variant]?.lower[greekLowerIndex] ?? codePoint;
1125
1163
  if (codePoint === 8711) return GREEK_VARIANTS[variant]?.nabla ?? codePoint;
1126
1164
  if (codePoint === 8706) return GREEK_VARIANTS[variant]?.partial ?? codePoint;
1165
+ const symbolField = GREEK_SYMBOL_BASES.get(codePoint);
1166
+ if (symbolField !== void 0) {
1167
+ const entry = GREEK_VARIANTS[variant];
1168
+ return entry === void 0 ? codePoint : entry[symbolField];
1169
+ }
1127
1170
  return codePoint;
1128
1171
  }
1129
1172
  function applyMathVariant(text, variant) {
@@ -823,7 +823,13 @@ const GREEK_VARIANTS = {
823
823
  120538
824
824
  ],
825
825
  nabla: 120513,
826
- partial: 120539
826
+ partial: 120539,
827
+ epsilon: 120540,
828
+ theta: 120541,
829
+ kappa: 120542,
830
+ phi: 120543,
831
+ rho: 120544,
832
+ pi: 120545
827
833
  },
828
834
  italic: {
829
835
  upper: [
@@ -879,7 +885,13 @@ const GREEK_VARIANTS = {
879
885
  120596
880
886
  ],
881
887
  nabla: 120571,
882
- partial: 120597
888
+ partial: 120597,
889
+ epsilon: 120598,
890
+ theta: 120599,
891
+ kappa: 120600,
892
+ phi: 120601,
893
+ rho: 120602,
894
+ pi: 120603
883
895
  },
884
896
  "bold-italic": {
885
897
  upper: [
@@ -935,7 +947,13 @@ const GREEK_VARIANTS = {
935
947
  120654
936
948
  ],
937
949
  nabla: 120629,
938
- partial: 120655
950
+ partial: 120655,
951
+ epsilon: 120656,
952
+ theta: 120657,
953
+ kappa: 120658,
954
+ phi: 120659,
955
+ rho: 120660,
956
+ pi: 120661
939
957
  },
940
958
  "bold-sans-serif": {
941
959
  upper: [
@@ -991,7 +1009,13 @@ const GREEK_VARIANTS = {
991
1009
  120712
992
1010
  ],
993
1011
  nabla: 120687,
994
- partial: 120713
1012
+ partial: 120713,
1013
+ epsilon: 120714,
1014
+ theta: 120715,
1015
+ kappa: 120716,
1016
+ phi: 120717,
1017
+ rho: 120718,
1018
+ pi: 120719
995
1019
  },
996
1020
  "sans-serif-bold-italic": {
997
1021
  upper: [
@@ -1047,9 +1071,23 @@ const GREEK_VARIANTS = {
1047
1071
  120770
1048
1072
  ],
1049
1073
  nabla: 120745,
1050
- partial: 120771
1074
+ partial: 120771,
1075
+ epsilon: 120772,
1076
+ theta: 120773,
1077
+ kappa: 120774,
1078
+ phi: 120775,
1079
+ rho: 120776,
1080
+ pi: 120777
1051
1081
  }
1052
1082
  };
1083
+ const GREEK_SYMBOL_BASES = /* @__PURE__ */ new Map([
1084
+ [1013, "epsilon"],
1085
+ [977, "theta"],
1086
+ [1008, "kappa"],
1087
+ [981, "phi"],
1088
+ [1009, "rho"],
1089
+ [982, "pi"]
1090
+ ]);
1053
1091
  const DIGIT_VARIANTS = {
1054
1092
  bold: [
1055
1093
  120782,
@@ -1123,6 +1161,11 @@ function mapMathVariant(codePoint, variant) {
1123
1161
  if (greekLowerIndex !== -1) return GREEK_VARIANTS[variant]?.lower[greekLowerIndex] ?? codePoint;
1124
1162
  if (codePoint === 8711) return GREEK_VARIANTS[variant]?.nabla ?? codePoint;
1125
1163
  if (codePoint === 8706) return GREEK_VARIANTS[variant]?.partial ?? codePoint;
1164
+ const symbolField = GREEK_SYMBOL_BASES.get(codePoint);
1165
+ if (symbolField !== void 0) {
1166
+ const entry = GREEK_VARIANTS[variant];
1167
+ return entry === void 0 ? codePoint : entry[symbolField];
1168
+ }
1126
1169
  return codePoint;
1127
1170
  }
1128
1171
  function applyMathVariant(text, variant) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js.documents",
3
- "version": "1.60.2",
3
+ "version": "1.61.0",
4
4
  "description": "Bidirectional docx/pptx <-> PDF conversion and a read+write editable OOXML document model, built on ooxml.js and Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -58,12 +58,12 @@
58
58
  ],
59
59
  "license": "MIT",
60
60
  "dependencies": {
61
- "document-schema.js": "^1.7.0",
61
+ "document-schema.js": "^2.0.0",
62
62
  "fflate": "^0.8.3",
63
63
  "markdown-codec": "github:ExaDev/markdown-codec#beda0a89d92fffd153d5dcd05d767b404b721cda",
64
- "odf.js": "^1.10.3",
65
- "ooxml.js": "^2.2.3",
66
- "pdf-codec": "^1.1.3",
64
+ "odf.js": "^1.13.2",
65
+ "ooxml.js": "^2.5.2",
66
+ "pdf-codec": "^1.4.2",
67
67
  "zod": "^4.4.3"
68
68
  },
69
69
  "devDependencies": {