@uurtech/jdf-cli 0.2.2 → 0.2.3

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
@@ -712,7 +712,7 @@ function columnBands(rows) {
712
712
  return bands.map(({ x0, x1 }) => ({ x0, x1 }));
713
713
  }
714
714
  var numeric = (s) => /^[\s$€£¥+\-−–]*[\d.,]+\s*(%|ms|s|k|m|b|M|K|B|x|×)?\s*(\/\w+)?$/i.test(s.trim()) || /^[+\-−]?\d/.test(s.trim()) && /\d$/.test(s.trim().replace(/[%)]$/, ""));
715
- function detectTables(runs, shapes, pageWidthMm) {
715
+ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
716
716
  const out = [];
717
717
  const used = /* @__PURE__ */ new Set();
718
718
  const rows = groupRows(runs, () => false);
@@ -761,6 +761,14 @@ function detectTables(runs, shapes, pageWidthMm) {
761
761
  i++;
762
762
  continue;
763
763
  }
764
+ if (gutters.length && !hasLattice) {
765
+ const b = best.bands;
766
+ const straddles = b.some((band, k) => k < b.length - 1 && gutters.some((g) => band.x1 <= g.x1 + 1 && b[k + 1].x0 >= g.x0 - 1) && band.x1 - band.x0 > 30 && b[k + 1].x1 - b[k + 1].x0 > 30);
767
+ if (straddles) {
768
+ i++;
769
+ continue;
770
+ }
771
+ }
764
772
  const bands = best.bands;
765
773
  const cellText = (row, b) => row.cells.filter((c) => c.x0 < bands[b].x1 - 0.2 && c.x1 > bands[b].x0 + 0.2).map((c) => c.run.text.trim()).join(" ").trim();
766
774
  const grid = [];
@@ -848,8 +856,165 @@ function detectTables(runs, shapes, pageWidthMm) {
848
856
  return out;
849
857
  }
850
858
 
851
- // ../../packages/jdf-pdf-import/src/core.ts
859
+ // ../../packages/jdf-pdf-import/src/columns.ts
860
+ function detectGutters(lines, bodyFontSize, pageWmm) {
861
+ const body = lines.filter((l) => l.text.trim().length > 0 && Math.abs(l.fontSize - bodyFontSize) <= 1.5 && l.width < pageWmm * 0.6 && l.width > 0);
862
+ if (body.length < 12) return [];
863
+ const minX = Math.min(...body.map((l) => l.x));
864
+ const maxX = Math.max(...body.map((l) => l.x + l.width));
865
+ const span = maxX - minX;
866
+ if (span < pageWmm * 0.4) return [];
867
+ const BIN = 0.5;
868
+ const n = Math.ceil(span / BIN) + 1;
869
+ const cov = new Array(n).fill(0);
870
+ for (const l of body) {
871
+ const a = Math.max(0, Math.floor((l.x - minX) / BIN)), b = Math.min(n, Math.ceil((l.x + l.width - minX) / BIN));
872
+ for (let i2 = a; i2 < b; i2++) cov[i2]++;
873
+ }
874
+ const noise = Math.max(2, Math.round(body.length * 0.05));
875
+ const gutters = [];
876
+ let i = 0;
877
+ while (i < n) {
878
+ if (cov[i] > noise) {
879
+ i++;
880
+ continue;
881
+ }
882
+ let j = i;
883
+ while (j < n && cov[j] <= noise) j++;
884
+ const g0 = minX + i * BIN, g1 = minX + j * BIN;
885
+ if (g1 - g0 >= 3 && g0 > minX + span * 0.15 && g1 < maxX - span * 0.15) gutters.push({ x0: g0, x1: g1 });
886
+ i = j;
887
+ }
888
+ const solid = gutters.every((g) => body.filter((l) => l.x + l.width <= g.x0 + 0.5).length >= 4 && body.filter((l) => l.x >= g.x1 - 0.5).length >= 4);
889
+ return solid ? gutters : [];
890
+ }
891
+ function orderByColumns(els, gutters, pageWmm) {
892
+ if (!gutters.length || els.length < 2) return els;
893
+ const bands = [];
894
+ let left = 0;
895
+ for (const g of gutters) {
896
+ bands.push({ x0: left, x1: g.x0 });
897
+ left = g.x1;
898
+ }
899
+ bands.push({ x0: left, x1: pageWmm });
900
+ const colOf = (e) => {
901
+ const x = e.position?.x ?? 0;
902
+ const k = bands.findIndex((b) => x >= b.x0 && x < b.x1);
903
+ if (k >= 0) return k;
904
+ let best = 0, d = Infinity;
905
+ bands.forEach((b, i) => {
906
+ const dd = Math.min(Math.abs(x - b.x0), Math.abs(x - b.x1));
907
+ if (dd < d) {
908
+ d = dd;
909
+ best = i;
910
+ }
911
+ });
912
+ return best;
913
+ };
914
+ const spans = (e) => {
915
+ const c = colOf(e);
916
+ if (c >= bands.length - 1) return false;
917
+ const right = (e.position?.x ?? 0) + (e.width ?? 0);
918
+ const next = bands[c + 1];
919
+ return right > (next.x0 + next.x1) / 2;
920
+ };
921
+ const sorted = els.slice().sort((a, b) => (a.position?.y ?? 0) - (b.position?.y ?? 0) || (a.position?.x ?? 0) - (b.position?.x ?? 0));
922
+ const out = [];
923
+ let band = [];
924
+ const flush = () => {
925
+ for (let c = 0; c < bands.length; c++) for (const e of band) if (colOf(e) === c) out.push(e);
926
+ band = [];
927
+ };
928
+ for (const e of sorted) {
929
+ if (spans(e)) {
930
+ flush();
931
+ out.push(e);
932
+ } else band.push(e);
933
+ }
934
+ flush();
935
+ return out;
936
+ }
937
+
938
+ // ../../packages/jdf-pdf-import/src/paragraphs.ts
852
939
  var PT_TO_MM2 = 0.352778;
940
+ var LIST_START = /^\s*(?:[•◦▪●■\-–—*]\s|\(?\d{1,3}[.)]\s|\(?[a-zA-Z][.)]\s|[ivx]{1,4}[.)]\s)/;
941
+ var TERMINAL = /[.!?:;]["'”’)\]]*$/;
942
+ function textOf(e) {
943
+ if (e.type === "text") return String(e.content ?? "");
944
+ return (e.runs ?? []).map((r) => String(r.text ?? "")).join("");
945
+ }
946
+ function foldParagraphs(elements, meta, pageWmm) {
947
+ const foldable = (e) => !!meta.get(e) && !!e.position && (e.type === "text" && !e.heading || e.type === "richtext");
948
+ const out = [];
949
+ let i = 0;
950
+ while (i < elements.length) {
951
+ const first = elements[i];
952
+ if (!foldable(first)) {
953
+ out.push(first);
954
+ i++;
955
+ continue;
956
+ }
957
+ const para = [first];
958
+ let j = i + 1;
959
+ while (j < elements.length) {
960
+ const prev = para[para.length - 1], next = elements[j];
961
+ if (!foldable(next) || !continues(para, prev, next, meta)) break;
962
+ para.push(next);
963
+ j++;
964
+ }
965
+ out.push(para.length > 1 ? merge(para, meta, pageWmm) : first);
966
+ i = j;
967
+ }
968
+ return out;
969
+ }
970
+ function continues(para, prev, next, meta) {
971
+ const mp = meta.get(prev), mn = meta.get(next); meta.get(para[0]);
972
+ if (mp.face !== mn.face || Math.abs(mp.size - mn.size) >= 0.5) return false;
973
+ const lineH = mp.size * PT_TO_MM2;
974
+ const pitch = next.position.y - prev.position.y;
975
+ if (pitch < lineH * 0.9 || pitch > lineH * 1.75) return false;
976
+ const dx = next.position.x - (para.length === 1 ? prev.position.x : para[0].position.x);
977
+ if (para.length === 1 ? Math.abs(dx) > 6 : Math.abs(dx) > 1.2) return false;
978
+ if (para.length >= 2 && Math.abs(next.position.x - para[1].position.x) > 1.2) return false;
979
+ const nText = textOf(next), pText = textOf(prev).trimEnd();
980
+ if (LIST_START.test(nText) || !nText.trim()) return false;
981
+ const maxW = Math.max(mp.w, mn.w, ...para.map((e) => meta.get(e).w));
982
+ const full = mp.w >= maxW * 0.85;
983
+ const endsHyphen = /[-‐‑]$/.test(pText);
984
+ const softEnd = !TERMINAL.test(pText);
985
+ const startsLower = /^[a-z(\[]/.test(nText.trimStart());
986
+ return full || endsHyphen || softEnd && (startsLower || mp.w >= maxW * 0.6);
987
+ }
988
+ function merge(para, meta, pageWmm) {
989
+ const first = para[0], last = para[para.length - 1];
990
+ const m0 = meta.get(first);
991
+ const lineH = m0.size * PT_TO_MM2;
992
+ const x = Math.min(...para.map((e) => e.position.x));
993
+ const indent = first.position.x - x;
994
+ const right = Math.max(...para.map((e) => e.position.x + meta.get(e).w));
995
+ const pitch = (last.position.y - first.position.y) / (para.length - 1);
996
+ const width = Math.min(pageWmm - x, (right - x) * 1.2 + lineH * 0.4);
997
+ const joiner = () => "\n";
998
+ const style = { ...first.style ?? {}, lineHeight: Math.round(pitch / lineH * 100) / 100, ...indent > 0.5 ? { textIndent: Math.round(indent * 100) / 100 } : {} };
999
+ const allText = para.every((e) => e.type === "text" && !e.link);
1000
+ const base = { position: { x, y: first.position.y }, width: Math.round(width * 100) / 100, style };
1001
+ if (allText) {
1002
+ const content = para.map((e) => String(e.content ?? "").trim().replace(/[ \t]+/g, " ")).join(joiner());
1003
+ const { height: _hh2, ...firstRest } = first;
1004
+ return { ...firstRest, ...base, content };
1005
+ }
1006
+ const runs = [];
1007
+ for (const e of para) {
1008
+ const lineRuns = e.type === "text" ? [{ text: String(e.content ?? "").trim(), ...e.style?.fontWeight === "bold" ? { bold: true } : {}, ...e.style?.fontStyle === "italic" ? { italic: true } : {}, ...e.style?.color && e.style.color !== "#000000" ? { color: e.style.color } : {}, ...e.link ? { link: e.link } : {} }] : (e.runs ?? []).map((r) => ({ ...r }));
1009
+ if (runs.length) runs[runs.length - 1].text = `${String(runs[runs.length - 1].text).trimEnd()}${joiner()}`;
1010
+ runs.push(...lineRuns);
1011
+ }
1012
+ const { content: _c, heading: _h, link: _l, height: _hh, ...rest } = first;
1013
+ return { ...rest, ...base, type: "richtext", runs, style: { fontSize: style.fontSize, fontFamily: style.fontFamily, lineHeight: style.lineHeight, ...style.textIndent ? { textIndent: style.textIndent } : {}, ...style.opacity != null ? { opacity: style.opacity } : {} } };
1014
+ }
1015
+
1016
+ // ../../packages/jdf-pdf-import/src/core.ts
1017
+ var PT_TO_MM3 = 0.352778;
853
1018
  function classifyFont(name) {
854
1019
  const n = (name || "").toLowerCase();
855
1020
  const bold = /bold|black|heavy|semibold|demibold|extrabold/.test(n);
@@ -959,10 +1124,10 @@ async function walkOps(page, OPS, viewport) {
959
1124
  const minY = Math.min(...ys), maxY = Math.max(...ys);
960
1125
  imagePositions.push({
961
1126
  name,
962
- x: minX * PT_TO_MM2,
963
- y: minY * PT_TO_MM2,
964
- w: (maxX - minX) * PT_TO_MM2,
965
- h: (maxY - minY) * PT_TO_MM2,
1127
+ x: minX * PT_TO_MM3,
1128
+ y: minY * PT_TO_MM3,
1129
+ w: (maxX - minX) * PT_TO_MM3,
1130
+ h: (maxY - minY) * PT_TO_MM3,
966
1131
  inline,
967
1132
  maskFill
968
1133
  });
@@ -978,13 +1143,13 @@ async function walkOps(page, OPS, viewport) {
978
1143
  const br = toViewport(r.x + r.w, r.y);
979
1144
  shapes.push({
980
1145
  kind: "rect",
981
- x: Math.min(tl.x, br.x) * PT_TO_MM2,
982
- y: Math.min(tl.y, br.y) * PT_TO_MM2,
983
- width: Math.abs(br.x - tl.x) * PT_TO_MM2,
984
- height: Math.abs(br.y - tl.y) * PT_TO_MM2,
1146
+ x: Math.min(tl.x, br.x) * PT_TO_MM3,
1147
+ y: Math.min(tl.y, br.y) * PT_TO_MM3,
1148
+ width: Math.abs(br.x - tl.x) * PT_TO_MM3,
1149
+ height: Math.abs(br.y - tl.y) * PT_TO_MM3,
985
1150
  fill: isFill ? gs.fill : void 0,
986
1151
  stroke: isStroke ? gs.stroke : void 0,
987
- strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM2 : void 0,
1152
+ strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
988
1153
  opacity: isFill ? gs.fillAlpha : gs.strokeAlpha
989
1154
  });
990
1155
  }
@@ -998,13 +1163,13 @@ async function walkOps(page, OPS, viewport) {
998
1163
  const h = Math.abs(br.y - tl.y);
999
1164
  shapes.push({
1000
1165
  kind: "rect",
1001
- x: x * PT_TO_MM2,
1002
- y: y * PT_TO_MM2,
1003
- width: w * PT_TO_MM2,
1004
- height: h * PT_TO_MM2,
1166
+ x: x * PT_TO_MM3,
1167
+ y: y * PT_TO_MM3,
1168
+ width: w * PT_TO_MM3,
1169
+ height: h * PT_TO_MM3,
1005
1170
  fill: isFill ? gs.fill : void 0,
1006
1171
  stroke: isStroke ? gs.stroke : void 0,
1007
- strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM2 : void 0,
1172
+ strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
1008
1173
  opacity: isFill ? gs.fillAlpha : gs.strokeAlpha
1009
1174
  });
1010
1175
  } else if (pathSegments.length === 2 && pathSegments[0].type === "M" && pathSegments[1].type === "L") {
@@ -1016,35 +1181,35 @@ async function walkOps(page, OPS, viewport) {
1016
1181
  const minY = Math.min(va.y, vb.y);
1017
1182
  const maxX = Math.max(va.x, vb.x);
1018
1183
  const maxY = Math.max(va.y, vb.y);
1019
- const x1Local = (va.x - minX) * PT_TO_MM2;
1020
- const y1Local = (va.y - minY) * PT_TO_MM2;
1021
- const x2Local = (vb.x - minX) * PT_TO_MM2;
1022
- const y2Local = (vb.y - minY) * PT_TO_MM2;
1023
- const wLocal = Math.max(0.05, (maxX - minX) * PT_TO_MM2);
1024
- const hLocal = Math.max(0.05, (maxY - minY) * PT_TO_MM2);
1184
+ const x1Local = (va.x - minX) * PT_TO_MM3;
1185
+ const y1Local = (va.y - minY) * PT_TO_MM3;
1186
+ const x2Local = (vb.x - minX) * PT_TO_MM3;
1187
+ const y2Local = (vb.y - minY) * PT_TO_MM3;
1188
+ const wLocal = Math.max(0.05, (maxX - minX) * PT_TO_MM3);
1189
+ const hLocal = Math.max(0.05, (maxY - minY) * PT_TO_MM3);
1025
1190
  const dx = Math.abs(va.x - vb.x);
1026
1191
  const dy = Math.abs(va.y - vb.y);
1027
1192
  const axisAligned = dx < 0.5 || dy < 0.5;
1028
1193
  if (axisAligned) {
1029
1194
  shapes.push({
1030
1195
  kind: "line",
1031
- x: minX * PT_TO_MM2,
1032
- y: minY * PT_TO_MM2,
1196
+ x: minX * PT_TO_MM3,
1197
+ y: minY * PT_TO_MM3,
1033
1198
  width: wLocal,
1034
1199
  height: hLocal,
1035
1200
  stroke: isStroke ? gs.stroke : void 0,
1036
- strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM2 : void 0,
1201
+ strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
1037
1202
  opacity: gs.strokeAlpha
1038
1203
  });
1039
1204
  } else {
1040
1205
  shapes.push({
1041
1206
  kind: "path",
1042
- x: minX * PT_TO_MM2,
1043
- y: minY * PT_TO_MM2,
1207
+ x: minX * PT_TO_MM3,
1208
+ y: minY * PT_TO_MM3,
1044
1209
  width: wLocal,
1045
1210
  height: hLocal,
1046
1211
  stroke: isStroke ? gs.stroke : void 0,
1047
- strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM2 : void 0,
1212
+ strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
1048
1213
  opacity: gs.strokeAlpha,
1049
1214
  path: `M ${x1Local.toFixed(2)} ${y1Local.toFixed(2)} L ${x2Local.toFixed(2)} ${y2Local.toFixed(2)}`
1050
1215
  });
@@ -1077,20 +1242,20 @@ async function walkOps(page, OPS, viewport) {
1077
1242
  if (seg.type === "Z") return "Z";
1078
1243
  const p = [];
1079
1244
  for (let i = 0; i < seg.pts.length; i += 2) {
1080
- p.push(((seg.pts[i] - minX) * PT_TO_MM2).toFixed(2));
1081
- p.push(((seg.pts[i + 1] - minY) * PT_TO_MM2).toFixed(2));
1245
+ p.push(((seg.pts[i] - minX) * PT_TO_MM3).toFixed(2));
1246
+ p.push(((seg.pts[i + 1] - minY) * PT_TO_MM3).toFixed(2));
1082
1247
  }
1083
1248
  return `${seg.type} ${p.join(" ")}`;
1084
1249
  }).join(" ");
1085
1250
  shapes.push({
1086
1251
  kind: "path",
1087
- x: minX * PT_TO_MM2,
1088
- y: minY * PT_TO_MM2,
1089
- width: bw * PT_TO_MM2,
1090
- height: bh * PT_TO_MM2,
1252
+ x: minX * PT_TO_MM3,
1253
+ y: minY * PT_TO_MM3,
1254
+ width: bw * PT_TO_MM3,
1255
+ height: bh * PT_TO_MM3,
1091
1256
  fill: isFill ? gs.fill : void 0,
1092
1257
  stroke: isStroke ? gs.stroke : void 0,
1093
- strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM2 : void 0,
1258
+ strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
1094
1259
  opacity: isFill ? gs.fillAlpha : gs.strokeAlpha,
1095
1260
  path: d
1096
1261
  });
@@ -1431,10 +1596,10 @@ async function extractLinks(doc, page, viewport) {
1431
1596
  const xMax = Math.max(c1.x, c2.x);
1432
1597
  const yMax = Math.max(c1.y, c2.y);
1433
1598
  const rectMm = {
1434
- x: xMin * PT_TO_MM2,
1435
- y: yMin * PT_TO_MM2,
1436
- w: (xMax - xMin) * PT_TO_MM2,
1437
- h: (yMax - yMin) * PT_TO_MM2
1599
+ x: xMin * PT_TO_MM3,
1600
+ y: yMin * PT_TO_MM3,
1601
+ w: (xMax - xMin) * PT_TO_MM3,
1602
+ h: (yMax - yMin) * PT_TO_MM3
1438
1603
  };
1439
1604
  const url = a.url || a.unsafeUrl;
1440
1605
  const destPage = url ? void 0 : await resolveDestPage(doc, a.dest);
@@ -1472,10 +1637,10 @@ async function extractFormWidgets(page, viewport) {
1472
1637
  })).filter((o) => o.value !== "") : [];
1473
1638
  out.push({
1474
1639
  rectMm: {
1475
- x: xMin * PT_TO_MM2,
1476
- y: yMin * PT_TO_MM2,
1477
- w: (xMax - xMin) * PT_TO_MM2,
1478
- h: (yMax - yMin) * PT_TO_MM2
1640
+ x: xMin * PT_TO_MM3,
1641
+ y: yMin * PT_TO_MM3,
1642
+ w: (xMax - xMin) * PT_TO_MM3,
1643
+ h: (yMax - yMin) * PT_TO_MM3
1479
1644
  },
1480
1645
  fieldType: a.fieldType || "",
1481
1646
  fieldName: a.fieldName || `field-${out.length + 1}`,
@@ -1764,12 +1929,12 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1764
1929
  const w = safeNum(it.width, 0);
1765
1930
  runs.push({
1766
1931
  text: it.str,
1767
- x: safeNum(vx * PT_TO_MM2, 0),
1768
- y: safeNum(yTop * PT_TO_MM2, 0),
1932
+ x: safeNum(vx * PT_TO_MM3, 0),
1933
+ y: safeNum(yTop * PT_TO_MM3, 0),
1769
1934
  fontSize: safeNum(fontSize, 10),
1770
1935
  fontName: it.fontName,
1771
- width: safeNum(w * PT_TO_MM2, 0),
1772
- height: safeNum((it.height || fontSize) * PT_TO_MM2, fontSize * PT_TO_MM2),
1936
+ width: safeNum(w * PT_TO_MM3, 0),
1937
+ height: safeNum((it.height || fontSize) * PT_TO_MM3, fontSize * PT_TO_MM3),
1773
1938
  color: op?.fill || "#000000",
1774
1939
  opacity: invisible ? 0 : safeNum(op?.alpha, 1)
1775
1940
  });
@@ -1792,10 +1957,10 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1792
1957
  }
1793
1958
  const sameLine = Math.abs(last.y - r.y) <= Y_TOL;
1794
1959
  const sameStyle = Math.abs(last.fontSize - r.fontSize) < 0.4 && (last.fontName === r.fontName || fontKey(last.fontName) === fontKey(r.fontName)) && last.color === r.color && Math.abs(last.opacity - r.opacity) < 0.05;
1795
- const emMm = r.fontSize * PT_TO_MM2;
1960
+ const emMm = r.fontSize * PT_TO_MM3;
1796
1961
  const extent = (t) => {
1797
1962
  if (!/\s$/.test(t.text)) return t.width;
1798
- const em = t.fontSize * PT_TO_MM2;
1963
+ const em = t.fontSize * PT_TO_MM3;
1799
1964
  const est = Math.max(1, t.text.trim().length) * em * kGlyph + em * 0.25;
1800
1965
  if (stretchedSpaces) return Math.min(t.width, est);
1801
1966
  return t.width > est * 1.4 ? est : t.width;
@@ -1814,11 +1979,19 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1814
1979
  }
1815
1980
  }
1816
1981
  const elements = [];
1982
+ const lineMeta = /* @__PURE__ */ new WeakMap();
1817
1983
  const tRuns = lines.map((l) => {
1818
1984
  const cls = fontMap.get(l.fontName) || classifyFont(l.fontName || "");
1819
1985
  return { text: l.text, x: l.x, y: l.y, width: l.width, height: l.height, fontSize: l.fontSize, fontName: l.fontName, color: l.color, bold: cls.weight === "bold" };
1820
1986
  });
1821
- const detected = options.detectTables === false ? [] : detectTables(tRuns, ops.shapes, pageW * PT_TO_MM2);
1987
+ const sizeChars = /* @__PURE__ */ new Map();
1988
+ for (const l of lines) {
1989
+ const k = Math.round(l.fontSize * 2) / 2;
1990
+ sizeChars.set(k, (sizeChars.get(k) ?? 0) + l.text.length);
1991
+ }
1992
+ const bodyFontSize = [...sizeChars.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 0;
1993
+ const gutters = options.readingOrder === false ? [] : detectGutters(lines.map((l) => ({ text: l.text, x: l.x, y: l.y, width: l.width, fontSize: l.fontSize })), bodyFontSize, pageW * PT_TO_MM3);
1994
+ const detected = options.detectTables === false ? [] : detectTables(tRuns, ops.shapes, pageW * PT_TO_MM3, gutters);
1822
1995
  const consumedLines = /* @__PURE__ */ new Set();
1823
1996
  const consumedShapes = /* @__PURE__ */ new Set();
1824
1997
  const tableAtLine = /* @__PURE__ */ new Map();
@@ -1827,7 +2000,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1827
2000
  for (const k of t.shapeIdx) consumedShapes.add(k);
1828
2001
  tableAtLine.set(Math.min(...t.lineIdx), t.element);
1829
2002
  }
1830
- const pageWmm = pageW * PT_TO_MM2, pageHmm = pageH * PT_TO_MM2;
2003
+ const pageWmm = pageW * PT_TO_MM3, pageHmm = pageH * PT_TO_MM3;
1831
2004
  ops.shapes.forEach((sh, shapeIdx) => {
1832
2005
  if (consumedShapes.has(shapeIdx)) return;
1833
2006
  if (sh.width < 0.3 && sh.height < 0.3) return;
@@ -1871,12 +2044,6 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1871
2044
  fit: "fill"
1872
2045
  });
1873
2046
  }
1874
- const sizeChars = /* @__PURE__ */ new Map();
1875
- for (const l of lines) {
1876
- const k = Math.round(l.fontSize * 2) / 2;
1877
- sizeChars.set(k, (sizeChars.get(k) ?? 0) + l.text.length);
1878
- }
1879
- const bodyFontSize = [...sizeChars.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 0;
1880
2047
  const rowOf = /* @__PURE__ */ new Map();
1881
2048
  const rowStartOf = /* @__PURE__ */ new Map();
1882
2049
  const nextOnRow = /* @__PURE__ */ new Map();
@@ -1884,10 +2051,10 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1884
2051
  const order = lines.map((_, i) => i).filter((i) => !consumedLines.has(i));
1885
2052
  for (let a = 0; a < order.length; a++) {
1886
2053
  const i = order[a], li = lines[i];
1887
- const tolY = Math.max(0.6, li.fontSize * PT_TO_MM2 * 0.35);
1888
2054
  let bestNext = -1, bestX = Infinity;
1889
2055
  for (let b = 0; b < order.length; b++) {
1890
2056
  const j = order[b], lj = lines[j];
2057
+ const tolY = Math.max(0.6, Math.max(li.fontSize, lj.fontSize) * PT_TO_MM3 * 0.5);
1891
2058
  if (j === i || Math.abs(lj.y - li.y) > tolY || lj.x <= li.x) continue;
1892
2059
  if (lj.x < bestX) {
1893
2060
  bestX = lj.x;
@@ -1904,7 +2071,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1904
2071
  let cur = i;
1905
2072
  while (nextOnRow.has(cur)) {
1906
2073
  const j = nextOnRow.get(cur), lc = lines[cur], lj = lines[j];
1907
- const em = Math.min(lc.fontSize, lj.fontSize) * PT_TO_MM2;
2074
+ const em = Math.min(lc.fontSize, lj.fontSize) * PT_TO_MM3;
1908
2075
  const gap = lj.x - (lc.x + lc.width);
1909
2076
  if (gap < -em * 0.3 || gap > em * 0.6) break;
1910
2077
  row.push(j);
@@ -1929,9 +2096,9 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1929
2096
  const first = lines[row[0]], last = lines[row[row.length - 1]];
1930
2097
  const base = runStyle(first);
1931
2098
  const rowEnd = last.x + last.width;
1932
- const measuredW = Math.max((rowEnd - first.x) * 1.2 + first.fontSize * PT_TO_MM2 * 0.4, first.fontSize * PT_TO_MM2);
2099
+ const measuredW = Math.max((rowEnd - first.x) * 1.2 + first.fontSize * PT_TO_MM3 * 0.4, first.fontSize * PT_TO_MM3);
1933
2100
  const nextIdx2 = nextOnRow.get(row[row.length - 1]);
1934
- const cap2 = nextIdx2 != null ? lines[nextIdx2].x - first.x - first.fontSize * PT_TO_MM2 * 0.3 : pageWmm - first.x;
2101
+ const cap2 = nextIdx2 != null ? lines[nextIdx2].x - first.x - first.fontSize * PT_TO_MM3 * 0.3 : pageWmm - first.x;
1935
2102
  const runs2 = [];
1936
2103
  row.forEach((idx, k) => {
1937
2104
  const r = lines[idx];
@@ -1940,7 +2107,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1940
2107
  if (k > 0) {
1941
2108
  const prev2 = lines[row[k - 1]];
1942
2109
  const gap = r.x - (prev2.x + prev2.width);
1943
- if (gap > r.fontSize * PT_TO_MM2 * 0.08 && !/\s$/.test(prev2.text) && !/^\s/.test(text2)) text2 = " " + text2;
2110
+ if (gap > r.fontSize * PT_TO_MM3 * 0.08 && !/\s$/.test(prev2.text) && !/^\s/.test(text2)) text2 = " " + text2;
1944
2111
  }
1945
2112
  const run = { text: text2 };
1946
2113
  if (st.bold) run.bold = true;
@@ -1954,13 +2121,16 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1954
2121
  });
1955
2122
  const style2 = { fontSize: Math.round(first.fontSize * 10) / 10, fontFamily: base.cls.family };
1956
2123
  if (first.opacity < 0.999) style2.opacity = Math.round(first.opacity * 100) / 100;
1957
- elements.push({
2124
+ const rt = {
1958
2125
  type: "richtext",
1959
2126
  runs: runs2,
1960
2127
  position: { x: Math.max(0, Math.round(first.x * 100) / 100), y: Math.max(0, Math.round(Math.min(...row.map((i) => lines[i].y)) * 100) / 100) },
1961
- width: Math.max(2, Math.round(Math.max(first.fontSize * PT_TO_MM2, Math.min(measuredW, cap2)) * 100) / 100),
2128
+ width: Math.max(2, Math.round(Math.max(first.fontSize * PT_TO_MM3, Math.min(measuredW, cap2)) * 100) / 100),
1962
2129
  style: style2
1963
- });
2130
+ };
2131
+ const dominant = row.map((i) => lines[i]).sort((a, b) => b.text.trim().length - a.text.trim().length)[0];
2132
+ lineMeta.set(rt, { w: Math.max(first.fontSize * PT_TO_MM3, rowEnd - first.x), size: dominant.fontSize, face: fontKey(dominant.fontName) });
2133
+ elements.push(rt);
1964
2134
  return;
1965
2135
  }
1966
2136
  const cls = fontMap.get(l.fontName) || classifyFont(l.fontName || "");
@@ -1973,10 +2143,10 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1973
2143
  if (l.color !== "#000000") style.color = l.color;
1974
2144
  if (l.opacity < 0.999) style.opacity = Math.round(l.opacity * 100) / 100;
1975
2145
  const link = findLinkForRun2(l);
1976
- const measured = Math.max(l.width * 1.2 + l.fontSize * PT_TO_MM2 * 0.4, l.fontSize * PT_TO_MM2);
2146
+ const measured = Math.max(l.width * 1.2 + l.fontSize * PT_TO_MM3 * 0.4, l.fontSize * PT_TO_MM3);
1977
2147
  const remaining = Math.max(measured, pageWmm - l.x);
1978
2148
  const nextIdx = nextOnRow.get(lineIdx);
1979
- const cap = nextIdx != null ? Math.max(l.fontSize * PT_TO_MM2, lines[nextIdx].x - l.x - l.fontSize * PT_TO_MM2 * 0.3) : Infinity;
2149
+ const cap = nextIdx != null ? Math.max(l.fontSize * PT_TO_MM3, lines[nextIdx].x - l.x - l.fontSize * PT_TO_MM3 * 0.3) : Infinity;
1980
2150
  const elWidth = Math.min(measured, remaining, cap);
1981
2151
  const text = {
1982
2152
  type: "text",
@@ -1997,14 +2167,26 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
1997
2167
  else if (link.destPage != null) text.link = { type: "internal", target: `#page-${link.destPage + 1}` };
1998
2168
  }
1999
2169
  const prev = elements[elements.length - 1];
2000
- if (text.heading && prev && prev.type === "text" && prev.heading === text.heading && !link && !prev.link && Math.abs(prev.style?.fontSize - style.fontSize) < 0.5 && Math.abs(prev.position.x - text.position.x) < 1 && text.position.y - prev.position.y < l.fontSize * PT_TO_MM2 * 2.2 && text.position.y > prev.position.y) {
2170
+ if (text.heading && prev && prev.type === "text" && prev.heading === text.heading && !link && !prev.link && Math.abs(prev.style?.fontSize - style.fontSize) < 0.5 && Math.abs(prev.position.x - text.position.x) < 1 && text.position.y - prev.position.y < l.fontSize * PT_TO_MM3 * 2.2 && text.position.y > prev.position.y) {
2001
2171
  prev.content = `${prev.content} ${text.content}`.replace(/\s+/g, " ");
2002
2172
  prev.tocEntry = prev.content;
2003
2173
  prev.width = Math.max(prev.width ?? 0, text.width ?? 0);
2004
2174
  return;
2005
2175
  }
2176
+ lineMeta.set(text, { w: Math.max(l.fontSize * PT_TO_MM3, l.width), size: l.fontSize, face: fontKey(l.fontName) });
2006
2177
  elements.push(text);
2007
2178
  });
2179
+ if (options.readingOrder !== false) {
2180
+ if (gutters.length) {
2181
+ const isFlow = (e) => e.type === "text" || e.type === "richtext" || e.type === "table";
2182
+ const flow = elements.filter(isFlow), rest = elements.filter((e) => !isFlow(e));
2183
+ elements.splice(0, elements.length, ...rest, ...orderByColumns(flow, gutters, pageWmm));
2184
+ }
2185
+ }
2186
+ if (options.foldParagraphs !== false) {
2187
+ const folded = foldParagraphs(elements, lineMeta, pageWmm);
2188
+ elements.splice(0, elements.length, ...folded);
2189
+ }
2008
2190
  for (const w of formWidgets) {
2009
2191
  if (w.pushButton) continue;
2010
2192
  const baseEl = {
@@ -2039,7 +2221,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
2039
2221
  }
2040
2222
  pages.push({
2041
2223
  id: `page-${pi}`,
2042
- pageSize: { width: Math.round(pageW * PT_TO_MM2 * 100) / 100, height: Math.round(pageH * PT_TO_MM2 * 100) / 100 },
2224
+ pageSize: { width: Math.round(pageW * PT_TO_MM3 * 100) / 100, height: Math.round(pageH * PT_TO_MM3 * 100) / 100 },
2043
2225
  margins: { top: 0, right: 0, bottom: 0, left: 0 },
2044
2226
  elements
2045
2227
  });
@@ -103,6 +103,7 @@
103
103
  "backgroundColor": { "type": "string" },
104
104
  "textAlign": { "type": "string", "enum": ["left","center","right","justify"] },
105
105
  "lineHeight": { "type": "number" },
106
+ "textIndent": { "type": "number", "description": "First-line indent in the document unit." },
106
107
  "letterSpacing": { "oneOf": [{ "type": "number" }, { "type": "string" }] },
107
108
  "padding": { "oneOf": [{ "type": "number" }, { "type": "string" }, { "$ref": "#/definitions/Margins" }] },
108
109
  "margin": { "oneOf": [{ "type": "number" }, { "type": "string" }, { "$ref": "#/definitions/Margins" }] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uurtech/jdf-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Command-line tool for the JDF (JSON Document Format) — validate and convert documents.",
5
5
  "license": "MIT",
6
6
  "author": "Ugur Kazdal",