@stll/folio-core 0.49.0 → 0.50.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/dist/ai-edits/headless.d.ts +37 -1
- package/dist/ai-edits/headless.js +47 -16
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +8 -7
- package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
- package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
- package/dist/display-list/selectDisplayPages.d.ts +6 -0
- package/dist/display-list/selectDisplayPages.js +44 -0
- package/dist/export-pdf.d.ts +35 -2
- package/dist/export-pdf.js +51 -20
- package/dist/fonts/fontsourceFaces.d.ts +47 -0
- package/dist/fonts/fontsourceFaces.js +230 -0
- package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
- package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
- package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
- package/dist/layout-bridge/convert/flowBorders.js +31 -0
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
- package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
- package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
- package/dist/layout-bridge/convert/imageConversion.js +118 -0
- package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
- package/dist/layout-bridge/convert/listMarkers.js +98 -0
- package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
- package/dist/layout-bridge/convert/noteReferences.js +29 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
- package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
- package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
- package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
- package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
- package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
- package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
- package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
- package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
- package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
- package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
- package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
- package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
- package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
- package/dist/layout-bridge/convert/tableConversion.js +303 -0
- package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
- package/dist/layout-bridge/convert/textBoxFill.js +42 -0
- package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
- package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
- package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2082
- package/dist/layout-bridge/engine/hitTest.js +1 -1
- package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
- package/dist/layout-bridge/engine/measuring/index.js +2 -1
- package/dist/layout-bridge/engine/selectionRects.js +1 -1
- package/dist/layout-engine/imageLayout.d.ts +9 -0
- package/dist/layout-engine/imageLayout.js +48 -0
- package/dist/layout-engine/index.d.ts +3 -18
- package/dist/layout-engine/index.js +12 -937
- package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
- package/dist/layout-engine/layoutFlowShared.js +8 -0
- package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
- package/dist/layout-engine/measure/crossRunWords.js +172 -0
- package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
- package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
- package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
- package/dist/layout-engine/measure/justification.d.ts +32 -0
- package/dist/layout-engine/measure/justification.js +83 -0
- package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
- package/dist/layout-engine/measure/lineFitting.js +145 -0
- package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
- package/dist/layout-engine/measure/lineTypography.js +124 -0
- package/dist/layout-engine/measure/measureBlocks.js +2 -1
- package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
- package/dist/layout-engine/measure/measureParagraph.js +32 -734
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
- package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
- package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
- package/dist/layout-engine/measure/paragraphTabs.js +78 -0
- package/dist/layout-engine/paragraphLayout.d.ts +23 -0
- package/dist/layout-engine/paragraphLayout.js +204 -0
- package/dist/layout-engine/sectionLayout.d.ts +45 -0
- package/dist/layout-engine/sectionLayout.js +216 -0
- package/dist/layout-engine/tableLayout.d.ts +17 -0
- package/dist/layout-engine/tableLayout.js +369 -0
- package/dist/layout-engine/textBoxLayout.d.ts +15 -0
- package/dist/layout-engine/textBoxLayout.js +137 -0
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
- package/dist/paged-layout/sectionBlockWidths.js +1 -1
- package/dist/server.d.ts +3 -2
- package/package.json +2 -2
|
@@ -35,6 +35,34 @@ type FolioApplyOperationsOptions = {
|
|
|
35
35
|
};
|
|
36
36
|
/** Options for {@link FolioDocxReviewer.applyDocumentOperations}. */
|
|
37
37
|
type FolioApplyDocumentOperationsOptions = Omit<FolioApplyOperationsOptions, "mode">;
|
|
38
|
+
/**
|
|
39
|
+
* Why a save rewrote the whole package instead of patching the edited
|
|
40
|
+
* paragraphs into the original: `structuralChange` (paragraphs added or
|
|
41
|
+
* removed, or styles, headers, footers or final section properties staged),
|
|
42
|
+
* `untrackedChange` (an edit the change tracker cannot key to a paragraph), or
|
|
43
|
+
* `selectiveDeclined` (the patch could not express the edit safely).
|
|
44
|
+
*/
|
|
45
|
+
type FolioReviewerRepackReason = "structuralChange" | "untrackedChange" | "selectiveDeclined";
|
|
46
|
+
/** Options for {@link FolioDocxReviewer.save}. */
|
|
47
|
+
type FolioReviewerSaveOptions = {
|
|
48
|
+
/**
|
|
49
|
+
* `"allow"` (default) falls back to a full repack when the selective patch
|
|
50
|
+
* does not apply; `"refuse"` reports the reason and writes nothing.
|
|
51
|
+
*/
|
|
52
|
+
repack?: "allow" | "refuse";
|
|
53
|
+
};
|
|
54
|
+
/** Result of {@link FolioDocxReviewer.save}. */
|
|
55
|
+
type FolioReviewerSaveResult = {
|
|
56
|
+
type: "selective";
|
|
57
|
+
buffer: ArrayBuffer;
|
|
58
|
+
} | {
|
|
59
|
+
type: "full-repack";
|
|
60
|
+
reason: FolioReviewerRepackReason;
|
|
61
|
+
buffer: ArrayBuffer;
|
|
62
|
+
} | {
|
|
63
|
+
type: "repackRefused";
|
|
64
|
+
reason: FolioReviewerRepackReason;
|
|
65
|
+
};
|
|
38
66
|
/** Options for {@link FolioDocxReviewer.getContentAsText}. */
|
|
39
67
|
type FolioGetContentAsTextOptions = {
|
|
40
68
|
/**
|
|
@@ -248,6 +276,8 @@ type FolioReviewReplyInput = {
|
|
|
248
276
|
/** Reply author; defaults to the reviewer's author. */
|
|
249
277
|
author?: string;
|
|
250
278
|
initials?: string;
|
|
279
|
+
/** ISO-8601 reply date; defaults to the wall clock. */
|
|
280
|
+
date?: string;
|
|
251
281
|
};
|
|
252
282
|
/** A comment thread discovered in the document. */
|
|
253
283
|
type FolioReviewComment = {
|
|
@@ -506,6 +536,12 @@ declare class FolioDocxReviewer {
|
|
|
506
536
|
* structural edits — the same two-tier path the editor's save uses.
|
|
507
537
|
*/
|
|
508
538
|
toBuffer(): Promise<ArrayBuffer>;
|
|
539
|
+
/**
|
|
540
|
+
* Serialise like {@link toBuffer} and report which path wrote the package.
|
|
541
|
+
* With `repack: "refuse"`, a save the selective patch cannot express
|
|
542
|
+
* returns the reason instead of rewriting every part.
|
|
543
|
+
*/
|
|
544
|
+
save({ repack: repackPolicy }?: FolioReviewerSaveOptions): Promise<FolioReviewerSaveResult>;
|
|
509
545
|
private assertResolvedStoriesSerialized;
|
|
510
546
|
/**
|
|
511
547
|
* Attempt the selective patch, treating a throw the same as a decline. Odd
|
|
@@ -580,4 +616,4 @@ type ApplyFolioAIEditsToBufferResult = FolioAIEditApplyResult & {
|
|
|
580
616
|
*/
|
|
581
617
|
declare const applyFolioAIEditsToBuffer: (buffer: ArrayBuffer, operations: FolioAIEditOperation[], options?: ApplyFolioAIEditsToBufferOptions) => Promise<ApplyFolioAIEditsToBufferResult>;
|
|
582
618
|
//#endregion
|
|
583
|
-
export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioGetContentAsTextOptions, FolioMatchStoryTableGeometryOptions, FolioNumberingLevel, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, FolioReviewedStory, FolioReviewedView, type FolioRevisionStamp, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, getFolioDocxComparisonAccess, isFolioResolvedReviewedView, isFolioReviewedView };
|
|
619
|
+
export { ApplyFolioAIEditsToBufferOptions, ApplyFolioAIEditsToBufferResult, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsOptions, FolioApplyDocumentOperationsToStoryOptions, FolioApplyOperationsOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioDocxReviewer, FolioDocxReviewerOptions, FolioEditableDocumentStoryHandle, FolioGetContentAsTextOptions, FolioMatchStoryTableGeometryOptions, FolioNumberingLevel, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, type FolioReviewChange, FolioReviewChangeFilter, type FolioReviewChangeKind, FolioReviewComment, FolioReviewCommentFilter, FolioReviewCommentReply, FolioReviewReplyInput, FolioReviewedStory, FolioReviewedView, FolioReviewerRepackReason, FolioReviewerSaveOptions, FolioReviewerSaveResult, type FolioRevisionStamp, UnsupportedFolioReviewedViewError, applyFolioAIEditsToBuffer, getFolioDocxComparisonAccess, isFolioResolvedReviewedView, isFolioReviewedView };
|
|
@@ -68,16 +68,10 @@ import JSZip from "jszip";
|
|
|
68
68
|
*/
|
|
69
69
|
let commentIdCursor = Date.now();
|
|
70
70
|
let undoHandleCursor = Date.now();
|
|
71
|
-
|
|
72
|
-
* Build the note-free comment thread the apply layer references by id.
|
|
73
|
-
* Mirrors `commentsHelpers.createComment` (a pure object literal there) so a
|
|
74
|
-
* headless `commentOnBlock` / `comment` op serialises the same `comments.xml`
|
|
75
|
-
* shape the editor produces.
|
|
76
|
-
*/
|
|
77
|
-
const createReviewerComment = (id, text, author) => ({
|
|
71
|
+
const createReviewerComment = ({ id, text, author, date }) => ({
|
|
78
72
|
id,
|
|
79
73
|
author,
|
|
80
|
-
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
74
|
+
date: date ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
81
75
|
content: [{
|
|
82
76
|
type: "paragraph",
|
|
83
77
|
formatting: {},
|
|
@@ -848,7 +842,12 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
848
842
|
...tableTemplates !== void 0 && { tableTemplates },
|
|
849
843
|
...replacementBackground !== void 0 && { replacementBackground },
|
|
850
844
|
createCommentId: (text) => {
|
|
851
|
-
const comment = createReviewerComment(
|
|
845
|
+
const comment = createReviewerComment({
|
|
846
|
+
id: this.nextCommentId(),
|
|
847
|
+
text,
|
|
848
|
+
author: this.author,
|
|
849
|
+
date: revisionStamp?.date
|
|
850
|
+
});
|
|
852
851
|
this.createdComments.push(comment);
|
|
853
852
|
return comment.id;
|
|
854
853
|
},
|
|
@@ -1080,7 +1079,8 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
1080
1079
|
const reply = createReply([...this.baseDocument.package.document.comments ?? [], ...this.createdComments], parentId, {
|
|
1081
1080
|
author: input.author ?? this.author,
|
|
1082
1081
|
text: input.text,
|
|
1083
|
-
...input.initials !== void 0 ? { initials: input.initials } : {}
|
|
1082
|
+
...input.initials !== void 0 ? { initials: input.initials } : {},
|
|
1083
|
+
...input.date !== void 0 && { date: input.date }
|
|
1084
1084
|
});
|
|
1085
1085
|
if (!reply) return null;
|
|
1086
1086
|
this.createdComments.push(reply);
|
|
@@ -1344,12 +1344,21 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
1344
1344
|
structuralChange ||= hasStructuralChanges(entry.state);
|
|
1345
1345
|
untrackedChanges ||= hasUntrackedChanges(entry.state);
|
|
1346
1346
|
}
|
|
1347
|
+
let path = {
|
|
1348
|
+
type: "selective-first",
|
|
1349
|
+
changedParaIds
|
|
1350
|
+
};
|
|
1351
|
+
if (structuralChange) path = {
|
|
1352
|
+
type: "full-repack",
|
|
1353
|
+
reason: "structuralChange"
|
|
1354
|
+
};
|
|
1355
|
+
else if (untrackedChanges) path = {
|
|
1356
|
+
type: "full-repack",
|
|
1357
|
+
reason: "untrackedChange"
|
|
1358
|
+
};
|
|
1347
1359
|
return {
|
|
1348
1360
|
document: this.documentFromStateSnapshot(snapshot),
|
|
1349
|
-
path
|
|
1350
|
-
type: "selective-first",
|
|
1351
|
-
changedParaIds
|
|
1352
|
-
},
|
|
1361
|
+
path,
|
|
1353
1362
|
changedNoteParaIds,
|
|
1354
1363
|
sectionReferenceRemovals: snapshot.sectionReferenceRemovals,
|
|
1355
1364
|
sectionEndpointRemoval: getTrackedSectionEndpointRemoval(snapshot.mainState),
|
|
@@ -1363,12 +1372,30 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
1363
1372
|
* structural edits — the same two-tier path the editor's save uses.
|
|
1364
1373
|
*/
|
|
1365
1374
|
async toBuffer() {
|
|
1375
|
+
const result = await this.save();
|
|
1376
|
+
if (result.type === "repackRefused") return panic("A save that allows a full repack refused to repack", { result });
|
|
1377
|
+
return result.buffer;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Serialise like {@link toBuffer} and report which path wrote the package.
|
|
1381
|
+
* With `repack: "refuse"`, a save the selective patch cannot express
|
|
1382
|
+
* returns the reason instead of rewriting every part.
|
|
1383
|
+
*/
|
|
1384
|
+
async save({ repack: repackPolicy = "allow" } = {}) {
|
|
1366
1385
|
const save = this.captureSaveSnapshot();
|
|
1367
1386
|
const selective = save.path.type === "selective-first" ? await this.trySelectiveSave(save.document, save.path.changedParaIds) : null;
|
|
1368
1387
|
if (selective) {
|
|
1369
1388
|
await this.assertResolvedStoriesSerialized(selective, save.resolvedStoryExpectations);
|
|
1370
|
-
return
|
|
1389
|
+
return {
|
|
1390
|
+
type: "selective",
|
|
1391
|
+
buffer: selective
|
|
1392
|
+
};
|
|
1371
1393
|
}
|
|
1394
|
+
const reason = save.path.type === "full-repack" ? save.path.reason : "selectiveDeclined";
|
|
1395
|
+
if (repackPolicy === "refuse") return {
|
|
1396
|
+
type: "repackRefused",
|
|
1397
|
+
reason
|
|
1398
|
+
};
|
|
1372
1399
|
const repackDocument = {
|
|
1373
1400
|
...save.document,
|
|
1374
1401
|
originalBuffer: this.originalBuffer
|
|
@@ -1385,7 +1412,11 @@ var FolioDocxReviewer = class FolioDocxReviewer {
|
|
|
1385
1412
|
repack: repackReferences
|
|
1386
1413
|
}) : await repackReferences();
|
|
1387
1414
|
await this.assertResolvedStoriesSerialized(buffer, save.resolvedStoryExpectations);
|
|
1388
|
-
return
|
|
1415
|
+
return {
|
|
1416
|
+
type: "full-repack",
|
|
1417
|
+
reason,
|
|
1418
|
+
buffer
|
|
1419
|
+
};
|
|
1389
1420
|
}
|
|
1390
1421
|
async assertResolvedStoriesSerialized(buffer, expectations) {
|
|
1391
1422
|
if (expectations.length === 0) return;
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { DisplayColor, DisplayFontFace, DisplayImageSource, DisplayList, DisplayPage } from "../types.js";
|
|
2
2
|
//#region src/display-list/dom/renderDisplayListToDom.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* How the backend addresses image and embedded-font bytes: `objectUrl` mints
|
|
5
|
+
* a `blob:` URL where the runtime can (a live page, which revokes them), and
|
|
6
|
+
* `dataUrl` always inlines the bytes (markup that leaves the process, such as
|
|
7
|
+
* a serialized page, where a `blob:` URL would resolve to nothing).
|
|
8
|
+
*/
|
|
9
|
+
type DisplayBinaryUrls = "objectUrl" | "dataUrl";
|
|
3
10
|
type RenderDisplayListOptions = {
|
|
4
11
|
/** Document to create elements in. */
|
|
5
12
|
readonly doc: Document;
|
|
6
13
|
/** Page background, painted before any primitive. */
|
|
7
14
|
readonly pageBackground?: DisplayColor;
|
|
15
|
+
/** Defaults to `objectUrl`. */
|
|
16
|
+
readonly binaryUrls?: DisplayBinaryUrls;
|
|
8
17
|
};
|
|
9
18
|
/**
|
|
10
19
|
* A page's glyph runs and images index tables that live on the list, and an
|
|
@@ -23,4 +32,4 @@ declare const renderDisplayPageToDom: (page: DisplayPage, options: RenderDisplay
|
|
|
23
32
|
/** One `div.layout-page` per display page, in page order. */
|
|
24
33
|
declare const renderDisplayListToDom: (list: DisplayList, options: RenderDisplayListOptions) => HTMLElement[];
|
|
25
34
|
//#endregion
|
|
26
|
-
export { RenderDisplayListOptions, RenderDisplayPageOptions, renderDisplayListToDom, renderDisplayPageToDom };
|
|
35
|
+
export { DisplayBinaryUrls, RenderDisplayListOptions, RenderDisplayPageOptions, renderDisplayListToDom, renderDisplayPageToDom };
|
|
@@ -103,8 +103,8 @@ const resolveImage = (ref, images) => {
|
|
|
103
103
|
};
|
|
104
104
|
/** A `Blob` part cannot be backed by shared memory, so prove it is not. */
|
|
105
105
|
const isBlobPart = (bytes) => bytes.buffer instanceof ArrayBuffer;
|
|
106
|
-
const binarySrc = (bytes, mimeType) => {
|
|
107
|
-
if (isBlobPart(bytes) && typeof Blob === "function" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function") return URL.createObjectURL(new Blob([bytes], { type: mimeType }));
|
|
106
|
+
const binarySrc = (bytes, mimeType, urls) => {
|
|
107
|
+
if (urls === "objectUrl" && isBlobPart(bytes) && typeof Blob === "function" && typeof URL !== "undefined" && typeof URL.createObjectURL === "function") return URL.createObjectURL(new Blob([bytes], { type: mimeType }));
|
|
108
108
|
return bytesToDataUrl(bytes, mimeType);
|
|
109
109
|
};
|
|
110
110
|
const fontFaceRule = (face, embedded, src) => `@font-face { font-family: ${quoteFontFamily(embedded.id)}; src: url("${src}"); font-weight: ${face.weight}; font-style: ${face.italic ? "italic" : "normal"}; }`;
|
|
@@ -113,14 +113,14 @@ const fontFaceRule = (face, embedded, src) => `@font-face { font-family: ${quote
|
|
|
113
113
|
* once per render and shared by every page: an id maps to one blob, so the
|
|
114
114
|
* bytes are held once and one revoke releases them.
|
|
115
115
|
*/
|
|
116
|
-
const embeddedFontFaceCss = (fonts) => {
|
|
116
|
+
const embeddedFontFaceCss = (fonts, urls) => {
|
|
117
117
|
const srcById = /* @__PURE__ */ new Map();
|
|
118
118
|
const rules = [];
|
|
119
119
|
for (const face of fonts) {
|
|
120
120
|
const embedded = face.embedded;
|
|
121
121
|
if (embedded === void 0) continue;
|
|
122
122
|
const cached = srcById.get(embedded.id);
|
|
123
|
-
const src = cached ?? binarySrc(embedded.bytes, FONT_MIME_TYPE);
|
|
123
|
+
const src = cached ?? binarySrc(embedded.bytes, FONT_MIME_TYPE, urls);
|
|
124
124
|
if (cached === void 0) srcById.set(embedded.id, src);
|
|
125
125
|
rules.push(fontFaceRule(face, embedded, src));
|
|
126
126
|
}
|
|
@@ -399,7 +399,7 @@ const paintImage = ({ image, rect, crop, opacity, brightness, contrast }, contex
|
|
|
399
399
|
element.style.height = px(fullHeight);
|
|
400
400
|
}
|
|
401
401
|
element.alt = "";
|
|
402
|
-
element.src = binarySrc(source.bytes, IMAGE_MIME_TYPES[source.format]);
|
|
402
|
+
element.src = binarySrc(source.bytes, IMAGE_MIME_TYPES[source.format], context.binaryUrls);
|
|
403
403
|
clip.append(element);
|
|
404
404
|
context.parent.append(clip);
|
|
405
405
|
};
|
|
@@ -497,6 +497,7 @@ const renderPage = (page, options) => {
|
|
|
497
497
|
}
|
|
498
498
|
const context = {
|
|
499
499
|
doc: options.doc,
|
|
500
|
+
binaryUrls: options.binaryUrls ?? "objectUrl",
|
|
500
501
|
fonts: options.fonts,
|
|
501
502
|
images: options.images,
|
|
502
503
|
parent: element,
|
|
@@ -595,11 +596,11 @@ const paintRegionTree = ({ primitives, regions }, context) => {
|
|
|
595
596
|
};
|
|
596
597
|
const renderDisplayPageToDom = (page, options) => renderPage(page, {
|
|
597
598
|
...options,
|
|
598
|
-
fontFaceCss: embeddedFontFaceCss(options.fonts)
|
|
599
|
+
fontFaceCss: embeddedFontFaceCss(options.fonts, options.binaryUrls ?? "objectUrl")
|
|
599
600
|
});
|
|
600
601
|
/** One `div.layout-page` per display page, in page order. */
|
|
601
602
|
const renderDisplayListToDom = (list, options) => {
|
|
602
|
-
const fontFaceCss = embeddedFontFaceCss(list.fonts);
|
|
603
|
+
const fontFaceCss = embeddedFontFaceCss(list.fonts, options.binaryUrls ?? "objectUrl");
|
|
603
604
|
return list.pages.map((page, pageIndex) => renderPage(page, {
|
|
604
605
|
...options,
|
|
605
606
|
fonts: list.fonts,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DisplayColor, DisplayList } from "../types.js";
|
|
2
|
+
//#region src/display-list/html/renderDisplayListToHtml.d.ts
|
|
3
|
+
declare const escapeHtmlText: (value: string) => string;
|
|
4
|
+
type RenderDisplayListToHtmlOptions = {
|
|
5
|
+
/** `@font-face` rules for the families the list names, placed in the head. */
|
|
6
|
+
readonly fontFaceCss?: string;
|
|
7
|
+
/** The document title. */
|
|
8
|
+
readonly title?: string;
|
|
9
|
+
/** Space between pages. Defaults to 0: pages stack flush. */
|
|
10
|
+
readonly pageGapPx?: number;
|
|
11
|
+
/** CSS color behind the pages. Defaults to white. */
|
|
12
|
+
readonly canvasColor?: string;
|
|
13
|
+
/** Page background, painted before any primitive. */
|
|
14
|
+
readonly pageBackground?: DisplayColor;
|
|
15
|
+
};
|
|
16
|
+
/** The pages' markup, one slot per display page, in page order. */
|
|
17
|
+
declare const renderDisplayListPagesToHtml: (list: DisplayList, options?: Pick<RenderDisplayListToHtmlOptions, "pageBackground">) => string[];
|
|
18
|
+
/** A complete HTML document showing every page of the list. */
|
|
19
|
+
declare const renderDisplayListToHtml: (list: DisplayList, options?: RenderDisplayListToHtmlOptions) => string;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { RenderDisplayListToHtmlOptions, escapeHtmlText, renderDisplayListPagesToHtml, renderDisplayListToHtml };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { renderDisplayListToDom } from "../dom/renderDisplayListToDom.js";
|
|
2
|
+
//#region src/display-list/html/renderDisplayListToHtml.ts
|
|
3
|
+
/**
|
|
4
|
+
* A display list as one HTML document, with no browser and no DOM.
|
|
5
|
+
*
|
|
6
|
+
* The DOM backend paints into whatever `Document` it is handed. This module
|
|
7
|
+
* hands it a document with only what the backend reaches for and serializes
|
|
8
|
+
* the result, so a server, a CLI or a harness can produce the page a browser
|
|
9
|
+
* would show from the same painter the editor uses. Binary data (images,
|
|
10
|
+
* embedded faces) is inlined as `data:` URLs, because a `blob:` URL minted in
|
|
11
|
+
* this process resolves to nothing once the markup leaves it.
|
|
12
|
+
*/
|
|
13
|
+
const VOID_TAGS = /* @__PURE__ */ new Set([
|
|
14
|
+
"img",
|
|
15
|
+
"br",
|
|
16
|
+
"hr"
|
|
17
|
+
]);
|
|
18
|
+
const createStubElement = (tagName) => {
|
|
19
|
+
const children = [];
|
|
20
|
+
const attributes = {};
|
|
21
|
+
return {
|
|
22
|
+
tagName,
|
|
23
|
+
style: {},
|
|
24
|
+
dataset: {},
|
|
25
|
+
attributes,
|
|
26
|
+
children,
|
|
27
|
+
className: "",
|
|
28
|
+
id: "",
|
|
29
|
+
textContent: "",
|
|
30
|
+
alt: "",
|
|
31
|
+
src: "",
|
|
32
|
+
href: "",
|
|
33
|
+
title: "",
|
|
34
|
+
append: (...nodes) => {
|
|
35
|
+
children.push(...nodes);
|
|
36
|
+
},
|
|
37
|
+
appendChild: (node) => {
|
|
38
|
+
children.push(node);
|
|
39
|
+
return node;
|
|
40
|
+
},
|
|
41
|
+
setAttribute: (name, value) => {
|
|
42
|
+
attributes[name] = value;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A `Document` with only what the DOM backend reaches for. A backend that
|
|
48
|
+
* grows a new requirement throws on the missing property here rather than
|
|
49
|
+
* silently serializing less than it painted.
|
|
50
|
+
*/
|
|
51
|
+
const stubDocument = () => {
|
|
52
|
+
return { createElement: createStubElement };
|
|
53
|
+
};
|
|
54
|
+
const asStub = (element) => element;
|
|
55
|
+
/** A vendor property is camelCase with no leading capital, so its leading dash is explicit. */
|
|
56
|
+
const VENDOR_PREFIXES = [
|
|
57
|
+
"webkit-",
|
|
58
|
+
"moz-",
|
|
59
|
+
"ms-",
|
|
60
|
+
"o-"
|
|
61
|
+
];
|
|
62
|
+
const toKebabCase = (name) => {
|
|
63
|
+
const hyphenated = name.replaceAll(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`);
|
|
64
|
+
return VENDOR_PREFIXES.some((prefix) => hyphenated.startsWith(prefix)) ? `-${hyphenated}` : hyphenated;
|
|
65
|
+
};
|
|
66
|
+
const escapeHtmlText = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
67
|
+
const escapeAttribute = (value) => escapeHtmlText(value).replaceAll("\"", """);
|
|
68
|
+
const serializeStyle = (style) => Object.entries(style).map(([property, value]) => `${toKebabCase(property)}: ${value}`).join("; ");
|
|
69
|
+
/**
|
|
70
|
+
* Serialized without one character of added whitespace: the backend sets
|
|
71
|
+
* `white-space: pre` on every run, so an indentation newline would be painted.
|
|
72
|
+
*/
|
|
73
|
+
const RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["style", "script"]);
|
|
74
|
+
/** Text placed inside a `<style>` or `<script>`: a `</` cannot close the element early. */
|
|
75
|
+
const rawElementText = (text) => text.replaceAll("</", "<\\/");
|
|
76
|
+
const serializeElement = (element) => {
|
|
77
|
+
const attributes = [];
|
|
78
|
+
if (element.className !== "") attributes.push(`class="${escapeAttribute(element.className)}"`);
|
|
79
|
+
if (element.id !== "") attributes.push(`id="${escapeAttribute(element.id)}"`);
|
|
80
|
+
if (element.href !== "") attributes.push(`href="${escapeAttribute(element.href)}"`);
|
|
81
|
+
if (element.src !== "") attributes.push(`src="${escapeAttribute(element.src)}"`);
|
|
82
|
+
if (element.title !== "") attributes.push(`title="${escapeAttribute(element.title)}"`);
|
|
83
|
+
if (element.tagName === "img") attributes.push(`alt="${escapeAttribute(element.alt)}"`);
|
|
84
|
+
for (const [name, value] of Object.entries(element.attributes)) attributes.push(`${name}="${escapeAttribute(value)}"`);
|
|
85
|
+
for (const [key, value] of Object.entries(element.dataset)) attributes.push(`data-${toKebabCase(key)}="${escapeAttribute(value)}"`);
|
|
86
|
+
const style = serializeStyle(element.style);
|
|
87
|
+
if (style !== "") attributes.push(`style="${escapeAttribute(style)}"`);
|
|
88
|
+
const open = [element.tagName, ...attributes].join(" ");
|
|
89
|
+
if (VOID_TAGS.has(element.tagName)) return `<${open} />`;
|
|
90
|
+
if (element.children.length > 0) return `<${open}>${element.children.map(serializeElement).join("")}</${element.tagName}>`;
|
|
91
|
+
return `<${open}>${RAW_TEXT_TAGS.has(element.tagName) ? rawElementText(element.textContent) : escapeHtmlText(element.textContent)}</${element.tagName}>`;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Each page sits in a slot of whole pixels. A page height is fractional (A4 is
|
|
95
|
+
* 1122.52 px), so pages stacked in normal flow would start at fractional
|
|
96
|
+
* offsets; the slot rounds the offset while the page keeps its exact size.
|
|
97
|
+
*/
|
|
98
|
+
const pageSlot = (markup, page) => `<div class="layout-page-slot" style="position: relative; overflow: hidden; width: ${String(Math.ceil(page.widthPx))}px; height: ${String(Math.ceil(page.heightPx))}px">${markup}</div>`;
|
|
99
|
+
/** The pages' markup, one slot per display page, in page order. */
|
|
100
|
+
const renderDisplayListPagesToHtml = (list, options = {}) => renderDisplayListToDom(list, {
|
|
101
|
+
doc: stubDocument(),
|
|
102
|
+
binaryUrls: "dataUrl",
|
|
103
|
+
...options.pageBackground !== void 0 && { pageBackground: options.pageBackground }
|
|
104
|
+
}).map((element, index) => {
|
|
105
|
+
const markup = serializeElement(asStub(element));
|
|
106
|
+
const page = list.pages.at(index);
|
|
107
|
+
return page === void 0 ? markup : pageSlot(markup, page);
|
|
108
|
+
});
|
|
109
|
+
/** A complete HTML document showing every page of the list. */
|
|
110
|
+
const renderDisplayListToHtml = (list, options = {}) => {
|
|
111
|
+
const gap = options.pageGapPx ?? 0;
|
|
112
|
+
const canvas = rawElementText(options.canvasColor ?? "#fff");
|
|
113
|
+
const layout = gap > 0 ? `body { display: flex; flex-direction: column; align-items: center; gap: ${String(gap)}px; padding: ${String(gap)}px 0; }` : "";
|
|
114
|
+
return [
|
|
115
|
+
"<!doctype html>",
|
|
116
|
+
`<html><head><meta charset="utf-8"><title>${escapeHtmlText(options.title ?? "")}</title><style>`,
|
|
117
|
+
rawElementText(options.fontFaceCss ?? ""),
|
|
118
|
+
`html, body { margin: 0; padding: 0; background: ${canvas}; }`,
|
|
119
|
+
layout,
|
|
120
|
+
"</style></head><body>",
|
|
121
|
+
renderDisplayListPagesToHtml(list, options).join(""),
|
|
122
|
+
"</body></html>"
|
|
123
|
+
].join("\n");
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
export { escapeHtmlText, renderDisplayListPagesToHtml, renderDisplayListToHtml };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DisplayList } from "./types.js";
|
|
2
|
+
//#region src/display-list/selectDisplayPages.d.ts
|
|
3
|
+
/** Keep `pageIndices` (0-based, in the order given; duplicates are dropped). */
|
|
4
|
+
declare const selectDisplayPages: (list: DisplayList, pageIndices: readonly number[]) => DisplayList;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { selectDisplayPages };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//#region src/display-list/selectDisplayPages.ts
|
|
2
|
+
/** Keep `pageIndices` (0-based, in the order given; duplicates are dropped). */
|
|
3
|
+
const selectDisplayPages = (list, pageIndices) => {
|
|
4
|
+
const kept = [...new Set(pageIndices)].filter((index) => Number.isInteger(index) && index >= 0 && index < list.pages.length);
|
|
5
|
+
const remap = new Map(kept.map((original, next) => [original, next]));
|
|
6
|
+
const relink = (link) => {
|
|
7
|
+
if (link.target.kind !== "page") return [link];
|
|
8
|
+
const pageIndex = remap.get(link.target.pageIndex);
|
|
9
|
+
return pageIndex === void 0 ? [] : [{
|
|
10
|
+
...link,
|
|
11
|
+
target: {
|
|
12
|
+
...link.target,
|
|
13
|
+
pageIndex
|
|
14
|
+
}
|
|
15
|
+
}];
|
|
16
|
+
};
|
|
17
|
+
const pages = kept.flatMap((index) => {
|
|
18
|
+
const page = list.pages[index];
|
|
19
|
+
return page === void 0 ? [] : [{
|
|
20
|
+
...page,
|
|
21
|
+
links: page.links.flatMap(relink)
|
|
22
|
+
}];
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
...list,
|
|
26
|
+
pages,
|
|
27
|
+
outline: list.outline.flatMap((entry) => {
|
|
28
|
+
const pageIndex = remap.get(entry.pageIndex);
|
|
29
|
+
return pageIndex === void 0 ? [] : [{
|
|
30
|
+
...entry,
|
|
31
|
+
pageIndex
|
|
32
|
+
}];
|
|
33
|
+
}),
|
|
34
|
+
unsupported: list.unsupported.flatMap((entry) => {
|
|
35
|
+
const pageIndex = remap.get(entry.pageIndex);
|
|
36
|
+
return pageIndex === void 0 ? [] : [{
|
|
37
|
+
...entry,
|
|
38
|
+
pageIndex
|
|
39
|
+
}];
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { selectDisplayPages };
|
package/dist/export-pdf.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DocxInput } from "./utils/docxInput.js";
|
|
2
|
-
import { DisplayMetadata, DisplayUnsupported } from "./display-list/types.js";
|
|
2
|
+
import { DisplayList, DisplayMetadata, DisplayUnsupported } from "./display-list/types.js";
|
|
3
3
|
import { HeadlessFontSource, HeadlessFontSubstitution } from "./fonts/headlessMeasure.js";
|
|
4
4
|
import { HeadlessLayoutGap } from "./headless-layout.js";
|
|
5
5
|
import { PdfSubstitution } from "./pdf/fonts.js";
|
|
@@ -23,6 +23,11 @@ type ExportDocxToPdfOptions = {
|
|
|
23
23
|
readonly timestamp: string;
|
|
24
24
|
readonly metadata?: DisplayMetadata;
|
|
25
25
|
readonly producer?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 0-based indices of the pages to write, in order. Links and outline
|
|
28
|
+
* entries to other pages are dropped. Omit for every page.
|
|
29
|
+
*/
|
|
30
|
+
readonly pages?: readonly number[];
|
|
26
31
|
};
|
|
27
32
|
type ExportDocxToPdfResult = {
|
|
28
33
|
readonly bytes: Uint8Array;
|
|
@@ -38,6 +43,34 @@ type ExportDocxToPdfResult = {
|
|
|
38
43
|
/** Code points painted as `.notdef` because no supplied face covers them. */
|
|
39
44
|
readonly unencodable: readonly PdfUnencodable[];
|
|
40
45
|
};
|
|
46
|
+
type BuildDocxDisplayListOptions = {
|
|
47
|
+
/** Supplies face binaries for measurement. */
|
|
48
|
+
readonly fonts: HeadlessFontSource;
|
|
49
|
+
readonly metadata?: DisplayMetadata;
|
|
50
|
+
};
|
|
51
|
+
type BuildDocxDisplayListResult = {
|
|
52
|
+
/** The pages every backend paints: the PDF writer, the DOM and HTML backends. */
|
|
53
|
+
readonly list: DisplayList;
|
|
54
|
+
/** Stories the headless pipeline does not paginate. */
|
|
55
|
+
readonly layoutGaps: readonly HeadlessLayoutGap[];
|
|
56
|
+
/** Faces measured with a stand-in. */
|
|
57
|
+
readonly measurementSubstitutions: readonly HeadlessFontSubstitution[];
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Lay a package out headlessly and build its display list: the one structure
|
|
61
|
+
* the PDF writer and the DOM backend both paint. Runs one at a time with
|
|
62
|
+
* every other headless build and export, for the reason
|
|
63
|
+
* {@link runExclusively} gives.
|
|
64
|
+
*/
|
|
65
|
+
declare const buildDocxDisplayList: (input: DocxInput, options: BuildDocxDisplayListOptions) => Promise<Result<BuildDocxDisplayListResult, ExportPdfError>>;
|
|
41
66
|
declare const exportDocxToPdf: (input: DocxInput, options: ExportDocxToPdfOptions) => Promise<Result<ExportDocxToPdfResult, ExportPdfError>>;
|
|
67
|
+
type WriteDisplayListPdfOptions = Pick<ExportDocxToPdfOptions, "fonts" | "timestamp" | "producer">;
|
|
68
|
+
type WriteDisplayListPdfResult = Pick<ExportDocxToPdfResult, "bytes" | "pageCount" | "embeddingSubstitutions" | "unencodable">;
|
|
69
|
+
/**
|
|
70
|
+
* Write a display list built by {@link buildDocxDisplayList} (or narrowed by
|
|
71
|
+
* `selectDisplayPages`) as a PDF, embedding faces from the same source the
|
|
72
|
+
* layout measured with.
|
|
73
|
+
*/
|
|
74
|
+
declare const writeDisplayListPdf: (list: DisplayList, options: WriteDisplayListPdfOptions) => Promise<Result<WriteDisplayListPdfResult, ExportPdfError>>;
|
|
42
75
|
//#endregion
|
|
43
|
-
export { ExportDocxToPdfOptions, ExportDocxToPdfResult, ExportPdfError, exportDocxToPdf };
|
|
76
|
+
export { BuildDocxDisplayListOptions, BuildDocxDisplayListResult, ExportDocxToPdfOptions, ExportDocxToPdfResult, ExportPdfError, WriteDisplayListPdfOptions, WriteDisplayListPdfResult, buildDocxDisplayList, exportDocxToPdf, writeDisplayListPdf };
|
package/dist/export-pdf.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildDisplayList } from "./display-list/build/buildDisplayList.js";
|
|
2
2
|
import { displayCommentsFrom } from "./display-list/build/commentAnnotations.js";
|
|
3
|
+
import { selectDisplayPages } from "./display-list/selectDisplayPages.js";
|
|
3
4
|
import { installHeadlessMeasureProvider } from "./fonts/headlessMeasure.js";
|
|
4
5
|
import { layoutDocxHeadless } from "./headless-layout.js";
|
|
5
6
|
import { getMeasureProvider, setMeasureProvider } from "./layout-engine/measure/measureProvider.js";
|
|
@@ -52,16 +53,59 @@ const runExclusively = (run) => {
|
|
|
52
53
|
exportQueue = current.then(() => void 0, () => void 0);
|
|
53
54
|
return current;
|
|
54
55
|
};
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Lay a package out headlessly and build its display list: the one structure
|
|
58
|
+
* the PDF writer and the DOM backend both paint. Runs one at a time with
|
|
59
|
+
* every other headless build and export, for the reason
|
|
60
|
+
* {@link runExclusively} gives.
|
|
61
|
+
*/
|
|
62
|
+
const buildDocxDisplayList = (input, options) => runExclusively(async () => {
|
|
57
63
|
const callerProvider = getMeasureProvider();
|
|
58
64
|
try {
|
|
59
|
-
return await
|
|
65
|
+
return await buildWithHeadlessProvider(input, options);
|
|
60
66
|
} finally {
|
|
61
67
|
setMeasureProvider(callerProvider);
|
|
62
68
|
}
|
|
69
|
+
});
|
|
70
|
+
const exportDocxToPdf = async (input, options) => {
|
|
71
|
+
const built = await buildDocxDisplayList(input, {
|
|
72
|
+
fonts: options.fonts,
|
|
73
|
+
...options.metadata === void 0 ? {} : { metadata: options.metadata }
|
|
74
|
+
});
|
|
75
|
+
if (built.isErr()) return Result.err(built.error);
|
|
76
|
+
const list = options.pages === void 0 ? built.value.list : selectDisplayPages(built.value.list, options.pages);
|
|
77
|
+
const written = await writeDisplayListPdf(list, options);
|
|
78
|
+
if (written.isErr()) return Result.err(written.error);
|
|
79
|
+
return Result.ok({
|
|
80
|
+
...written.value,
|
|
81
|
+
unsupported: list.unsupported,
|
|
82
|
+
layoutGaps: built.value.layoutGaps,
|
|
83
|
+
measurementSubstitutions: built.value.measurementSubstitutions
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Write a display list built by {@link buildDocxDisplayList} (or narrowed by
|
|
88
|
+
* `selectDisplayPages`) as a PDF, embedding faces from the same source the
|
|
89
|
+
* layout measured with.
|
|
90
|
+
*/
|
|
91
|
+
const writeDisplayListPdf = async (list, options) => {
|
|
92
|
+
const written = await writePdf(list, {
|
|
93
|
+
fonts: { load: (face) => options.fonts.load(toFontRequest(face)) },
|
|
94
|
+
timestamp: options.timestamp,
|
|
95
|
+
...options.producer === void 0 ? {} : { producer: options.producer }
|
|
96
|
+
});
|
|
97
|
+
if (written.isErr()) return Result.err(new ExportPdfError({
|
|
98
|
+
message: written.error.message,
|
|
99
|
+
cause: written.error
|
|
100
|
+
}));
|
|
101
|
+
return Result.ok({
|
|
102
|
+
bytes: written.value.bytes,
|
|
103
|
+
pageCount: list.pages.length,
|
|
104
|
+
embeddingSubstitutions: written.value.substitutions,
|
|
105
|
+
unencodable: written.value.unencodable
|
|
106
|
+
});
|
|
63
107
|
};
|
|
64
|
-
const
|
|
108
|
+
const buildWithHeadlessProvider = async (input, options) => {
|
|
65
109
|
const headless = installHeadlessMeasureProvider(options.fonts);
|
|
66
110
|
const laidOut = await layoutDocxHeadless(input, { pageGap: 0 });
|
|
67
111
|
if (laidOut.isErr()) return Result.err(new ExportPdfError({
|
|
@@ -77,24 +121,11 @@ const exportWithHeadlessProvider = async (input, options) => {
|
|
|
77
121
|
...laidOut.value.document.package.document.comments === void 0 ? {} : { comments: displayCommentsFrom(laidOut.value.document.package.document.comments) },
|
|
78
122
|
...options.metadata === void 0 ? {} : { metadata: options.metadata }
|
|
79
123
|
});
|
|
80
|
-
const written = await writePdf(list, {
|
|
81
|
-
fonts: { load: (face) => options.fonts.load(toFontRequest(face)) },
|
|
82
|
-
timestamp: options.timestamp,
|
|
83
|
-
...options.producer === void 0 ? {} : { producer: options.producer }
|
|
84
|
-
});
|
|
85
|
-
if (written.isErr()) return Result.err(new ExportPdfError({
|
|
86
|
-
message: written.error.message,
|
|
87
|
-
cause: written.error
|
|
88
|
-
}));
|
|
89
124
|
return Result.ok({
|
|
90
|
-
|
|
91
|
-
pageCount: list.pages.length,
|
|
92
|
-
unsupported: list.unsupported,
|
|
125
|
+
list,
|
|
93
126
|
layoutGaps: laidOut.value.unsupported,
|
|
94
|
-
measurementSubstitutions: headless.substitutions()
|
|
95
|
-
embeddingSubstitutions: written.value.substitutions,
|
|
96
|
-
unencodable: written.value.unencodable
|
|
127
|
+
measurementSubstitutions: headless.substitutions()
|
|
97
128
|
});
|
|
98
129
|
};
|
|
99
130
|
//#endregion
|
|
100
|
-
export { ExportPdfError, exportDocxToPdf };
|
|
131
|
+
export { ExportPdfError, buildDocxDisplayList, exportDocxToPdf, writeDisplayListPdf };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HeadlessFontSource } from "./headlessMeasure.js";
|
|
2
|
+
//#region src/fonts/fontsourceFaces.d.ts
|
|
3
|
+
/** Where `@fontsource` files come from: `null` when the package or file is absent. */
|
|
4
|
+
type FontsourceFiles = {
|
|
5
|
+
/** Bytes of `@fontsource/<packageName>/<relativePath>`. */
|
|
6
|
+
readonly read: (packageName: string, relativePath: string) => Uint8Array | null;
|
|
7
|
+
};
|
|
8
|
+
/** Each bundled family and the `@fontsource` package that ships it. */
|
|
9
|
+
declare const FONTSOURCE_PACKAGES: {
|
|
10
|
+
readonly Arimo: "arimo";
|
|
11
|
+
readonly Caladea: "caladea";
|
|
12
|
+
readonly Carlito: "carlito";
|
|
13
|
+
readonly Cousine: "cousine";
|
|
14
|
+
readonly Lato: "lato";
|
|
15
|
+
readonly "Noto Sans Arabic": "noto-sans-arabic";
|
|
16
|
+
readonly "Source Sans 3": "source-sans-3";
|
|
17
|
+
readonly Tinos: "tinos";
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* A CSS `<string>` token, quoted and escaped. A family name comes from an
|
|
21
|
+
* authored document and is emitted into a `<style>` element, so the
|
|
22
|
+
* backslash is escaped first, then the quote, and line terminators become hex
|
|
23
|
+
* escapes, which CSS strings require.
|
|
24
|
+
*/
|
|
25
|
+
declare const cssString: (value: string) => string;
|
|
26
|
+
type FontsourceFontFaceCssOptions = {
|
|
27
|
+
/**
|
|
28
|
+
* Families to declare beyond the names the faces answer to directly. Pass a
|
|
29
|
+
* display list's own `fonts` families: a face reached through the fallback
|
|
30
|
+
* chain has no rule of its own, and the page names it by its authored name.
|
|
31
|
+
*/
|
|
32
|
+
readonly families?: readonly string[];
|
|
33
|
+
};
|
|
34
|
+
type FontsourceFaces = {
|
|
35
|
+
/** Binaries for the headless measurer and the PDF writer. */
|
|
36
|
+
readonly source: HeadlessFontSource;
|
|
37
|
+
/**
|
|
38
|
+
* `@font-face` rules with the `.woff` bytes inlined as `data:` URLs, one
|
|
39
|
+
* rule per served subset of each face, so a browser paints each code point
|
|
40
|
+
* from the binary that was measured.
|
|
41
|
+
*/
|
|
42
|
+
readonly fontFaceCss: (options?: FontsourceFontFaceCssOptions) => string;
|
|
43
|
+
};
|
|
44
|
+
/** The `@fontsource` faces, read through `files` and cached per face. */
|
|
45
|
+
declare const createFontsourceFaces: (files: FontsourceFiles) => FontsourceFaces;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { FONTSOURCE_PACKAGES, FontsourceFaces, FontsourceFiles, FontsourceFontFaceCssOptions, createFontsourceFaces, cssString };
|