@stll/folio-core 0.22.2 → 0.23.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.
Files changed (62) hide show
  1. package/dist/ai-edits/apply.d.ts +1 -1
  2. package/dist/controller/layoutPipeline.js +28 -0
  3. package/dist/document-operations.d.ts +4 -4
  4. package/dist/document-operations.js +17 -1
  5. package/dist/docx/imageParser.js +6 -9
  6. package/dist/docx/rezip.d.ts +1 -1
  7. package/dist/docx/rezip.js +12 -31
  8. package/dist/docx/server/inspectDocxPackage.js +8 -7
  9. package/dist/docx/styleParser.js +16 -16
  10. package/dist/docx/xmlParser.d.ts +7 -3
  11. package/dist/docx/xmlParser.js +20 -7
  12. package/dist/layout-bridge/convert/toFlowBlocks.js +35 -61
  13. package/dist/layout-engine/measure/measureHelpers.d.ts +2 -1
  14. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  15. package/dist/layout-engine/types.d.ts +6 -0
  16. package/dist/layout-painter/anchoredImagePosition.d.ts +2 -1
  17. package/dist/layout-painter/anchoredImagePosition.js +14 -6
  18. package/dist/layout-painter/imageLayout.d.ts +1 -1
  19. package/dist/layout-painter/renderPage.js +11 -1
  20. package/dist/layout-painter/renderParagraph.js +3 -4
  21. package/dist/managers/AutoSaveManager.js +8 -40
  22. package/dist/managers/TableSelectionManager.d.ts +1 -1
  23. package/dist/managers/autoSaveCodec.d.ts +17 -0
  24. package/dist/managers/autoSaveCodec.js +109 -0
  25. package/dist/prosemirror/attrs/index.js +278 -29
  26. package/dist/prosemirror/commands/comments.js +3 -3
  27. package/dist/prosemirror/commands/formatting.js +19 -19
  28. package/dist/prosemirror/commands/index.d.ts +2 -2
  29. package/dist/prosemirror/commands/paragraph.js +32 -32
  30. package/dist/prosemirror/commands/propertyChangeScope.d.ts +5 -3
  31. package/dist/prosemirror/commands/propertyChangeScope.js +9 -8
  32. package/dist/prosemirror/commands/table.d.ts +10 -52
  33. package/dist/prosemirror/commands/table.js +34 -34
  34. package/dist/prosemirror/conversion/fromProseDoc.js +33 -20
  35. package/dist/prosemirror/conversion/sdtAttrs.d.ts +2 -1
  36. package/dist/prosemirror/conversion/sdtAttrs.js +9 -4
  37. package/dist/prosemirror/conversion/toProseDoc.js +1 -0
  38. package/dist/prosemirror/extensions/ExtensionManager.d.ts +6 -3
  39. package/dist/prosemirror/extensions/ExtensionManager.js +5 -3
  40. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +1 -1
  41. package/dist/prosemirror/extensions/core/ParagraphExtension.js +30 -3
  42. package/dist/prosemirror/extensions/features/ListExtension.js +4 -4
  43. package/dist/prosemirror/extensions/marks/UnderlineExtension.js +9 -7
  44. package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
  45. package/dist/prosemirror/extensions/marks/markUtils.js +92 -16
  46. package/dist/prosemirror/extensions/nodes/ShapeExtension.d.ts +1 -1
  47. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +1 -1
  48. package/dist/prosemirror/extensions/types.d.ts +134 -1
  49. package/dist/prosemirror/index.d.ts +2 -2
  50. package/dist/prosemirror/plugins/selectionTracker.js +25 -94
  51. package/dist/prosemirror/revisionCarriers.js +3 -1
  52. package/dist/prosemirror/schema/index.d.ts +2 -2
  53. package/dist/prosemirror/schema/index.js +87 -0
  54. package/dist/prosemirror/schema/nodes.d.ts +14 -2
  55. package/dist/prosemirror/selectionMarks.d.ts +11 -0
  56. package/dist/prosemirror/selectionMarks.js +19 -0
  57. package/dist/prosemirror/selectionState.d.ts +11 -5
  58. package/dist/prosemirror/selectionState.js +99 -74
  59. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
  60. package/dist/utils/formatToStyle.js +1 -1
  61. package/dist/utils/tableOperations.d.ts +7 -6
  62. package/package.json +3 -3
