@ai-matrx/print 0.7.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1
4
+
5
+ - **Fixed (PDF):** a heading followed by a table taller than a page vanished
6
+ together with the whole table — keep-with-next glued them into one
7
+ unbreakable block and pdfmake drops a block that cannot fit a page. A
8
+ heading now travels only with blocks that always fit (a short paragraph,
9
+ a figure, a short list); tables are never unbreakable (rows never split,
10
+ the header row repeats, a caption above keeps with its table); code blocks
11
+ stay whole up to 30 lines. Found printing a real note.
12
+ - **Fixed (PDF):** emoji and pictographs printed as empty boxes (the embedded
13
+ fonts have no glyphs for them). They are left out of the PDF and announced
14
+ once with the `glyph-substituted` notice; Word, EPUB and HTML keep them.
15
+ - Consumer action: none — refresh to latest.
16
+
3
17
  ## 0.7.0
4
18
 
5
19
  - **Added: `@ai-matrx/print/document` — print-grade documents from markdown.**
package/dist/document.cjs CHANGED
@@ -1456,9 +1456,24 @@ var courierCanDraw = (s) => {
1456
1456
  }
1457
1457
  return true;
1458
1458
  };
1459
+ var NO_GLYPH = /[\p{Extended_Pictographic}\u{1F1E6}-\u{1F1FF}\u200D\uFE0F\u20E3]/gu;
1460
+ function drawable(text, c) {
1461
+ if (!NO_GLYPH.test(text)) return text;
1462
+ NO_GLYPH.lastIndex = 0;
1463
+ if (!c.glyphs.announced) {
1464
+ c.glyphs.announced = true;
1465
+ c.tree.notices.push({
1466
+ code: "glyph-substituted",
1467
+ message: "Emoji and pictographs were left out of the PDF: its fonts cannot draw them (Word, EPUB and HTML keep them).",
1468
+ remedy: "Use words or plain symbols instead of emoji if they must appear in the PDF."
1469
+ });
1470
+ }
1471
+ return text.replace(NO_GLYPH, "").replace(/ {2,}/g, " ");
1472
+ }
1459
1473
  function runs2(nodes, c, st = {}) {
1460
1474
  const out = [];
1461
- const base = (text) => {
1475
+ const base = (raw) => {
1476
+ const text = drawable(raw, c);
1462
1477
  const r = { text };
1463
1478
  if (st.bold) r.bold = true;
1464
1479
  if (st.italics) r.italics = true;
@@ -1535,6 +1550,8 @@ var capNode = (cap, c, where) => ({
1535
1550
  text: [{ text: cap.label, bold: true, color: COLOR.heading }, ...cap.children.length ? [": ", ...runs2(cap.children, c)] : []],
1536
1551
  style: "caption",
1537
1552
  id: cap.id ? `${cap.kind}:${cap.id}` : void 0,
1553
+ // A caption above its table is never the last line on a page.
1554
+ ...where === "above" ? { headlineLevel: 9 } : {},
1538
1555
  margin: where === "above" ? [0, 0, 0, 4] : [0, 4, 0, 0]
1539
1556
  });
1540
1557
  function estimateLines(text, c) {
@@ -1557,13 +1574,12 @@ function taskItem(checked, content, c) {
1557
1574
  columnGap: 2
1558
1575
  };
1559
1576
  }
1560
- function gluable(b) {
1577
+ function gluable(b, c) {
1561
1578
  if (!b) return false;
1562
- if (b.type === "paragraph" || b.type === "figure" || b.type === "rule") return true;
1563
- if (b.type === "code") return b.value.split("\n").length <= 45;
1564
- if (b.type === "table") return b.rows.length <= 20;
1565
- if (b.type === "list") return b.items.length <= 12;
1566
- if (b.type === "blockquote") return b.children.length <= 4;
1579
+ if (b.type === "paragraph") return estimateLines(plain(b.children), c) <= 5;
1580
+ if (b.type === "figure" || b.type === "rule") return true;
1581
+ if (b.type === "list")
1582
+ return b.items.length <= 4 && b.items.every((i) => i.children.length === 1 && i.children[0]?.type === "paragraph");
1567
1583
  return false;
1568
1584
  }
1569
1585
  function blocks2(list, c) {
@@ -1572,7 +1588,7 @@ function blocks2(list, c) {
1572
1588
  for (let i = 0; i < list.length; i++) {
1573
1589
  const b = list[i];
1574
1590
  let node = block2(b, c);
1575
- if (node && b.type === "heading" && gluable(list[i + 1])) {
1591
+ if (node && b.type === "heading" && gluable(list[i + 1], c)) {
1576
1592
  const next = block2(list[i + 1], c);
1577
1593
  if (next) {
1578
1594
  node = { stack: [node, next], unbreakable: true };
@@ -1642,7 +1658,7 @@ function block2(b, c) {
1642
1658
  const lines = b.value.replace(/\n$/, "").split("\n");
1643
1659
  const size = c.s.fontSize * 0.8;
1644
1660
  const stack = lines.map((l) => {
1645
- const t2 = l.replace(/\t/g, " ") || " ";
1661
+ const t2 = drawable(l.replace(/\t/g, " "), c) || " ";
1646
1662
  return courierCanDraw(t2) ? { text: t2, font: "Courier", fontSize: size, preserveLeadingSpaces: true } : { text: t2, fontSize: size, preserveLeadingSpaces: true };
1647
1663
  });
1648
1664
  return {
@@ -1658,8 +1674,9 @@ function block2(b, c) {
1658
1674
  paddingBottom: () => 6
1659
1675
  },
1660
1676
  margin: [0, 0, 0, 8],
1661
- // Keep a block that fits on one page together.
1662
- unbreakable: lines.length <= 45
1677
+ // Keep a block that fits on one page together (30 lines fits even a
1678
+ // landscape page; a taller unbreakable block would be dropped).
1679
+ unbreakable: lines.length <= 30
1663
1680
  };
1664
1681
  }
1665
1682
  case "table": {
@@ -1693,7 +1710,7 @@ function block2(b, c) {
1693
1710
  const stack = [];
1694
1711
  if (b.caption) stack.push(capNode(b.caption, c, "above"));
1695
1712
  stack.push(table);
1696
- return { stack, margin: [0, 2, 0, 10], unbreakable: b.rows.length <= 20 };
1713
+ return { stack, margin: [0, 2, 0, 10] };
1697
1714
  }
1698
1715
  case "figure": {
1699
1716
  const img = c.images?.get(b.url);
@@ -1779,6 +1796,7 @@ function buildPdfDefinition(tree, images, pageCountSink) {
1779
1796
  const headings = [];
1780
1797
  for (const sec of tree.sections) for (const b of sec.blocks) if (b.type === "heading") headings.push(b.level);
1781
1798
  const c = {
1799
+ glyphs: { announced: false },
1782
1800
  tree,
1783
1801
  images,
1784
1802
  s,
@@ -1900,7 +1918,7 @@ async function documentToPdf(tree, images) {
1900
1918
  }
1901
1919
  async function headingPages(tree, images) {
1902
1920
  const pdfMake = await loadPdfMake();
1903
- const def = buildPdfDefinition(tree, images);
1921
+ const def = buildPdfDefinition({ ...tree, notices: [] }, images);
1904
1922
  await pdfMake.createPdf(def).getBuffer();
1905
1923
  const want = new Set(tree.toc.map((e) => e.id));
1906
1924
  const pages = /* @__PURE__ */ new Map();