reamkit 1.25.0 → 1.25.1

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.
@@ -51,7 +51,7 @@ function readPdf(bytes, password = "", layout = "flow", filters = {}) {
51
51
  losses.push({
52
52
  severity: "dropped",
53
53
  feature: FEATURES.images,
54
- detail: "PDF clipping paths and bare-shading (sh) vector regions are not reconstructed"
54
+ detail: "PDF bare-shading (sh) vector regions are not reconstructed"
55
55
  });
56
56
  return {
57
57
  doc: reconstruction.doc,
@@ -447,19 +447,49 @@ function shapeDrawingXml(shape, losses, state, scope) {
447
447
  function xfrmXml(t, cx, cy) {
448
448
  return `<a:xfrm${t?.rotation60k !== void 0 ? ` rot="${t.rotation60k}"` : ""}${t?.flipH ? " flipH=\"1\"" : ""}${t?.flipV ? " flipV=\"1\"" : ""}><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm>`;
449
449
  }
450
+ /**
451
+ * The factor that carries a path into whole units, and the space it lands in.
452
+ *
453
+ * §20.1.10.16 — ST_Coordinate is a LONG: a path states its geometry in whole
454
+ * units, and a fractional one is not a value the attribute admits. Word does
455
+ * not round it, and does not ignore the shape: it refuses the document, saying
456
+ * only that it "experienced an error trying to open the file".
457
+ *
458
+ * A path space is arbitrary — `a:path w`/`h` declare the coordinate system the
459
+ * shape's extent is mapped onto — so a path that does not land on whole units
460
+ * is restated in a finer space rather than rounded where it stands. A path read
461
+ * off a PDF is in points, and a point split ten thousand ways is finer than any
462
+ * renderer draws. The factor is bounded so the space stays far inside the
463
+ * coordinate range whatever the path's own size, and a path already on whole
464
+ * units is left exactly as it is.
465
+ */
466
+ var PATH_SPACE = 1e7;
467
+ function pathScale(g) {
468
+ const values = [g.pathWidth, g.pathHeight];
469
+ for (const c of g.commands) if (c.cmd === "move" || c.cmd === "line") values.push(c.x, c.y);
470
+ else if (c.cmd === "cubic") values.push(c.x1, c.y1, c.x2, c.y2, c.x, c.y);
471
+ else if (c.cmd === "quad") values.push(c.x1, c.y1, c.x, c.y);
472
+ else if (c.cmd === "arc") values.push(c.wR, c.hR);
473
+ if (values.every((v) => Number.isInteger(v))) return 1;
474
+ const span = Math.max(...values.map((v) => Number.isFinite(v) ? Math.abs(v) : 0), 1);
475
+ return Math.max(1, Math.floor(PATH_SPACE / span));
476
+ }
450
477
  function geomXml(g) {
451
478
  if (g.kind === "custom" && g.custom) {
479
+ const k = pathScale(g.custom);
480
+ const c1 = (v) => Number.isFinite(v) ? Math.round(v * k) : 0;
452
481
  const cmds = g.custom.commands.map((c) => {
453
482
  switch (c.cmd) {
454
- case "move": return `<a:moveTo><a:pt x="${c.x}" y="${c.y}"/></a:moveTo>`;
455
- case "line": return `<a:lnTo><a:pt x="${c.x}" y="${c.y}"/></a:lnTo>`;
456
- case "cubic": return `<a:cubicBezTo><a:pt x="${c.x1}" y="${c.y1}"/><a:pt x="${c.x2}" y="${c.y2}"/><a:pt x="${c.x}" y="${c.y}"/></a:cubicBezTo>`;
457
- case "quad": return `<a:quadBezTo><a:pt x="${c.x1}" y="${c.y1}"/><a:pt x="${c.x}" y="${c.y}"/></a:quadBezTo>`;
458
- case "arc": return `<a:arcTo wR="${c.wR}" hR="${c.hR}" stAng="${c.stAng}" swAng="${c.swAng}"/>`;
483
+ case "move": return `<a:moveTo><a:pt x="${c1(c.x)}" y="${c1(c.y)}"/></a:moveTo>`;
484
+ case "line": return `<a:lnTo><a:pt x="${c1(c.x)}" y="${c1(c.y)}"/></a:lnTo>`;
485
+ case "cubic": return `<a:cubicBezTo><a:pt x="${c1(c.x1)}" y="${c1(c.y1)}"/><a:pt x="${c1(c.x2)}" y="${c1(c.y2)}"/><a:pt x="${c1(c.x)}" y="${c1(c.y)}"/></a:cubicBezTo>`;
486
+ case "quad": return `<a:quadBezTo><a:pt x="${c1(c.x1)}" y="${c1(c.y1)}"/><a:pt x="${c1(c.x)}" y="${c1(c.y)}"/></a:quadBezTo>`;
487
+ case "arc": return `<a:arcTo wR="${c1(c.wR)}" hR="${c1(c.hR)}" stAng="${Math.round(c.stAng)}" swAng="${Math.round(c.swAng)}"/>`;
459
488
  case "close": return "<a:close/>";
460
489
  }
461
490
  }).join("");
462
- const { pathWidth: w, pathHeight: h } = g.custom;
491
+ const w = c1(g.custom.pathWidth);
492
+ const h = c1(g.custom.pathHeight);
463
493
  return `<a:custGeom><a:avLst/><a:gdLst/><a:rect l="0" t="0" r="${w}" b="${h}"/><a:pathLst><a:path w="${w}" h="${h}">${cmds}</a:path></a:pathLst></a:custGeom>`;
464
494
  }
465
495
  const gds = g.adjust && g.adjust.size > 0 ? [...g.adjust].map(([n, v]) => `<a:gd name="${escapeAttr(n)}" fmla="val ${v}"/>`).join("") : "";
@@ -691,25 +721,47 @@ function pPrBody(p) {
691
721
  if (ind) out.push(ind);
692
722
  const spacing = spacingXml(p);
693
723
  if (spacing) out.push(spacing);
694
- if (p.alignment !== DEFAULT_PARA.alignment) out.push(`<w:jc w:val="${p.alignment}"/>`);
724
+ if (JC.has(p.alignment) && p.alignment !== DEFAULT_PARA.alignment) out.push(`<w:jc w:val="${p.alignment}"/>`);
695
725
  return out.join("");
696
726
  }
697
727
  function pPrWithSect(p, closingSectPr) {
698
728
  const inner = pPrBody(p) + (closingSectPr ?? "");
699
729
  return inner !== "" ? `<w:pPr>${inner}</w:pPr>` : "";
700
730
  }
731
+ /**
732
+ * Whether a measurement is one the file can state.
733
+ *
734
+ * A property that was COMPUTED rather than read can arrive `NaN` — an
735
+ * `undefined` that went through arithmetic, a share of a width that was zero —
736
+ * and `NaN` is not a value ST_SignedTwipsMeasure admits. Word does not ignore
737
+ * the attribute: it refuses the whole document, with no indication of what it
738
+ * objected to. `Ream.parse(pdf).convert('docx')` wrote 355 of them on one form.
739
+ *
740
+ * So a measurement is written only when there IS one, and a paragraph that
741
+ * cannot say how it is indented says nothing and inherits, which is what an
742
+ * absent attribute means (§17.3.1.12).
743
+ */
744
+ var stated = (pt) => Number.isFinite(pt);
745
+ /** §17.18.44 ST_Jc — the tokens the attribute admits, so nothing else is written. */
746
+ var JC = new Set([
747
+ "left",
748
+ "right",
749
+ "center",
750
+ "both",
751
+ "distribute"
752
+ ]);
701
753
  function indXml(p) {
702
754
  const attrs = [];
703
- if (p.indentLeft !== DEFAULT_PARA.indentLeft) attrs.push(`w:left="${twips(p.indentLeft)}"`);
704
- if (p.indentRight !== DEFAULT_PARA.indentRight) attrs.push(`w:right="${twips(p.indentRight)}"`);
705
- if (p.indentFirstLine !== DEFAULT_PARA.indentFirstLine) if (p.indentFirstLine < 0) attrs.push(`w:hanging="${twips(-p.indentFirstLine)}"`);
755
+ if (stated(p.indentLeft) && p.indentLeft !== DEFAULT_PARA.indentLeft) attrs.push(`w:left="${twips(p.indentLeft)}"`);
756
+ if (stated(p.indentRight) && p.indentRight !== DEFAULT_PARA.indentRight) attrs.push(`w:right="${twips(p.indentRight)}"`);
757
+ if (stated(p.indentFirstLine) && p.indentFirstLine !== DEFAULT_PARA.indentFirstLine) if (p.indentFirstLine < 0) attrs.push(`w:hanging="${twips(-p.indentFirstLine)}"`);
706
758
  else attrs.push(`w:firstLine="${twips(p.indentFirstLine)}"`);
707
759
  return attrs.length > 0 ? `<w:ind ${attrs.join(" ")}/>` : "";
708
760
  }
709
761
  function spacingXml(p) {
710
762
  const attrs = [];
711
- if (p.spacingBefore !== DEFAULT_PARA.spacingBefore) attrs.push(`w:before="${twips(p.spacingBefore)}"`);
712
- if (p.spacingAfter !== DEFAULT_PARA.spacingAfter) attrs.push(`w:after="${twips(p.spacingAfter)}"`);
763
+ if (stated(p.spacingBefore) && p.spacingBefore !== DEFAULT_PARA.spacingBefore) attrs.push(`w:before="${twips(p.spacingBefore)}"`);
764
+ if (stated(p.spacingAfter) && p.spacingAfter !== DEFAULT_PARA.spacingAfter) attrs.push(`w:after="${twips(p.spacingAfter)}"`);
713
765
  if ((p.spacingLineRule !== DEFAULT_PARA.spacingLineRule || p.spacingLine !== DEFAULT_PARA.spacingLine) && p.spacingLine > 0) {
714
766
  const lineVal = p.spacingLineRule === "auto" ? Math.round(p.spacingLine * 12) : twips(p.spacingLine);
715
767
  attrs.push(`w:line="${lineVal}"`, `w:lineRule="${p.spacingLineRule}"`);
@@ -752,12 +804,12 @@ function levelXml(level) {
752
804
  }
753
805
  function rawParaPrXml(p) {
754
806
  const attrs = [];
755
- if (p.indentLeft !== void 0) attrs.push(`w:left="${twips(p.indentLeft)}"`);
756
- if (p.indentRight !== void 0) attrs.push(`w:right="${twips(p.indentRight)}"`);
757
- if (p.indentFirstLine !== void 0) if (p.indentFirstLine < 0) attrs.push(`w:hanging="${twips(-p.indentFirstLine)}"`);
807
+ if (p.indentLeft !== void 0 && stated(p.indentLeft)) attrs.push(`w:left="${twips(p.indentLeft)}"`);
808
+ if (p.indentRight !== void 0 && stated(p.indentRight)) attrs.push(`w:right="${twips(p.indentRight)}"`);
809
+ if (p.indentFirstLine !== void 0 && stated(p.indentFirstLine)) if (p.indentFirstLine < 0) attrs.push(`w:hanging="${twips(-p.indentFirstLine)}"`);
758
810
  else attrs.push(`w:firstLine="${twips(p.indentFirstLine)}"`);
759
811
  const ind = attrs.length > 0 ? `<w:ind ${attrs.join(" ")}/>` : "";
760
- const jc = p.alignment !== void 0 ? `<w:jc w:val="${p.alignment}"/>` : "";
812
+ const jc = p.alignment !== void 0 && JC.has(p.alignment) ? `<w:jc w:val="${p.alignment}"/>` : "";
761
813
  return ind || jc ? `<w:pPr>${jc}${ind}</w:pPr>` : "";
762
814
  }
763
815
  function rawRunPrXml(r) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reamkit",
3
- "version": "1.25.0",
3
+ "version": "1.25.1",
4
4
  "description": "Ream — convert DOCX, XLSX, PPTX and PDF to PDF, SVG, HTML, DOCX and XLSX, built from scratch on the ECMA-376 and ISO 32000 specifications. Parse once, convert anywhere.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Krassavin <info@reamkit.dev>",