@@ -17,7 +17,7 @@ type FolioAIEditView = {
17
17
  type ApplyFolioAIEditOperationsOptions = {
18
18
  view: FolioAIEditView;
19
19
  snapshot: FolioAIEditSnapshot;
20
- operations: FolioAIEditOperation[];
20
+ operations: readonly FolioAIEditOperation[];
21
21
  mode?: FolioAIEditApplyMode;
22
22
  author?: string;
23
23
  /** Optional author initials (w:initials) stamped alongside the author. */
@@ -58,6 +58,29 @@ function bodyBlocksClearSectionHeaderFooter(blocks, { authoredMargins, sectionHe
58
58
  });
59
59
  return changed ? nextBlocks : blocks;
60
60
  }
61
+ const arePageMarginsEqual = (left, right) => left.top === right.top && left.right === right.right && left.bottom === right.bottom && left.left === right.left && left.header === right.header && left.footer === right.footer;
62
+ const mirrorPageMarginsIfNeeded = (authoredMargins, pageNumber, mirrorMargins) => mirrorMargins && pageNumber % 2 === 0 ? {
63
+ ...authoredMargins,
64
+ left: authoredMargins.right,
65
+ right: authoredMargins.left
66
+ } : authoredMargins;
67
+ function attachAuthoredMarginsToLayoutPages(layout, options) {
68
+ let changed = false;
69
+ const pages = layout.pages.map((page) => {
70
+ const sectionProperties = options.sectionPropertiesForMargins[page.sectionIndex ?? 0];
71
+ const authoredMargins = mirrorPageMarginsIfNeeded(sectionProperties ? getMargins(sectionProperties) : options.authoredMargins, page.number, options.mirrorMargins);
72
+ if (page.authoredMargins && arePageMarginsEqual(page.authoredMargins, authoredMargins)) return page;
73
+ changed = true;
74
+ return {
75
+ ...page,
76
+ authoredMargins
77
+ };
78
+ });
79
+ return changed ? {
80
+ ...layout,
81
+ pages
82
+ } : layout;
83
+ }
61
84
  function runLayoutPipeline(deps, state, options = {}) {
62
85
  const { contentWidth, columns, pageSize, margins, pageGap, showMarginGuides, marginGuideColor, syncCoordinator, headerContent, footerContent, firstPageHeaderContent, firstPageFooterContent, headerContentRId, footerContentRId, firstPageHeaderContentRId, firstPageFooterContentRId, sectionHeaderFooterRefs, theme: _theme, sectionProperties, document, defaultTabStop, mirrorMargins, styles, layout, hfPMs, painter, pagesContainer, session, renderHfFromContentOrPm, renderHeaderFooterContentByRId, documentFontsAreLoaded, buildFootnoteRenderItems, describeInvalidHighlightMarks, emptyTemplatePreviewEntries: EMPTY_TEMPLATE_PREVIEW_ENTRIES } = deps;
63
86
  let outcome = {};
@@ -341,6 +364,11 @@ function runLayoutPipeline(deps, state, options = {}) {
341
364
  };
342
365
  let stabilizedFieldValues;
343
366
  stabilizeFieldWidths();
367
+ newLayout = attachAuthoredMarginsToLayoutPages(newLayout, {
368
+ authoredMargins: margins,
369
+ mirrorMargins,
370
+ sectionPropertiesForMargins
371
+ });
344
372
  const rebuildHeaderFooterForLayout = () => {
345
373
  const seqValues = buildSeqValues(newBlocks);
346
374
  const bookmarkTextInputs = stabilizedFieldValues === void 0 ? { seqValues } : {
@@ -56,10 +56,10 @@ declare class InvalidFolioDocumentOperationBatchError extends InvalidFolioDocume
56
56
  declare const assertSupportedFolioDocumentOperationVersion: (value: unknown) => typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
57
57
  type FolioDocumentOperationBatch = {
58
58
  readonly version: typeof FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION;
59
- operations: FolioDocumentOperation[];
60
- mode?: FolioDocumentOperationMode;
61
- atomic?: boolean;
62
- dryRun?: boolean;
59
+ readonly operations: readonly FolioDocumentOperation[];
60
+ readonly mode?: FolioDocumentOperationMode;
61
+ readonly atomic?: boolean;
62
+ readonly dryRun?: boolean;
63
63
  };
64
64
  declare const parseFolioDocumentOperationBatch: (value: unknown) => FolioDocumentOperationBatch;
65
65
  type FolioDocumentOperationStatus = "committed" | "previewed" | "rejected";
@@ -34,6 +34,7 @@ const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze([
34
34
  ]);
35
35
  const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS = Object.freeze(["blockTextHash"]);
36
36
  const FOLIO_DOCUMENT_OPERATION_BATCH_MODES = Object.freeze(["best-effort", "atomic"]);
37
+ const parsedFolioDocumentOperationBatches = /* @__PURE__ */ new WeakSet();
37
38
  const DIRECT_AND_TRACKED_MODES = Object.freeze(["direct", "tracked-changes"]);
38
39
  const DIRECT_TRACKED_AND_SUGGESTED_MODES = FOLIO_DOCUMENT_OPERATION_MODES;
39
40
  const DIRECT_AND_SUGGESTED_MODES = Object.freeze(["direct", "suggested"]);
@@ -83,6 +84,17 @@ const assertSupportedFolioDocumentOperationVersion = (value) => {
83
84
  });
84
85
  };
85
86
  const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
87
+ const isParsedFolioDocumentOperationBatch = (value) => typeof value === "object" && value !== null && parsedFolioDocumentOperationBatches.has(value);
88
+ const freezeParsedValue = (value) => {
89
+ if (Array.isArray(value)) {
90
+ for (const entry of value) freezeParsedValue(entry);
91
+ Object.freeze(value);
92
+ return;
93
+ }
94
+ if (!isPlainObject(value)) return;
95
+ for (const entry of Object.values(value)) freezeParsedValue(entry);
96
+ Object.freeze(value);
97
+ };
86
98
  const invalidBatch = (path, reason) => {
87
99
  throw new InvalidFolioDocumentOperationBatchError({
88
100
  message: `Invalid document operation batch at ${path}: ${reason}.`,
@@ -508,6 +520,7 @@ const parseDocumentOperation = (value, index) => {
508
520
  return invalidBatch(`${path}.type`, `unsupported operation type "${type}"`);
509
521
  };
510
522
  const parseFolioDocumentOperationBatch = (value) => {
523
+ if (isParsedFolioDocumentOperationBatch(value)) return value;
511
524
  if (!isPlainObject(value)) return invalidBatch("$", "expected an object");
512
525
  assertAllowedKeys(value, "$", [
513
526
  "version",
@@ -529,13 +542,16 @@ const parseFolioDocumentOperationBatch = (value) => {
529
542
  if (operationIds.has(operation.id)) return invalidBatch(`$.operations[${index}].id`, "expected a unique operation id");
530
543
  operationIds.add(operation.id);
531
544
  }
532
- return {
545
+ const parsedBatch = {
533
546
  version,
534
547
  operations: parsedOperations,
535
548
  ...mode !== void 0 && { mode },
536
549
  ...atomic !== void 0 && { atomic },
537
550
  ...dryRun !== void 0 && { dryRun }
538
551
  };
552
+ freezeParsedValue(parsedBatch);
553
+ parsedFolioDocumentOperationBatches.add(parsedBatch);
554
+ return parsedBatch;
539
555
  };
540
556
  const recoveryByReason = {
541
557
  missingBlock: "refreshDocument",
@@ -4,7 +4,7 @@ import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
4
4
  import { WRAP_ELEMENT_NAMES, parsePositionH, parsePositionV, parseWrapElement } from "./drawingUtils.js";
5
5
  import { resolveTarget } from "./relsParser.js";
6
6
  import { isTextBoxDrawing } from "./textBoxParser.js";
7
- import { findByFullName, findChild, getAttribute, getChildElements, parseNumericAttribute } from "./xmlParser.js";
7
+ import { findByFullName, findChild, getAttribute, getChildElements, parseNumericAttribute, parseOnOffValue } from "./xmlParser.js";
8
8
  //#region src/docx/imageParser.ts
9
9
  /**
10
10
  * Convert rotation value (1/60000 of a degree) to degrees
@@ -74,7 +74,7 @@ function parseDocProps(docPr) {
74
74
  const name = getAttribute(docPr, null, "name");
75
75
  const descr = getAttribute(docPr, null, "descr");
76
76
  const title = getAttribute(docPr, null, "title");
77
- const decorative = getAttribute(docPr, null, "decorative") === "1";
77
+ const decorative = parseOnOffValue(getAttribute(docPr, null, "decorative")) === true;
78
78
  const hlinkClickEl = findChild(docPr, "a", "hlinkClick");
79
79
  const hlinkRId = hlinkClickEl ? getAttribute(hlinkClickEl, "r", "id") : null;
80
80
  return {
@@ -92,8 +92,8 @@ function parseDocProps(docPr) {
92
92
  function parseTransform(xfrm) {
93
93
  if (!xfrm) return;
94
94
  const rot = getAttribute(xfrm, null, "rot");
95
- const flipH = getAttribute(xfrm, null, "flipH") === "1";
96
- const flipV = getAttribute(xfrm, null, "flipV") === "1";
95
+ const flipH = parseOnOffValue(getAttribute(xfrm, null, "flipH")) === true;
96
+ const flipV = parseOnOffValue(getAttribute(xfrm, null, "flipV")) === true;
97
97
  const rotation = rotToDegrees(rot);
98
98
  if (rotation === void 0 && !flipH && !flipV) return;
99
99
  const transform = {};
@@ -155,10 +155,7 @@ function parseImageCrop(blipFill) {
155
155
  * spec-defined default.
156
156
  */
157
157
  function parseOnOffAttr(element, name) {
158
- const raw = getAttribute(element, null, name);
159
- if (raw === null) return;
160
- if (raw === "1" || raw === "true" || raw === "on") return true;
161
- if (raw === "0" || raw === "false" || raw === "off") return false;
158
+ return parseOnOffValue(getAttribute(element, null, name));
162
159
  }
163
160
  /**
164
161
  * Parse `<a:alphaModFix amt="..."/>` inside the `a:blip` element. The
@@ -358,7 +355,7 @@ function parseAnchor(anchorEl, rels, media) {
358
355
  const size = parseExtent(findByFullName(anchorEl, "wp:extent"));
359
356
  const padding = parseEffectExtent(findByFullName(anchorEl, "wp:effectExtent"));
360
357
  const props = parseDocProps(findByFullName(anchorEl, "wp:docPr"));
361
- const behindDoc = getAttribute(anchorEl, null, "behindDoc") === "1";
358
+ const behindDoc = parseOnOffValue(getAttribute(anchorEl, null, "behindDoc")) === true;
362
359
  const layoutInCell = parseOnOffAttr(anchorEl, "layoutInCell");
363
360
  const allowOverlap = parseOnOffAttr(anchorEl, "allowOverlap");
364
361
  const anchorDistT = parseNumericAttribute(anchorEl, null, "distT");
@@ -145,7 +145,7 @@ declare function updateCoreProperties(corePropsXml: string, { updateModifiedDate
145
145
  * @param buffer - Buffer to validate
146
146
  * @returns Promise resolving to validation result
147
147
  */
148
- declare function validateDocx(buffer: ArrayBuffer): Promise<{
148
+ declare const validateDocx: (buffer: ArrayBuffer) => Promise<{
149
149
  valid: boolean;
150
150
  errors: string[];
151
151
  warnings: string[];
@@ -21,6 +21,7 @@ import { isPreservableDocxEntry } from "./unzip.js";
21
21
  import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml } from "./xmlParser.js";
22
22
  import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
23
23
  import { panic } from "better-result";
24
+ import { validateDocxPackage } from "@stll/docx-core";
24
25
  import JSZip from "jszip";
25
26
  //#region src/docx/rezip.ts
26
27
  /**
@@ -1223,38 +1224,18 @@ function getContentTypeForExtension(extension, mimeType) {
1223
1224
  * @param buffer - Buffer to validate
1224
1225
  * @returns Promise resolving to validation result
1225
1226
  */
1226
- async function validateDocx(buffer) {
1227
- const errors = [];
1228
- const warnings = [];
1229
- try {
1230
- const zip = await JSZip.loadAsync(buffer);
1231
- for (const file of ["[Content_Types].xml", "word/document.xml"]) if (!zip.file(file)) errors.push(`Missing required file: ${file}`);
1232
- for (const file of [
1233
- "_rels/.rels",
1234
- "word/_rels/document.xml.rels",
1235
- "word/styles.xml"
1236
- ]) if (!zip.file(file)) warnings.push(`Missing recommended file: ${file}`);
1237
- const docFile = zip.file("word/document.xml");
1238
- if (docFile) {
1239
- const docXml = await docFile.async("text");
1240
- if (!docXml.includes("<?xml")) warnings.push("document.xml missing XML declaration");
1241
- if (!docXml.includes("<w:document")) errors.push("document.xml missing w:document element");
1242
- if (!docXml.includes("<w:body>")) errors.push("document.xml missing w:body element");
1243
- }
1244
- const ctFile = zip.file("[Content_Types].xml");
1245
- if (ctFile) {
1246
- const ctXml = await ctFile.async("text");
1247
- if (!ctXml.includes("word/document.xml") && !ctXml.includes("application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml")) warnings.push("Content_Types.xml may be missing document.xml type declaration");
1248
- }
1249
- } catch (error) {
1250
- errors.push(`Failed to read as ZIP: ${error instanceof Error ? error.message : "Unknown error"}`);
1251
- }
1252
- return {
1253
- valid: errors.length === 0,
1254
- errors,
1255
- warnings
1227
+ const validateDocx = async (buffer) => {
1228
+ const result = await validateDocxPackage(buffer);
1229
+ return result.valid ? {
1230
+ valid: true,
1231
+ errors: [],
1232
+ warnings: []
1233
+ } : {
1234
+ valid: false,
1235
+ errors: [result.error],
1236
+ warnings: []
1256
1237
  };
1257
- }
1238
+ };
1258
1239
  /**
1259
1240
  * Check if buffer looks like a DOCX file (quick check)
1260
1241
  *
@@ -1,6 +1,6 @@
1
1
  import { getAttributeAnyPrefix, getLocalName, parseXmlDocument } from "../xmlParser.js";
2
2
  import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
3
- import { TaggedError } from "better-result";
3
+ import { Result, TaggedError } from "better-result";
4
4
  import { createHash } from "node:crypto";
5
5
  //#region src/docx/server/inspectDocxPackage.ts
6
6
  const FOLIO_DOCX_PACKAGE_INSPECTION_VERSION = 1;
@@ -32,16 +32,17 @@ const decodeXml = (bytes, part) => {
32
32
  let encoding = "utf-8";
33
33
  if (bytes.length >= 2 && (bytes[0] === 255 && bytes[1] === 254 || bytes[0] === 60 && bytes[1] === 0)) encoding = "utf-16le";
34
34
  else if (bytes.length >= 2 && (bytes[0] === 254 && bytes[1] === 255 || bytes[0] === 0 && bytes[1] === 60)) encoding = "utf-16be";
35
- try {
36
- return new TextDecoder(encoding, { fatal: true }).decode(bytes);
37
- } catch (cause) {
38
- throw new FolioDocxPackageInspectionError({
35
+ const decoded = Result.try({
36
+ try: () => new TextDecoder(encoding, { fatal: true }).decode(bytes),
37
+ catch: (cause) => new FolioDocxPackageInspectionError({
39
38
  message: `Failed to decode XML package part "${part}"`,
40
39
  code: "xml-decode-failed",
41
40
  part,
42
41
  cause
43
- });
44
- }
42
+ })
43
+ });
44
+ if (decoded.isOk()) return decoded.value;
45
+ throw decoded.error;
45
46
  };
46
47
  const parseContentTypes = (xml) => {
47
48
  const declarations = {
@@ -2,7 +2,7 @@ import { mergeParagraphFormatting } from "../utils/paragraphFormattingMerge.js";
2
2
  import { mergeTextFormatting } from "../utils/textFormattingMerge.js";
3
3
  import { BorderStyleSchema, ConditionalStyleTypeSchema, EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, StyleTypeSchema, TabLeaderSchema, TabStopAlignmentSchema, TableCellTextDirectionSchema, TableRowHeightRuleSchema, TableWidthTypeSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
4
4
  import { resolveThemeFontRef } from "./themeParser.js";
5
- import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
5
+ import { findChild, findChildren, getAttribute, getLocalName, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXmlDocument } from "./xmlParser.js";
6
6
  //#region src/docx/styleParser.ts
7
7
  /**
8
8
  * Parse text formatting properties (w:rPr)
@@ -224,9 +224,9 @@ function parseBorderSpec(border) {
224
224
  const space = parseNumericAttribute(border, "w", "space");
225
225
  if (space !== void 0) spec.space = space;
226
226
  const shadowAttr = getAttribute(border, "w", "shadow");
227
- if (shadowAttr) spec.shadow = shadowAttr === "1" || shadowAttr === "true";
227
+ if (shadowAttr) spec.shadow = parseOnOffValue(shadowAttr) ?? false;
228
228
  const frame = getAttribute(border, "w", "frame");
229
- if (frame) spec.frame = frame === "1" || frame === "true";
229
+ if (frame) spec.frame = parseOnOffValue(frame) ?? false;
230
230
  return spec;
231
231
  }
232
232
  /**
@@ -278,9 +278,9 @@ function parseParagraphProperties(pPr, theme) {
278
278
  const lineRule = narrowEnum(getAttribute(spacing, "w", "lineRule"), LineSpacingRuleSchema);
279
279
  if (lineRule) formatting.lineSpacingRule = lineRule;
280
280
  const beforeAuto = getAttribute(spacing, "w", "beforeAutospacing");
281
- if (beforeAuto) formatting.beforeAutospacing = beforeAuto === "1" || beforeAuto === "true";
281
+ if (beforeAuto) formatting.beforeAutospacing = parseOnOffValue(beforeAuto) ?? false;
282
282
  const afterAuto = getAttribute(spacing, "w", "afterAutospacing");
283
- if (afterAuto) formatting.afterAutospacing = afterAuto === "1" || afterAuto === "true";
283
+ if (afterAuto) formatting.afterAutospacing = parseOnOffValue(afterAuto) ?? false;
284
284
  }
285
285
  const ind = findChild(pPr, "w", "ind");
286
286
  if (ind) {
@@ -452,17 +452,17 @@ function parseTableLook(tblLook) {
452
452
  }
453
453
  }
454
454
  const firstColumn = getAttribute(tblLook, "w", "firstColumn");
455
- if (firstColumn) look.firstColumn = firstColumn === "1";
455
+ if (firstColumn) look.firstColumn = parseOnOffValue(firstColumn) ?? false;
456
456
  const firstRow = getAttribute(tblLook, "w", "firstRow");
457
- if (firstRow) look.firstRow = firstRow === "1";
457
+ if (firstRow) look.firstRow = parseOnOffValue(firstRow) ?? false;
458
458
  const lastColumn = getAttribute(tblLook, "w", "lastColumn");
459
- if (lastColumn) look.lastColumn = lastColumn === "1";
459
+ if (lastColumn) look.lastColumn = parseOnOffValue(lastColumn) ?? false;
460
460
  const lastRow = getAttribute(tblLook, "w", "lastRow");
461
- if (lastRow) look.lastRow = lastRow === "1";
461
+ if (lastRow) look.lastRow = parseOnOffValue(lastRow) ?? false;
462
462
  const noHBand = getAttribute(tblLook, "w", "noHBand");
463
- if (noHBand) look.noHBand = noHBand === "1";
463
+ if (noHBand) look.noHBand = parseOnOffValue(noHBand) ?? false;
464
464
  const noVBand = getAttribute(tblLook, "w", "noVBand");
465
- if (noVBand) look.noVBand = noVBand === "1";
465
+ if (noVBand) look.noVBand = parseOnOffValue(noVBand) ?? false;
466
466
  return Object.keys(look).length > 0 ? look : void 0;
467
467
  }
468
468
  /**
@@ -669,7 +669,7 @@ function parseStyle(styleEl, theme) {
669
669
  type: narrowEnum(rawType, StyleTypeSchema) ?? "paragraph"
670
670
  };
671
671
  const defaultAttr = getAttribute(styleEl, "w", "default");
672
- if (defaultAttr) style.default = defaultAttr === "1" || defaultAttr === "true";
672
+ if (defaultAttr) style.default = parseOnOffValue(defaultAttr) ?? false;
673
673
  const children = collectStyleChildren(styleEl);
674
674
  const nameEl = children.name;
675
675
  if (nameEl) {
@@ -871,10 +871,10 @@ function parseStyleDefinitionsFromDocument(doc, theme, resolvedStyles) {
871
871
  const latentStylesEl = findChild(doc, "w", "latentStyles");
872
872
  if (latentStylesEl) {
873
873
  const latentStyles = {
874
- defLockedState: getAttribute(latentStylesEl, "w", "defLockedState") === "1",
875
- defSemiHidden: getAttribute(latentStylesEl, "w", "defSemiHidden") === "1",
876
- defUnhideWhenUsed: getAttribute(latentStylesEl, "w", "defUnhideWhenUsed") === "1",
877
- defQFormat: getAttribute(latentStylesEl, "w", "defQFormat") === "1"
874
+ defLockedState: parseOnOffValue(getAttribute(latentStylesEl, "w", "defLockedState")) ?? false,
875
+ defSemiHidden: parseOnOffValue(getAttribute(latentStylesEl, "w", "defSemiHidden")) ?? false,
876
+ defUnhideWhenUsed: parseOnOffValue(getAttribute(latentStylesEl, "w", "defUnhideWhenUsed")) ?? false,
877
+ defQFormat: parseOnOffValue(getAttribute(latentStylesEl, "w", "defQFormat")) ?? false
878
878
  };
879
879
  const defUIPriority = parseNumericAttribute(latentStylesEl, "w", "defUIPriority");
880
880
  if (defUIPriority !== void 0) latentStyles.defUIPriority = defUIPriority;
@@ -272,13 +272,17 @@ declare function parseNumberingLevelAttribute(element: XmlElement | null | undef
272
272
  * (`5000`); normalize those to the ECMA-376 unit the layout engine expects.
273
273
  */
274
274
  declare function parseTableMeasurementValue(element: XmlElement | null | undefined, widthType: string): number | undefined;
275
+ /**
276
+ * Parse an OOXML `ST_OnOff` lexical value.
277
+ */
278
+ declare function parseOnOffValue(value: string | null | undefined): boolean | undefined;
275
279
  /**
276
280
  * Parse a boolean value from an attribute or element presence
277
281
  *
278
282
  * OOXML boolean conventions:
279
283
  * - Element presence with no val attribute = true
280
- * - w:val="true" or w:val="1" = true
281
- * - w:val="false" or w:val="0" = false
284
+ * - w:val="true", w:val="1", or w:val="on" = true
285
+ * - w:val="false", w:val="0", or w:val="off" = false
282
286
  *
283
287
  * @param element - Element to check
284
288
  * @param namespace - Namespace for val attribute
@@ -334,4 +338,4 @@ declare function mergeXmlnsDeclarations(inherited: Record<string, string>, eleme
334
338
  */
335
339
  declare function cloneWithXmlnsDeclarations(element: XmlElement, xmlnsDecls: Record<string, string>): XmlElement;
336
340
  //#endregion
337
- export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlElement, XmlNamespaceScope, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseTableMeasurementValue, parseXml, parseXmlDocument };
341
+ export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlElement, XmlNamespaceScope, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
@@ -438,8 +438,7 @@ function getTextContent(element) {
438
438
  function hasFlag(element, namespace, name) {
439
439
  const value = getAttribute(element, namespace, name);
440
440
  if (value === null) return false;
441
- if (value === "0" || value === "false" || value === "off") return false;
442
- return true;
441
+ return parseOnOffValue(value) ?? true;
443
442
  }
444
443
  /**
445
444
  * Check if a child element exists (used for boolean flags in OOXML)
@@ -515,12 +514,27 @@ function parseTableMeasurementValue(element, widthType) {
515
514
  return Number.isNaN(num) ? void 0 : num;
516
515
  }
517
516
  /**
517
+ * Parse an OOXML `ST_OnOff` lexical value.
518
+ */
519
+ function parseOnOffValue(value) {
520
+ if (value === null || value === void 0) return;
521
+ switch (value) {
522
+ case "1":
523
+ case "true":
524
+ case "on": return true;
525
+ case "0":
526
+ case "false":
527
+ case "off": return false;
528
+ default: return;
529
+ }
530
+ }
531
+ /**
518
532
  * Parse a boolean value from an attribute or element presence
519
533
  *
520
534
  * OOXML boolean conventions:
521
535
  * - Element presence with no val attribute = true
522
- * - w:val="true" or w:val="1" = true
523
- * - w:val="false" or w:val="0" = false
536
+ * - w:val="true", w:val="1", or w:val="on" = true
537
+ * - w:val="false", w:val="0", or w:val="off" = false
524
538
  *
525
539
  * @param element - Element to check
526
540
  * @param namespace - Namespace for val attribute
@@ -541,8 +555,7 @@ function parseBooleanElement(element, namespace = "w") {
541
555
  }
542
556
  }
543
557
  if (val === null) return true;
544
- if (val === "0" || val === "false" || val === "off") return false;
545
- return true;
558
+ return parseOnOffValue(val) ?? true;
546
559
  }
547
560
  /**
548
561
  * Deep find - search recursively for an element
@@ -699,4 +712,4 @@ function cloneWithXmlnsDeclarations(element, xmlnsDecls) {
699
712
  return element;
700
713
  }
701
714
  //#endregion
702
- export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseTableMeasurementValue, parseXml, parseXmlDocument };
715
+ export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
@@ -9,7 +9,6 @@ import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
9
9
  import { runShadingAttrsToShading } from "../../prosemirror/conversion/runShadingMark.js";
10
10
  import { directionIsRtl } from "../../prosemirror/paragraphDirection.js";
11
11
  import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
12
- import { NUMBER_FORMAT_VALUES } from "../../types/documentEnumValues.js";
13
12
  import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
14
13
  import { resolveThemeFont } from "../../utils/fontResolver.js";
15
14
  import { resolveShadingFill } from "../../utils/formatToStyle.js";
@@ -364,7 +363,7 @@ const resolveWesternThemeFont = (fontFamily, theme) => {
364
363
  return (themeRef ? resolveThemeFont(themeRef, theme?.fontScheme) : null) ?? fontFamily.ascii ?? fontFamily.hAnsi ?? void 0;
365
364
  };
366
365
  const resolveComplexScriptThemeFont = (fontFamily, theme) => {
367
- return (fontFamily.cstheme ? resolveThemeFont(fontFamily.cstheme, theme?.fontScheme) : null) ?? fontFamily.cs ?? void 0;
366
+ return (fontFamily.csTheme ? resolveThemeFont(fontFamily.csTheme, theme?.fontScheme) : null) ?? fontFamily.cs ?? void 0;
368
367
  };
369
368
  const resolveEastAsiaThemeFont = (fontFamily, theme) => {
370
369
  return (fontFamily.eastAsiaTheme ? resolveThemeFont(fontFamily.eastAsiaTheme, theme?.fontScheme) : null) ?? fontFamily.eastAsia ?? void 0;
@@ -650,78 +649,54 @@ function toListMarkerRevision(kind, info) {
650
649
  }
651
650
  function isAddedNumberingChange(change) {
652
651
  const previousFormatting = change.previousFormatting;
653
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && previousFormatting["numPr"] == null;
652
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && previousFormatting.numPr == null;
654
653
  }
655
654
  function isRemovedNumberingChange(change) {
656
655
  const previousFormatting = change.previousFormatting;
657
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting["numPr"]);
656
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr);
658
657
  }
659
658
  function isChangedNumberingChange(currentNumPr, change) {
660
659
  const previousFormatting = change.previousFormatting;
661
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting["numPr"]) && !areListNumPrEqual(previousFormatting["numPr"], currentNumPr);
660
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr) && !areListNumPrEqual(previousFormatting.numPr, currentNumPr);
662
661
  }
663
662
  function areListNumPrEqual(left, right) {
664
663
  return left.numId === right.numId && left.ilvl === right.ilvl;
665
664
  }
666
- function isRecord(value) {
667
- return typeof value === "object" && value !== null;
668
- }
669
665
  function isListNumPr(value) {
670
- if (!isRecord(value)) return false;
671
- const numId = value["numId"];
672
- const ilvl = value["ilvl"];
673
- return (numId === void 0 || typeof numId === "number") && (ilvl === void 0 || typeof ilvl === "number");
674
- }
675
- function isNumberFormat(value) {
676
- return NUMBER_FORMAT_VALUES.some((format) => format === value);
677
- }
678
- function readNumberFormats(value) {
679
- if (!Array.isArray(value)) return;
680
- const formats = [];
681
- for (const item of value) {
682
- if (!isNumberFormat(item)) return;
683
- formats.push(item);
684
- }
685
- return formats;
686
- }
687
- function readListMarkerSuffix(value) {
688
- if (value === "tab" || value === "space" || value === "nothing") return value;
689
- }
690
- function readListMarkerAlignment(value) {
691
- if (value === "left" || value === "center" || value === "right") return value;
666
+ return value !== void 0 && value !== null;
692
667
  }
693
668
  function toPreviousListAttrs(previousFormatting) {
694
669
  const attrs = {};
695
- const numPr = previousFormatting["numPr"];
670
+ const numPr = previousFormatting.numPr;
696
671
  if (isListNumPr(numPr)) attrs.numPr = numPr;
697
- const listIsBullet = previousFormatting["listIsBullet"];
698
- if (typeof listIsBullet === "boolean") attrs.listIsBullet = listIsBullet;
699
- const listIsLegal = previousFormatting["listIsLegal"];
700
- if (typeof listIsLegal === "boolean") attrs.listIsLegal = listIsLegal;
701
- const listMarker = previousFormatting["listMarker"];
702
- if (typeof listMarker === "string") attrs.listMarker = listMarker;
703
- const listNumFmt = previousFormatting["listNumFmt"];
704
- if (isNumberFormat(listNumFmt)) attrs.listNumFmt = listNumFmt;
705
- const listLevelNumFmts = readNumberFormats(previousFormatting["listLevelNumFmts"]);
706
- if (listLevelNumFmts) attrs.listLevelNumFmts = listLevelNumFmts;
707
- const listLevelStarts = previousFormatting["listLevelStarts"];
708
- if (Array.isArray(listLevelStarts) && listLevelStarts.every((value) => typeof value === "number")) attrs.listLevelStarts = listLevelStarts;
709
- const listAbstractNumId = previousFormatting["listAbstractNumId"];
710
- if (typeof listAbstractNumId === "number") attrs.listAbstractNumId = listAbstractNumId;
711
- const listStartOverride = previousFormatting["listStartOverride"];
712
- if (typeof listStartOverride === "number") attrs.listStartOverride = listStartOverride;
713
- const listMarkerHidden = previousFormatting["listMarkerHidden"];
714
- if (typeof listMarkerHidden === "boolean") attrs.listMarkerHidden = listMarkerHidden;
715
- const listMarkerFontFamily = previousFormatting["listMarkerFontFamily"];
716
- if (typeof listMarkerFontFamily === "string") attrs.listMarkerFontFamily = listMarkerFontFamily;
717
- const listMarkerFontSize = previousFormatting["listMarkerFontSize"];
718
- if (typeof listMarkerFontSize === "number") attrs.listMarkerFontSize = listMarkerFontSize;
719
- const listMarkerBold = previousFormatting["listMarkerBold"];
720
- if (typeof listMarkerBold === "boolean") attrs.listMarkerBold = listMarkerBold;
721
- const listMarkerAlignment = readListMarkerAlignment(previousFormatting["listMarkerAlignment"]);
722
- if (listMarkerAlignment) attrs.listMarkerAlignment = listMarkerAlignment;
723
- const listMarkerSuffix = readListMarkerSuffix(previousFormatting["listMarkerSuffix"]);
724
- if (listMarkerSuffix) attrs.listMarkerSuffix = listMarkerSuffix;
672
+ const listIsBullet = previousFormatting.listIsBullet;
673
+ if (listIsBullet !== void 0) attrs.listIsBullet = listIsBullet;
674
+ const listIsLegal = previousFormatting.listIsLegal;
675
+ if (listIsLegal !== void 0) attrs.listIsLegal = listIsLegal;
676
+ const listMarker = previousFormatting.listMarker;
677
+ if (listMarker !== void 0) attrs.listMarker = listMarker;
678
+ const listNumFmt = previousFormatting.listNumFmt;
679
+ if (listNumFmt !== void 0) attrs.listNumFmt = listNumFmt;
680
+ const listLevelNumFmts = previousFormatting.listLevelNumFmts;
681
+ if (listLevelNumFmts !== void 0) attrs.listLevelNumFmts = listLevelNumFmts;
682
+ const listLevelStarts = previousFormatting.listLevelStarts;
683
+ if (listLevelStarts !== void 0) attrs.listLevelStarts = listLevelStarts;
684
+ const listAbstractNumId = previousFormatting.listAbstractNumId;
685
+ if (listAbstractNumId !== void 0) attrs.listAbstractNumId = listAbstractNumId;
686
+ const listStartOverride = previousFormatting.listStartOverride;
687
+ if (listStartOverride !== void 0) attrs.listStartOverride = listStartOverride;
688
+ const listMarkerHidden = previousFormatting.listMarkerHidden;
689
+ if (listMarkerHidden !== void 0) attrs.listMarkerHidden = listMarkerHidden;
690
+ const listMarkerFontFamily = previousFormatting.listMarkerFontFamily;
691
+ if (listMarkerFontFamily !== void 0) attrs.listMarkerFontFamily = listMarkerFontFamily;
692
+ const listMarkerFontSize = previousFormatting.listMarkerFontSize;
693
+ if (listMarkerFontSize !== void 0) attrs.listMarkerFontSize = listMarkerFontSize;
694
+ const listMarkerBold = previousFormatting.listMarkerBold;
695
+ if (listMarkerBold !== void 0) attrs.listMarkerBold = listMarkerBold;
696
+ const listMarkerAlignment = previousFormatting.listMarkerAlignment;
697
+ if (listMarkerAlignment !== void 0) attrs.listMarkerAlignment = listMarkerAlignment;
698
+ const listMarkerSuffix = previousFormatting.listMarkerSuffix;
699
+ if (listMarkerSuffix !== void 0) attrs.listMarkerSuffix = listMarkerSuffix;
725
700
  return attrs;
726
701
  }
727
702
  function resolveDeletedListMarker(previousListAttrs, listCounters, listAbstractCounters, listSeenNumIds) {
@@ -848,8 +823,7 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
848
823
  if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
849
824
  if (directionIsRtl(pmAttrs.direction)) attrs.bidi = true;
850
825
  if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
851
- const pPrChange = pmAttrs._propertyChanges;
852
- const propertyChanges = Array.isArray(pPrChange) ? pPrChange : [];
826
+ const propertyChanges = pmAttrs._propertyChanges ?? [];
853
827
  let changedNumberingChange;
854
828
  if (pmAttrs.numPr) {
855
829
  const numPr = {};
@@ -3,6 +3,7 @@ import { FontStyle } from "./measureTypes.js";
3
3
  //#region src/layout-engine/measure/measureHelpers.d.ts
4
4
  declare const DEFAULT_FONT_SIZE = 11;
5
5
  declare const DEFAULT_FONT_FAMILY = "Calibri";
6
+ declare const DOCX_SCRIPT_FONT_SCALE = 0.75;
6
7
  /**
7
8
  * Build a measurement `FontStyle` from a run's formatting. Single source of
8
9
  * truth for run → FontStyle so every measurement path (layout line-breaking,
@@ -87,4 +88,4 @@ declare function halfPtToPx(halfPt: number): number;
87
88
  */
88
89
  declare function pxToHalfPt(px: number): number;
89
90
  //#endregion
90
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
91
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DOCX_SCRIPT_FONT_SCALE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
@@ -9,12 +9,14 @@ import { FONT_KERNING_MODE, getRunFontKerningMode } from "./textMeasurementPolic
9
9
  * char-offset geometry. The layout engine imports these directly so it never
10
10
  * transitively pulls the canvas measurement backend; the canvas implementation
11
11
  * (`measureContainer.ts`) consumes them too.
12
+ * Script sizing: eigenpal/docx-editor@585413d0 (Apache-2.0), modified for Folio.
12
13
  */
13
14
  const TWIPS_PER_INCH = 1440;
14
15
  const PX_PER_INCH = 96;
15
16
  const TWIPS_PER_PX = TWIPS_PER_INCH / PX_PER_INCH;
16
17
  const DEFAULT_FONT_SIZE = 11;
17
18
  const DEFAULT_FONT_FAMILY = "Calibri";
19
+ const DOCX_SCRIPT_FONT_SCALE = .75;
18
20
  /**
19
21
  * Build a measurement `FontStyle` from a run's formatting. Single source of
20
22
  * truth for run → FontStyle so every measurement path (layout line-breaking,
@@ -24,7 +26,8 @@ const DEFAULT_FONT_FAMILY = "Calibri";
24
26
  * and size fallbacks for runs that declare neither.
25
27
  */
26
28
  function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
27
- const fontSize = run.fontSize ?? fallbackFontSize;
29
+ const baseFontSize = run.fontSize ?? fallbackFontSize;
30
+ const fontSize = run.superscript || run.subscript ? baseFontSize * DOCX_SCRIPT_FONT_SCALE : baseFontSize;
28
31
  return {
29
32
  fontFamily: run.fontFamily ?? fallbackFontFamily,
30
33
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
@@ -168,4 +171,4 @@ function pxToHalfPt(px) {
168
171
  return pxToPt(px) * 2;
169
172
  }
170
173
  //#endregion
171
- export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
174
+ export { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DOCX_SCRIPT_FONT_SCALE, buildFontString, buildRunFontStyle, clearFontResolvedCache, findCharacterAtX, getResolvedData, getXForCharacter, halfPtToPx, ptToPx, pxToHalfPt, pxToPt, pxToTwips, twipsToPx };
@@ -1063,6 +1063,12 @@ type Page = {
1063
1063
  fragments: Fragment[];
1064
1064
  /** Page margins. */
1065
1065
  margins: PageMargins;
1066
+ /**
1067
+ * Authored section margins before header/footer clearance expands the body
1068
+ * content box insets. Page-margin-relative anchors use these page-setup
1069
+ * landmarks instead of furniture-expanded content insets.
1070
+ */
1071
+ authoredMargins?: PageMargins;
1066
1072
  /** Page size (width, height). */
1067
1073
  size: {
1068
1074
  w: number;