documents.js 1.33.3 → 1.34.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/index.js CHANGED
@@ -98,7 +98,8 @@ const LayoutItemSchema = z.discriminatedUnion("kind", [
98
98
  const LayoutPageSchema = z.object({
99
99
  widthPt: z.number().positive(),
100
100
  heightPt: z.number().positive(),
101
- items: z.array(LayoutItemSchema)
101
+ items: z.array(LayoutItemSchema),
102
+ notes: z.string().optional()
102
103
  });
103
104
  const LayoutImageAssetSchema = z.object({
104
105
  format: z.enum(["png", "jpeg"]),
@@ -1307,16 +1308,21 @@ const DML_NS = "http://schemas.openxmlformats.org/drawingml/2006/main";
1307
1308
  const R_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
1308
1309
  const DEFAULT_SLIDE_WIDTH_EMU = "12192000";
1309
1310
  const DEFAULT_SLIDE_HEIGHT_EMU = "6858000";
1311
+ const DEFAULT_NOTES_WIDTH_EMU = "6858000";
1312
+ const DEFAULT_NOTES_HEIGHT_EMU = "9144000";
1310
1313
  const SLIDE_MASTER_PART_PATH = "ppt/slideMasters/slideMaster1.xml";
1311
1314
  const SLIDE_LAYOUT_PART_PATH = "ppt/slideLayouts/slideLayout1.xml";
1312
1315
  const THEME_PART_PATH = "ppt/theme/theme1.xml";
1313
1316
  const PRESENTATION_PART_PATH$1 = "ppt/presentation.xml";
1317
+ const NOTES_MASTER_PART_PATH = "ppt/notesMasters/notesMaster1.xml";
1314
1318
  const SLIDE_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
1315
1319
  const SLIDE_LAYOUT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
1316
1320
  const THEME_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.theme+xml";
1321
+ const NOTES_MASTER_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
1317
1322
  const SLIDE_MASTER_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster";
1318
1323
  const SLIDE_LAYOUT_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
1319
1324
  const THEME_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
1325
+ const NOTES_MASTER_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster";
1320
1326
  const MIN_MASTER_OR_LAYOUT_ID = 2147483648;
1321
1327
  function declaration() {
1322
1328
  return {
@@ -1453,6 +1459,37 @@ function buildSlideLayout() {
1453
1459
  preserve: "1"
1454
1460
  }, [el("p:cSld", { name: "Blank" }, [buildEmptyGroupSpTree()]), el("p:clrMapOvr", {}, [el("a:masterClrMapping")])]);
1455
1461
  }
1462
+ function buildNotesMaster() {
1463
+ return el("p:notesMaster", {
1464
+ "xmlns:p": PML_NS,
1465
+ "xmlns:a": DML_NS
1466
+ }, [el("p:cSld", {}, [buildWhiteBackground(), buildEmptyGroupSpTree()]), buildIdentityColorMap()]);
1467
+ }
1468
+ function ensureNotesMaster(pkg) {
1469
+ if (pkg.parts[NOTES_MASTER_PART_PATH] !== void 0) return NOTES_MASTER_PART_PATH;
1470
+ const masterToThemeTarget = buildRelativeTarget(NOTES_MASTER_PART_PATH, THEME_PART_PATH);
1471
+ addRelationship(pkg, NOTES_MASTER_PART_PATH, {
1472
+ type: THEME_REL_TYPE,
1473
+ target: masterToThemeTarget
1474
+ });
1475
+ pkg.parts[NOTES_MASTER_PART_PATH] = {
1476
+ kind: "xml",
1477
+ nodes: [declaration(), buildNotesMaster()]
1478
+ };
1479
+ ensureContentTypeOverride(pkg, NOTES_MASTER_PART_PATH, NOTES_MASTER_CONTENT_TYPE);
1480
+ const presentationToNotesMasterTarget = buildRelativeTarget(PRESENTATION_PART_PATH$1, NOTES_MASTER_PART_PATH);
1481
+ const relId = addRelationship(pkg, PRESENTATION_PART_PATH$1, {
1482
+ type: NOTES_MASTER_REL_TYPE,
1483
+ target: presentationToNotesMasterTarget
1484
+ });
1485
+ const presentationPart = pkg.parts[PRESENTATION_PART_PATH$1];
1486
+ const presentationElement = presentationPart?.kind === "xml" ? presentationPart.nodes.find((n) => n.type === "element") : void 0;
1487
+ if (presentationElement === void 0) throw new Error("ensureNotesMaster: package has no ppt/presentation.xml element");
1488
+ const sldMasterIdLstIndex = presentationElement.children.findIndex((c) => c.type === "element" && c.tag === "p:sldMasterIdLst");
1489
+ const notesMasterIdLst = el("p:notesMasterIdLst", {}, [el("p:notesMasterId", { "r:id": relId })]);
1490
+ presentationElement.children.splice(sldMasterIdLstIndex === -1 ? 0 : sldMasterIdLstIndex + 1, 0, notesMasterIdLst);
1491
+ return NOTES_MASTER_PART_PATH;
1492
+ }
1456
1493
  function createEmptyPptxPackage() {
1457
1494
  const contentTypes = el("Types", { xmlns: CONTENT_TYPES_NS }, [
1458
1495
  el("Default", {
@@ -1534,6 +1571,9 @@ function createEmptyPptxPackage() {
1534
1571
  })]), el("p:sldIdLst"), el("p:sldSz", {
1535
1572
  cx: DEFAULT_SLIDE_WIDTH_EMU,
1536
1573
  cy: DEFAULT_SLIDE_HEIGHT_EMU
1574
+ }), el("p:notesSz", {
1575
+ cx: DEFAULT_NOTES_WIDTH_EMU,
1576
+ cy: DEFAULT_NOTES_HEIGHT_EMU
1537
1577
  }));
1538
1578
  return pkg;
1539
1579
  }
@@ -1756,7 +1796,7 @@ function nextIdIn(root) {
1756
1796
  const NOTES_SLIDE_RELATIONSHIP_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide";
1757
1797
  const NOTES_SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
1758
1798
  function buildMinimalNotesSlide(text) {
1759
- return el("p:notes", {}, [el("p:cSld", {}, [el("p:spTree", {}, [el("p:sp", {}, [
1799
+ const body = el("p:sp", {}, [
1760
1800
  el("p:nvSpPr", {}, [
1761
1801
  el("p:cNvPr", {
1762
1802
  id: "2",
@@ -1768,13 +1808,25 @@ function buildMinimalNotesSlide(text) {
1768
1808
  idx: "1"
1769
1809
  })])
1770
1810
  ]),
1771
- el("p:spPr"),
1811
+ el("p:spPr", {}, [el("a:xfrm", {}, [el("a:off", {
1812
+ x: "685800",
1813
+ y: "4400550"
1814
+ }), el("a:ext", {
1815
+ cx: "5486400",
1816
+ cy: "4200525"
1817
+ })])]),
1772
1818
  el("p:txBody", {}, [
1773
1819
  el("a:bodyPr"),
1774
1820
  el("a:lstStyle"),
1775
1821
  el("a:p", {}, [el("a:r", {}, [el("a:t", {}, [txt(encodeXmlText(text))])])])
1776
1822
  ])
1777
- ])])])]);
1823
+ ]);
1824
+ const spTree = buildEmptyGroupSpTree();
1825
+ spTree.children.push(body);
1826
+ return el("p:notes", {
1827
+ "xmlns:p": PML_NS,
1828
+ "xmlns:a": DML_NS
1829
+ }, [el("p:cSld", {}, [spTree]), el("p:clrMapOvr", {}, [el("a:masterClrMapping")])]);
1778
1830
  }
1779
1831
  var PptxSlide = class {
1780
1832
  container;
@@ -1825,12 +1877,17 @@ var PptxSlide = class {
1825
1877
  const existingRel = [...resolveRelationships$1(pkg, slidePartPath).values()].find((r) => r.type === NOTES_SLIDE_RELATIONSHIP_TYPE);
1826
1878
  let notesPartPath;
1827
1879
  if (existingRel === void 0) {
1828
- notesPartPath = `ppt/notesSlides/notesSlide${Object.keys(pkg.parts).filter((p) => p.startsWith("ppt/notesSlides/")).length + 1}.xml`;
1880
+ notesPartPath = `ppt/notesSlides/notesSlide${Object.keys(pkg.parts).filter((p) => /^ppt\/notesSlides\/notesSlide\d+\.xml$/.test(p)).length + 1}.xml`;
1829
1881
  ensureContentTypeOverride(pkg, notesPartPath, NOTES_SLIDE_CONTENT_TYPE);
1830
1882
  addRelationship(pkg, slidePartPath, {
1831
1883
  type: NOTES_SLIDE_RELATIONSHIP_TYPE,
1832
1884
  target: buildRelativeTarget(slidePartPath, notesPartPath)
1833
1885
  });
1886
+ const notesMasterPartPath = ensureNotesMaster(pkg);
1887
+ addRelationship(pkg, notesPartPath, {
1888
+ type: NOTES_MASTER_REL_TYPE,
1889
+ target: buildRelativeTarget(notesPartPath, notesMasterPartPath)
1890
+ });
1834
1891
  } else notesPartPath = existingRel.target;
1835
1892
  pkg.parts[notesPartPath] = {
1836
1893
  kind: "xml",
@@ -5736,419 +5793,91 @@ function runContentStream(bytes, resources, initialState, context, items, depth)
5736
5793
  }
5737
5794
  }
5738
5795
  //#endregion
5739
- //#region src/pdf/read.ts
5740
- const PDF_HEADER_BYTES = new TextEncoder().encode("%PDF-");
5741
- const HEADER_SEARCH_WINDOW = 1024;
5742
- const DEFAULT_PAGE_WIDTH_PT = 612;
5743
- const DEFAULT_PAGE_HEIGHT_PT = 792;
5744
- function hasPdfHeader(bytes) {
5745
- const window = bytes.subarray(0, Math.min(HEADER_SEARCH_WINDOW, bytes.length));
5746
- outer: for (let i = 0; i <= window.length - PDF_HEADER_BYTES.length; i++) {
5747
- for (let j = 0; j < PDF_HEADER_BYTES.length; j++) if (window[i + j] !== PDF_HEADER_BYTES[j]) continue outer;
5748
- return true;
5749
- }
5750
- return false;
5796
+ //#region src/image/png-decode.ts
5797
+ const PNG_SIGNATURE = [
5798
+ 137,
5799
+ 80,
5800
+ 78,
5801
+ 71,
5802
+ 13,
5803
+ 10,
5804
+ 26,
5805
+ 10
5806
+ ];
5807
+ function requireDataView(bytes) {
5808
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
5751
5809
  }
5752
- function readPdf(bytes, options) {
5753
- const sink = options?.sink ?? NOOP_DIAGNOSTIC_SINK;
5754
- const signal = options?.signal;
5755
- if (!hasPdfHeader(bytes)) throw new PdfParseError("pdf/no-header", "no \"%PDF-\" header found within the first bytes of the file; this does not look like a PDF at all");
5756
- const doc = openPdfDocument(bytes, sink);
5757
- const resolver = doc;
5758
- const fontResolver = createFontResolver({
5759
- resolver,
5760
- sink
5761
- });
5762
- const images = {};
5763
- const imageIdCache = /* @__PURE__ */ new Map();
5764
- const pages = doc.pages().map((pageDict) => {
5765
- throwIfAborted(signal);
5766
- return readPage(pageDict, resolver, fontResolver, images, imageIdCache, sink);
5767
- });
5768
- return {
5769
- formatVersion: 1,
5770
- metadata: readMetadata(doc.trailer, resolver),
5771
- pages,
5772
- images
5773
- };
5810
+ function readChunks(bytes, onWarning) {
5811
+ const chunks = [];
5812
+ const view = requireDataView(bytes);
5813
+ let offset = PNG_SIGNATURE.length;
5814
+ while (offset + 8 <= bytes.length) {
5815
+ const length = view.getUint32(offset);
5816
+ const typeBytes = bytes.subarray(offset + 4, offset + 8);
5817
+ const type = new TextDecoder("latin1").decode(typeBytes);
5818
+ const dataStart = offset + 8;
5819
+ const dataEnd = dataStart + length;
5820
+ if (dataEnd + 4 > bytes.length) throw new Error(`PNG chunk '${type}' declares a length that runs past the end of the file`);
5821
+ const data = bytes.subarray(dataStart, dataEnd);
5822
+ if (onWarning !== void 0) {
5823
+ if (view.getUint32(dataEnd) !== crc32(concatBytes([typeBytes, data]))) onWarning(`PNG chunk '${type}' failed its CRC32 check`);
5824
+ }
5825
+ chunks.push({
5826
+ type,
5827
+ data
5828
+ });
5829
+ offset = dataEnd + 4;
5830
+ if (type === "IEND") break;
5831
+ }
5832
+ return chunks;
5774
5833
  }
5775
- function readMediaBox(page) {
5776
- const arr = asArray(dictGet(page, "MediaBox"));
5777
- if (arr === void 0) return {
5778
- llx: 0,
5779
- lly: 0,
5780
- urx: DEFAULT_PAGE_WIDTH_PT,
5781
- ury: DEFAULT_PAGE_HEIGHT_PT
5782
- };
5783
- const a = asNumber(arr[0]) ?? 0;
5784
- const b = asNumber(arr[1]) ?? 0;
5785
- const c = asNumber(arr[2]) ?? DEFAULT_PAGE_WIDTH_PT;
5786
- const d = asNumber(arr[3]) ?? DEFAULT_PAGE_HEIGHT_PT;
5834
+ function parseIhdr(data) {
5835
+ const view = requireDataView(data);
5787
5836
  return {
5788
- llx: Math.min(a, c),
5789
- lly: Math.min(b, d),
5790
- urx: Math.max(a, c),
5791
- ury: Math.max(b, d)
5837
+ width: view.getUint32(0),
5838
+ height: view.getUint32(4),
5839
+ bitDepth: data[8],
5840
+ colorType: data[9],
5841
+ interlace: data[12]
5792
5842
  };
5793
5843
  }
5794
- function normalizeRotation(rotate) {
5795
- if (rotate === void 0) return 0;
5796
- const normalized = (Math.round(rotate / 90) * 90 % 360 + 360) % 360;
5797
- return normalized === 90 || normalized === 180 || normalized === 270 ? normalized : 0;
5844
+ function channelsForColorType(colorType) {
5845
+ if (colorType === 0) return 1;
5846
+ if (colorType === 2) return 3;
5847
+ if (colorType === 3) return 1;
5848
+ if (colorType === 4) return 2;
5849
+ if (colorType === 6) return 4;
5850
+ throw new Error(`unsupported PNG colour type: ${colorType}`);
5798
5851
  }
5799
- function pageRotationTransform(rotation, w, h) {
5800
- if (rotation === 90) return {
5801
- matrix: [
5802
- 0,
5803
- -1,
5804
- 1,
5805
- 0,
5806
- 0,
5807
- w
5808
- ],
5809
- widthPt: h,
5810
- heightPt: w
5811
- };
5812
- if (rotation === 180) return {
5813
- matrix: [
5814
- -1,
5815
- 0,
5816
- 0,
5817
- -1,
5818
- w,
5819
- h
5820
- ],
5821
- widthPt: w,
5822
- heightPt: h
5823
- };
5824
- if (rotation === 270) return {
5825
- matrix: [
5826
- 0,
5827
- 1,
5828
- -1,
5829
- 0,
5830
- h,
5831
- 0
5832
- ],
5833
- widthPt: h,
5834
- heightPt: w
5835
- };
5836
- return {
5837
- matrix: [
5838
- 1,
5839
- 0,
5840
- 0,
5841
- 1,
5842
- 0,
5843
- 0
5844
- ],
5845
- widthPt: w,
5846
- heightPt: h
5847
- };
5852
+ function filterBpp(bitDepth, channels) {
5853
+ return Math.max(1, Math.ceil(bitDepth * channels / 8));
5848
5854
  }
5849
- function readPageContentBytes(page, resolver, sink) {
5850
- const contentsObj = resolver.resolve(dictGet(page, "Contents"));
5851
- if (contentsObj?.kind === "stream") return decodeStream(contentsObj.raw, contentsObj.dict, sink).bytes;
5852
- if (contentsObj?.kind === "array") {
5853
- const chunks = [];
5854
- for (const item of contentsObj.items) {
5855
- const streamObj = resolver.resolve(item);
5856
- if (streamObj?.kind === "stream") chunks.push(decodeStream(streamObj.raw, streamObj.dict, sink).bytes, new Uint8Array([10]));
5855
+ function unpackRow(rowBytes, width, channels, bitDepth) {
5856
+ const sampleCount = width * channels;
5857
+ const samples = new Array(sampleCount);
5858
+ if (bitDepth === 8) for (let i = 0; i < sampleCount; i++) samples[i] = rowBytes[i];
5859
+ else if (bitDepth === 16) for (let i = 0; i < sampleCount; i++) samples[i] = rowBytes[i * 2];
5860
+ else {
5861
+ const mask = (1 << bitDepth) - 1;
5862
+ for (let i = 0; i < sampleCount; i++) {
5863
+ const bitOffset = i * bitDepth;
5864
+ const byteIndex = bitOffset >> 3;
5865
+ const shift = 8 - bitDepth - (bitOffset & 7);
5866
+ samples[i] = rowBytes[byteIndex] >> shift & mask;
5857
5867
  }
5858
- return concatBytes(chunks);
5859
5868
  }
5860
- return /* @__PURE__ */ new Uint8Array(0);
5869
+ return samples;
5861
5870
  }
5862
- function readPage(page, resolver, fontResolver, images, imageIdCache, sink) {
5863
- const resources = resolver.resolveDict(dictGet(page, "Resources"));
5864
- const mediaBox = readMediaBox(page);
5865
- const rotationResult = pageRotationTransform(normalizeRotation(asNumber(dictGet(page, "Rotate"))), mediaBox.urx - mediaBox.llx, mediaBox.ury - mediaBox.lly);
5866
- const pageMatrix = multiplyMatrices(translationMatrix(-mediaBox.llx, -mediaBox.lly), rotationResult.matrix);
5867
- const items = [];
5868
- if (resources !== void 0) {
5869
- const extracted = interpretContentStream(readPageContentBytes(page, resolver, sink), resources, {
5870
- fontMetrics: fontResolver.metrics,
5871
- resolver,
5872
- sink
5873
- });
5874
- for (const item of extracted) {
5875
- const converted = convertExtractedItem(item, pageMatrix, fontResolver, images, imageIdCache, resolver, sink);
5876
- if (converted !== void 0) items.push(converted);
5877
- }
5878
- } else sink({
5879
- code: "pdf/object-missing-value",
5880
- severity: "warning",
5881
- message: "page has no /Resources dict; its content stream cannot be interpreted"
5882
- });
5883
- items.push(...readLinkAnnotations(page, pageMatrix, resolver));
5884
- return {
5885
- widthPt: rotationResult.widthPt,
5886
- heightPt: rotationResult.heightPt,
5887
- items
5888
- };
5871
+ function readTrnsGrayValue(trns) {
5872
+ return requireDataView(trns).getUint16(0);
5889
5873
  }
5890
- function convertExtractedItem(item, pageMatrix, fontResolver, images, imageIdCache, resolver, sink) {
5891
- if (item.kind === "text") return convertText(item, pageMatrix, fontResolver);
5892
- if (item.kind === "rect") return convertRect(item, pageMatrix);
5893
- if (item.kind === "image") return convertImage(item, pageMatrix, images, imageIdCache, resolver, sink);
5894
- return convertInlineImage(item, pageMatrix, images, resolver, sink);
5895
- }
5896
- function convertText(item, pageMatrix, fontResolver) {
5897
- const font = fontResolver.resolve(item.fontResourceName, item.resources);
5898
- const text = font?.decodeToUnicode(item.codes) ?? "";
5899
- if (text.length === 0) return;
5900
- const startTrm = multiplyMatrices(item.startMatrix, pageMatrix);
5901
- const endTrm = multiplyMatrices(item.endMatrix, pageMatrix);
5902
- const widthPt = Math.hypot(endTrm[4] - startTrm[4], endTrm[5] - startTrm[5]);
5903
- const sizePt = matrixScaleX(startTrm);
5904
- const rotationDeg = matrixRotationDegrees(startTrm);
5905
- const layoutFont = {
5906
- family: font?.family ?? "Helvetica",
5907
- weight: font?.bold === true ? "bold" : "normal",
5908
- style: font?.italic === true ? "italic" : "normal"
5909
- };
5910
- return {
5911
- kind: "text",
5912
- text,
5913
- xPt: startTrm[4],
5914
- yPt: startTrm[5],
5915
- font: layoutFont,
5916
- sizePt: sizePt > 0 ? sizePt : item.sizePt,
5917
- color: item.color,
5918
- widthPt,
5919
- rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0
5920
- };
5921
- }
5922
- function convertRect(item, pageMatrix) {
5923
- const p1 = applyMatrix(pageMatrix, {
5924
- x: item.xPt,
5925
- y: item.yPt
5926
- });
5927
- const p2 = applyMatrix(pageMatrix, {
5928
- x: item.xPt + item.widthPt,
5929
- y: item.yPt + item.heightPt
5930
- });
5931
- return {
5932
- kind: "rect",
5933
- xPt: Math.min(p1.x, p2.x),
5934
- yPt: Math.min(p1.y, p2.y),
5935
- widthPt: Math.abs(p2.x - p1.x),
5936
- heightPt: Math.abs(p2.y - p1.y),
5937
- fill: item.color
5938
- };
5939
- }
5940
- function imagePlacementFrom(matrix) {
5941
- const rotationDeg = matrixRotationDegrees(matrix);
5942
- return {
5943
- xPt: matrix[4],
5944
- yPt: matrix[5],
5945
- widthPt: matrixScaleX(matrix),
5946
- heightPt: matrixScaleY(matrix),
5947
- rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0
5948
- };
5949
- }
5950
- function registerExtractedImage(format, bytes, widthPx, heightPx, images) {
5951
- const imageId = `img${crc32(bytes).toString(16)}`;
5952
- if (!(imageId in images)) images[imageId] = {
5953
- format,
5954
- base64: bytesToBase64$1(bytes),
5955
- widthPx,
5956
- heightPx
5957
- };
5958
- return imageId;
5959
- }
5960
- function resolveCachedImageId(dict, raw, images, cache, resolver, sink) {
5961
- if (cache.has(dict)) return cache.get(dict) ?? void 0;
5962
- const decoded = readImageXObject(dict, raw, resolver, sink);
5963
- if (decoded === void 0) {
5964
- cache.set(dict, null);
5965
- return;
5966
- }
5967
- const imageId = registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images);
5968
- cache.set(dict, imageId);
5969
- return imageId;
5970
- }
5971
- function convertImage(item, pageMatrix, images, cache, resolver, sink) {
5972
- const xobjects = resolver.resolveDict(dictGet(item.resources, "XObject"));
5973
- const xobj = xobjects !== void 0 ? resolver.resolve(dictGet(xobjects, item.resourceName)) : void 0;
5974
- if (xobj?.kind !== "stream") return;
5975
- const imageId = resolveCachedImageId(xobj.dict, xobj.raw, images, cache, resolver, sink);
5976
- if (imageId === void 0) return;
5977
- return {
5978
- kind: "image",
5979
- imageId,
5980
- ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix))
5981
- };
5982
- }
5983
- function convertInlineImage(item, pageMatrix, images, resolver, sink) {
5984
- const decoded = readImageXObject(item.dict, item.data, resolver, sink);
5985
- if (decoded === void 0) return;
5986
- return {
5987
- kind: "image",
5988
- imageId: registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images),
5989
- ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix))
5990
- };
5991
- }
5992
- function readLinkAnnotations(page, pageMatrix, resolver) {
5993
- const annotsArr = asArray(dictGet(page, "Annots"));
5994
- if (annotsArr === void 0) return [];
5995
- const links = [];
5996
- for (const annotRef of annotsArr) {
5997
- const annot = resolver.resolveDict(annotRef);
5998
- if (annot === void 0 || asName(dictGet(annot, "Subtype")) !== "Link") continue;
5999
- const uri = readLinkUri(annot, resolver);
6000
- const rectArr = asArray(dictGet(annot, "Rect"));
6001
- if (uri === void 0 || rectArr === void 0) continue;
6002
- const x1 = asNumber(rectArr[0]) ?? 0;
6003
- const y1 = asNumber(rectArr[1]) ?? 0;
6004
- const x2 = asNumber(rectArr[2]) ?? 0;
6005
- const y2 = asNumber(rectArr[3]) ?? 0;
6006
- const p1 = applyMatrix(pageMatrix, {
6007
- x: Math.min(x1, x2),
6008
- y: Math.min(y1, y2)
6009
- });
6010
- const p2 = applyMatrix(pageMatrix, {
6011
- x: Math.max(x1, x2),
6012
- y: Math.max(y1, y2)
6013
- });
6014
- links.push({
6015
- kind: "link",
6016
- uri,
6017
- xPt: Math.min(p1.x, p2.x),
6018
- yPt: Math.min(p1.y, p2.y),
6019
- widthPt: Math.abs(p2.x - p1.x),
6020
- heightPt: Math.abs(p2.y - p1.y)
6021
- });
6022
- }
6023
- return links;
6024
- }
6025
- function readLinkUri(annot, resolver) {
6026
- const action = resolver.resolveDict(dictGet(annot, "A"));
6027
- if (action === void 0 || asName(dictGet(action, "S")) !== "URI") return;
6028
- const uriObj = dictGet(action, "URI");
6029
- return uriObj?.kind === "string" ? decodePdfString(uriObj.bytes) : void 0;
6030
- }
6031
- function decodePdfString(bytes) {
6032
- if (bytes.length >= 2 && bytes[0] === 254 && bytes[1] === 255) {
6033
- let out = "";
6034
- for (let i = 2; i + 1 < bytes.length; i += 2) out += String.fromCharCode((bytes[i] ?? 0) << 8 | (bytes[i + 1] ?? 0));
6035
- return out;
6036
- }
6037
- return Array.from(bytes, (b) => String.fromCharCode(b)).join("");
6038
- }
6039
- const PDF_DATE_PATTERN = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([+\-Z])?(\d{2})?'?(\d{2})?'?$/;
6040
- function parsePdfDate(raw) {
6041
- if (raw === void 0) return;
6042
- const match = PDF_DATE_PATTERN.exec(raw);
6043
- if (match === null) return;
6044
- const [, year, month = "01", day = "01", hour = "00", minute = "00", second = "00", tzSign, tzHour = "00", tzMinute = "00"] = match;
6045
- return `${year}-${month}-${day}T${hour}:${minute}:${second}${tzSign === void 0 || tzSign === "Z" ? "Z" : `${tzSign}${tzHour}:${tzMinute}`}`;
6046
- }
6047
- function readMetadata(trailer, resolver) {
6048
- const info = resolver.resolveDict(dictGet(trailer, "Info"));
6049
- if (info === void 0) return {};
6050
- const stringField = (key) => {
6051
- const obj = dictGet(info, key);
6052
- return obj?.kind === "string" ? decodePdfString(obj.bytes) : void 0;
6053
- };
6054
- const keywords = stringField("Keywords")?.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
6055
- return {
6056
- title: stringField("Title"),
6057
- author: stringField("Author"),
6058
- subject: stringField("Subject"),
6059
- keywords: keywords !== void 0 && keywords.length > 0 ? keywords : void 0,
6060
- creator: stringField("Creator"),
6061
- producer: stringField("Producer"),
6062
- createdIso: parsePdfDate(stringField("CreationDate")),
6063
- modifiedIso: parsePdfDate(stringField("ModDate"))
6064
- };
6065
- }
6066
- //#endregion
6067
- //#region src/image/png-decode.ts
6068
- const PNG_SIGNATURE = [
6069
- 137,
6070
- 80,
6071
- 78,
6072
- 71,
6073
- 13,
6074
- 10,
6075
- 26,
6076
- 10
6077
- ];
6078
- function requireDataView(bytes) {
6079
- return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
6080
- }
6081
- function readChunks(bytes, onWarning) {
6082
- const chunks = [];
6083
- const view = requireDataView(bytes);
6084
- let offset = PNG_SIGNATURE.length;
6085
- while (offset + 8 <= bytes.length) {
6086
- const length = view.getUint32(offset);
6087
- const typeBytes = bytes.subarray(offset + 4, offset + 8);
6088
- const type = new TextDecoder("latin1").decode(typeBytes);
6089
- const dataStart = offset + 8;
6090
- const dataEnd = dataStart + length;
6091
- if (dataEnd + 4 > bytes.length) throw new Error(`PNG chunk '${type}' declares a length that runs past the end of the file`);
6092
- const data = bytes.subarray(dataStart, dataEnd);
6093
- if (onWarning !== void 0) {
6094
- if (view.getUint32(dataEnd) !== crc32(concatBytes([typeBytes, data]))) onWarning(`PNG chunk '${type}' failed its CRC32 check`);
6095
- }
6096
- chunks.push({
6097
- type,
6098
- data
6099
- });
6100
- offset = dataEnd + 4;
6101
- if (type === "IEND") break;
6102
- }
6103
- return chunks;
6104
- }
6105
- function parseIhdr(data) {
6106
- const view = requireDataView(data);
6107
- return {
6108
- width: view.getUint32(0),
6109
- height: view.getUint32(4),
6110
- bitDepth: data[8],
6111
- colorType: data[9],
6112
- interlace: data[12]
6113
- };
6114
- }
6115
- function channelsForColorType(colorType) {
6116
- if (colorType === 0) return 1;
6117
- if (colorType === 2) return 3;
6118
- if (colorType === 3) return 1;
6119
- if (colorType === 4) return 2;
6120
- if (colorType === 6) return 4;
6121
- throw new Error(`unsupported PNG colour type: ${colorType}`);
6122
- }
6123
- function filterBpp(bitDepth, channels) {
6124
- return Math.max(1, Math.ceil(bitDepth * channels / 8));
6125
- }
6126
- function unpackRow(rowBytes, width, channels, bitDepth) {
6127
- const sampleCount = width * channels;
6128
- const samples = new Array(sampleCount);
6129
- if (bitDepth === 8) for (let i = 0; i < sampleCount; i++) samples[i] = rowBytes[i];
6130
- else if (bitDepth === 16) for (let i = 0; i < sampleCount; i++) samples[i] = rowBytes[i * 2];
6131
- else {
6132
- const mask = (1 << bitDepth) - 1;
6133
- for (let i = 0; i < sampleCount; i++) {
6134
- const bitOffset = i * bitDepth;
6135
- const byteIndex = bitOffset >> 3;
6136
- const shift = 8 - bitDepth - (bitOffset & 7);
6137
- samples[i] = rowBytes[byteIndex] >> shift & mask;
6138
- }
6139
- }
6140
- return samples;
6141
- }
6142
- function readTrnsGrayValue(trns) {
6143
- return requireDataView(trns).getUint16(0);
6144
- }
6145
- function readTrnsRgbKey(trns) {
6146
- const view = requireDataView(trns);
6147
- return [
6148
- view.getUint16(0),
6149
- view.getUint16(2),
6150
- view.getUint16(4)
6151
- ];
5874
+ function readTrnsRgbKey(trns) {
5875
+ const view = requireDataView(trns);
5876
+ return [
5877
+ view.getUint16(0),
5878
+ view.getUint16(2),
5879
+ view.getUint16(4)
5880
+ ];
6152
5881
  }
6153
5882
  function scaleToByte(sample, bitDepth) {
6154
5883
  if (bitDepth === 16) return sample;
@@ -6615,175 +6344,535 @@ function buildLinkAnnotDict(link) {
6615
6344
  })
6616
6345
  });
6617
6346
  }
6618
- function isLinkItem(item) {
6619
- return item.kind === "link";
6347
+ function isLinkItem(item) {
6348
+ return item.kind === "link";
6349
+ }
6350
+ const NOTES_ANNOTATION_HIDDEN_FLAG = 2;
6351
+ const NOTES_ANNOTATION_AUTHOR = "documents.js:notes";
6352
+ function buildNotesAnnotDict(notes) {
6353
+ return pdfDict({
6354
+ Type: pdfName("Annot"),
6355
+ Subtype: pdfName("Text"),
6356
+ Rect: pdfArray([
6357
+ 0,
6358
+ 0,
6359
+ 0,
6360
+ 0
6361
+ ].map((n) => pdfNum(n))),
6362
+ Contents: textToPdfString(notes),
6363
+ T: textToPdfString(NOTES_ANNOTATION_AUTHOR),
6364
+ F: pdfNum(NOTES_ANNOTATION_HIDDEN_FLAG)
6365
+ });
6366
+ }
6367
+ function xrefEntry(offset, generation, inUse) {
6368
+ return `${offset.toString().padStart(10, "0")} ${generation.toString().padStart(5, "0")} ${inUse ? "n" : "f"} \n`;
6369
+ }
6370
+ function writePdf(doc, options = {}) {
6371
+ const compress = options.compress ?? true;
6372
+ const measurer = createStandardFontMeasurer();
6373
+ let nextObjNum = 1;
6374
+ const catalogNum = nextObjNum++;
6375
+ const pagesNum = nextObjNum++;
6376
+ const infoNum = nextObjNum++;
6377
+ const fontNames = /* @__PURE__ */ new Set();
6378
+ const imageIds = /* @__PURE__ */ new Set();
6379
+ for (const page of doc.pages) for (const item of page.items) if (item.kind === "text") fontNames.add(resolveStandardFont(item.font.family, item.font.weight === "bold", item.font.style === "italic").standardName);
6380
+ else if (item.kind === "image") imageIds.add(item.imageId);
6381
+ const fontAllocs = /* @__PURE__ */ new Map();
6382
+ for (const [index, name] of [...fontNames].sort().entries()) {
6383
+ const fontNum = nextObjNum++;
6384
+ const descNum = nextObjNum++;
6385
+ fontAllocs.set(name, {
6386
+ fontNum,
6387
+ descNum,
6388
+ resourceName: `F${index + 1}`
6389
+ });
6390
+ }
6391
+ const imageAllocs = /* @__PURE__ */ new Map();
6392
+ for (const [index, imageId] of [...imageIds].sort().entries()) {
6393
+ const asset = doc.images[imageId];
6394
+ if (asset === void 0) throw new Error(`LayoutDocument references image "${imageId}" but it is not present in images`);
6395
+ const prepared = prepareImage(asset, compress);
6396
+ const imageNum = nextObjNum++;
6397
+ const smaskNum = prepared.alpha === void 0 ? void 0 : nextObjNum++;
6398
+ imageAllocs.set(imageId, {
6399
+ imageNum,
6400
+ smaskNum,
6401
+ resourceName: `Im${index + 1}`,
6402
+ prepared
6403
+ });
6404
+ }
6405
+ const pageAllocs = doc.pages.map(() => ({
6406
+ pageNum: nextObjNum++,
6407
+ contentsNum: nextObjNum++
6408
+ }));
6409
+ const objects = [];
6410
+ objects.push({
6411
+ num: catalogNum,
6412
+ value: pdfDict({
6413
+ Type: pdfName("Catalog"),
6414
+ Pages: pdfRef(pagesNum, 0)
6415
+ })
6416
+ });
6417
+ objects.push({
6418
+ num: pagesNum,
6419
+ value: pdfDict({
6420
+ Type: pdfName("Pages"),
6421
+ Kids: pdfArray(pageAllocs.map((p) => pdfRef(p.pageNum, 0))),
6422
+ Count: pdfNum(doc.pages.length)
6423
+ })
6424
+ });
6425
+ objects.push({
6426
+ num: infoNum,
6427
+ value: buildInfoDict(doc)
6428
+ });
6429
+ for (const [standardName, alloc] of fontAllocs) {
6430
+ const { font, descriptor } = buildFontObjects(standardName, pdfRef(alloc.descNum, 0));
6431
+ objects.push({
6432
+ num: alloc.fontNum,
6433
+ value: font
6434
+ });
6435
+ objects.push({
6436
+ num: alloc.descNum,
6437
+ value: descriptor
6438
+ });
6439
+ }
6440
+ for (const alloc of imageAllocs.values()) {
6441
+ if (alloc.smaskNum !== void 0 && alloc.prepared.alpha !== void 0) {
6442
+ alloc.prepared.dict.entries.set("SMask", pdfRef(alloc.smaskNum, 0));
6443
+ objects.push({
6444
+ num: alloc.smaskNum,
6445
+ value: pdfStream(alloc.prepared.alpha.dict, alloc.prepared.alpha.raw)
6446
+ });
6447
+ }
6448
+ objects.push({
6449
+ num: alloc.imageNum,
6450
+ value: pdfStream(alloc.prepared.dict, alloc.prepared.raw)
6451
+ });
6452
+ }
6453
+ const resourceEntries = /* @__PURE__ */ new Map();
6454
+ if (fontAllocs.size > 0) resourceEntries.set("Font", pdfDict(new Map([...fontAllocs.values()].map((alloc) => [alloc.resourceName, pdfRef(alloc.fontNum, 0)]))));
6455
+ if (imageAllocs.size > 0) resourceEntries.set("XObject", pdfDict(new Map([...imageAllocs.values()].map((alloc) => [alloc.resourceName, pdfRef(alloc.imageNum, 0)]))));
6456
+ const resourcesDict = pdfDict(resourceEntries);
6457
+ const context = {
6458
+ measurer,
6459
+ resolveFont: (font) => {
6460
+ const standardName = resolveStandardFont(font.family, font.weight === "bold", font.style === "italic").standardName;
6461
+ const alloc = fontAllocs.get(standardName);
6462
+ if (alloc === void 0) throw new Error(`font "${standardName}" was not pre-allocated -- this is a writePdf internal invariant violation`);
6463
+ return {
6464
+ resourceName: alloc.resourceName,
6465
+ standardName
6466
+ };
6467
+ },
6468
+ resolveImage: (imageId) => {
6469
+ const alloc = imageAllocs.get(imageId);
6470
+ if (alloc === void 0) throw new Error(`image "${imageId}" was not pre-allocated -- this is a writePdf internal invariant violation`);
6471
+ return { resourceName: alloc.resourceName };
6472
+ }
6473
+ };
6474
+ doc.pages.forEach((page, pageIndex) => {
6475
+ throwIfAborted(options.signal);
6476
+ const { pageNum, contentsNum } = pageAllocs[pageIndex];
6477
+ const { bytes: contentBytes, substitutions } = writeContentStream(page.items, context);
6478
+ for (const substitution of substitutions) options.onSubstitution?.(substitution, { pageIndex });
6479
+ const finalContentBytes = compress ? deflate(contentBytes) : contentBytes;
6480
+ const contentsDict = pdfDict(compress ? { Filter: pdfName("FlateDecode") } : {});
6481
+ objects.push({
6482
+ num: contentsNum,
6483
+ value: pdfStream(contentsDict, finalContentBytes)
6484
+ });
6485
+ const annots = page.items.filter(isLinkItem).map((link) => buildLinkAnnotDict(link));
6486
+ if (page.notes !== void 0 && page.notes.length > 0) annots.push(buildNotesAnnotDict(page.notes));
6487
+ const pageEntries = /* @__PURE__ */ new Map([
6488
+ ["Type", pdfName("Page")],
6489
+ ["Parent", pdfRef(pagesNum, 0)],
6490
+ ["MediaBox", pdfArray([
6491
+ 0,
6492
+ 0,
6493
+ page.widthPt,
6494
+ page.heightPt
6495
+ ].map((n) => pdfNum(n)))],
6496
+ ["Resources", resourcesDict],
6497
+ ["Contents", pdfRef(contentsNum, 0)]
6498
+ ]);
6499
+ if (annots.length > 0) pageEntries.set("Annots", pdfArray(annots));
6500
+ objects.push({
6501
+ num: pageNum,
6502
+ value: pdfDict(pageEntries)
6503
+ });
6504
+ });
6505
+ const writer = new ByteWriter();
6506
+ writer.writeAscii("%PDF-1.7\n");
6507
+ const offsets = /* @__PURE__ */ new Map();
6508
+ for (const { num, value } of objects) {
6509
+ offsets.set(num, writer.length);
6510
+ writer.writeAscii(`${num} 0 obj\n`);
6511
+ writeObject(writer, value);
6512
+ writer.writeAscii("\nendobj\n");
6513
+ }
6514
+ const maxObjNum = nextObjNum - 1;
6515
+ const xrefOffset = writer.length;
6516
+ writer.writeAscii("xref\n");
6517
+ writer.writeAscii(`0 ${maxObjNum + 1}\n`);
6518
+ writer.writeAscii(xrefEntry(0, 65535, false));
6519
+ for (let num = 1; num <= maxObjNum; num++) {
6520
+ const offset = offsets.get(num);
6521
+ if (offset === void 0) throw new Error(`object ${num} was allocated but never written -- this is a writePdf internal invariant violation`);
6522
+ writer.writeAscii(xrefEntry(offset, 0, true));
6523
+ }
6524
+ writer.writeAscii("trailer\n");
6525
+ writeObject(writer, pdfDict({
6526
+ Size: pdfNum(maxObjNum + 1),
6527
+ Root: pdfRef(catalogNum, 0),
6528
+ Info: pdfRef(infoNum, 0)
6529
+ }));
6530
+ writer.writeAscii("\nstartxref\n");
6531
+ writer.writeAscii(`${xrefOffset}\n`);
6532
+ writer.writeAscii("%%EOF");
6533
+ return writer.toBytes();
6534
+ }
6535
+ //#endregion
6536
+ //#region src/pdf/read.ts
6537
+ const PDF_HEADER_BYTES = new TextEncoder().encode("%PDF-");
6538
+ const HEADER_SEARCH_WINDOW = 1024;
6539
+ const DEFAULT_PAGE_WIDTH_PT = 612;
6540
+ const DEFAULT_PAGE_HEIGHT_PT = 792;
6541
+ function hasPdfHeader(bytes) {
6542
+ const window = bytes.subarray(0, Math.min(HEADER_SEARCH_WINDOW, bytes.length));
6543
+ outer: for (let i = 0; i <= window.length - PDF_HEADER_BYTES.length; i++) {
6544
+ for (let j = 0; j < PDF_HEADER_BYTES.length; j++) if (window[i + j] !== PDF_HEADER_BYTES[j]) continue outer;
6545
+ return true;
6546
+ }
6547
+ return false;
6548
+ }
6549
+ function readPdf(bytes, options) {
6550
+ const sink = options?.sink ?? NOOP_DIAGNOSTIC_SINK;
6551
+ const signal = options?.signal;
6552
+ if (!hasPdfHeader(bytes)) throw new PdfParseError("pdf/no-header", "no \"%PDF-\" header found within the first bytes of the file; this does not look like a PDF at all");
6553
+ const doc = openPdfDocument(bytes, sink);
6554
+ const resolver = doc;
6555
+ const fontResolver = createFontResolver({
6556
+ resolver,
6557
+ sink
6558
+ });
6559
+ const images = {};
6560
+ const imageIdCache = /* @__PURE__ */ new Map();
6561
+ const pages = doc.pages().map((pageDict) => {
6562
+ throwIfAborted(signal);
6563
+ return readPage(pageDict, resolver, fontResolver, images, imageIdCache, sink);
6564
+ });
6565
+ return {
6566
+ formatVersion: 1,
6567
+ metadata: readMetadata(doc.trailer, resolver),
6568
+ pages,
6569
+ images
6570
+ };
6571
+ }
6572
+ function readMediaBox(page) {
6573
+ const arr = asArray(dictGet(page, "MediaBox"));
6574
+ if (arr === void 0) return {
6575
+ llx: 0,
6576
+ lly: 0,
6577
+ urx: DEFAULT_PAGE_WIDTH_PT,
6578
+ ury: DEFAULT_PAGE_HEIGHT_PT
6579
+ };
6580
+ const a = asNumber(arr[0]) ?? 0;
6581
+ const b = asNumber(arr[1]) ?? 0;
6582
+ const c = asNumber(arr[2]) ?? DEFAULT_PAGE_WIDTH_PT;
6583
+ const d = asNumber(arr[3]) ?? DEFAULT_PAGE_HEIGHT_PT;
6584
+ return {
6585
+ llx: Math.min(a, c),
6586
+ lly: Math.min(b, d),
6587
+ urx: Math.max(a, c),
6588
+ ury: Math.max(b, d)
6589
+ };
6590
+ }
6591
+ function normalizeRotation(rotate) {
6592
+ if (rotate === void 0) return 0;
6593
+ const normalized = (Math.round(rotate / 90) * 90 % 360 + 360) % 360;
6594
+ return normalized === 90 || normalized === 180 || normalized === 270 ? normalized : 0;
6595
+ }
6596
+ function pageRotationTransform(rotation, w, h) {
6597
+ if (rotation === 90) return {
6598
+ matrix: [
6599
+ 0,
6600
+ -1,
6601
+ 1,
6602
+ 0,
6603
+ 0,
6604
+ w
6605
+ ],
6606
+ widthPt: h,
6607
+ heightPt: w
6608
+ };
6609
+ if (rotation === 180) return {
6610
+ matrix: [
6611
+ -1,
6612
+ 0,
6613
+ 0,
6614
+ -1,
6615
+ w,
6616
+ h
6617
+ ],
6618
+ widthPt: w,
6619
+ heightPt: h
6620
+ };
6621
+ if (rotation === 270) return {
6622
+ matrix: [
6623
+ 0,
6624
+ 1,
6625
+ -1,
6626
+ 0,
6627
+ h,
6628
+ 0
6629
+ ],
6630
+ widthPt: h,
6631
+ heightPt: w
6632
+ };
6633
+ return {
6634
+ matrix: [
6635
+ 1,
6636
+ 0,
6637
+ 0,
6638
+ 1,
6639
+ 0,
6640
+ 0
6641
+ ],
6642
+ widthPt: w,
6643
+ heightPt: h
6644
+ };
6645
+ }
6646
+ function readPageContentBytes(page, resolver, sink) {
6647
+ const contentsObj = resolver.resolve(dictGet(page, "Contents"));
6648
+ if (contentsObj?.kind === "stream") return decodeStream(contentsObj.raw, contentsObj.dict, sink).bytes;
6649
+ if (contentsObj?.kind === "array") {
6650
+ const chunks = [];
6651
+ for (const item of contentsObj.items) {
6652
+ const streamObj = resolver.resolve(item);
6653
+ if (streamObj?.kind === "stream") chunks.push(decodeStream(streamObj.raw, streamObj.dict, sink).bytes, new Uint8Array([10]));
6654
+ }
6655
+ return concatBytes(chunks);
6656
+ }
6657
+ return /* @__PURE__ */ new Uint8Array(0);
6658
+ }
6659
+ function readPage(page, resolver, fontResolver, images, imageIdCache, sink) {
6660
+ const resources = resolver.resolveDict(dictGet(page, "Resources"));
6661
+ const mediaBox = readMediaBox(page);
6662
+ const rotationResult = pageRotationTransform(normalizeRotation(asNumber(dictGet(page, "Rotate"))), mediaBox.urx - mediaBox.llx, mediaBox.ury - mediaBox.lly);
6663
+ const pageMatrix = multiplyMatrices(translationMatrix(-mediaBox.llx, -mediaBox.lly), rotationResult.matrix);
6664
+ const items = [];
6665
+ if (resources !== void 0) {
6666
+ const extracted = interpretContentStream(readPageContentBytes(page, resolver, sink), resources, {
6667
+ fontMetrics: fontResolver.metrics,
6668
+ resolver,
6669
+ sink
6670
+ });
6671
+ for (const item of extracted) {
6672
+ const converted = convertExtractedItem(item, pageMatrix, fontResolver, images, imageIdCache, resolver, sink);
6673
+ if (converted !== void 0) items.push(converted);
6674
+ }
6675
+ } else sink({
6676
+ code: "pdf/object-missing-value",
6677
+ severity: "warning",
6678
+ message: "page has no /Resources dict; its content stream cannot be interpreted"
6679
+ });
6680
+ items.push(...readLinkAnnotations(page, pageMatrix, resolver));
6681
+ const notes = readPageNotes(page, resolver);
6682
+ return {
6683
+ widthPt: rotationResult.widthPt,
6684
+ heightPt: rotationResult.heightPt,
6685
+ items,
6686
+ ...notes !== void 0 ? { notes } : {}
6687
+ };
6688
+ }
6689
+ function convertExtractedItem(item, pageMatrix, fontResolver, images, imageIdCache, resolver, sink) {
6690
+ if (item.kind === "text") return convertText(item, pageMatrix, fontResolver);
6691
+ if (item.kind === "rect") return convertRect(item, pageMatrix);
6692
+ if (item.kind === "image") return convertImage(item, pageMatrix, images, imageIdCache, resolver, sink);
6693
+ return convertInlineImage(item, pageMatrix, images, resolver, sink);
6694
+ }
6695
+ function convertText(item, pageMatrix, fontResolver) {
6696
+ const font = fontResolver.resolve(item.fontResourceName, item.resources);
6697
+ const text = font?.decodeToUnicode(item.codes) ?? "";
6698
+ if (text.length === 0) return;
6699
+ const startTrm = multiplyMatrices(item.startMatrix, pageMatrix);
6700
+ const endTrm = multiplyMatrices(item.endMatrix, pageMatrix);
6701
+ const widthPt = Math.hypot(endTrm[4] - startTrm[4], endTrm[5] - startTrm[5]);
6702
+ const sizePt = matrixScaleX(startTrm);
6703
+ const rotationDeg = matrixRotationDegrees(startTrm);
6704
+ const layoutFont = {
6705
+ family: font?.family ?? "Helvetica",
6706
+ weight: font?.bold === true ? "bold" : "normal",
6707
+ style: font?.italic === true ? "italic" : "normal"
6708
+ };
6709
+ return {
6710
+ kind: "text",
6711
+ text,
6712
+ xPt: startTrm[4],
6713
+ yPt: startTrm[5],
6714
+ font: layoutFont,
6715
+ sizePt: sizePt > 0 ? sizePt : item.sizePt,
6716
+ color: item.color,
6717
+ widthPt,
6718
+ rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0
6719
+ };
6720
+ }
6721
+ function convertRect(item, pageMatrix) {
6722
+ const p1 = applyMatrix(pageMatrix, {
6723
+ x: item.xPt,
6724
+ y: item.yPt
6725
+ });
6726
+ const p2 = applyMatrix(pageMatrix, {
6727
+ x: item.xPt + item.widthPt,
6728
+ y: item.yPt + item.heightPt
6729
+ });
6730
+ return {
6731
+ kind: "rect",
6732
+ xPt: Math.min(p1.x, p2.x),
6733
+ yPt: Math.min(p1.y, p2.y),
6734
+ widthPt: Math.abs(p2.x - p1.x),
6735
+ heightPt: Math.abs(p2.y - p1.y),
6736
+ fill: item.color
6737
+ };
6738
+ }
6739
+ function imagePlacementFrom(matrix) {
6740
+ const rotationDeg = matrixRotationDegrees(matrix);
6741
+ return {
6742
+ xPt: matrix[4],
6743
+ yPt: matrix[5],
6744
+ widthPt: matrixScaleX(matrix),
6745
+ heightPt: matrixScaleY(matrix),
6746
+ rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0
6747
+ };
6748
+ }
6749
+ function registerExtractedImage(format, bytes, widthPx, heightPx, images) {
6750
+ const imageId = `img${crc32(bytes).toString(16)}`;
6751
+ if (!(imageId in images)) images[imageId] = {
6752
+ format,
6753
+ base64: bytesToBase64$1(bytes),
6754
+ widthPx,
6755
+ heightPx
6756
+ };
6757
+ return imageId;
6758
+ }
6759
+ function resolveCachedImageId(dict, raw, images, cache, resolver, sink) {
6760
+ if (cache.has(dict)) return cache.get(dict) ?? void 0;
6761
+ const decoded = readImageXObject(dict, raw, resolver, sink);
6762
+ if (decoded === void 0) {
6763
+ cache.set(dict, null);
6764
+ return;
6765
+ }
6766
+ const imageId = registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images);
6767
+ cache.set(dict, imageId);
6768
+ return imageId;
6769
+ }
6770
+ function convertImage(item, pageMatrix, images, cache, resolver, sink) {
6771
+ const xobjects = resolver.resolveDict(dictGet(item.resources, "XObject"));
6772
+ const xobj = xobjects !== void 0 ? resolver.resolve(dictGet(xobjects, item.resourceName)) : void 0;
6773
+ if (xobj?.kind !== "stream") return;
6774
+ const imageId = resolveCachedImageId(xobj.dict, xobj.raw, images, cache, resolver, sink);
6775
+ if (imageId === void 0) return;
6776
+ return {
6777
+ kind: "image",
6778
+ imageId,
6779
+ ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix))
6780
+ };
6620
6781
  }
6621
- function xrefEntry(offset, generation, inUse) {
6622
- return `${offset.toString().padStart(10, "0")} ${generation.toString().padStart(5, "0")} ${inUse ? "n" : "f"} \n`;
6782
+ function convertInlineImage(item, pageMatrix, images, resolver, sink) {
6783
+ const decoded = readImageXObject(item.dict, item.data, resolver, sink);
6784
+ if (decoded === void 0) return;
6785
+ return {
6786
+ kind: "image",
6787
+ imageId: registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images),
6788
+ ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix))
6789
+ };
6623
6790
  }
6624
- function writePdf(doc, options = {}) {
6625
- const compress = options.compress ?? true;
6626
- const measurer = createStandardFontMeasurer();
6627
- let nextObjNum = 1;
6628
- const catalogNum = nextObjNum++;
6629
- const pagesNum = nextObjNum++;
6630
- const infoNum = nextObjNum++;
6631
- const fontNames = /* @__PURE__ */ new Set();
6632
- const imageIds = /* @__PURE__ */ new Set();
6633
- for (const page of doc.pages) for (const item of page.items) if (item.kind === "text") fontNames.add(resolveStandardFont(item.font.family, item.font.weight === "bold", item.font.style === "italic").standardName);
6634
- else if (item.kind === "image") imageIds.add(item.imageId);
6635
- const fontAllocs = /* @__PURE__ */ new Map();
6636
- for (const [index, name] of [...fontNames].sort().entries()) {
6637
- const fontNum = nextObjNum++;
6638
- const descNum = nextObjNum++;
6639
- fontAllocs.set(name, {
6640
- fontNum,
6641
- descNum,
6642
- resourceName: `F${index + 1}`
6643
- });
6644
- }
6645
- const imageAllocs = /* @__PURE__ */ new Map();
6646
- for (const [index, imageId] of [...imageIds].sort().entries()) {
6647
- const asset = doc.images[imageId];
6648
- if (asset === void 0) throw new Error(`LayoutDocument references image "${imageId}" but it is not present in images`);
6649
- const prepared = prepareImage(asset, compress);
6650
- const imageNum = nextObjNum++;
6651
- const smaskNum = prepared.alpha === void 0 ? void 0 : nextObjNum++;
6652
- imageAllocs.set(imageId, {
6653
- imageNum,
6654
- smaskNum,
6655
- resourceName: `Im${index + 1}`,
6656
- prepared
6657
- });
6658
- }
6659
- const pageAllocs = doc.pages.map(() => ({
6660
- pageNum: nextObjNum++,
6661
- contentsNum: nextObjNum++
6662
- }));
6663
- const objects = [];
6664
- objects.push({
6665
- num: catalogNum,
6666
- value: pdfDict({
6667
- Type: pdfName("Catalog"),
6668
- Pages: pdfRef(pagesNum, 0)
6669
- })
6670
- });
6671
- objects.push({
6672
- num: pagesNum,
6673
- value: pdfDict({
6674
- Type: pdfName("Pages"),
6675
- Kids: pdfArray(pageAllocs.map((p) => pdfRef(p.pageNum, 0))),
6676
- Count: pdfNum(doc.pages.length)
6677
- })
6678
- });
6679
- objects.push({
6680
- num: infoNum,
6681
- value: buildInfoDict(doc)
6682
- });
6683
- for (const [standardName, alloc] of fontAllocs) {
6684
- const { font, descriptor } = buildFontObjects(standardName, pdfRef(alloc.descNum, 0));
6685
- objects.push({
6686
- num: alloc.fontNum,
6687
- value: font
6791
+ function readLinkAnnotations(page, pageMatrix, resolver) {
6792
+ const annotsArr = asArray(dictGet(page, "Annots"));
6793
+ if (annotsArr === void 0) return [];
6794
+ const links = [];
6795
+ for (const annotRef of annotsArr) {
6796
+ const annot = resolver.resolveDict(annotRef);
6797
+ if (annot === void 0 || asName(dictGet(annot, "Subtype")) !== "Link") continue;
6798
+ const uri = readLinkUri(annot, resolver);
6799
+ const rectArr = asArray(dictGet(annot, "Rect"));
6800
+ if (uri === void 0 || rectArr === void 0) continue;
6801
+ const x1 = asNumber(rectArr[0]) ?? 0;
6802
+ const y1 = asNumber(rectArr[1]) ?? 0;
6803
+ const x2 = asNumber(rectArr[2]) ?? 0;
6804
+ const y2 = asNumber(rectArr[3]) ?? 0;
6805
+ const p1 = applyMatrix(pageMatrix, {
6806
+ x: Math.min(x1, x2),
6807
+ y: Math.min(y1, y2)
6688
6808
  });
6689
- objects.push({
6690
- num: alloc.descNum,
6691
- value: descriptor
6809
+ const p2 = applyMatrix(pageMatrix, {
6810
+ x: Math.max(x1, x2),
6811
+ y: Math.max(y1, y2)
6692
6812
  });
6693
- }
6694
- for (const alloc of imageAllocs.values()) {
6695
- if (alloc.smaskNum !== void 0 && alloc.prepared.alpha !== void 0) {
6696
- alloc.prepared.dict.entries.set("SMask", pdfRef(alloc.smaskNum, 0));
6697
- objects.push({
6698
- num: alloc.smaskNum,
6699
- value: pdfStream(alloc.prepared.alpha.dict, alloc.prepared.alpha.raw)
6700
- });
6701
- }
6702
- objects.push({
6703
- num: alloc.imageNum,
6704
- value: pdfStream(alloc.prepared.dict, alloc.prepared.raw)
6813
+ links.push({
6814
+ kind: "link",
6815
+ uri,
6816
+ xPt: Math.min(p1.x, p2.x),
6817
+ yPt: Math.min(p1.y, p2.y),
6818
+ widthPt: Math.abs(p2.x - p1.x),
6819
+ heightPt: Math.abs(p2.y - p1.y)
6705
6820
  });
6706
6821
  }
6707
- const resourceEntries = /* @__PURE__ */ new Map();
6708
- if (fontAllocs.size > 0) resourceEntries.set("Font", pdfDict(new Map([...fontAllocs.values()].map((alloc) => [alloc.resourceName, pdfRef(alloc.fontNum, 0)]))));
6709
- if (imageAllocs.size > 0) resourceEntries.set("XObject", pdfDict(new Map([...imageAllocs.values()].map((alloc) => [alloc.resourceName, pdfRef(alloc.imageNum, 0)]))));
6710
- const resourcesDict = pdfDict(resourceEntries);
6711
- const context = {
6712
- measurer,
6713
- resolveFont: (font) => {
6714
- const standardName = resolveStandardFont(font.family, font.weight === "bold", font.style === "italic").standardName;
6715
- const alloc = fontAllocs.get(standardName);
6716
- if (alloc === void 0) throw new Error(`font "${standardName}" was not pre-allocated -- this is a writePdf internal invariant violation`);
6717
- return {
6718
- resourceName: alloc.resourceName,
6719
- standardName
6720
- };
6721
- },
6722
- resolveImage: (imageId) => {
6723
- const alloc = imageAllocs.get(imageId);
6724
- if (alloc === void 0) throw new Error(`image "${imageId}" was not pre-allocated -- this is a writePdf internal invariant violation`);
6725
- return { resourceName: alloc.resourceName };
6726
- }
6727
- };
6728
- doc.pages.forEach((page, pageIndex) => {
6729
- throwIfAborted(options.signal);
6730
- const { pageNum, contentsNum } = pageAllocs[pageIndex];
6731
- const { bytes: contentBytes, substitutions } = writeContentStream(page.items, context);
6732
- for (const substitution of substitutions) options.onSubstitution?.(substitution, { pageIndex });
6733
- const finalContentBytes = compress ? deflate(contentBytes) : contentBytes;
6734
- const contentsDict = pdfDict(compress ? { Filter: pdfName("FlateDecode") } : {});
6735
- objects.push({
6736
- num: contentsNum,
6737
- value: pdfStream(contentsDict, finalContentBytes)
6738
- });
6739
- const annots = page.items.filter(isLinkItem).map((link) => buildLinkAnnotDict(link));
6740
- const pageEntries = /* @__PURE__ */ new Map([
6741
- ["Type", pdfName("Page")],
6742
- ["Parent", pdfRef(pagesNum, 0)],
6743
- ["MediaBox", pdfArray([
6744
- 0,
6745
- 0,
6746
- page.widthPt,
6747
- page.heightPt
6748
- ].map((n) => pdfNum(n)))],
6749
- ["Resources", resourcesDict],
6750
- ["Contents", pdfRef(contentsNum, 0)]
6751
- ]);
6752
- if (annots.length > 0) pageEntries.set("Annots", pdfArray(annots));
6753
- objects.push({
6754
- num: pageNum,
6755
- value: pdfDict(pageEntries)
6756
- });
6757
- });
6758
- const writer = new ByteWriter();
6759
- writer.writeAscii("%PDF-1.7\n");
6760
- const offsets = /* @__PURE__ */ new Map();
6761
- for (const { num, value } of objects) {
6762
- offsets.set(num, writer.length);
6763
- writer.writeAscii(`${num} 0 obj\n`);
6764
- writeObject(writer, value);
6765
- writer.writeAscii("\nendobj\n");
6822
+ return links;
6823
+ }
6824
+ function readLinkUri(annot, resolver) {
6825
+ const action = resolver.resolveDict(dictGet(annot, "A"));
6826
+ if (action === void 0 || asName(dictGet(action, "S")) !== "URI") return;
6827
+ const uriObj = dictGet(action, "URI");
6828
+ return uriObj?.kind === "string" ? decodePdfString(uriObj.bytes) : void 0;
6829
+ }
6830
+ function readPageNotes(page, resolver) {
6831
+ const annotsArr = asArray(dictGet(page, "Annots"));
6832
+ if (annotsArr === void 0) return;
6833
+ for (const annotRef of annotsArr) {
6834
+ const annot = resolver.resolveDict(annotRef);
6835
+ if (annot === void 0 || asName(dictGet(annot, "Subtype")) !== "Text") continue;
6836
+ const titleObj = dictGet(annot, "T");
6837
+ if ((titleObj?.kind === "string" ? decodePdfString(titleObj.bytes) : void 0) !== "documents.js:notes") continue;
6838
+ const contentsObj = dictGet(annot, "Contents");
6839
+ if (contentsObj?.kind === "string") return decodePdfString(contentsObj.bytes);
6766
6840
  }
6767
- const maxObjNum = nextObjNum - 1;
6768
- const xrefOffset = writer.length;
6769
- writer.writeAscii("xref\n");
6770
- writer.writeAscii(`0 ${maxObjNum + 1}\n`);
6771
- writer.writeAscii(xrefEntry(0, 65535, false));
6772
- for (let num = 1; num <= maxObjNum; num++) {
6773
- const offset = offsets.get(num);
6774
- if (offset === void 0) throw new Error(`object ${num} was allocated but never written -- this is a writePdf internal invariant violation`);
6775
- writer.writeAscii(xrefEntry(offset, 0, true));
6841
+ }
6842
+ function decodePdfString(bytes) {
6843
+ if (bytes.length >= 2 && bytes[0] === 254 && bytes[1] === 255) {
6844
+ let out = "";
6845
+ for (let i = 2; i + 1 < bytes.length; i += 2) out += String.fromCharCode((bytes[i] ?? 0) << 8 | (bytes[i + 1] ?? 0));
6846
+ return out;
6776
6847
  }
6777
- writer.writeAscii("trailer\n");
6778
- writeObject(writer, pdfDict({
6779
- Size: pdfNum(maxObjNum + 1),
6780
- Root: pdfRef(catalogNum, 0),
6781
- Info: pdfRef(infoNum, 0)
6782
- }));
6783
- writer.writeAscii("\nstartxref\n");
6784
- writer.writeAscii(`${xrefOffset}\n`);
6785
- writer.writeAscii("%%EOF");
6786
- return writer.toBytes();
6848
+ return Array.from(bytes, (b) => String.fromCharCode(b)).join("");
6849
+ }
6850
+ const PDF_DATE_PATTERN = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([+\-Z])?(\d{2})?'?(\d{2})?'?$/;
6851
+ function parsePdfDate(raw) {
6852
+ if (raw === void 0) return;
6853
+ const match = PDF_DATE_PATTERN.exec(raw);
6854
+ if (match === null) return;
6855
+ const [, year, month = "01", day = "01", hour = "00", minute = "00", second = "00", tzSign, tzHour = "00", tzMinute = "00"] = match;
6856
+ return `${year}-${month}-${day}T${hour}:${minute}:${second}${tzSign === void 0 || tzSign === "Z" ? "Z" : `${tzSign}${tzHour}:${tzMinute}`}`;
6857
+ }
6858
+ function readMetadata(trailer, resolver) {
6859
+ const info = resolver.resolveDict(dictGet(trailer, "Info"));
6860
+ if (info === void 0) return {};
6861
+ const stringField = (key) => {
6862
+ const obj = dictGet(info, key);
6863
+ return obj?.kind === "string" ? decodePdfString(obj.bytes) : void 0;
6864
+ };
6865
+ const keywords = stringField("Keywords")?.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
6866
+ return {
6867
+ title: stringField("Title"),
6868
+ author: stringField("Author"),
6869
+ subject: stringField("Subject"),
6870
+ keywords: keywords !== void 0 && keywords.length > 0 ? keywords : void 0,
6871
+ creator: stringField("Creator"),
6872
+ producer: stringField("Producer"),
6873
+ createdIso: parsePdfDate(stringField("CreationDate")),
6874
+ modifiedIso: parsePdfDate(stringField("ModDate"))
6875
+ };
6787
6876
  }
6788
6877
  //#endregion
6789
6878
  //#region src/pdf/codec.ts
@@ -7415,7 +7504,8 @@ function convertSlide(slide, measurer, images) {
7415
7504
  return {
7416
7505
  widthPt: slide.size.widthPt,
7417
7506
  heightPt: slide.size.heightPt,
7418
- items
7507
+ items,
7508
+ ...slide.notes.length > 0 ? { notes: slide.notes } : {}
7419
7509
  };
7420
7510
  }
7421
7511
  function convertPresentationToLayout(doc, options) {
@@ -7653,7 +7743,7 @@ function reconstructSlide(page, images) {
7653
7743
  heightPt: page.heightPt
7654
7744
  },
7655
7745
  shapes: [...imageShapes, ...textShapes],
7656
- notes: ""
7746
+ notes: page.notes ?? ""
7657
7747
  };
7658
7748
  }
7659
7749
  function splitLineByLargeGaps(line) {