@uurtech/jdf-cli 0.2.4 → 0.2.6
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 +542 -67
- package/dist/jdf-schema.json +24 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -765,12 +765,13 @@ function columnBands(rows) {
|
|
|
765
765
|
const countOf = /* @__PURE__ */ new Map();
|
|
766
766
|
for (const m of body) countOf.set(m.r, (countOf.get(m.r) ?? 0) + 1);
|
|
767
767
|
const maxCount = Math.max(...countOf.values());
|
|
768
|
-
|
|
768
|
+
let anchorRows = new Set(rows.filter((r) => countOf.get(r) === maxCount));
|
|
769
769
|
let bands = clusterCells(body.filter((m) => anchorRows.has(m.r)));
|
|
770
770
|
if (hasConflict(bands)) {
|
|
771
771
|
const first = rows.find((r) => anchorRows.has(r));
|
|
772
772
|
bands = clusterCells(body.filter((m) => m.r === first));
|
|
773
773
|
if (hasConflict(bands)) return null;
|
|
774
|
+
anchorRows = /* @__PURE__ */ new Set([first]);
|
|
774
775
|
}
|
|
775
776
|
for (const m of body) {
|
|
776
777
|
if (anchorRows.has(m.r)) continue;
|
|
@@ -893,7 +894,8 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
893
894
|
y0: blockRows[0].y - blockRows[0].h * 2.5,
|
|
894
895
|
y1: blockRows[blockRows.length - 1].y + blockRows[blockRows.length - 1].h * 3
|
|
895
896
|
} : null;
|
|
896
|
-
const
|
|
897
|
+
const thinH = (s) => (s.kind === "line" || s.kind === "rect") && s.height < 0.6 && s.width >= s.height && !!(s.fill || s.stroke);
|
|
898
|
+
const gridShapes = bbox ? shapes.map((s, k) => ({ s, k })).filter(({ s }) => s.y >= bbox.y0 - 1 && s.y + s.height <= bbox.y1 + 1 && (s.kind === "line" || s.kind === "rect") && (thinH(s) ? Math.min(s.x + s.width, bbox.x1) - Math.max(s.x, bbox.x0) >= s.width * 0.5 : s.x >= bbox.x0 - 1 && s.x + s.width <= bbox.x1 + 1)) : [];
|
|
897
899
|
const tableW = bbox ? bbox.x1 - bbox.x0 : 0;
|
|
898
900
|
const isRule = (s) => s.kind === "line" || s.kind === "rect" && (s.height < 0.6 || s.width < 0.6) && !!(s.fill || s.stroke);
|
|
899
901
|
const ruleYs = gridShapes.filter(({ s }) => isRule(s) && s.width >= s.height && s.width >= tableW * 0.45).map(({ s }) => s.y + s.height / 2).sort((a, b) => a - b);
|
|
@@ -978,10 +980,29 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
978
980
|
for (let r = 1; r < blockRows.length; r++) pitches.push(blockRows[r].y - blockRows[r - 1].y);
|
|
979
981
|
const medPitch = pitches.length ? pitches.slice().sort((a, b) => a - b)[Math.floor(pitches.length / 2)] : blockRows[0].h * 1.3;
|
|
980
982
|
let pendingLabel = null;
|
|
983
|
+
const pendingCells = /* @__PURE__ */ new Map();
|
|
984
|
+
const flushPendingCells = () => {
|
|
985
|
+
if (!pendingCells.size) return;
|
|
986
|
+
const cells = bands.map(() => "");
|
|
987
|
+
let row = null;
|
|
988
|
+
for (const [b, pc] of pendingCells) {
|
|
989
|
+
cells[b] = pc.text;
|
|
990
|
+
lineIdx.push(...pc.idx);
|
|
991
|
+
row = row ?? pc.row;
|
|
992
|
+
}
|
|
993
|
+
grid.push(cells);
|
|
994
|
+
gridRows.push(row);
|
|
995
|
+
pendingCells.clear();
|
|
996
|
+
};
|
|
981
997
|
const flushPending = () => {
|
|
982
998
|
if (!pendingLabel) return;
|
|
983
999
|
const cells = bands.map(() => "");
|
|
984
1000
|
cells[0] = pendingLabel.text;
|
|
1001
|
+
for (const [b, pc] of pendingCells) {
|
|
1002
|
+
cells[b] = pc.text;
|
|
1003
|
+
lineIdx.push(...pc.idx);
|
|
1004
|
+
}
|
|
1005
|
+
pendingCells.clear();
|
|
985
1006
|
grid.push(cells);
|
|
986
1007
|
gridRows.push(pendingLabel.row);
|
|
987
1008
|
lineIdx.push(...pendingLabel.idx);
|
|
@@ -993,6 +1014,15 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
993
1014
|
const c = row.cells[0];
|
|
994
1015
|
const b = assign.get(c) ?? bands.findIndex((bb) => overlaps(c, bb));
|
|
995
1016
|
if (b > 0) {
|
|
1017
|
+
const prevRow2 = gridRows[gridRows.length - 1];
|
|
1018
|
+
if (denseRules && prevRow2 && ruleBetween(prevRow2, row) && !pendingLabel) {
|
|
1019
|
+
const pc = pendingCells.get(b);
|
|
1020
|
+
if (pc) {
|
|
1021
|
+
pc.text = `${pc.text} ${c.run.text.trim()}`.trim();
|
|
1022
|
+
pc.idx.push(...cellIdx(c));
|
|
1023
|
+
} else pendingCells.set(b, { text: c.run.text.trim(), idx: cellIdx(c), row });
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
996
1026
|
flushPending();
|
|
997
1027
|
grid[grid.length - 1][b] = (grid[grid.length - 1][b] + " " + c.run.text.trim()).trim();
|
|
998
1028
|
lineIdx.push(...cellIdx(c));
|
|
@@ -1018,7 +1048,16 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1018
1048
|
continue;
|
|
1019
1049
|
}
|
|
1020
1050
|
const prevRow = gridRows[gridRows.length - 1];
|
|
1021
|
-
|
|
1051
|
+
const prevLastY = (() => {
|
|
1052
|
+
let y = prevRow?.y ?? row.y;
|
|
1053
|
+
for (let q = r - 1; q >= 0 && blockRows[q] !== prevRow; q--) {
|
|
1054
|
+
if (blockRows[q].cells.length === 1) {
|
|
1055
|
+
y = Math.max(y, blockRows[q].y);
|
|
1056
|
+
} else break;
|
|
1057
|
+
}
|
|
1058
|
+
return y;
|
|
1059
|
+
})();
|
|
1060
|
+
if (prevRow && !pendingLabel && !pendingCells.size && row.y - prevLastY > medPitch * 1.7 && grid.length) {
|
|
1022
1061
|
grid.push(bands.map(() => ""));
|
|
1023
1062
|
gridRows.push(row);
|
|
1024
1063
|
}
|
|
@@ -1028,11 +1067,24 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1028
1067
|
lineIdx.push(...pendingLabel.idx);
|
|
1029
1068
|
pendingLabel = null;
|
|
1030
1069
|
}
|
|
1070
|
+
if (pendingCells.size) {
|
|
1071
|
+
gridRows[gridRows.length - 1];
|
|
1072
|
+
const pcRow = [...pendingCells.values()][0].row;
|
|
1073
|
+
if (denseRules && ruleBetween(pcRow, row)) flushPendingCells();
|
|
1074
|
+
else {
|
|
1075
|
+
for (const [b, pc] of pendingCells) {
|
|
1076
|
+
cells[b] = `${pc.text} ${cells[b]}`.trim();
|
|
1077
|
+
lineIdx.push(...pc.idx);
|
|
1078
|
+
}
|
|
1079
|
+
pendingCells.clear();
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1031
1082
|
grid.push(cells);
|
|
1032
1083
|
gridRows.push(row);
|
|
1033
1084
|
for (const c of row.cells) lineIdx.push(...cellIdx(c));
|
|
1034
1085
|
}
|
|
1035
1086
|
flushPending();
|
|
1087
|
+
flushPendingCells();
|
|
1036
1088
|
for (let r = 1; r < grid.length; r++) {
|
|
1037
1089
|
const cur2 = grid[r], prev = grid[r - 1];
|
|
1038
1090
|
if (cur2[0] !== "" || !prev[0] || !prev.some(Boolean) || !cur2.some(Boolean)) continue;
|
|
@@ -1066,6 +1118,13 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1066
1118
|
i++;
|
|
1067
1119
|
continue;
|
|
1068
1120
|
}
|
|
1121
|
+
const fillRatio = grid.flat().filter(Boolean).length / Math.max(1, grid.length * bands.length);
|
|
1122
|
+
const multiFilled = grid.filter((r) => r.filter(Boolean).length >= 2).length;
|
|
1123
|
+
if (grid.length <= 3 && fillRatio < 0.6 || fillRatio < 0.5 && multiFilled <= 2 || bands.length >= 6 && fillRatio < 0.3) {
|
|
1124
|
+
dbg("reject sparse block", grid[0]);
|
|
1125
|
+
i++;
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1069
1128
|
const first = blockRows[0];
|
|
1070
1129
|
const headerBg = rowFill(first);
|
|
1071
1130
|
const firstBold = first.cells.every((c) => c.run.bold);
|
|
@@ -1082,7 +1141,8 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1082
1141
|
const firstRule = ruleYs.find((y) => y > gridRows[0].y + gridRows[0].h * 0.5);
|
|
1083
1142
|
if (firstRule != null) {
|
|
1084
1143
|
const above = gridRows.filter((r) => r.y + r.h * 0.5 < firstRule).length;
|
|
1085
|
-
|
|
1144
|
+
const figuresAbove = grid.slice(0, above).some((row) => row.some((v, k) => isNumCol[k] && v && numeric(v)));
|
|
1145
|
+
if (above >= 1 && above <= 5 && gridRows.length - above >= 2 && !figuresAbove) headerRows = Math.max(headerRows, above);
|
|
1086
1146
|
}
|
|
1087
1147
|
}
|
|
1088
1148
|
if (numCols > 0) {
|
|
@@ -1099,17 +1159,77 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1099
1159
|
headerRows = Math.max(headerRows, h);
|
|
1100
1160
|
}
|
|
1101
1161
|
if (headerRows > 0 && grid.length - headerRows < 1) headerRows = 0;
|
|
1102
|
-
const
|
|
1162
|
+
const shortVerticals = gridShapes.filter(({ s }) => isRule(s) && s.height > s.width).length;
|
|
1163
|
+
const hasGrid = verticalRules >= 2 || shortVerticals >= 2 && denseRules && ruleYs.length >= 2;
|
|
1164
|
+
const anyVertical = shortVerticals >= 2;
|
|
1165
|
+
if (hasGrid || anyVertical) {
|
|
1166
|
+
const bx0 = Math.min(...bands.map((b) => b.x0)), bx1 = Math.max(...bands.map((b) => b.x1));
|
|
1167
|
+
const partial = gridShapes.filter(({ s }) => isRule(s) && s.width >= s.height && s.width >= tableW * 0.3 && (s.x > bx0 + 3 || s.x + s.width < bx1 - 3)).length;
|
|
1168
|
+
if (partial >= 2) {
|
|
1169
|
+
dbg("reject rowspan grid", grid[0]);
|
|
1170
|
+
i++;
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
let colBands = bands;
|
|
1175
|
+
let colGrid = grid;
|
|
1176
|
+
let colNum = isNumCol;
|
|
1177
|
+
let edges = null;
|
|
1178
|
+
if (hasGrid) {
|
|
1179
|
+
const acc = /* @__PURE__ */ new Map();
|
|
1180
|
+
const blockH = blockRows[blockRows.length - 1].y + blockRows[blockRows.length - 1].h - blockRows[0].y;
|
|
1181
|
+
for (const { s } of gridShapes) {
|
|
1182
|
+
if (!(isRule(s) && s.height > s.width)) continue;
|
|
1183
|
+
const cx = s.x + s.width / 2;
|
|
1184
|
+
const key = [...acc.keys()].find((x) => Math.abs(x - cx) < 1) ?? cx;
|
|
1185
|
+
acc.set(key, (acc.get(key) ?? 0) + s.height);
|
|
1186
|
+
}
|
|
1187
|
+
const xs = [...acc.entries()].filter(([, h]) => h >= blockH * 0.5).map(([x]) => x).sort((a, b) => a - b);
|
|
1188
|
+
dbg(`grid rules x=${xs.map((x) => x.toFixed(1)).join(",")} bands=${bands.length}`);
|
|
1189
|
+
if (xs.length >= 3) {
|
|
1190
|
+
const bandCol = bands.map((b) => {
|
|
1191
|
+
const c = (b.x0 + b.x1) / 2;
|
|
1192
|
+
return xs.findIndex((x, k) => k < xs.length - 1 && c > x && c < xs[k + 1]);
|
|
1193
|
+
});
|
|
1194
|
+
const straddles = (c) => xs.some((x) => c.x0 < x - 1.5 && c.x1 > x + 1.5);
|
|
1195
|
+
const crowded = bandCol.some((c, k) => c >= 0 && bandCol.indexOf(c) !== k);
|
|
1196
|
+
const inside = !crowded && !blockRows.some((row) => row.cells.some(straddles));
|
|
1197
|
+
if (!inside) {
|
|
1198
|
+
dbg("reject merged-cell grid", grid[0]);
|
|
1199
|
+
i++;
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
if (xs.length - 1 >= bands.length && bandCol.every((c) => c >= 0) && new Set(bandCol).size === bandCol.length) {
|
|
1203
|
+
edges = xs;
|
|
1204
|
+
colBands = xs.slice(0, -1).map((x, k) => ({ x0: x, x1: xs[k + 1] }));
|
|
1205
|
+
colGrid = grid.map((row) => {
|
|
1206
|
+
const out2 = colBands.map(() => "");
|
|
1207
|
+
row.forEach((v, k) => {
|
|
1208
|
+
out2[bandCol[k]] = v;
|
|
1209
|
+
});
|
|
1210
|
+
return out2;
|
|
1211
|
+
});
|
|
1212
|
+
colNum = colBands.map((_, k) => {
|
|
1213
|
+
const b = bandCol.indexOf(k);
|
|
1214
|
+
return b >= 0 && isNumCol[b];
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
const columns = colBands.map((b, k) => {
|
|
1103
1220
|
const col = { width: Math.round((b.x1 - b.x0) * 10) / 10 };
|
|
1104
|
-
if (
|
|
1221
|
+
if (colNum[k]) col.align = "right";
|
|
1105
1222
|
return col;
|
|
1106
1223
|
});
|
|
1107
|
-
const x0 = Math.max(0, bands[0].x0 - 2.5);
|
|
1108
|
-
const x1 = Math.min(pageW, bands[bands.length - 1].x1 + 2.5);
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1224
|
+
const x0 = edges ? edges[0] : Math.max(0, bands[0].x0 - 2.5);
|
|
1225
|
+
const x1 = edges ? edges[edges.length - 1] : Math.min(pageW, bands[bands.length - 1].x1 + 2.5);
|
|
1226
|
+
if (!edges) {
|
|
1227
|
+
edges = [x0];
|
|
1228
|
+
for (let k = 1; k < bands.length; k++) edges.push(Math.max(edges[k - 1] + 2, Math.min((bands[k - 1].x1 + bands[k].x0) / 2, x1 - 2 * (bands.length - k))));
|
|
1229
|
+
edges.push(Math.max(edges[edges.length - 1] + 2, x1));
|
|
1230
|
+
}
|
|
1231
|
+
for (let k = 0; k < colBands.length; k++) columns[k].width = Math.round((edges[k + 1] - edges[k]) * 10) / 10;
|
|
1232
|
+
const colOfBand = colBands === bands ? bands.map((_, k) => k) : bands.map((b) => colBands.findIndex((cb) => (b.x0 + b.x1) / 2 > cb.x0 && (b.x0 + b.x1) / 2 < cb.x1));
|
|
1113
1233
|
const bodyRows = gridRows.slice(headerRows).filter((r, k, arr) => grid[headerRows + k]?.some(Boolean));
|
|
1114
1234
|
const rowFills = bodyRows.map(rowFill).map((f) => f?.fill ?? null);
|
|
1115
1235
|
const odd = rowFills.filter((_, k) => k % 2 === 1), even = rowFills.filter((_, k) => k % 2 === 0);
|
|
@@ -1118,7 +1238,6 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1118
1238
|
const altColor = oddAlt || evenAlt;
|
|
1119
1239
|
const borderShape = gridShapes.find(({ s }) => isRule(s));
|
|
1120
1240
|
const borderColor = borderShape ? borderShape.s.stroke || borderShape.s.fill : void 0;
|
|
1121
|
-
const hasGrid = verticalRules >= 2;
|
|
1122
1241
|
const sizeChars = /* @__PURE__ */ new Map();
|
|
1123
1242
|
for (const row of blockRows) for (const c of row.cells) {
|
|
1124
1243
|
const k = Math.round(c.run.fontSize * 2) / 2;
|
|
@@ -1129,26 +1248,74 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1129
1248
|
const padV = Math.max(0.2, Math.min(2.5, (medPitch - lineMm) / 2));
|
|
1130
1249
|
const padH = Math.max(0.5, Math.min(3, fontSize * PT_TO_MM * 0.35));
|
|
1131
1250
|
const y0 = headerBg ? Math.min(...headerBg.rects.map(({ s }) => s.y)) : first.y - padV;
|
|
1251
|
+
const tally = (pick) => {
|
|
1252
|
+
const m = /* @__PURE__ */ new Map();
|
|
1253
|
+
for (const row of blockRows) for (const c of row.cells) {
|
|
1254
|
+
const k = pick(c);
|
|
1255
|
+
if (k) m.set(k, (m.get(k) ?? 0) + c.run.text.trim().length);
|
|
1256
|
+
}
|
|
1257
|
+
return [...m.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
1258
|
+
};
|
|
1259
|
+
const tableFamily = tally((c) => c.run.family);
|
|
1260
|
+
const tableColor = tally((c) => c.run.color) ?? "#000000";
|
|
1261
|
+
const runsAt = (r, b) => {
|
|
1262
|
+
const src = gridRows[r];
|
|
1263
|
+
if (!src) return [];
|
|
1264
|
+
const own = src.cells.filter((c) => assign.get(c) === b).map((c) => c.run);
|
|
1265
|
+
if (own.length || b !== 0) return own;
|
|
1266
|
+
const at = blockRows.indexOf(src);
|
|
1267
|
+
for (let q = at - 1; q >= Math.max(0, at - 3); q--) {
|
|
1268
|
+
const row = blockRows[q];
|
|
1269
|
+
if (row.cells.length !== 1) break;
|
|
1270
|
+
if ((assign.get(row.cells[0]) ?? 0) === 0) return [row.cells[0].run];
|
|
1271
|
+
}
|
|
1272
|
+
return [];
|
|
1273
|
+
};
|
|
1274
|
+
const styledCell = (text, runs2) => {
|
|
1275
|
+
if (!text || !runs2.length) return text;
|
|
1276
|
+
const st = {};
|
|
1277
|
+
const chars = (f) => runs2.filter(f).reduce((a, r) => a + r.text.trim().length, 0) / Math.max(1, runs2.reduce((a, r) => a + r.text.trim().length, 0));
|
|
1278
|
+
if (chars((r) => !!r.bold) >= 0.5) st.fontWeight = "bold";
|
|
1279
|
+
if (chars((r) => !!r.italic) >= 0.5) st.fontStyle = "italic";
|
|
1280
|
+
const color = runs2.slice().sort((a, b) => b.text.trim().length - a.text.trim().length)[0].color;
|
|
1281
|
+
if (color && color !== tableColor) st.color = color;
|
|
1282
|
+
const fam = runs2.slice().sort((a, b) => b.text.trim().length - a.text.trim().length)[0].family;
|
|
1283
|
+
if (fam && tableFamily && fam !== tableFamily) st.fontFamily = fam;
|
|
1284
|
+
const size = Math.round(runs2[0].fontSize * 2) / 2;
|
|
1285
|
+
if (Math.abs(size - fontSize) >= 1) st.fontSize = size;
|
|
1286
|
+
return Object.keys(st).length ? { content: text, style: st } : text;
|
|
1287
|
+
};
|
|
1288
|
+
const bodyStart = (() => {
|
|
1289
|
+
let k = headerRows;
|
|
1290
|
+
while (k < colGrid.length - 1 && colGrid[k].every((c) => !c)) k++;
|
|
1291
|
+
return k;
|
|
1292
|
+
})();
|
|
1293
|
+
const styledRows = colGrid.slice(bodyStart).map((row, ri) => {
|
|
1294
|
+
const r = bodyStart + ri;
|
|
1295
|
+
return row.map((text, col) => {
|
|
1296
|
+
const b = colOfBand.indexOf(col);
|
|
1297
|
+
return styledCell(text, b >= 0 ? runsAt(r, b) : []);
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
const tableStyle = { fontSize, lineHeight: 1.2, padding: `${Math.round(padV * 100) / 100}mm ${Math.round(padH * 100) / 100}mm` };
|
|
1301
|
+
if (tableFamily) tableStyle.fontFamily = tableFamily;
|
|
1302
|
+
if (tableColor !== "#000000") tableStyle.color = tableColor;
|
|
1132
1303
|
const element = {
|
|
1133
1304
|
type: "table",
|
|
1134
1305
|
position: { x: Math.round(x0 * 100) / 100, y: Math.round(Math.max(0, y0) * 100) / 100 },
|
|
1135
1306
|
width: Math.round((x1 - x0) * 100) / 100,
|
|
1136
1307
|
columns,
|
|
1137
|
-
rows:
|
|
1138
|
-
|
|
1139
|
-
while (body.length > 1 && body[0].every((c) => !c)) body.shift();
|
|
1140
|
-
return body;
|
|
1141
|
-
})(),
|
|
1142
|
-
style: { fontSize, lineHeight: 1.2, padding: `${Math.round(padV * 100) / 100}mm ${Math.round(padH * 100) / 100}mm` }
|
|
1308
|
+
rows: styledRows,
|
|
1309
|
+
style: tableStyle
|
|
1143
1310
|
};
|
|
1144
1311
|
if (headerRows > 0) {
|
|
1145
|
-
element.headers =
|
|
1312
|
+
element.headers = colBands.map((_, k) => colGrid.slice(0, headerRows).map((r) => r[k]).filter(Boolean).join(" ").trim());
|
|
1146
1313
|
const hs = { fontWeight: "bold" };
|
|
1147
1314
|
if (headerBg) hs.backgroundColor = headerBg.fill;
|
|
1148
1315
|
if (headerBg) {
|
|
1149
|
-
const
|
|
1150
|
-
for (const c of blockRows.slice(0, headerRows).flatMap((r) => r.cells))
|
|
1151
|
-
const hc = [...
|
|
1316
|
+
const tally2 = /* @__PURE__ */ new Map();
|
|
1317
|
+
for (const c of blockRows.slice(0, headerRows).flatMap((r) => r.cells)) tally2.set(c.run.color, (tally2.get(c.run.color) ?? 0) + c.run.text.length);
|
|
1318
|
+
const hc = [...tally2.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
1152
1319
|
if (hc && hc !== "#000000") hs.color = hc;
|
|
1153
1320
|
}
|
|
1154
1321
|
element.headerStyle = hs;
|
|
@@ -1163,13 +1330,13 @@ function detectTables(runs, shapes, pageWidthMm, gutters = []) {
|
|
|
1163
1330
|
if (headerBg) for (const { k } of headerBg.rects) fillRects.add(k);
|
|
1164
1331
|
if (headerRows > 0) {
|
|
1165
1332
|
const hy0 = gridRows[0].y - gridRows[0].h * 0.5, hy1 = gridRows[headerRows - 1].y + gridRows[headerRows - 1].h * 1.2;
|
|
1166
|
-
for (const { s, k } of gridShapes) if (s.kind === "rect" && s.fill && s.fill.toLowerCase() !== "#ffffff" && !isRule(s) && s.y < hy1 && s.y + s.height > hy0 && s.y >= hy0 - gridRows[0].h) fillRects.add(k);
|
|
1333
|
+
for (const { s, k } of gridShapes) if (s.kind === "rect" && s.fill && s.fill.toLowerCase() !== "#ffffff" && !isRule(s) && s.width >= tableW * 0.5 && s.y < hy1 && s.y + s.height > hy0 && s.y >= hy0 - gridRows[0].h) fillRects.add(k);
|
|
1167
1334
|
}
|
|
1168
1335
|
if (altColor) for (const row of blockRows) {
|
|
1169
1336
|
const f = rowFill(row);
|
|
1170
1337
|
if (f) for (const { k } of f.rects) fillRects.add(k);
|
|
1171
1338
|
}
|
|
1172
|
-
const consumedShapes = gridShapes.filter(({ s, k }) => isRule(s) || fillRects.has(k)).map(({ k }) => k);
|
|
1339
|
+
const consumedShapes = gridShapes.filter(({ s, k }) => hasGrid && isRule(s) || fillRects.has(k)).map(({ k }) => k);
|
|
1173
1340
|
dbg(`table ${grid.length} rows \xD7 ${bands.length} cols at y=${element.position.y}`, element.headers ?? grid[0]);
|
|
1174
1341
|
for (const k of lineIdx) used.add(k);
|
|
1175
1342
|
out.push({ element, lineIdx, shapeIdx: consumedShapes });
|
|
@@ -1317,9 +1484,12 @@ function merge(para, meta, pageWmm) {
|
|
|
1317
1484
|
const pitch = (last.position.y - first.position.y) / (para.length - 1);
|
|
1318
1485
|
const width = Math.min(pageWmm - x, (right - x) * 1.2 + lineH * 0.4);
|
|
1319
1486
|
const joiner = () => "\n";
|
|
1320
|
-
const
|
|
1487
|
+
const lineHeight = Math.round(pitch / lineH * 100) / 100;
|
|
1488
|
+
const style = { ...first.style ?? {}, lineHeight, ...indent > 0.5 ? { textIndent: Math.round(indent * 100) / 100 } : {} };
|
|
1321
1489
|
const allText = para.every((e) => e.type === "text" && !e.link);
|
|
1322
|
-
const
|
|
1490
|
+
const y = Math.max(0, first.position.y - (lineHeight - 1) / 2 * lineH);
|
|
1491
|
+
const height = Math.round(para.length * lineHeight * lineH * 100) / 100;
|
|
1492
|
+
const base = { position: { x, y: Math.round(y * 100) / 100 }, width: Math.round(width * 100) / 100, height, style };
|
|
1323
1493
|
if (allText) {
|
|
1324
1494
|
const content = para.map((e) => String(e.content ?? "").trim().replace(/[ \t]+/g, " ")).join(joiner());
|
|
1325
1495
|
const { height: _hh2, ...firstRest } = first;
|
|
@@ -1337,18 +1507,123 @@ function merge(para, meta, pageWmm) {
|
|
|
1337
1507
|
|
|
1338
1508
|
// ../../packages/jdf-pdf-import/src/core.ts
|
|
1339
1509
|
var PT_TO_MM3 = 0.352778;
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1510
|
+
var SYMBOL_PUA = {
|
|
1511
|
+
61623: "\u2022",
|
|
1512
|
+
61607: "\u25AA",
|
|
1513
|
+
61608: "\u25FB",
|
|
1514
|
+
61618: "\u25A0",
|
|
1515
|
+
61600: "\u25E6",
|
|
1516
|
+
61616: "\u25E6",
|
|
1517
|
+
61558: "\u2756",
|
|
1518
|
+
61656: "\u27A2",
|
|
1519
|
+
61692: "\u2713",
|
|
1520
|
+
61694: "\u2611",
|
|
1521
|
+
61603: "\u2610",
|
|
1522
|
+
61680: "\u21E8",
|
|
1523
|
+
61664: "\u2192",
|
|
1524
|
+
61614: "\u2192",
|
|
1525
|
+
61613: "\u2190",
|
|
1526
|
+
61622: "\xB6",
|
|
1527
|
+
61485: "\u2212",
|
|
1528
|
+
61630: "\u2015",
|
|
1529
|
+
61655: "\u2022",
|
|
1530
|
+
61604: "\u25C6",
|
|
1531
|
+
61686: "\u2716",
|
|
1532
|
+
61691: "\u2717",
|
|
1533
|
+
61672: "\u21D2",
|
|
1534
|
+
61648: "\u2014"
|
|
1535
|
+
};
|
|
1536
|
+
function mapSymbolGlyphs(str) {
|
|
1537
|
+
let out = "";
|
|
1538
|
+
for (const ch of str) {
|
|
1539
|
+
const c = ch.codePointAt(0);
|
|
1540
|
+
if (c >= 57344 && c <= 63743) {
|
|
1541
|
+
const m = SYMBOL_PUA[c];
|
|
1542
|
+
out += m ?? "\u2022";
|
|
1543
|
+
} else out += ch;
|
|
1544
|
+
}
|
|
1545
|
+
return out;
|
|
1546
|
+
}
|
|
1547
|
+
function probeFontProgram(data) {
|
|
1548
|
+
if (!(data instanceof Uint8Array) || data.length < 12) return null;
|
|
1549
|
+
try {
|
|
1550
|
+
const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1551
|
+
let off = 0;
|
|
1552
|
+
if (dv.getUint32(0) === 1953784678) off = dv.getUint32(12);
|
|
1553
|
+
const numTables = dv.getUint16(off + 4);
|
|
1554
|
+
if (numTables > 64) return null;
|
|
1555
|
+
const out = { flags: {} };
|
|
1556
|
+
let found = false;
|
|
1557
|
+
for (let i = 0; i < numTables; i++) {
|
|
1558
|
+
const rec = off + 12 + i * 16;
|
|
1559
|
+
if (rec + 16 > data.length) break;
|
|
1560
|
+
const tag = String.fromCharCode(dv.getUint8(rec), dv.getUint8(rec + 1), dv.getUint8(rec + 2), dv.getUint8(rec + 3));
|
|
1561
|
+
const toff = dv.getUint32(rec + 8), tlen = dv.getUint32(rec + 12);
|
|
1562
|
+
if (toff + tlen > data.length) continue;
|
|
1563
|
+
if (tag === "name") {
|
|
1564
|
+
const count = dv.getUint16(toff + 2), strOff = dv.getUint16(toff + 4);
|
|
1565
|
+
for (let k = 0; k < count; k++) {
|
|
1566
|
+
const r = toff + 6 + k * 12;
|
|
1567
|
+
if (r + 12 > data.length) break;
|
|
1568
|
+
const pid = dv.getUint16(r), nid = dv.getUint16(r + 6), len = dv.getUint16(r + 8), so = dv.getUint16(r + 10);
|
|
1569
|
+
if (nid !== 1 && nid !== 2) continue;
|
|
1570
|
+
const start = toff + strOff + so;
|
|
1571
|
+
if (start + len > data.length) continue;
|
|
1572
|
+
let str = "";
|
|
1573
|
+
if (pid === 3 || pid === 0) {
|
|
1574
|
+
for (let j = 0; j + 1 < len; j += 2) str += String.fromCharCode(dv.getUint16(start + j));
|
|
1575
|
+
} else {
|
|
1576
|
+
for (let j = 0; j < len; j++) str += String.fromCharCode(dv.getUint8(start + j));
|
|
1577
|
+
}
|
|
1578
|
+
str = str.replace(/\0/g, "").trim();
|
|
1579
|
+
if (!str) continue;
|
|
1580
|
+
if (nid === 1 && !out.family) out.family = str;
|
|
1581
|
+
if (nid === 2 && !out.subfamily) out.subfamily = str;
|
|
1582
|
+
found = true;
|
|
1583
|
+
}
|
|
1584
|
+
} else if (tag === "OS/2" && tlen >= 42) {
|
|
1585
|
+
const p = Array.from(data.subarray(toff + 32, toff + 42));
|
|
1586
|
+
const familyClass = dv.getUint8(toff + 30);
|
|
1587
|
+
if (p[0] === 2) {
|
|
1588
|
+
if (p[1] >= 11 && p[1] <= 15) out.flags.serif = false;
|
|
1589
|
+
else if (p[1] >= 2 && p[1] <= 10) out.flags.serif = true;
|
|
1590
|
+
if (p[2] >= 7) out.flags.bold = true;
|
|
1591
|
+
if (p[3] === 9) out.flags.monospace = true;
|
|
1592
|
+
if (p[7] >= 9 && p[7] <= 15) out.flags.italic = true;
|
|
1593
|
+
found = true;
|
|
1594
|
+
} else if (familyClass >= 1 && familyClass <= 7) {
|
|
1595
|
+
out.flags.serif = true;
|
|
1596
|
+
found = true;
|
|
1597
|
+
} else if (familyClass === 8) {
|
|
1598
|
+
out.flags.serif = false;
|
|
1599
|
+
found = true;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
if (out.family && (/^[A-Z]{6}\+/.test(out.family) || /^\d+$/.test(out.family))) out.family = void 0;
|
|
1604
|
+
return found ? out : null;
|
|
1605
|
+
} catch {
|
|
1606
|
+
return null;
|
|
1351
1607
|
}
|
|
1608
|
+
}
|
|
1609
|
+
function classifyFont(name, flags = {}) {
|
|
1610
|
+
const n = (name || "").toLowerCase().replace(/[\s_-]+/g, "");
|
|
1611
|
+
const bold = flags.bold || /bold|black|heavy|semibold|demibold|extrabold/.test(n);
|
|
1612
|
+
const italic = flags.italic || /italic|oblique/.test(n);
|
|
1613
|
+
let family;
|
|
1614
|
+
if (n.includes("calibri") || n.includes("carlito")) family = "Carlito, Calibri, sans-serif";
|
|
1615
|
+
else if (n.includes("cambria")) family = "Caladea, Cambria, serif";
|
|
1616
|
+
else if (n.includes("georgia")) family = "Gelasio, Georgia, serif";
|
|
1617
|
+
else if (n.includes("courier") || n.includes("mono") || n.includes("consolas") || n.includes("menlo") || n.includes("sourcecode") || n.includes("firacode")) family = "Cousine, 'Courier New', JetBrains Mono, monospace";
|
|
1618
|
+
else if (n.includes("times") || n.includes("roman") || n.includes("garamond") || n.includes("baskerville") || n.includes("bookantiqua") || n.includes("palatino") || n.includes("minion")) family = "Tinos, 'Times New Roman', serif";
|
|
1619
|
+
else if (n.includes("plexsans")) family = "'IBM Plex Sans', Arimo, sans-serif";
|
|
1620
|
+
else if (n.includes("plexserif")) family = "'IBM Plex Serif', Tinos, serif";
|
|
1621
|
+
else if (n.includes("arial") || n.includes("helvetica") || n.includes("liberationsans") || n.includes("arimo")) family = "Arimo, Arial, Helvetica, sans-serif";
|
|
1622
|
+
else if (n.includes("segoe") || n.includes("verdana") || n.includes("tahoma") || n.includes("roboto") || n.includes("inter") || n.includes("noto") || n.includes("opensans") || n.includes("lato") || n.includes("aptos") || n.includes("sans")) family = "Arimo, Arial, Helvetica, sans-serif";
|
|
1623
|
+
else if (n.includes("serif")) family = "Tinos, 'Times New Roman', serif";
|
|
1624
|
+
else if (flags.monospace) family = "Cousine, 'Courier New', monospace";
|
|
1625
|
+
else if (flags.serif === true) family = "Tinos, 'Times New Roman', serif";
|
|
1626
|
+
else family = "Arimo, Arial, Helvetica, sans-serif";
|
|
1352
1627
|
return {
|
|
1353
1628
|
family,
|
|
1354
1629
|
weight: bold ? "bold" : void 0,
|
|
@@ -1417,6 +1692,33 @@ function averageStops(stops) {
|
|
|
1417
1692
|
}
|
|
1418
1693
|
return n ? rgbToHex(r / n, g / n, b / n) : null;
|
|
1419
1694
|
}
|
|
1695
|
+
function irToGradient(ir, toVp, m) {
|
|
1696
|
+
if (!Array.isArray(ir) || ir[0] !== "RadialAxial" || !Array.isArray(ir[3])) return null;
|
|
1697
|
+
const stops = ir[3].filter((st) => Array.isArray(st) && typeof st[1] === "string").map((st) => ({ offset: Math.round(st[0] * 1e3) / 1e3, color: st[1] }));
|
|
1698
|
+
if (stops.length < 2) return null;
|
|
1699
|
+
const type = ir[2] === 3 ? "radial" : "linear";
|
|
1700
|
+
const g = { type, stops };
|
|
1701
|
+
if (type === "linear" && Array.isArray(ir[4]) && Array.isArray(ir[5])) {
|
|
1702
|
+
const p = (pt) => {
|
|
1703
|
+
const q = m ? tx(m, pt[0], pt[1]) : { x: pt[0], y: pt[1] };
|
|
1704
|
+
return toVp(q.x, q.y);
|
|
1705
|
+
};
|
|
1706
|
+
const a = p(ir[4]), b = p(ir[5]);
|
|
1707
|
+
g.angle = Math.round((Math.atan2(b.x - a.x, -(b.y - a.y)) * 180 / Math.PI + 360) % 360);
|
|
1708
|
+
}
|
|
1709
|
+
return g;
|
|
1710
|
+
}
|
|
1711
|
+
function patternIR(page, arg) {
|
|
1712
|
+
if (!Array.isArray(arg) || arg[0] !== "Shading") return null;
|
|
1713
|
+
const id = arg[1];
|
|
1714
|
+
try {
|
|
1715
|
+
const store = typeof id === "string" && id.startsWith("g_") ? page.commonObjs : page.objs;
|
|
1716
|
+
if (typeof store?.has === "function" && !store.has(id)) return null;
|
|
1717
|
+
return store.get(id);
|
|
1718
|
+
} catch {
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1420
1722
|
function patternToColor(page, arg) {
|
|
1421
1723
|
if (!Array.isArray(arg)) return null;
|
|
1422
1724
|
if (arg[0] === "TilingPattern") {
|
|
@@ -1456,10 +1758,21 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1456
1758
|
const opList = await page.getOperatorList({ annotationMode: annotationModeDisable });
|
|
1457
1759
|
const fnArr = opList.fnArray;
|
|
1458
1760
|
const argsArr = opList.argsArray;
|
|
1761
|
+
let opIndex = 0;
|
|
1459
1762
|
const gs = {
|
|
1460
1763
|
ctm: [1, 0, 0, 1, 0, 0],
|
|
1764
|
+
// Gradient behind the current fill colour (a shading pattern set with scn);
|
|
1765
|
+
// `fill` keeps the average colour so exporters without gradients still paint.
|
|
1766
|
+
fillGradient: void 0,
|
|
1767
|
+
// Clip path in viewport space (set by W / W*); `sh` paints exactly this area.
|
|
1768
|
+
clip: void 0,
|
|
1461
1769
|
fill: "#000000",
|
|
1462
1770
|
stroke: "#000000",
|
|
1771
|
+
// Blend mode from the ExtGState (BM). JDF has no blend modes; a multiply /
|
|
1772
|
+
// darken layer is approximated by opacity ∝ how dark it is (a light-grey
|
|
1773
|
+
// "light streak" over a blue banner becomes nearly transparent instead of
|
|
1774
|
+
// an opaque grey blob), a screen / lighten layer by how light it is.
|
|
1775
|
+
blend: "normal",
|
|
1463
1776
|
lineWidth: 1,
|
|
1464
1777
|
fillAlpha: 1,
|
|
1465
1778
|
strokeAlpha: 1,
|
|
@@ -1473,6 +1786,24 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1473
1786
|
rise: 0
|
|
1474
1787
|
};
|
|
1475
1788
|
const snapshot = () => ({ ...gs, ctm: [...gs.ctm] });
|
|
1789
|
+
const luma = (hex) => {
|
|
1790
|
+
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex || "");
|
|
1791
|
+
return m ? (0.2126 * parseInt(m[1], 16) + 0.7152 * parseInt(m[2], 16) + 0.0722 * parseInt(m[3], 16)) / 255 : 0.5;
|
|
1792
|
+
};
|
|
1793
|
+
const groupStack = [];
|
|
1794
|
+
const isNormal = (b) => b === "normal" || b === "source-over" || b === "compatible";
|
|
1795
|
+
const fillOpacity = (color) => {
|
|
1796
|
+
let alpha = gs.fillAlpha;
|
|
1797
|
+
let b = gs.blend;
|
|
1798
|
+
for (let q = groupStack.length - 1; q >= 0; q--) {
|
|
1799
|
+
alpha *= groupStack[q].alpha;
|
|
1800
|
+
if (isNormal(b)) b = groupStack[q].blend;
|
|
1801
|
+
}
|
|
1802
|
+
const l = luma(color ?? gs.fill);
|
|
1803
|
+
if (b === "multiply" || b === "darken" || b === "color-burn" || b === "colorburn") return alpha * Math.max(0.08, 1 - l);
|
|
1804
|
+
if (b === "screen" || b === "lighten" || b === "color-dodge" || b === "colordodge") return alpha * Math.max(0.08, l);
|
|
1805
|
+
return alpha;
|
|
1806
|
+
};
|
|
1476
1807
|
const stack = [];
|
|
1477
1808
|
const textOps = [];
|
|
1478
1809
|
const shapes = [];
|
|
@@ -1486,6 +1817,7 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1486
1817
|
const minX = Math.min(...xs), maxX = Math.max(...xs);
|
|
1487
1818
|
const minY = Math.min(...ys), maxY = Math.max(...ys);
|
|
1488
1819
|
imagePositions.push({
|
|
1820
|
+
seq: opIndex,
|
|
1489
1821
|
name,
|
|
1490
1822
|
x: minX * PT_TO_MM3,
|
|
1491
1823
|
y: minY * PT_TO_MM3,
|
|
@@ -1500,20 +1832,55 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1500
1832
|
let pathRects = [];
|
|
1501
1833
|
let pathStart = null;
|
|
1502
1834
|
let pathLast = null;
|
|
1835
|
+
function clipFromPath() {
|
|
1836
|
+
const rects = [...pathRects, ...pathRect ? [pathRect] : []];
|
|
1837
|
+
const pts = [];
|
|
1838
|
+
let d = "";
|
|
1839
|
+
if (pathSegments.length) {
|
|
1840
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
1841
|
+
const vp = pathSegments.map((seg) => {
|
|
1842
|
+
if (seg.type === "Z") return seg;
|
|
1843
|
+
const out = [];
|
|
1844
|
+
for (let k = 0; k < seg.pts.length; k += 2) {
|
|
1845
|
+
const v = toViewport(seg.pts[k], seg.pts[k + 1]);
|
|
1846
|
+
out.push(v.x, v.y);
|
|
1847
|
+
if (v.x < minX) minX = v.x;
|
|
1848
|
+
if (v.x > maxX) maxX = v.x;
|
|
1849
|
+
if (v.y < minY) minY = v.y;
|
|
1850
|
+
if (v.y > maxY) maxY = v.y;
|
|
1851
|
+
}
|
|
1852
|
+
return { type: seg.type, pts: out };
|
|
1853
|
+
});
|
|
1854
|
+
if (!isFinite(minX)) return void 0;
|
|
1855
|
+
d = vp.map((seg) => seg.type === "Z" ? "Z" : `${seg.type} ${Array.from({ length: seg.pts.length / 2 }, (_, k) => `${((seg.pts[2 * k] - minX) * PT_TO_MM3).toFixed(2)} ${((seg.pts[2 * k + 1] - minY) * PT_TO_MM3).toFixed(2)}`).join(" ")}`).join(" ");
|
|
1856
|
+
return { d, x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
1857
|
+
}
|
|
1858
|
+
if (rects.length) {
|
|
1859
|
+
for (const r of rects) {
|
|
1860
|
+
const a = toViewport(r.x, r.y), b = toViewport(r.x + r.w, r.y + r.h);
|
|
1861
|
+
pts.push([Math.min(a.x, b.x), Math.min(a.y, b.y), Math.max(a.x, b.x), Math.max(a.y, b.y)]);
|
|
1862
|
+
}
|
|
1863
|
+
const x0 = Math.min(...pts.map((p) => p[0])), y0 = Math.min(...pts.map((p) => p[1])), x1 = Math.max(...pts.map((p) => p[2])), y1 = Math.max(...pts.map((p) => p[3]));
|
|
1864
|
+
return { d: "", x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
1865
|
+
}
|
|
1866
|
+
return void 0;
|
|
1867
|
+
}
|
|
1503
1868
|
function flushPath(isFill, isStroke) {
|
|
1504
1869
|
for (const r of pathRects) {
|
|
1505
1870
|
const tl = toViewport(r.x, r.y + r.h);
|
|
1506
1871
|
const br = toViewport(r.x + r.w, r.y);
|
|
1507
1872
|
shapes.push({
|
|
1873
|
+
seq: opIndex,
|
|
1508
1874
|
kind: "rect",
|
|
1509
1875
|
x: Math.min(tl.x, br.x) * PT_TO_MM3,
|
|
1510
1876
|
y: Math.min(tl.y, br.y) * PT_TO_MM3,
|
|
1511
1877
|
width: Math.abs(br.x - tl.x) * PT_TO_MM3,
|
|
1512
1878
|
height: Math.abs(br.y - tl.y) * PT_TO_MM3,
|
|
1513
1879
|
fill: isFill ? gs.fill : void 0,
|
|
1880
|
+
gradient: isFill ? gs.fillGradient : void 0,
|
|
1514
1881
|
stroke: isStroke ? gs.stroke : void 0,
|
|
1515
1882
|
strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
|
|
1516
|
-
opacity: isFill ?
|
|
1883
|
+
opacity: isFill ? fillOpacity() : gs.strokeAlpha
|
|
1517
1884
|
});
|
|
1518
1885
|
}
|
|
1519
1886
|
pathRects = [];
|
|
@@ -1525,15 +1892,17 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1525
1892
|
const w = Math.abs(br.x - tl.x);
|
|
1526
1893
|
const h = Math.abs(br.y - tl.y);
|
|
1527
1894
|
shapes.push({
|
|
1895
|
+
seq: opIndex,
|
|
1528
1896
|
kind: "rect",
|
|
1529
1897
|
x: x * PT_TO_MM3,
|
|
1530
1898
|
y: y * PT_TO_MM3,
|
|
1531
1899
|
width: w * PT_TO_MM3,
|
|
1532
1900
|
height: h * PT_TO_MM3,
|
|
1533
1901
|
fill: isFill ? gs.fill : void 0,
|
|
1902
|
+
gradient: isFill ? gs.fillGradient : void 0,
|
|
1534
1903
|
stroke: isStroke ? gs.stroke : void 0,
|
|
1535
1904
|
strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
|
|
1536
|
-
opacity: isFill ?
|
|
1905
|
+
opacity: isFill ? fillOpacity() : gs.strokeAlpha
|
|
1537
1906
|
});
|
|
1538
1907
|
} else if (pathSegments.length === 2 && pathSegments[0].type === "M" && pathSegments[1].type === "L") {
|
|
1539
1908
|
const a = pathSegments[0].pts;
|
|
@@ -1555,6 +1924,7 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1555
1924
|
const axisAligned = dx < 0.5 || dy < 0.5;
|
|
1556
1925
|
if (axisAligned) {
|
|
1557
1926
|
shapes.push({
|
|
1927
|
+
seq: opIndex,
|
|
1558
1928
|
kind: "line",
|
|
1559
1929
|
x: minX * PT_TO_MM3,
|
|
1560
1930
|
y: minY * PT_TO_MM3,
|
|
@@ -1566,6 +1936,7 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1566
1936
|
});
|
|
1567
1937
|
} else {
|
|
1568
1938
|
shapes.push({
|
|
1939
|
+
seq: opIndex,
|
|
1569
1940
|
kind: "path",
|
|
1570
1941
|
x: minX * PT_TO_MM3,
|
|
1571
1942
|
y: minY * PT_TO_MM3,
|
|
@@ -1610,17 +1981,21 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1610
1981
|
}
|
|
1611
1982
|
return `${seg.type} ${p.join(" ")}`;
|
|
1612
1983
|
}).join(" ");
|
|
1984
|
+
const straight = vpSegments.every((seg) => seg.type === "M" || seg.type === "L" || seg.type === "Z");
|
|
1985
|
+
const thin = isFill && straight && Math.min(bw, bh) * PT_TO_MM3 < 0.6 && Math.max(bw, bh) * PT_TO_MM3 >= 1;
|
|
1613
1986
|
shapes.push({
|
|
1614
|
-
|
|
1987
|
+
seq: opIndex,
|
|
1988
|
+
kind: thin ? "rect" : "path",
|
|
1615
1989
|
x: minX * PT_TO_MM3,
|
|
1616
1990
|
y: minY * PT_TO_MM3,
|
|
1617
1991
|
width: bw * PT_TO_MM3,
|
|
1618
1992
|
height: bh * PT_TO_MM3,
|
|
1619
1993
|
fill: isFill ? gs.fill : void 0,
|
|
1994
|
+
gradient: isFill ? gs.fillGradient : void 0,
|
|
1620
1995
|
stroke: isStroke ? gs.stroke : void 0,
|
|
1621
1996
|
strokeWidth: isStroke ? gs.lineWidth * PT_TO_MM3 : void 0,
|
|
1622
|
-
opacity: isFill ?
|
|
1623
|
-
path: d
|
|
1997
|
+
opacity: isFill ? fillOpacity() : gs.strokeAlpha,
|
|
1998
|
+
path: thin ? void 0 : d
|
|
1624
1999
|
});
|
|
1625
2000
|
}
|
|
1626
2001
|
}
|
|
@@ -1630,6 +2005,7 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1630
2005
|
pathLast = null;
|
|
1631
2006
|
}
|
|
1632
2007
|
for (let i = 0; i < fnArr.length; i++) {
|
|
2008
|
+
opIndex = i;
|
|
1633
2009
|
const fn = fnArr[i];
|
|
1634
2010
|
const args = argsArr[i] || [];
|
|
1635
2011
|
if (fn === OPS.save) {
|
|
@@ -1639,11 +2015,15 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1639
2015
|
if (s) Object.assign(gs, s);
|
|
1640
2016
|
} else if (fn === OPS.paintFormXObjectBegin) {
|
|
1641
2017
|
stack.push(snapshot());
|
|
2018
|
+
groupStack.push({ blend: gs.blend, alpha: gs.fillAlpha });
|
|
2019
|
+
gs.blend = "normal";
|
|
2020
|
+
gs.fillAlpha = 1;
|
|
1642
2021
|
const matrix = args[0];
|
|
1643
2022
|
if (Array.isArray(matrix) && matrix.length === 6) gs.ctm = multiplyCtm(matrix, gs.ctm);
|
|
1644
2023
|
} else if (fn === OPS.paintFormXObjectEnd) {
|
|
1645
2024
|
const s = stack.pop();
|
|
1646
2025
|
if (s) Object.assign(gs, s);
|
|
2026
|
+
groupStack.pop();
|
|
1647
2027
|
} else if (fn === OPS.beginText) {
|
|
1648
2028
|
tm = [1, 0, 0, 1, 0, 0];
|
|
1649
2029
|
tlm = [1, 0, 0, 1, 0, 0];
|
|
@@ -1675,6 +2055,9 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1675
2055
|
} else if (fn === OPS.setFillColorN) {
|
|
1676
2056
|
const c = patternToColor(page, args[0]);
|
|
1677
2057
|
if (c) gs.fill = c;
|
|
2058
|
+
const ir = patternIR(page, args[0]);
|
|
2059
|
+
const pm = Array.isArray(args[0]) && Array.isArray(args[0][2]) ? args[0][2] : void 0;
|
|
2060
|
+
gs.fillGradient = ir ? irToGradient(ir, toViewport, pm) ?? void 0 : void 0;
|
|
1678
2061
|
} else if (fn === OPS.setStrokeColorN) {
|
|
1679
2062
|
const c = patternToColor(page, args[0]);
|
|
1680
2063
|
if (c) gs.stroke = c;
|
|
@@ -1682,10 +2065,12 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1682
2065
|
gs.ctm = multiplyCtm(args, gs.ctm);
|
|
1683
2066
|
} else if (fn === OPS.setFillRGBColor) {
|
|
1684
2067
|
gs.fill = rgbToHex(args[0], args[1], args[2]);
|
|
2068
|
+
gs.fillGradient = void 0;
|
|
1685
2069
|
} else if (fn === OPS.setStrokeRGBColor) {
|
|
1686
2070
|
gs.stroke = rgbToHex(args[0], args[1], args[2]);
|
|
1687
2071
|
} else if (fn === OPS.setFillGray) {
|
|
1688
2072
|
gs.fill = rgbToHex(args[0], args[0], args[0]);
|
|
2073
|
+
gs.fillGradient = void 0;
|
|
1689
2074
|
} else if (fn === OPS.setStrokeGray) {
|
|
1690
2075
|
gs.stroke = rgbToHex(args[0], args[0], args[0]);
|
|
1691
2076
|
} else if (fn === OPS.setFillCMYKColor || fn === OPS.setStrokeCMYKColor) {
|
|
@@ -1694,8 +2079,10 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1694
2079
|
const g = (1 - m) * (1 - k) * 255;
|
|
1695
2080
|
const b = (1 - y) * (1 - k) * 255;
|
|
1696
2081
|
const hex = rgbToHex(r, g, b);
|
|
1697
|
-
if (fn === OPS.setFillCMYKColor)
|
|
1698
|
-
|
|
2082
|
+
if (fn === OPS.setFillCMYKColor) {
|
|
2083
|
+
gs.fill = hex;
|
|
2084
|
+
gs.fillGradient = void 0;
|
|
2085
|
+
} else gs.stroke = hex;
|
|
1699
2086
|
} else if (fn === OPS.setLineWidth) {
|
|
1700
2087
|
gs.lineWidth = args[0];
|
|
1701
2088
|
} else if (fn === OPS.setTextRenderingMode) {
|
|
@@ -1709,6 +2096,7 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1709
2096
|
if (key === "LW") gs.lineWidth = val;
|
|
1710
2097
|
else if (key === "ca") gs.fillAlpha = val;
|
|
1711
2098
|
else if (key === "CA") gs.strokeAlpha = val;
|
|
2099
|
+
else if (key === "BM") gs.blend = String(Array.isArray(val) ? val[0] : val).toLowerCase();
|
|
1712
2100
|
}
|
|
1713
2101
|
}
|
|
1714
2102
|
} else if (fn === OPS.showText) {
|
|
@@ -1793,11 +2181,50 @@ async function walkOps(page, OPS, viewport) {
|
|
|
1793
2181
|
const isStroke = fn === OPS.stroke || fn === OPS.fillStroke || fn === OPS.eoFillStroke || fn === OPS.closeFillStroke || fn === OPS.closeStroke || fn === OPS.closeEOFillStroke;
|
|
1794
2182
|
flushPath(isFill, isStroke);
|
|
1795
2183
|
} else if (fn === OPS.endPath || fn === OPS.clip || fn === OPS.eoClip) {
|
|
2184
|
+
if (fn !== OPS.endPath) {
|
|
2185
|
+
const c = clipFromPath();
|
|
2186
|
+
const prev = gs.clip;
|
|
2187
|
+
if (c && prev) {
|
|
2188
|
+
const x0 = Math.max(c.x, prev.x), y0 = Math.max(c.y, prev.y);
|
|
2189
|
+
const x1 = Math.min(c.x + c.w, prev.x + prev.w), y1 = Math.min(c.y + c.h, prev.y + prev.h);
|
|
2190
|
+
if (x1 <= x0 || y1 <= y0) gs.clip = { d: "", x: x0, y: y0, w: 0, h: 0 };
|
|
2191
|
+
else if (c.d || !prev.d) gs.clip = c.d ? c : { d: "", x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
|
|
2192
|
+
else gs.clip = prev;
|
|
2193
|
+
} else gs.clip = c ?? prev;
|
|
2194
|
+
}
|
|
1796
2195
|
pathSegments = [];
|
|
1797
2196
|
pathRects = [];
|
|
1798
2197
|
pathRect = null;
|
|
1799
2198
|
pathStart = null;
|
|
1800
2199
|
pathLast = null;
|
|
2200
|
+
} else if (fn === OPS.shadingFill) {
|
|
2201
|
+
let ir = args[0];
|
|
2202
|
+
if (typeof ir === "string") {
|
|
2203
|
+
try {
|
|
2204
|
+
const store = ir.startsWith("g_") ? page.commonObjs : page.objs;
|
|
2205
|
+
ir = typeof store?.has === "function" && store.has(ir) ? store.get(ir) : null;
|
|
2206
|
+
} catch {
|
|
2207
|
+
ir = null;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
const g = irToGradient(ir, (x, y) => toViewport(...Object.values(tx(gs.ctm, x, y))));
|
|
2211
|
+
const avg = Array.isArray(ir) && ir[0] === "RadialAxial" ? averageStops(ir[3]) : null;
|
|
2212
|
+
const clipArea = gs.clip;
|
|
2213
|
+
if ((g || avg) && !(clipArea && (clipArea.w < 0.5 || clipArea.h < 0.5))) {
|
|
2214
|
+
const area = clipArea ?? { d: "", x: 0, y: 0, w: viewport.width, h: viewport.height };
|
|
2215
|
+
shapes.push({
|
|
2216
|
+
seq: opIndex,
|
|
2217
|
+
kind: area.d ? "path" : "rect",
|
|
2218
|
+
x: area.x * PT_TO_MM3,
|
|
2219
|
+
y: area.y * PT_TO_MM3,
|
|
2220
|
+
width: Math.max(0.1, area.w * PT_TO_MM3),
|
|
2221
|
+
height: Math.max(0.1, area.h * PT_TO_MM3),
|
|
2222
|
+
fill: avg ?? (g ? g.stops[0].color : void 0),
|
|
2223
|
+
gradient: g ?? void 0,
|
|
2224
|
+
opacity: fillOpacity(avg ?? g?.stops[0].color),
|
|
2225
|
+
path: area.d || void 0
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
1801
2228
|
} else if (fn === OPS.paintImageXObject) {
|
|
1802
2229
|
pushImage(String(args[0]), gs.ctm);
|
|
1803
2230
|
} else if (fn === OPS.paintImageXObjectRepeat) {
|
|
@@ -2108,6 +2535,9 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2108
2535
|
// newer Node. Browser entry leaves this unset (= false = real worker
|
|
2109
2536
|
// via GlobalWorkerOptions.workerSrc); node entry sets `true`.
|
|
2110
2537
|
disableWorker: runtime.disableWorker === true,
|
|
2538
|
+
// Keep font flags (serif / monospace / bold / italic) on the font objects
|
|
2539
|
+
// so classifyFont can pick a metric-compatible family for unknown names.
|
|
2540
|
+
fontExtraProperties: true,
|
|
2111
2541
|
isEvalSupported: false,
|
|
2112
2542
|
// Keep going past malformed content streams instead of failing the page.
|
|
2113
2543
|
stopAtErrors: false,
|
|
@@ -2186,6 +2616,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2186
2616
|
for (const k of Object.keys(textContent.styles || {})) {
|
|
2187
2617
|
const s = textContent.styles[k];
|
|
2188
2618
|
let realName = s.fontFamily || k;
|
|
2619
|
+
const flags = {};
|
|
2189
2620
|
try {
|
|
2190
2621
|
const has = typeof page.commonObjs.has === "function" ? page.commonObjs.has(k) : false;
|
|
2191
2622
|
if (has) {
|
|
@@ -2201,6 +2632,15 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2201
2632
|
page.commonObjs.get(k, (font) => {
|
|
2202
2633
|
if (font?.name) realName = font.name;
|
|
2203
2634
|
else if (font?.loadedName) realName = font.loadedName;
|
|
2635
|
+
if (font) {
|
|
2636
|
+
const probe = probeFontProgram(font.data);
|
|
2637
|
+
const anonymous = /^(?:[A-Z]{6}\+)?(?:CIDFont\+)?F\d+$/i.test(String(font.name || "")) || !/[a-z]{3}/i.test(String(font.name || "").replace(/^[A-Z]{6}\+/, ""));
|
|
2638
|
+
if (probe?.family && anonymous) realName = probe.family + (probe.subfamily ? " " + probe.subfamily : "");
|
|
2639
|
+
else if (probe?.subfamily && /bold|italic|oblique/i.test(probe.subfamily) && !/bold|italic|oblique/i.test(realName)) realName += " " + probe.subfamily;
|
|
2640
|
+
if (probe) Object.assign(flags, probe.flags);
|
|
2641
|
+
if (flags.monospace == null && font.isMonospace) flags.monospace = true;
|
|
2642
|
+
if (flags.serif == null && !font.isSymbolicFont && font.isSerifFont) flags.serif = true;
|
|
2643
|
+
}
|
|
2204
2644
|
done();
|
|
2205
2645
|
});
|
|
2206
2646
|
} catch {
|
|
@@ -2211,7 +2651,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2211
2651
|
}
|
|
2212
2652
|
} catch {
|
|
2213
2653
|
}
|
|
2214
|
-
const cls = classifyFont(realName);
|
|
2654
|
+
const cls = classifyFont(realName, flags);
|
|
2215
2655
|
if (!cls.weight && /bold/i.test(s.fontFamily || "")) cls.weight = "bold";
|
|
2216
2656
|
if (!cls.style && /italic|oblique/i.test(s.fontFamily || "")) cls.style = "italic";
|
|
2217
2657
|
fontMap.set(k, cls);
|
|
@@ -2287,11 +2727,15 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2287
2727
|
if (mode === 7) return;
|
|
2288
2728
|
const invisible = mode === 3;
|
|
2289
2729
|
if (invisible && !keepInvisible) return;
|
|
2290
|
-
const ascent =
|
|
2730
|
+
const ascent = fontSize * 0.846;
|
|
2291
2731
|
const yTop = vy - ascent;
|
|
2292
2732
|
const w = safeNum(it.width, 0);
|
|
2733
|
+
const skew = fontSize > 0 && Math.abs(safeNum(tr?.[2], 0)) / fontSize > 0.1 && Math.abs(safeNum(tr?.[1], 0)) / fontSize < 0.1;
|
|
2734
|
+
const rotated = fontSize > 0 && Math.abs(safeNum(tr?.[1], 0)) / fontSize > 0.1;
|
|
2293
2735
|
runs.push({
|
|
2294
|
-
text: it.str,
|
|
2736
|
+
text: mapSymbolGlyphs(it.str),
|
|
2737
|
+
...skew ? { italic: true } : {},
|
|
2738
|
+
...rotated ? { rotated: true } : {},
|
|
2295
2739
|
x: safeNum(vx * PT_TO_MM3, 0),
|
|
2296
2740
|
y: safeNum(yTop * PT_TO_MM3, 0),
|
|
2297
2741
|
fontSize: safeNum(fontSize, 10),
|
|
@@ -2331,7 +2775,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2331
2775
|
continue;
|
|
2332
2776
|
}
|
|
2333
2777
|
const sameLine = Math.abs(last.y - r.y) <= Y_TOL;
|
|
2334
|
-
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;
|
|
2778
|
+
const sameStyle = Math.abs(last.fontSize - r.fontSize) < 0.4 && (last.fontName === r.fontName || fontKey(last.fontName) === fontKey(r.fontName)) && last.color === r.color && !!last.italic === !!r.italic && !!last.rotated === !!r.rotated && Math.abs(last.opacity - r.opacity) < 0.05;
|
|
2335
2779
|
const emMm = r.fontSize * PT_TO_MM3;
|
|
2336
2780
|
const gapMm = r.x - (last.x + extent(last));
|
|
2337
2781
|
const symAfterNumber = /^[$€£¥]$/.test(r.text.trim()) && /[\d)]\s*$/.test(last.text);
|
|
@@ -2352,7 +2796,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2352
2796
|
for (const l of lines) {
|
|
2353
2797
|
const t = l.text.trim();
|
|
2354
2798
|
const recent = keep.slice(-40);
|
|
2355
|
-
const dup = t.length ? recent.find((k) => Math.abs(k.y - l.y) <= 0.
|
|
2799
|
+
const dup = t.length ? recent.find((k) => Math.abs(k.y - l.y) <= 0.9 && Math.abs(k.x - l.x) <= 1.3 && Math.abs(k.fontSize - l.fontSize) < 0.6 && k.text.trim() === t) : void 0;
|
|
2356
2800
|
if (dup) {
|
|
2357
2801
|
dup.bold = true;
|
|
2358
2802
|
continue;
|
|
@@ -2366,7 +2810,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2366
2810
|
const lineMeta = /* @__PURE__ */ new WeakMap();
|
|
2367
2811
|
const tRuns = lines.map((l) => {
|
|
2368
2812
|
const cls = fontMap.get(l.fontName) || classifyFont(l.fontName || "");
|
|
2369
|
-
return { text: l.text, x: l.x, y: l.y, width: extent(l), height: l.height, fontSize: l.fontSize, fontName: l.fontName, color: l.color, bold: cls.weight === "bold" || !!l.bold };
|
|
2813
|
+
return { text: l.text, x: l.x, y: l.y, width: extent(l), height: l.height, fontSize: l.fontSize, fontName: l.fontName, color: l.color, bold: cls.weight === "bold" || !!l.bold, italic: cls.style === "italic" || !!l.italic, family: cls.family };
|
|
2370
2814
|
});
|
|
2371
2815
|
const sizeChars = /* @__PURE__ */ new Map();
|
|
2372
2816
|
for (const l of lines) {
|
|
@@ -2376,7 +2820,8 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2376
2820
|
const bodyFontSize = [...sizeChars.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 0;
|
|
2377
2821
|
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);
|
|
2378
2822
|
const pageShapes = mergeCollinearShapes(ops.shapes);
|
|
2379
|
-
const
|
|
2823
|
+
const isFormPage = formWidgets.length >= 5;
|
|
2824
|
+
const detected = options.detectTables === false || isFormPage ? [] : detectTables(tRuns, pageShapes, pageW * PT_TO_MM3, gutters);
|
|
2380
2825
|
const consumedLines = /* @__PURE__ */ new Set();
|
|
2381
2826
|
const consumedShapes = /* @__PURE__ */ new Set();
|
|
2382
2827
|
const tableAtLine = /* @__PURE__ */ new Map();
|
|
@@ -2386,6 +2831,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2386
2831
|
tableAtLine.set(Math.min(...t.lineIdx), t.element);
|
|
2387
2832
|
}
|
|
2388
2833
|
const pageWmm = pageW * PT_TO_MM3, pageHmm = pageH * PT_TO_MM3;
|
|
2834
|
+
const paintSeq = /* @__PURE__ */ new WeakMap();
|
|
2389
2835
|
pageShapes.forEach((sh, shapeIdx) => {
|
|
2390
2836
|
if (consumedShapes.has(shapeIdx)) return;
|
|
2391
2837
|
if (sh.width < 0.3 && sh.height < 0.3) return;
|
|
@@ -2400,6 +2846,8 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2400
2846
|
height: Math.max(0.1, Math.round(sh.height * 100) / 100)
|
|
2401
2847
|
};
|
|
2402
2848
|
if (sh.fill) shape.fill = sh.fill;
|
|
2849
|
+
if (sh.gradient) shape.gradient = sh.gradient;
|
|
2850
|
+
paintSeq.set(shape, sh.seq ?? 0);
|
|
2403
2851
|
if (sh.stroke) shape.stroke = { color: sh.stroke, width: sh.strokeWidth || 0.3 };
|
|
2404
2852
|
if (shapeType === "path" && sh.path) shape.path = sh.path;
|
|
2405
2853
|
if (sh.opacity != null && sh.opacity < 0.999) {
|
|
@@ -2420,15 +2868,18 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2420
2868
|
data: base64
|
|
2421
2869
|
};
|
|
2422
2870
|
}
|
|
2423
|
-
|
|
2871
|
+
const imgEl = {
|
|
2424
2872
|
type: "image",
|
|
2425
2873
|
resource: resourceKey,
|
|
2426
2874
|
position: { x: Math.round(pos.x * 100) / 100, y: Math.round(pos.y * 100) / 100 },
|
|
2427
2875
|
width: Math.max(1, Math.round(pos.w * 100) / 100),
|
|
2428
2876
|
height: Math.max(1, Math.round(pos.h * 100) / 100),
|
|
2429
2877
|
fit: "fill"
|
|
2430
|
-
}
|
|
2878
|
+
};
|
|
2879
|
+
paintSeq.set(imgEl, pos.seq ?? 0);
|
|
2880
|
+
elements.push(imgEl);
|
|
2431
2881
|
}
|
|
2882
|
+
elements.sort((a, b) => (paintSeq.get(a) ?? 0) - (paintSeq.get(b) ?? 0));
|
|
2432
2883
|
const rowOf = /* @__PURE__ */ new Map();
|
|
2433
2884
|
const rowStartOf = /* @__PURE__ */ new Map();
|
|
2434
2885
|
const nextOnRow = /* @__PURE__ */ new Map();
|
|
@@ -2440,7 +2891,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2440
2891
|
for (let b = 0; b < order.length; b++) {
|
|
2441
2892
|
const j = order[b], lj = lines[j];
|
|
2442
2893
|
const tolY = Math.max(0.6, Math.max(li.fontSize, lj.fontSize) * PT_TO_MM3 * 0.5);
|
|
2443
|
-
if (j === i || Math.abs(lj.y - li.y) > tolY || lj.x <= li.x) continue;
|
|
2894
|
+
if (j === i || lj.rotated || li.rotated || Math.abs(lj.y - li.y) > tolY || lj.x <= li.x) continue;
|
|
2444
2895
|
if (lj.x < bestX) {
|
|
2445
2896
|
bestX = lj.x;
|
|
2446
2897
|
bestNext = j;
|
|
@@ -2448,19 +2899,27 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2448
2899
|
}
|
|
2449
2900
|
if (bestNext >= 0) nextOnRow.set(i, bestNext);
|
|
2450
2901
|
}
|
|
2451
|
-
const
|
|
2902
|
+
const joinsLeft = (cur, j) => {
|
|
2903
|
+
const lc = lines[cur], lj = lines[j];
|
|
2904
|
+
const em = Math.min(lc.fontSize, lj.fontSize) * PT_TO_MM3;
|
|
2905
|
+
const gap = lj.x - (lc.x + extent(lc));
|
|
2906
|
+
const prose = (a, b) => /\s$/.test(a.text) && /^[a-zà-ÿ]/.test(b.text.trim()) && a.text.trim().length >= 3 && b.text.trim().length >= 12 && !/\d[\d,.]*$/.test(a.text.trim());
|
|
2907
|
+
const maxGap = prose(lc, lj) ? em * 2 : em * 0.6;
|
|
2908
|
+
return !(gap < -em * 0.3 || gap > maxGap);
|
|
2909
|
+
};
|
|
2910
|
+
const joined = /* @__PURE__ */ new Set();
|
|
2911
|
+
for (const i of order) {
|
|
2912
|
+
const j = nextOnRow.get(i);
|
|
2913
|
+
if (j != null && joinsLeft(i, j)) joined.add(j);
|
|
2914
|
+
}
|
|
2452
2915
|
for (const i of order) {
|
|
2453
|
-
if (
|
|
2916
|
+
if (joined.has(i)) continue;
|
|
2454
2917
|
const row = [i];
|
|
2455
|
-
seen.add(i);
|
|
2456
2918
|
let cur = i;
|
|
2457
2919
|
while (nextOnRow.has(cur)) {
|
|
2458
|
-
const j = nextOnRow.get(cur)
|
|
2459
|
-
|
|
2460
|
-
const gap = lj.x - (lc.x + extent(lc));
|
|
2461
|
-
if (gap < -em * 0.3 || gap > em * 0.6) break;
|
|
2920
|
+
const j = nextOnRow.get(cur);
|
|
2921
|
+
if (!joinsLeft(cur, j)) break;
|
|
2462
2922
|
row.push(j);
|
|
2463
|
-
seen.add(j);
|
|
2464
2923
|
cur = j;
|
|
2465
2924
|
}
|
|
2466
2925
|
rowOf.set(i, row);
|
|
@@ -2469,12 +2928,19 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2469
2928
|
}
|
|
2470
2929
|
const runStyle = (l) => {
|
|
2471
2930
|
const cls = fontMap.get(l.fontName) || classifyFont(l.fontName || "");
|
|
2472
|
-
return { cls, bold: cls.weight === "bold" || !!l.bold, italic: cls.style === "italic" };
|
|
2931
|
+
return { cls, bold: cls.weight === "bold" || !!l.bold, italic: cls.style === "italic" || !!l.italic };
|
|
2932
|
+
};
|
|
2933
|
+
const underWidgetValue = (l) => {
|
|
2934
|
+
const t = l.text.trim();
|
|
2935
|
+
if (!t) return false;
|
|
2936
|
+
const cx = l.x + extent(l) / 2, cy = l.y + l.height / 2;
|
|
2937
|
+
return formWidgets.some((w) => !w.pushButton && typeof w.fieldValue === "string" && w.fieldValue.trim() === t && cx >= w.rectMm.x && cx <= w.rectMm.x + w.rectMm.w && cy >= w.rectMm.y - 1 && cy <= w.rectMm.y + w.rectMm.h + 1);
|
|
2473
2938
|
};
|
|
2474
2939
|
lines.forEach((l, lineIdx) => {
|
|
2475
2940
|
const tableEl = tableAtLine.get(lineIdx);
|
|
2476
2941
|
if (tableEl) elements.push(tableEl);
|
|
2477
2942
|
if (consumedLines.has(lineIdx)) return;
|
|
2943
|
+
if (underWidgetValue(l)) return;
|
|
2478
2944
|
const row = rowOf.get(lineIdx);
|
|
2479
2945
|
if (!row) return;
|
|
2480
2946
|
if (row.length > 1) {
|
|
@@ -2504,13 +2970,15 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2504
2970
|
if (lk) run.link = lk.url ? lk.url : lk.destPage != null ? { type: "internal", target: `#page-${lk.destPage + 1}` } : void 0;
|
|
2505
2971
|
runs2.push(run);
|
|
2506
2972
|
});
|
|
2507
|
-
const style2 = { fontSize: Math.round(first.fontSize * 10) / 10, fontFamily: base.cls.family };
|
|
2973
|
+
const style2 = { fontSize: Math.round(first.fontSize * 10) / 10, fontFamily: base.cls.family, lineHeight: 1 };
|
|
2974
|
+
const rowHeight = Math.round(first.fontSize * PT_TO_MM3 * 100) / 100;
|
|
2508
2975
|
if (first.opacity < 0.999) style2.opacity = Math.round(first.opacity * 100) / 100;
|
|
2509
2976
|
const rt = {
|
|
2510
2977
|
type: "richtext",
|
|
2511
2978
|
runs: runs2,
|
|
2512
2979
|
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) },
|
|
2513
2980
|
width: Math.max(2, Math.round(Math.max(first.fontSize * PT_TO_MM3, Math.min(measuredW, cap2)) * 100) / 100),
|
|
2981
|
+
height: rowHeight,
|
|
2514
2982
|
style: style2
|
|
2515
2983
|
};
|
|
2516
2984
|
const dominant = row.map((i) => lines[i]).sort((a, b) => b.text.trim().length - a.text.trim().length)[0];
|
|
@@ -2521,10 +2989,11 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2521
2989
|
const cls = fontMap.get(l.fontName) || classifyFont(l.fontName || "");
|
|
2522
2990
|
const style = {
|
|
2523
2991
|
fontSize: Math.round(l.fontSize * 10) / 10,
|
|
2524
|
-
fontFamily: cls.family
|
|
2992
|
+
fontFamily: cls.family,
|
|
2993
|
+
lineHeight: 1
|
|
2525
2994
|
};
|
|
2526
2995
|
if (cls.weight === "bold" || l.bold) style.fontWeight = "bold";
|
|
2527
|
-
if (cls.style === "italic") style.fontStyle = "italic";
|
|
2996
|
+
if (cls.style === "italic" || l.italic) style.fontStyle = "italic";
|
|
2528
2997
|
if (l.color !== "#000000") style.color = l.color;
|
|
2529
2998
|
if (l.opacity < 0.999) style.opacity = Math.round(l.opacity * 100) / 100;
|
|
2530
2999
|
const link = findLinkForRun2(l);
|
|
@@ -2538,6 +3007,8 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2538
3007
|
content: l.text,
|
|
2539
3008
|
position: { x: Math.max(0, Math.round(l.x * 100) / 100), y: Math.max(0, Math.round(l.y * 100) / 100) },
|
|
2540
3009
|
width: Math.max(2, Math.round(elWidth * 100) / 100),
|
|
3010
|
+
// One line at lineHeight 1: the box is exactly one em tall.
|
|
3011
|
+
height: Math.round(l.fontSize * PT_TO_MM3 * 100) / 100,
|
|
2541
3012
|
style
|
|
2542
3013
|
};
|
|
2543
3014
|
if ((cls.weight === "bold" || l.bold) && l.text.trim().length <= 120 && !consumedLines.has(lineIdx)) {
|
|
@@ -2556,6 +3027,7 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2556
3027
|
prev.content = `${prev.content} ${text.content}`.replace(/\s+/g, " ");
|
|
2557
3028
|
prev.tocEntry = prev.content;
|
|
2558
3029
|
prev.width = Math.max(prev.width ?? 0, text.width ?? 0);
|
|
3030
|
+
prev.height = Math.round((text.position.y + (text.height ?? 0) - prev.position.y) * 100) / 100;
|
|
2559
3031
|
return;
|
|
2560
3032
|
}
|
|
2561
3033
|
lineMeta.set(text, { w: Math.max(l.fontSize * PT_TO_MM3, extent(l)), size: l.fontSize, face: fontKey(l.fontName) });
|
|
@@ -2604,6 +3076,9 @@ async function importPdfToJdf(source, title, runtime, options = {}) {
|
|
|
2604
3076
|
elements.push({ type: "signature", ...baseEl, value: "" });
|
|
2605
3077
|
}
|
|
2606
3078
|
}
|
|
3079
|
+
elements.forEach((e, k) => {
|
|
3080
|
+
if (e.id == null) e.id = `p${pi}-e${k + 1}`;
|
|
3081
|
+
});
|
|
2607
3082
|
pages.push({
|
|
2608
3083
|
id: `page-${pi}`,
|
|
2609
3084
|
pageSize: { width: Math.round(pageW * PT_TO_MM3 * 100) / 100, height: Math.round(pageH * PT_TO_MM3 * 100) / 100 },
|
package/dist/jdf-schema.json
CHANGED
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
"type": "object",
|
|
204
204
|
"required": ["type", "name"],
|
|
205
205
|
"properties": {
|
|
206
|
-
"type": { "const": "input" },
|
|
206
|
+
"type": { "const": "input" }, "id": { "type": "string" },
|
|
207
207
|
"name": { "type": "string", "minLength": 1 },
|
|
208
208
|
"inputType": { "type": "string", "enum": ["text","number","email","url","tel","date","time","datetime-local","password","color"] },
|
|
209
209
|
"value": { "type": "string" },
|
|
@@ -222,7 +222,7 @@
|
|
|
222
222
|
"type": "object",
|
|
223
223
|
"required": ["type", "name"],
|
|
224
224
|
"properties": {
|
|
225
|
-
"type": { "const": "textarea" },
|
|
225
|
+
"type": { "const": "textarea" }, "id": { "type": "string" },
|
|
226
226
|
"name": { "type": "string", "minLength": 1 },
|
|
227
227
|
"value": { "type": "string" },
|
|
228
228
|
"placeholder": { "type": "string" },
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"type": "object",
|
|
241
241
|
"required": ["type", "name"],
|
|
242
242
|
"properties": {
|
|
243
|
-
"type": { "const": "checkbox" },
|
|
243
|
+
"type": { "const": "checkbox" }, "id": { "type": "string" },
|
|
244
244
|
"name": { "type": "string", "minLength": 1 },
|
|
245
245
|
"checked": { "type": "boolean" },
|
|
246
246
|
"label": { "type": "string" },
|
|
@@ -256,7 +256,7 @@
|
|
|
256
256
|
"type": "object",
|
|
257
257
|
"required": ["type", "name", "options"],
|
|
258
258
|
"properties": {
|
|
259
|
-
"type": { "const": "select" },
|
|
259
|
+
"type": { "const": "select" }, "id": { "type": "string" },
|
|
260
260
|
"name": { "type": "string", "minLength": 1 },
|
|
261
261
|
"options": {
|
|
262
262
|
"type": "array",
|
|
@@ -285,7 +285,7 @@
|
|
|
285
285
|
"type": "object",
|
|
286
286
|
"required": ["type", "name"],
|
|
287
287
|
"properties": {
|
|
288
|
-
"type": { "const": "signature" },
|
|
288
|
+
"type": { "const": "signature" }, "id": { "type": "string" },
|
|
289
289
|
"name": { "type": "string", "minLength": 1 },
|
|
290
290
|
"value": { "type": "string" },
|
|
291
291
|
"label": { "type": "string" },
|
|
@@ -301,7 +301,7 @@
|
|
|
301
301
|
"type": "object",
|
|
302
302
|
"required": ["type", "content"],
|
|
303
303
|
"properties": {
|
|
304
|
-
"type": { "const": "text" },
|
|
304
|
+
"type": { "const": "text" }, "id": { "type": "string" },
|
|
305
305
|
"content": { "type": "string" },
|
|
306
306
|
"style": { "$ref": "#/definitions/StyleRef" },
|
|
307
307
|
"position": { "$ref": "#/definitions/Position" },
|
|
@@ -318,7 +318,7 @@
|
|
|
318
318
|
"type": "object",
|
|
319
319
|
"required": ["type", "runs"],
|
|
320
320
|
"properties": {
|
|
321
|
-
"type": { "const": "richtext" },
|
|
321
|
+
"type": { "const": "richtext" }, "id": { "type": "string" },
|
|
322
322
|
"runs": {
|
|
323
323
|
"type": "array",
|
|
324
324
|
"items": {
|
|
@@ -395,7 +395,7 @@
|
|
|
395
395
|
"type": "object",
|
|
396
396
|
"required": ["type", "rows"],
|
|
397
397
|
"properties": {
|
|
398
|
-
"type": { "const": "table" },
|
|
398
|
+
"type": { "const": "table" }, "id": { "type": "string" },
|
|
399
399
|
"columns": {
|
|
400
400
|
"type": "array",
|
|
401
401
|
"items": { "type": "object", "properties": { "width": { "oneOf": [{ "type": "string" }, { "type": "number" }] }, "header": { "type": "string" }, "align": { "type": "string", "enum": ["left","center","right","justify"] } } }
|
|
@@ -418,7 +418,7 @@
|
|
|
418
418
|
"type": "object",
|
|
419
419
|
"required": ["type", "items"],
|
|
420
420
|
"properties": {
|
|
421
|
-
"type": { "const": "list" },
|
|
421
|
+
"type": { "const": "list" }, "id": { "type": "string" },
|
|
422
422
|
"items": {
|
|
423
423
|
"type": "array",
|
|
424
424
|
"items": { "type": "object", "required": ["content"], "properties": { "content": { "type": "string" }, "style": { "$ref": "#/definitions/StyleRef" }, "children": { "type": "array", "items": { "$ref": "#/definitions/ListElement/properties/items/items" } }, "listType": { "type": "string", "enum": ["ordered","unordered"] } } }
|
|
@@ -433,13 +433,25 @@
|
|
|
433
433
|
"type": "object",
|
|
434
434
|
"required": ["type", "shape"],
|
|
435
435
|
"properties": {
|
|
436
|
-
"type": { "const": "shape" },
|
|
436
|
+
"type": { "const": "shape" }, "id": { "type": "string" },
|
|
437
437
|
"shape": { "type": "string", "enum": ["rect","circle","ellipse","line","path"] },
|
|
438
438
|
"position": { "$ref": "#/definitions/Position" }, "width": { "type": "number" }, "height": { "type": "number" },
|
|
439
439
|
"fill": { "type": "string" },
|
|
440
440
|
"stroke": { "oneOf": [{ "type": "string" }, { "type": "object", "properties": { "color": { "type": "string" }, "width": { "type": "number" } } }] },
|
|
441
441
|
"strokeWidth": { "type": "number" }, "borderRadius": { "type": "number" }, "path": { "type": "string" },
|
|
442
442
|
"points": { "type": "array", "items": { "$ref": "#/definitions/Position" } },
|
|
443
|
+
"gradient": {
|
|
444
|
+
"type": "object",
|
|
445
|
+
"required": ["type", "stops"],
|
|
446
|
+
"properties": {
|
|
447
|
+
"type": { "type": "string", "enum": ["linear", "radial"] },
|
|
448
|
+
"angle": { "type": "number" },
|
|
449
|
+
"stops": {
|
|
450
|
+
"type": "array", "minItems": 2,
|
|
451
|
+
"items": { "type": "object", "required": ["offset", "color"], "properties": { "offset": { "type": "number" }, "color": { "type": "string" } } }
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
},
|
|
443
455
|
"style": { "$ref": "#/definitions/StyleRef" }
|
|
444
456
|
}
|
|
445
457
|
},
|
|
@@ -447,7 +459,7 @@
|
|
|
447
459
|
"type": "object",
|
|
448
460
|
"required": ["type", "title", "elements"],
|
|
449
461
|
"properties": {
|
|
450
|
-
"type": { "const": "collapsible" },
|
|
462
|
+
"type": { "const": "collapsible" }, "id": { "type": "string" },
|
|
451
463
|
"title": { "type": "string" },
|
|
452
464
|
"elements": { "type": "array", "items": { "$ref": "#/definitions/Element" } },
|
|
453
465
|
"expanded": { "type": "boolean" },
|
|
@@ -459,7 +471,7 @@
|
|
|
459
471
|
"type": "object",
|
|
460
472
|
"required": ["type"],
|
|
461
473
|
"properties": {
|
|
462
|
-
"type": { "const": "toc" }, "title": { "type": "string" },
|
|
474
|
+
"type": { "const": "toc" }, "id": { "type": "string" }, "title": { "type": "string" },
|
|
463
475
|
"position": { "$ref": "#/definitions/Position" }, "width": { "type": "number" },
|
|
464
476
|
"depth": { "type": "integer", "minimum": 1, "maximum": 6 },
|
|
465
477
|
"style": { "$ref": "#/definitions/StyleRef" }
|