js.documents 7.2.0 → 7.2.2

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.
@@ -44,8 +44,86 @@ function dropOverlappingRepeatsWithinLine(items) {
44
44
  }
45
45
  return kept;
46
46
  }
47
+ const REDRAW_PASS_REWIND_TOLERANCE_PT = 2;
48
+ const MIN_REDRAW_PASS_TEXT_LENGTH = 12;
49
+ const REDRAW_PASS_SIMILARITY_THRESHOLD = .6;
50
+ function groupByBaselineInDocumentOrder(items) {
51
+ const groups = [];
52
+ for (const item of items) {
53
+ const tolerance = LINE_BASELINE_TOLERANCE_FACTOR * item.sizePt;
54
+ const group = groups.find((g) => Math.abs(g.baselineY - item.yPt) <= tolerance);
55
+ if (group === void 0) groups.push({
56
+ baselineY: item.yPt,
57
+ items: [item]
58
+ });
59
+ else group.items.push(item);
60
+ }
61
+ return groups.map((g) => g.items);
62
+ }
63
+ function splitIntoPasses(group) {
64
+ const passes = [];
65
+ for (const item of group) {
66
+ const current = passes.at(-1);
67
+ const itemRightPt = item.xPt + (item.widthPt ?? 0);
68
+ if (current !== void 0 && item.xPt >= current.endXPt - REDRAW_PASS_REWIND_TOLERANCE_PT) {
69
+ current.items.push(item);
70
+ current.endXPt = Math.max(current.endXPt, itemRightPt);
71
+ } else passes.push({
72
+ items: [item],
73
+ startXPt: item.xPt,
74
+ endXPt: itemRightPt
75
+ });
76
+ }
77
+ return passes;
78
+ }
79
+ function passText(pass) {
80
+ return pass.items.map((item) => item.text).join("").trim();
81
+ }
82
+ function passesOverlap(a, b) {
83
+ return a.startXPt < b.endXPt && b.startXPt < a.endXPt;
84
+ }
85
+ function levenshteinDistance(a, b) {
86
+ const memo = /* @__PURE__ */ new Map();
87
+ function distance(i, j) {
88
+ if (i === 0) return j;
89
+ if (j === 0) return i;
90
+ const key = `${String(i)}:${String(j)}`;
91
+ const cached = memo.get(key);
92
+ if (cached !== void 0) return cached;
93
+ const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
94
+ const result = Math.min(distance(i - 1, j) + 1, distance(i, j - 1) + 1, distance(i - 1, j - 1) + cost);
95
+ memo.set(key, result);
96
+ return result;
97
+ }
98
+ return distance(a.length, b.length);
99
+ }
100
+ function textSimilarity(a, b) {
101
+ const maxLength = Math.max(a.length, b.length);
102
+ if (maxLength === 0) return 1;
103
+ return 1 - levenshteinDistance(a, b) / maxLength;
104
+ }
105
+ function dropFuzzyRedrawnPasses(items) {
106
+ const toDrop = /* @__PURE__ */ new Set();
107
+ for (const group of groupByBaselineInDocumentOrder(items)) {
108
+ const passes = splitIntoPasses(group);
109
+ if (passes.length < 2) continue;
110
+ const [first, ...rest] = passes;
111
+ if (first === void 0) continue;
112
+ const firstText = passText(first);
113
+ if (firstText.length < MIN_REDRAW_PASS_TEXT_LENGTH) continue;
114
+ for (const candidate of rest) {
115
+ const candidateText = passText(candidate);
116
+ if (candidateText.length < MIN_REDRAW_PASS_TEXT_LENGTH) continue;
117
+ if (!passesOverlap(first, candidate)) continue;
118
+ if (textSimilarity(firstText, candidateText) < REDRAW_PASS_SIMILARITY_THRESHOLD) continue;
119
+ for (const item of candidate.items) toDrop.add(item);
120
+ }
121
+ }
122
+ if (toDrop.size === 0) return items;
123
+ return items.filter((item) => !toDrop.has(item));
124
+ }
47
125
  function clusterIntoLines(items) {
48
- const sorted = [...dropDuplicatePaints(items)].sort((a, b) => b.yPt - a.yPt || a.xPt - b.xPt);
126
+ const sorted = [...dropDuplicatePaints(dropFuzzyRedrawnPasses(items))].sort((a, b) => b.yPt - a.yPt || a.xPt - b.xPt);
49
127
  const working = [];
50
128
  for (const item of sorted) {
51
129
  const tolerance = LINE_BASELINE_TOLERANCE_FACTOR * item.sizePt;
@@ -43,8 +43,86 @@ function dropOverlappingRepeatsWithinLine(items) {
43
43
  }
44
44
  return kept;
45
45
  }
46
+ const REDRAW_PASS_REWIND_TOLERANCE_PT = 2;
47
+ const MIN_REDRAW_PASS_TEXT_LENGTH = 12;
48
+ const REDRAW_PASS_SIMILARITY_THRESHOLD = .6;
49
+ function groupByBaselineInDocumentOrder(items) {
50
+ const groups = [];
51
+ for (const item of items) {
52
+ const tolerance = LINE_BASELINE_TOLERANCE_FACTOR * item.sizePt;
53
+ const group = groups.find((g) => Math.abs(g.baselineY - item.yPt) <= tolerance);
54
+ if (group === void 0) groups.push({
55
+ baselineY: item.yPt,
56
+ items: [item]
57
+ });
58
+ else group.items.push(item);
59
+ }
60
+ return groups.map((g) => g.items);
61
+ }
62
+ function splitIntoPasses(group) {
63
+ const passes = [];
64
+ for (const item of group) {
65
+ const current = passes.at(-1);
66
+ const itemRightPt = item.xPt + (item.widthPt ?? 0);
67
+ if (current !== void 0 && item.xPt >= current.endXPt - REDRAW_PASS_REWIND_TOLERANCE_PT) {
68
+ current.items.push(item);
69
+ current.endXPt = Math.max(current.endXPt, itemRightPt);
70
+ } else passes.push({
71
+ items: [item],
72
+ startXPt: item.xPt,
73
+ endXPt: itemRightPt
74
+ });
75
+ }
76
+ return passes;
77
+ }
78
+ function passText(pass) {
79
+ return pass.items.map((item) => item.text).join("").trim();
80
+ }
81
+ function passesOverlap(a, b) {
82
+ return a.startXPt < b.endXPt && b.startXPt < a.endXPt;
83
+ }
84
+ function levenshteinDistance(a, b) {
85
+ const memo = /* @__PURE__ */ new Map();
86
+ function distance(i, j) {
87
+ if (i === 0) return j;
88
+ if (j === 0) return i;
89
+ const key = `${String(i)}:${String(j)}`;
90
+ const cached = memo.get(key);
91
+ if (cached !== void 0) return cached;
92
+ const cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
93
+ const result = Math.min(distance(i - 1, j) + 1, distance(i, j - 1) + 1, distance(i - 1, j - 1) + cost);
94
+ memo.set(key, result);
95
+ return result;
96
+ }
97
+ return distance(a.length, b.length);
98
+ }
99
+ function textSimilarity(a, b) {
100
+ const maxLength = Math.max(a.length, b.length);
101
+ if (maxLength === 0) return 1;
102
+ return 1 - levenshteinDistance(a, b) / maxLength;
103
+ }
104
+ function dropFuzzyRedrawnPasses(items) {
105
+ const toDrop = /* @__PURE__ */ new Set();
106
+ for (const group of groupByBaselineInDocumentOrder(items)) {
107
+ const passes = splitIntoPasses(group);
108
+ if (passes.length < 2) continue;
109
+ const [first, ...rest] = passes;
110
+ if (first === void 0) continue;
111
+ const firstText = passText(first);
112
+ if (firstText.length < MIN_REDRAW_PASS_TEXT_LENGTH) continue;
113
+ for (const candidate of rest) {
114
+ const candidateText = passText(candidate);
115
+ if (candidateText.length < MIN_REDRAW_PASS_TEXT_LENGTH) continue;
116
+ if (!passesOverlap(first, candidate)) continue;
117
+ if (textSimilarity(firstText, candidateText) < REDRAW_PASS_SIMILARITY_THRESHOLD) continue;
118
+ for (const item of candidate.items) toDrop.add(item);
119
+ }
120
+ }
121
+ if (toDrop.size === 0) return items;
122
+ return items.filter((item) => !toDrop.has(item));
123
+ }
46
124
  function clusterIntoLines(items) {
47
- const sorted = [...dropDuplicatePaints(items)].sort((a, b) => b.yPt - a.yPt || a.xPt - b.xPt);
125
+ const sorted = [...dropDuplicatePaints(dropFuzzyRedrawnPasses(items))].sort((a, b) => b.yPt - a.yPt || a.xPt - b.xPt);
48
126
  const working = [];
49
127
  for (const item of sorted) {
50
128
  const tolerance = LINE_BASELINE_TOLERANCE_FACTOR * item.sizePt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js.documents",
3
- "version": "7.2.0",
3
+ "version": "7.2.2",
4
4
  "description": "Bidirectional docx/pptx <-> PDF conversion and a read+write editable OOXML document model, built on ooxml.js and Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -83,14 +83,14 @@
83
83
  "document-schema.js": "^7.0.0",
84
84
  "fflate": "^0.8.3",
85
85
  "markdown-codec": "^6.3.6",
86
- "odf.js": "^7.1.6",
87
- "ooxml.js": "^8.0.1",
86
+ "odf.js": "^7.2.0",
87
+ "ooxml.js": "^8.1.0",
88
88
  "pdf-codec": "^4.0.5",
89
89
  "ppt-codec": "^1.2.6",
90
90
  "rtf-codec": "^4.0.1",
91
91
  "temml": "0.13.4",
92
92
  "wpd-codec": "^3.0.1",
93
- "xls-codec": "^4.1.6",
93
+ "xls-codec": "^4.2.0",
94
94
  "zod": "^4.4.3"
95
95
  },
96
96
  "devDependencies": {