kordoc 4.14.3 → 4.14.4

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.
Files changed (136) hide show
  1. package/README.md +16 -4
  2. package/dist/{-7XBPXCXN.js → -Z4TJ3QGK.js} +11 -11
  3. package/dist/{chunk-EBZUZC6F.cjs → chunk-5LAZQCUJ.cjs} +4 -4
  4. package/dist/{chunk-EBZUZC6F.cjs.map → chunk-5LAZQCUJ.cjs.map} +1 -1
  5. package/dist/{chunk-NQCUAGLR.js → chunk-6BWU6SQX.js} +1 -1
  6. package/dist/{chunk-2LQGSDZ6.js.map → chunk-6BWU6SQX.js.map} +1 -1
  7. package/dist/{chunk-2LQGSDZ6.js → chunk-6CUIXJOC.js} +1 -1
  8. package/dist/{chunk-NQCUAGLR.js.map → chunk-6CUIXJOC.js.map} +1 -1
  9. package/dist/{chunk-D6KQKMU3.js → chunk-7SE4EZ45.js} +2 -2
  10. package/dist/{chunk-YGBZABI4.js → chunk-D4U2TXDC.js} +6 -6
  11. package/dist/chunk-DPWWXUOG.js +2222 -0
  12. package/dist/chunk-DPWWXUOG.js.map +1 -0
  13. package/dist/{chunk-FAZR34CA.js → chunk-GLSBEEZJ.js} +2 -2
  14. package/dist/{chunk-L5EH2NVO.js → chunk-GUJ4Q73I.js} +2 -2
  15. package/dist/{chunk-JCVIOLBF.js → chunk-HIJUKZHM.js} +3 -3
  16. package/dist/{chunk-IW2CB755.cjs → chunk-IMAAFSAX.cjs} +392 -42
  17. package/dist/chunk-IMAAFSAX.cjs.map +1 -0
  18. package/dist/{chunk-GTINVM5K.js → chunk-LCW7SEPG.js} +2 -2
  19. package/dist/{chunk-4R67INKT.js → chunk-LFSQAMSM.js} +389 -39
  20. package/dist/chunk-LFSQAMSM.js.map +1 -0
  21. package/dist/{chunk-VJHAUMCF.js → chunk-MG6DJEX4.js} +389 -39
  22. package/dist/chunk-MG6DJEX4.js.map +1 -0
  23. package/dist/{chunk-QGW7NMTX.js → chunk-NLU6LBLY.js} +2 -2
  24. package/dist/{chunk-5VVS2QIJ.cjs → chunk-QEJUY6SK.cjs} +3 -3
  25. package/dist/{chunk-5VVS2QIJ.cjs.map → chunk-QEJUY6SK.cjs.map} +1 -1
  26. package/dist/{chunk-VYOGXNQ3.js → chunk-QIF2VXBQ.js} +2 -2
  27. package/dist/{chunk-I4NNYWIR.js → chunk-TITDJWVZ.js} +2 -2
  28. package/dist/{chunk-NOAQRCV6.cjs → chunk-USHQCF5T.cjs} +676 -75
  29. package/dist/chunk-USHQCF5T.cjs.map +1 -0
  30. package/dist/{chunk-VNFJPTU4.js → chunk-V3DX4DUF.js} +2 -2
  31. package/dist/{chunk-WB5LW52Z.js → chunk-VDN7LMPL.js} +2 -2
  32. package/dist/{chunk-IHZEFW4L.js → chunk-VJIL6PIL.js} +2 -2
  33. package/dist/{chunk-HX4UUF7Y.cjs → chunk-VWUYVEFN.cjs} +1 -1
  34. package/dist/{chunk-HX4UUF7Y.cjs.map → chunk-VWUYVEFN.cjs.map} +1 -1
  35. package/dist/{chunk-EMGF6EEV.cjs → chunk-WJHAU73B.cjs} +2 -2
  36. package/dist/{chunk-EMGF6EEV.cjs.map → chunk-WJHAU73B.cjs.map} +1 -1
  37. package/dist/{chunk-O2PGQNYE.js → chunk-WJJGW5PL.js} +2 -2
  38. package/dist/{chunk-CVEERPMP.js → chunk-XVXHXQIO.js} +9 -9
  39. package/dist/{chunk-4RJ5D7MT.js → chunk-YAKC46HZ.js} +2 -2
  40. package/dist/{chunk-A275ZQLX.js → chunk-YAVHVFRY.js} +653 -52
  41. package/dist/chunk-YAVHVFRY.js.map +1 -0
  42. package/dist/{chunk-UVVEBI22.js → chunk-YT3GC62E.js} +653 -52
  43. package/dist/chunk-YT3GC62E.js.map +1 -0
  44. package/dist/chunks-CCMRL4MJ.js +10 -0
  45. package/dist/cli.js +28 -28
  46. package/dist/cli.js.map +1 -1
  47. package/dist/{formula-CPO2TZGB.cjs → formula-5LOVHAKE.cjs} +9 -9
  48. package/dist/{formula-CPO2TZGB.cjs.map → formula-5LOVHAKE.cjs.map} +1 -1
  49. package/dist/{formula-X44GV7TN.js → formula-7RANCW5L.js} +3 -3
  50. package/dist/{formula-NOIDZEBA.js → formula-7UYJOVTB.js} +4 -4
  51. package/dist/{image-ocr-72RAR4WR.js → image-ocr-I6JOCATS.js} +8 -8
  52. package/dist/{image-ocr-SUGT64ZM.cjs → image-ocr-NYBJEOH2.cjs} +13 -13
  53. package/dist/{image-ocr-SUGT64ZM.cjs.map → image-ocr-NYBJEOH2.cjs.map} +1 -1
  54. package/dist/{image-ocr-R6W6A75P.js → image-ocr-ZV6PDBUQ.js} +6 -6
  55. package/dist/index.cjs +2137 -537
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +4 -3
  58. package/dist/index.d.ts +4 -3
  59. package/dist/index.js +1851 -251
  60. package/dist/index.js.map +1 -1
  61. package/dist/mcp.js +24 -24
  62. package/dist/mcp.js.map +1 -1
  63. package/dist/{model-bundle-57CLEBCO.js → model-bundle-2C5IYDLE.js} +5 -5
  64. package/dist/{models-7ELIFTD7.js → models-SEQZHZFT.js} +5 -5
  65. package/dist/{parser-4NKAYWAX.js → parser-3FNXEP6T.js} +485 -47
  66. package/dist/parser-3FNXEP6T.js.map +1 -0
  67. package/dist/{parser-HWFQRSUQ.js → parser-FJBJXRJK.js} +485 -47
  68. package/dist/parser-FJBJXRJK.js.map +1 -0
  69. package/dist/{parser-RANGXGBD.cjs → parser-HVEKF73B.cjs} +535 -97
  70. package/dist/parser-HVEKF73B.cjs.map +1 -0
  71. package/dist/pdf-ocr-JRDBJ6TA.cjs +14 -0
  72. package/dist/{pdf-ocr-VHSFACH7.cjs.map → pdf-ocr-JRDBJ6TA.cjs.map} +1 -1
  73. package/dist/pdf-ocr-UUBWBEHL.js +16 -0
  74. package/dist/pdf-ocr-VJSF2SQC.js +13 -0
  75. package/dist/{profile-io-V52PL5ZO.js → profile-io-RUFZOZO6.js} +3 -3
  76. package/dist/{rasterize-ZIGLJDOG.js → rasterize-IOYXR5SI.js} +3 -3
  77. package/dist/{redact-TIQEQI3M.js → redact-NMIKVHMP.js} +2 -2
  78. package/dist/{redact-doc-KXZ3TZAG.js → redact-doc-QBHNJ2BU.js} +98 -30
  79. package/dist/redact-doc-QBHNJ2BU.js.map +1 -0
  80. package/dist/{render-U5PE6V2E.js → render-YZ2TEC3N.js} +7 -7
  81. package/dist/seal-ECV44TUH.js +10 -0
  82. package/dist/{setup-YRDM4ODP.js → setup-MPIEP6XT.js} +3 -3
  83. package/dist/visual-ICMOWUCR.js +19 -0
  84. package/dist/{watch-5IOLBATT.js → watch-RYO7MN5B.js} +12 -12
  85. package/package.json +2 -2
  86. package/dist/chunk-4R67INKT.js.map +0 -1
  87. package/dist/chunk-A275ZQLX.js.map +0 -1
  88. package/dist/chunk-IH56ZISC.js +0 -620
  89. package/dist/chunk-IH56ZISC.js.map +0 -1
  90. package/dist/chunk-IW2CB755.cjs.map +0 -1
  91. package/dist/chunk-NOAQRCV6.cjs.map +0 -1
  92. package/dist/chunk-UVVEBI22.js.map +0 -1
  93. package/dist/chunk-VJHAUMCF.js.map +0 -1
  94. package/dist/chunks-I7TEMJXH.js +0 -10
  95. package/dist/parser-4NKAYWAX.js.map +0 -1
  96. package/dist/parser-HWFQRSUQ.js.map +0 -1
  97. package/dist/parser-RANGXGBD.cjs.map +0 -1
  98. package/dist/pdf-ocr-BNC34MNU.js +0 -13
  99. package/dist/pdf-ocr-QX2GB33Q.js +0 -16
  100. package/dist/pdf-ocr-VHSFACH7.cjs +0 -14
  101. package/dist/redact-doc-KXZ3TZAG.js.map +0 -1
  102. package/dist/seal-DNXJBILR.js +0 -10
  103. package/dist/visual-5XWMA4EV.js +0 -19
  104. /package/dist/{-7XBPXCXN.js.map → -Z4TJ3QGK.js.map} +0 -0
  105. /package/dist/{chunk-D6KQKMU3.js.map → chunk-7SE4EZ45.js.map} +0 -0
  106. /package/dist/{chunk-YGBZABI4.js.map → chunk-D4U2TXDC.js.map} +0 -0
  107. /package/dist/{chunk-FAZR34CA.js.map → chunk-GLSBEEZJ.js.map} +0 -0
  108. /package/dist/{chunk-L5EH2NVO.js.map → chunk-GUJ4Q73I.js.map} +0 -0
  109. /package/dist/{chunk-JCVIOLBF.js.map → chunk-HIJUKZHM.js.map} +0 -0
  110. /package/dist/{chunk-GTINVM5K.js.map → chunk-LCW7SEPG.js.map} +0 -0
  111. /package/dist/{chunk-QGW7NMTX.js.map → chunk-NLU6LBLY.js.map} +0 -0
  112. /package/dist/{chunk-VYOGXNQ3.js.map → chunk-QIF2VXBQ.js.map} +0 -0
  113. /package/dist/{chunk-I4NNYWIR.js.map → chunk-TITDJWVZ.js.map} +0 -0
  114. /package/dist/{chunk-VNFJPTU4.js.map → chunk-V3DX4DUF.js.map} +0 -0
  115. /package/dist/{chunk-WB5LW52Z.js.map → chunk-VDN7LMPL.js.map} +0 -0
  116. /package/dist/{chunk-IHZEFW4L.js.map → chunk-VJIL6PIL.js.map} +0 -0
  117. /package/dist/{chunk-O2PGQNYE.js.map → chunk-WJJGW5PL.js.map} +0 -0
  118. /package/dist/{chunk-CVEERPMP.js.map → chunk-XVXHXQIO.js.map} +0 -0
  119. /package/dist/{chunk-4RJ5D7MT.js.map → chunk-YAKC46HZ.js.map} +0 -0
  120. /package/dist/{chunks-I7TEMJXH.js.map → chunks-CCMRL4MJ.js.map} +0 -0
  121. /package/dist/{formula-X44GV7TN.js.map → formula-7RANCW5L.js.map} +0 -0
  122. /package/dist/{formula-NOIDZEBA.js.map → formula-7UYJOVTB.js.map} +0 -0
  123. /package/dist/{image-ocr-72RAR4WR.js.map → image-ocr-I6JOCATS.js.map} +0 -0
  124. /package/dist/{image-ocr-R6W6A75P.js.map → image-ocr-ZV6PDBUQ.js.map} +0 -0
  125. /package/dist/{model-bundle-57CLEBCO.js.map → model-bundle-2C5IYDLE.js.map} +0 -0
  126. /package/dist/{models-7ELIFTD7.js.map → models-SEQZHZFT.js.map} +0 -0
  127. /package/dist/{pdf-ocr-BNC34MNU.js.map → pdf-ocr-UUBWBEHL.js.map} +0 -0
  128. /package/dist/{pdf-ocr-QX2GB33Q.js.map → pdf-ocr-VJSF2SQC.js.map} +0 -0
  129. /package/dist/{profile-io-V52PL5ZO.js.map → profile-io-RUFZOZO6.js.map} +0 -0
  130. /package/dist/{rasterize-ZIGLJDOG.js.map → rasterize-IOYXR5SI.js.map} +0 -0
  131. /package/dist/{redact-TIQEQI3M.js.map → redact-NMIKVHMP.js.map} +0 -0
  132. /package/dist/{render-U5PE6V2E.js.map → render-YZ2TEC3N.js.map} +0 -0
  133. /package/dist/{seal-DNXJBILR.js.map → seal-ECV44TUH.js.map} +0 -0
  134. /package/dist/{setup-YRDM4ODP.js.map → setup-MPIEP6XT.js.map} +0 -0
  135. /package/dist/{visual-5XWMA4EV.js.map → visual-ICMOWUCR.js.map} +0 -0
  136. /package/dist/{watch-5IOLBATT.js.map → watch-RYO7MN5B.js.map} +0 -0
@@ -1,18 +1,28 @@
1
1
  import {
2
2
  ensureModelsIn,
3
3
  getModelsDir
4
- } from "./chunk-WB5LW52Z.js";
4
+ } from "./chunk-VDN7LMPL.js";
5
5
  import {
6
6
  extractPageBlocksWithLines
7
- } from "./chunk-UVVEBI22.js";
7
+ } from "./chunk-YT3GC62E.js";
8
8
 
9
9
  // src/ocr/ruling-lines.ts
10
- var INK_LUMA_MAX = 190;
10
+ var INK_LUMA_MAX = 205;
11
11
  var MIN_LINE_LENGTH_PT = 20;
12
12
  var MAX_LINE_WIDTH_PT = 2.5;
13
13
  var GAP_TOL_PX = 2;
14
14
  var BAND_OVERLAP_RATIO = 0.5;
15
15
  var SURROUND_INK_RATIO = 0.35;
16
+ var DOT_MAX_GAP_PT = 1.3;
17
+ var DOT_MAX_DASH_PT = 4;
18
+ var DOT_MAX_PERIOD_PT = 2.2;
19
+ var DOT_MIN_COUNT = 8;
20
+ var DOT_TOUCH_PT = 1.5;
21
+ var FILL_DELTA = 10;
22
+ var FILL_MIN_LENGTH_PT = 10;
23
+ var FILL_MIN_DEPTH_PT = 4;
24
+ var FILL_FLAT_TOL = 12;
25
+ var FILL_FLAT_RATIO = 0.8;
16
26
  function detectRulingLines(rgba, width, height, scale) {
17
27
  const minLenPx = Math.round(MIN_LINE_LENGTH_PT * scale);
18
28
  const maxThickPx = Math.max(1, Math.floor(MAX_LINE_WIDTH_PT * scale));
@@ -21,11 +31,164 @@ function detectRulingLines(rgba, width, height, scale) {
21
31
  const luma = rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29 >> 8;
22
32
  if (luma <= INK_LUMA_MAX && rgba[i + 3] >= 128) ink[p] = 1;
23
33
  }
24
- const horizontals = detectBands(ink, width, height, minLenPx, maxThickPx, false);
25
- const verticals = detectBands(ink, width, height, minLenPx, maxThickPx, true);
34
+ const solid = (at, line, span) => solidRuns(at, line, span, minLenPx);
35
+ const dot = {
36
+ maxGap: Math.max(2, Math.round(DOT_MAX_GAP_PT * scale)),
37
+ maxDash: Math.max(2, Math.round(DOT_MAX_DASH_PT * scale)),
38
+ maxPeriod: DOT_MAX_PERIOD_PT * scale
39
+ };
40
+ const dotted = (at, line, span) => dottedRuns(at, line, span, minLenPx, dot.maxGap, dot.maxDash, dot.maxPeriod);
41
+ const horizontals = detectBands(ink, width, height, maxThickPx, false, solid);
42
+ const verticals = detectBands(ink, width, height, maxThickPx, true, solid);
43
+ const dotH = detectBands(ink, width, height, maxThickPx, false, dotted);
44
+ const dotV = detectBands(ink, width, height, maxThickPx, true, dotted);
45
+ const tol = Math.max(2, Math.round(DOT_TOUCH_PT * scale));
46
+ const allH = [...horizontals, ...dotH], allV = [...verticals, ...dotV];
47
+ const touchesV = (a) => allV.some((p) => p.x1 >= a.x1 - tol && p.x1 <= a.x2 + tol && a.y1 >= p.y1 - tol && a.y1 <= p.y2 + tol);
48
+ const touchesH = (a) => allH.some((p) => p.y1 >= a.y1 - tol && p.y1 <= a.y2 + tol && a.x1 >= p.x1 - tol && a.x1 <= p.x2 + tol);
49
+ horizontals.push(...dotH.filter(touchesV));
50
+ verticals.push(...dotV.filter(touchesH));
51
+ const inkV = [...verticals];
52
+ const ruleH = horizontals.filter((p) => inkV.some((v) => v.x1 >= p.x1 - tol && v.x1 <= p.x2 + tol && p.y1 >= v.y1 - tol && p.y1 <= v.y2 + tol));
53
+ const fill = fillEdges(rgba, width, height, scale);
54
+ const fillH = fill.horizontals.filter((a) => ruleH.some((p) => Math.abs(p.x1 - a.x1) <= tol && Math.abs(p.x2 - a.x2) <= tol));
55
+ horizontals.push(...fillH);
56
+ const edgeH = [...ruleH, ...fillH];
57
+ verticals.push(...fill.verticals.filter((a) => inkV.some((p) => Math.abs(p.x1 - a.x1) <= tol && p.y1 <= a.y2 + tol && p.y2 >= a.y1 - tol) || edgeH.some((p) => (Math.abs(p.x1 - a.x1) <= tol || Math.abs(p.x2 - a.x1) <= tol) && p.y1 >= a.y1 - tol && p.y1 <= a.y2 + tol)));
26
58
  return { horizontals, verticals };
27
59
  }
28
- function detectBands(ink, width, height, minLenPx, maxThickPx, transpose) {
60
+ function fillEdges(rgba, width, height, scale) {
61
+ const k = Math.max(1, Math.round(scale));
62
+ const w = Math.floor(width / k), h = Math.floor(height / k), n = w * h;
63
+ const luma = new Uint8Array(n);
64
+ const hist = new Uint32Array(256);
65
+ for (let y = 0; y < h; y++) {
66
+ for (let x = 0; x < w; x++) {
67
+ let mx = 0;
68
+ for (let by = 0; by < k; by++) {
69
+ let i = ((y * k + by) * width + x * k) * 4;
70
+ for (let bx = 0; bx < k; bx++, i += 4) {
71
+ const l = rgba[i] * 77 + rgba[i + 1] * 150 + rgba[i + 2] * 29 >> 8;
72
+ if (l > mx) mx = l;
73
+ }
74
+ }
75
+ luma[y * w + x] = mx;
76
+ hist[mx]++;
77
+ }
78
+ }
79
+ let bg = 255;
80
+ for (let v = 0; v < 256; v++) if (hist[v] > hist[bg]) bg = v;
81
+ const tint = new Uint8Array(n);
82
+ for (let p = 0; p < n; p++) if (luma[p] < bg - FILL_DELTA) tint[p] = 1;
83
+ const depth = Math.max(2, Math.round(FILL_MIN_DEPTH_PT * scale / k));
84
+ const minLen = Math.round(FILL_MIN_LENGTH_PT * scale / k);
85
+ const solid = (at, line, span) => solidRuns(at, line, span, minLen);
86
+ const run = new Uint16Array(n);
87
+ const mask = new Uint8Array(n);
88
+ const out = { horizontals: [], verticals: [] };
89
+ for (const [dx, dy] of [[0, 1], [0, -1], [1, 0], [-1, 0]]) {
90
+ const step = dy * w + dx;
91
+ const ys = dy > 0 ? [h - 1, -1, -1] : [0, h, 1];
92
+ const xs = dx > 0 ? [w - 1, -1, -1] : [0, w, 1];
93
+ for (let y = ys[0]; y !== ys[1]; y += ys[2]) {
94
+ for (let x = xs[0]; x !== xs[1]; x += xs[2]) {
95
+ const p = y * w + x;
96
+ const nx = x + dx, ny = y + dy;
97
+ run[p] = tint[p] ? nx >= 0 && nx < w && ny >= 0 && ny < h ? Math.min(65535, run[p + step] + 1) : 1 : 0;
98
+ }
99
+ }
100
+ mask.fill(0);
101
+ for (let y = 0; y < h; y++) {
102
+ for (let x = 0; x < w; x++) {
103
+ const p = y * w + x;
104
+ if (run[p] < depth) continue;
105
+ const ox = x - dx, oy = y - dy;
106
+ if (ox < 0 || oy < 0 || ox >= w || oy >= h || !tint[p - step]) mask[p] = 1;
107
+ }
108
+ }
109
+ const vertical = dx !== 0;
110
+ const flat = (s) => {
111
+ const d = depth >> 1;
112
+ const vals = [];
113
+ if (vertical) {
114
+ const x = Math.round(s.x1) + dx * d;
115
+ for (let y = Math.round(s.y1); y <= s.y2; y++) if (x >= 0 && x < w) vals.push(luma[y * w + x]);
116
+ } else {
117
+ const y = Math.round(s.y1) + dy * d;
118
+ for (let x = Math.round(s.x1); x <= s.x2; x++) if (y >= 0 && y < h) vals.push(luma[y * w + x]);
119
+ }
120
+ if (!vals.length) return false;
121
+ const med = [...vals].sort((a, b) => a - b)[vals.length >> 1];
122
+ return vals.filter((v) => Math.abs(v - med) <= FILL_FLAT_TOL).length >= vals.length * FILL_FLAT_RATIO;
123
+ };
124
+ for (const s of detectBands(mask, w, h, 1, vertical, solid)) {
125
+ if (!flat(s)) continue;
126
+ const c = (v) => v * k + (k - 1) / 2;
127
+ (vertical ? out.verticals : out.horizontals).push({ x1: c(s.x1), y1: c(s.y1), x2: c(s.x2), y2: c(s.y2), thicknessPx: 1 });
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+ function solidRuns(at, line, span, minLenPx) {
133
+ const runs = [];
134
+ let runStart = -1;
135
+ let gap = 0;
136
+ for (let pos = 0; pos <= span; pos++) {
137
+ const on = pos < span && at(line, pos) === 1;
138
+ if (on) {
139
+ if (runStart < 0) runStart = pos;
140
+ gap = 0;
141
+ } else if (runStart >= 0) {
142
+ if (++gap > GAP_TOL_PX || pos >= span) {
143
+ const hi = pos - gap;
144
+ if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi });
145
+ runStart = -1;
146
+ gap = 0;
147
+ }
148
+ }
149
+ }
150
+ return runs;
151
+ }
152
+ function dottedRuns(at, line, span, minLenPx, maxGap, maxDash, maxPeriod) {
153
+ const runs = [];
154
+ const chain = [];
155
+ const median = (xs) => xs.sort((a, b) => a - b)[xs.length >> 1];
156
+ const flush = () => {
157
+ const cnt = chain.length >> 1;
158
+ if (cnt >= DOT_MIN_COUNT && chain[chain.length - 1] - chain[0] + 1 >= minLenPx) {
159
+ const periods = [], dashes = [], gaps = [];
160
+ for (let k = 0; k < cnt; k++) {
161
+ dashes.push(chain[2 * k + 1] - chain[2 * k] + 1);
162
+ if (k) {
163
+ periods.push(chain[2 * k] - chain[2 * k - 2]);
164
+ gaps.push(chain[2 * k] - chain[2 * k - 1] - 1);
165
+ }
166
+ }
167
+ if (median(periods) <= maxPeriod || median(dashes) >= 1.5 * median(gaps)) runs.push({ lo: chain[0], hi: chain[chain.length - 1] });
168
+ }
169
+ chain.length = 0;
170
+ };
171
+ let s = -1;
172
+ for (let pos = 0; pos <= span; pos++) {
173
+ const on = pos < span && at(line, pos) === 1;
174
+ if (on) {
175
+ if (s < 0) s = pos;
176
+ continue;
177
+ }
178
+ if (s < 0) continue;
179
+ const lo = s, hi = pos - 1;
180
+ s = -1;
181
+ if (hi - lo + 1 > maxDash) {
182
+ flush();
183
+ continue;
184
+ }
185
+ if (chain.length && lo - chain[chain.length - 1] - 1 > maxGap) flush();
186
+ chain.push(lo, hi);
187
+ }
188
+ flush();
189
+ return runs;
190
+ }
191
+ function detectBands(ink, width, height, maxThickPx, transpose, readRuns) {
29
192
  const lines = transpose ? width : height;
30
193
  const span = transpose ? height : width;
31
194
  const at = transpose ? (line, pos) => ink[pos * width + line] : (line, pos) => ink[line * width + pos];
@@ -50,23 +213,7 @@ function detectBands(ink, width, height, minLenPx, maxThickPx, transpose) {
50
213
  );
51
214
  };
52
215
  for (let line = 0; line < lines; line++) {
53
- const runs = [];
54
- let runStart = -1;
55
- let gap = 0;
56
- for (let pos = 0; pos <= span; pos++) {
57
- const on = pos < span && at(line, pos) === 1;
58
- if (on) {
59
- if (runStart < 0) runStart = pos;
60
- gap = 0;
61
- } else if (runStart >= 0) {
62
- if (++gap > GAP_TOL_PX || pos >= span) {
63
- const hi = pos - gap;
64
- if (hi - runStart + 1 >= minLenPx) runs.push({ lo: runStart, hi });
65
- runStart = -1;
66
- gap = 0;
67
- }
68
- }
69
- }
216
+ const runs = readRuns(at, line, span);
70
217
  const next = [];
71
218
  const used = /* @__PURE__ */ new Set();
72
219
  for (const band of open) {
@@ -309,6 +456,113 @@ function splitRowBands(gray, w, h, ink, minBandRatio) {
309
456
  return { y0, y1, x0, x1 };
310
457
  });
311
458
  }
459
+ function components(gray, w, h, ink) {
460
+ const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
461
+ const comps = [];
462
+ const label = new Int32Array(w * h);
463
+ const stack = [];
464
+ for (let p0 = 0; p0 < w * h; p0++) {
465
+ if (label[p0] || !isInk(gray[p0])) continue;
466
+ const c = { x0: w, x1: 0, y0: h, y1: 0, id: comps.length + 1 };
467
+ label[p0] = c.id;
468
+ stack.push(p0);
469
+ while (stack.length) {
470
+ const p = stack.pop();
471
+ const x = p % w, y = p / w | 0;
472
+ if (x < c.x0) c.x0 = x;
473
+ if (x + 1 > c.x1) c.x1 = x + 1;
474
+ if (y < c.y0) c.y0 = y;
475
+ if (y + 1 > c.y1) c.y1 = y + 1;
476
+ for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
477
+ const nx = x + dx, ny = y + dy;
478
+ if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
479
+ const q = ny * w + nx;
480
+ if (!label[q] && isInk(gray[q])) {
481
+ label[q] = c.id;
482
+ stack.push(q);
483
+ }
484
+ }
485
+ }
486
+ if (c.y1 - c.y0 < h * 0.85 && c.x1 - c.x0 < w * 0.85) comps.push(c);
487
+ }
488
+ return { comps, label };
489
+ }
490
+ function leadingTriangle(gray, w, h, ink) {
491
+ const { comps, label } = components(gray, w, h, ink);
492
+ let charH = 0;
493
+ for (const c of comps) charH = Math.max(charH, c.y1 - c.y0);
494
+ const first = comps.filter((c) => c.y1 - c.y0 >= charH * 0.4).sort((a, b) => a.x0 - b.x0)[0];
495
+ if (!first || charH < 8) return null;
496
+ const cw = first.x1 - first.x0, ch = first.y1 - first.y0;
497
+ if (cw < ch * 0.8 || cw > ch * 1.8) return null;
498
+ const rowSpan = (y) => {
499
+ let n = 0, lo = -1, hi = -1, runs = 0, prev = false;
500
+ for (let x = first.x0; x < first.x1; x++) {
501
+ const on = label[y * w + x] === first.id;
502
+ if (on) {
503
+ n++;
504
+ if (lo < 0) lo = x - first.x0;
505
+ hi = x - first.x0;
506
+ if (!prev) runs++;
507
+ }
508
+ prev = on;
509
+ }
510
+ return { n, lo, hi, runs };
511
+ };
512
+ const base = Math.max(rowSpan(first.y1 - 1).n, rowSpan(first.y1 - 2).n);
513
+ if (base < cw * 0.7) return null;
514
+ for (let y = first.y0; y < first.y0 + Math.max(1, Math.round(ch * 0.2)); y++) {
515
+ const r = rowSpan(y);
516
+ if (r.n === 0) continue;
517
+ if (r.hi - r.lo + 1 > cw * 0.4 || (r.lo + r.hi) / 2 < cw * 0.25 || (r.lo + r.hi) / 2 > cw * 0.75) return null;
518
+ }
519
+ const mid = rowSpan(first.y0 + Math.round(ch * 0.6));
520
+ if (mid.lo < 0 || mid.lo > cw * 0.3 || mid.hi < cw * 0.7) return null;
521
+ return mid.runs >= 2 ? "\u25B3" : "\u25B2";
522
+ }
523
+ function leaderRuns(gray, w, h, ink, minDots) {
524
+ const comps = components(gray, w, h, ink).comps.sort((a, b) => a.x0 - b.x0);
525
+ const blobs = [];
526
+ for (const c of comps) {
527
+ const last = blobs[blobs.length - 1];
528
+ if (last && c.x0 < last.x1) {
529
+ last.x1 = Math.max(last.x1, c.x1);
530
+ last.y0 = Math.min(last.y0, c.y0);
531
+ last.y1 = Math.max(last.y1, c.y1);
532
+ } else blobs.push({ x0: c.x0, x1: c.x1, y0: c.y0, y1: c.y1 });
533
+ }
534
+ let charH = 0;
535
+ for (const b of blobs) charH = Math.max(charH, b.y1 - b.y0);
536
+ if (charH < 8) return [];
537
+ const small = (c) => c.x1 - c.x0 <= charH * 0.3 && c.y1 - c.y0 <= charH * 0.3;
538
+ let bandTop = h, bandBot = 0;
539
+ for (const c of comps) if (!small(c)) {
540
+ bandTop = Math.min(bandTop, c.y0);
541
+ bandBot = Math.max(bandBot, c.y1);
542
+ }
543
+ const cy = (c) => (c.y0 + c.y1) / 2;
544
+ const isDot = (b) => small(b) && cy(b) >= bandTop && cy(b) <= bandBot;
545
+ const runs = [];
546
+ for (let i = 0; i < blobs.length; ) {
547
+ if (!isDot(blobs[i])) {
548
+ i++;
549
+ continue;
550
+ }
551
+ let j = i;
552
+ while (j + 1 < blobs.length && isDot(blobs[j + 1]) && blobs[j + 1].x0 - blobs[j].x1 <= charH && Math.abs(cy(blobs[j + 1]) - cy(blobs[i])) <= charH * 0.2) j++;
553
+ const x0 = blobs[i].x0, x1 = blobs[j].x1, y = cy(blobs[i]);
554
+ const onLine = comps.some((c) => !small(c) && c.y0 <= y && c.y1 >= y && (c.x1 <= x0 + 2 && c.x1 >= x0 - 2 * charH || c.x0 >= x1 - 2 && c.x0 <= x1 + 2 * charH));
555
+ const run = blobs.slice(i, j + 1).filter((b) => b.x0 > 0 && b.x1 < w);
556
+ const spread = (xs) => {
557
+ const v = [...xs].sort((a, b) => a - b).slice(xs.length >= 5 ? 1 : 0, xs.length >= 5 ? -1 : void 0);
558
+ return v[v.length - 1] / Math.max(1, v[0]);
559
+ };
560
+ const even = run.length >= 2 && spread(run.map((b) => b.x1 - b.x0)) <= 2.5 && spread(run.map((b) => b.y1 - b.y0)) <= 2.5 && spread(run.slice(1).map((b, k) => b.x0 - run[k].x0)) <= 1.8;
561
+ if (j - i + 1 >= minDots && onLine && even) runs.push([x0, x1]);
562
+ i = j + 1;
563
+ }
564
+ return runs;
565
+ }
312
566
  function inkBounds(gray, w, h, ink) {
313
567
  const isInk = (v) => ink.darkInk ? v <= ink.threshold : v > ink.threshold;
314
568
  const colInk = new Uint32Array(w);
@@ -346,6 +600,7 @@ function restoreSymbols(text) {
346
600
  s = s.replace(/^([Oo])(\s?)(?=[\uac00-\ud7a3])/, "\u25CB$2");
347
601
  s = s.replace(/(?<![A-Za-z])O{2,}(?![A-Za-z])/g, (m, i) => /[\uac00-\ud7a3]$/.test(s.slice(Math.max(0, i - 2), i).trimEnd()) || /^\s?[\uac00-\ud7a3]/.test(s.slice(i + m.length)) ? "\u25CB".repeat(m.length) : m);
348
602
  s = s.replace(/[\u2206\u0394]/g, "\u25B3");
603
+ s = s.replace(/\(cid:\d*\)/g, "");
349
604
  return smartQuotes(joinDigitGroups(s));
350
605
  }
351
606
  function joinDigitGroups(s) {
@@ -381,6 +636,53 @@ function smartQuotes(s) {
381
636
  function isDotFragment(text) {
382
637
  return /^[\s.\u00b7\u2024\u2025\u2026\u2027\u2219\u22c5\u318d]+$/.test(text);
383
638
  }
639
+ function joinLeaderItems(items, ends) {
640
+ const gone = /* @__PURE__ */ new Set();
641
+ const sameLine = (a, b) => Math.abs(a.y + a.h / 2 - (b.y + b.h / 2)) <= Math.max(a.h, b.h) / 2;
642
+ const merge = (left, right) => {
643
+ const x2 = Math.max(left.x + left.w, right.x + right.w), y2 = Math.max(left.y + left.h, right.y + right.h);
644
+ left.text = left.text.replace(/\s*\u2026$/, "") + " \u2026 " + right.text.replace(/^\u2026\s*/, "");
645
+ left.x = Math.min(left.x, right.x);
646
+ left.y = Math.min(left.y, right.y);
647
+ left.w = x2 - left.x;
648
+ left.h = y2 - left.y;
649
+ left.confidence = Math.min(left.confidence, right.confidence);
650
+ gone.add(right);
651
+ };
652
+ const neighbor = (it, toLeft) => {
653
+ let best = null;
654
+ for (const o of items) {
655
+ if (o === it || gone.has(o) || !sameLine(o, it)) continue;
656
+ if (toLeft ? o.x + o.w > it.x + 2 : o.x < it.x + it.w - 2) continue;
657
+ if (!best || (toLeft ? o.x + o.w > best.x + best.w : o.x < best.x)) best = o;
658
+ }
659
+ return best;
660
+ };
661
+ for (const it of [...items].sort((a, b) => b.x - a.x)) {
662
+ if (gone.has(it) || !ends.get(it)?.lead) continue;
663
+ const left = neighbor(it, true);
664
+ if (left) merge(left, it);
665
+ }
666
+ for (const it of [...items].sort((a, b) => a.x - b.x)) {
667
+ if (gone.has(it) || !ends.get(it)?.trail) continue;
668
+ const right = neighbor(it, false);
669
+ if (right) merge(it, right);
670
+ }
671
+ const toc = items.filter((it) => !gone.has(it) && /\s\u2026\s\d{1,4}$/.test(it.text));
672
+ if (toc.length >= 2) {
673
+ for (const it of items) {
674
+ if (gone.has(it) || toc.includes(it) || !/^[\s.:\u00b7\u2026]*\d{1,4}$/.test(it.text)) continue;
675
+ const x2 = it.x + it.w;
676
+ if (toc.filter((t) => Math.abs(t.x + t.w - x2) <= Math.max(t.h, it.h)).length < 2) continue;
677
+ const left = neighbor(it, true);
678
+ if (!left || toc.includes(left)) continue;
679
+ left.text = left.text.replace(/[\s.:\u00b7\u2026]+$/, "");
680
+ it.text = it.text.replace(/^[\s.:\u00b7\u2026]+/, "");
681
+ merge(left, it);
682
+ }
683
+ }
684
+ return items.filter((it) => !gone.has(it));
685
+ }
384
686
  function restoreBulletItems(items) {
385
687
  for (const it of items) {
386
688
  if (it.text !== "O" && it.text !== "o") continue;
@@ -471,7 +773,8 @@ var DEFAULT_OCR_TUNING = Object.freeze({
471
773
  splitTall: true,
472
774
  minInkContrast: 35,
473
775
  postprocess: true,
474
- tightBoxes: true
776
+ tightBoxes: true,
777
+ splitLeaders: true
475
778
  });
476
779
  var DET_MIN_SIZE = 3;
477
780
  var DET_MAX_BOXES = 1e3;
@@ -479,6 +782,9 @@ var REC_MIN_WIDTH = 320;
479
782
  var REC_BATCH_MAX_PIXELS = 48 * 16e3;
480
783
  var TALL_RATIO = 1.5;
481
784
  var ROTATE_RATIO = 3;
785
+ var LEADER_MIN_DOTS = 4;
786
+ var LEADER_CONTEXT = 0.5;
787
+ var MIN_INK_RATIO = 0.01;
482
788
  var DET_MEAN = [0.485, 0.456, 0.406];
483
789
  var DET_STD = [0.229, 0.224, 0.225];
484
790
  var OcrEngine = class _OcrEngine {
@@ -541,11 +847,13 @@ var OcrEngine = class _OcrEngine {
541
847
  if (width < DET_MIN_SIZE || height < DET_MIN_SIZE) return [];
542
848
  const boxes = await this.detect(rgba, width, height, tuning);
543
849
  const jobs = [];
850
+ const joins = [];
544
851
  let group = 0;
545
852
  for (const b of boxes) {
546
853
  const gray = grayCrop(rgba, width, b);
547
854
  const ink = inkStats(gray);
548
855
  if (tuning.minInkContrast > 0 && ink.contrast < tuning.minInkContrast) continue;
856
+ if (tuning.minInkContrast > 0 && ink.inkRatio < MIN_INK_RATIO) continue;
549
857
  if (tuning.splitTall && b.h >= b.w * TALL_RATIO) {
550
858
  const bands = splitRowBands(gray, b.w, b.h, ink, 0.45);
551
859
  if (bands.length >= 2) {
@@ -558,6 +866,24 @@ var OcrEngine = class _OcrEngine {
558
866
  continue;
559
867
  }
560
868
  }
869
+ const leaders = tuning.splitLeaders ? leaderRuns(gray, b.w, b.h, ink, LEADER_MIN_DOTS) : [];
870
+ if (leaders.length) {
871
+ const join2 = joins.length;
872
+ joins.push({ box: b, parts: [], trailDots: false });
873
+ let x0 = 0, dots = false;
874
+ for (const [a, c] of [...leaders, [b.w, b.w]]) {
875
+ const bare = { x: b.x + x0, y: b.y, w: a - x0, h: b.h };
876
+ const end = c > a ? Math.min(c, a + Math.round(b.h * LEADER_CONTEXT)) : a;
877
+ x0 = c;
878
+ if (bare.w >= DET_MIN_SIZE && inkStats(grayCrop(rgba, width, bare)).contrast >= Math.max(1, tuning.minInkContrast)) {
879
+ jobs.push({ box: { ...bare, w: end - (bare.x - b.x) }, rot: 0, group: group++, join: join2, dotsBefore: dots, trimDots: end > a });
880
+ dots = false;
881
+ }
882
+ if (c > a) dots = true;
883
+ }
884
+ joins[join2].trailDots = dots;
885
+ continue;
886
+ }
561
887
  jobs.push({ box: b, rot: 0, group: group++ });
562
888
  }
563
889
  const results = await this.recognizeJobs(rgba, width, jobs, tuning.recBatch);
@@ -568,27 +894,50 @@ var OcrEngine = class _OcrEngine {
568
894
  const cur = best.get(job.group);
569
895
  if (!cur || r.confidence > cur.confidence) best.set(job.group, { job, ...r });
570
896
  });
571
- const items = [];
897
+ let items = [];
572
898
  for (const { job, text: raw, confidence } of best.values()) {
573
- const text = tuning.postprocess ? restoreSymbols(raw.trim()) : raw;
899
+ let text = tuning.postprocess ? restoreSymbols(raw.trim()) : raw;
574
900
  if (!text.trim()) continue;
901
+ if (tuning.postprocess && /^\d/.test(text) && job.rot === 0) {
902
+ const g = grayCrop(rgba, width, job.box);
903
+ const tri = leadingTriangle(g, job.box.w, job.box.h, inkStats(g));
904
+ if (tri) text = tri + text;
905
+ }
575
906
  if (tuning.postprocess && isDotFragment(text)) continue;
576
907
  if (confidence < tuning.textScore) {
577
908
  if (stats) stats.droppedLowConf++;
578
909
  continue;
579
910
  }
580
- let b = job.box;
581
- if (tuning.tightBoxes) {
582
- const gray = grayCrop(rgba, width, b);
583
- const t = inkBounds(gray, b.w, b.h, inkStats(gray));
584
- b = { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 };
911
+ if (job.join !== void 0) {
912
+ if (job.trimDots) text = text.replace(/[\s.:\u00b7\u2022\u2024\u2025\u2026\u2219\u22c5\u318d]+$/u, "");
913
+ if (text) joins[job.join].parts.push({ x: job.box.x, text, dotsBefore: job.dotsBefore === true, confidence });
914
+ continue;
585
915
  }
586
- items.push({ text, x: b.x, y: b.y, w: b.w, h: b.h, confidence });
916
+ items.push({ text, ...this.itemBox(rgba, width, job.box, tuning), confidence });
587
917
  }
918
+ const leaderEnds = /* @__PURE__ */ new Map();
919
+ for (const j of joins) {
920
+ if (!j.parts.length) continue;
921
+ j.parts.sort((a, b) => a.x - b.x);
922
+ let text = "";
923
+ for (const p of j.parts) text += (p.dotsBefore ? text ? " \u2026 " : "\u2026" : text ? " " : "") + p.text;
924
+ if (j.trailDots) text += " \u2026";
925
+ const item = { text, ...this.itemBox(rgba, width, j.box, tuning), confidence: Math.min(...j.parts.map((p) => p.confidence)) };
926
+ items.push(item);
927
+ leaderEnds.set(item, { lead: j.parts[0].dotsBefore, trail: j.trailDots });
928
+ }
929
+ if (leaderEnds.size) items = joinLeaderItems(items, leaderEnds);
588
930
  if (tuning.postprocess) restoreBulletItems(items);
589
931
  items.sort((a, b) => a.y - b.y || a.x - b.x);
590
932
  return items;
591
933
  }
934
+ /** 결과 좌표 — det 박스 그대로 또는 박스 안 잉크 외곽 (tightBoxes) */
935
+ itemBox(rgba, width, b, tuning) {
936
+ if (!tuning.tightBoxes) return b;
937
+ const gray = grayCrop(rgba, width, b);
938
+ const t = inkBounds(gray, b.w, b.h, inkStats(gray));
939
+ return { x: b.x + t.x0, y: b.y + t.y0, w: t.x1 - t.x0, h: t.y1 - t.y0 };
940
+ }
592
941
  // ─── det ─────────────────────────────────────────────
593
942
  async detect(rgba, width, height, tuning) {
594
943
  const ratio = tuning.detLongSide / Math.max(width, height);
@@ -854,7 +1203,7 @@ function rotateRgba(rgba, width, height, deg) {
854
1203
  // src/ocr/pdf-ocr.ts
855
1204
  var OCR_RENDER_SCALE = 3;
856
1205
  var PAGE_TIMEOUT_MS = 12e4;
857
- async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTables = true) {
1206
+ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTables = true, vectorOps) {
858
1207
  const result = /* @__PURE__ */ new Map();
859
1208
  if (targets.size === 0) return result;
860
1209
  const pdfiumMod = await tryImport2(
@@ -880,7 +1229,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
880
1229
  onProgress?.(++done, targets.size);
881
1230
  try {
882
1231
  const blocks = await withTimeout(
883
- ocrOnePage(page, pageNo, mode, engine, warnings, detectTables),
1232
+ ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps?.get(pageNo)),
884
1233
  PAGE_TIMEOUT_MS,
885
1234
  `OCR \uD398\uC774\uC9C0 ${pageNo} \uD0C0\uC784\uC544\uC6C3 (${PAGE_TIMEOUT_MS / 1e3}\uCD08)`
886
1235
  );
@@ -899,7 +1248,7 @@ async function runPdfOcr(buffer, targets, mode, warnings, onProgress, detectTabl
899
1248
  }
900
1249
  return result;
901
1250
  }
902
- async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
1251
+ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables, vectorOps) {
903
1252
  const { originalWidth: pdfW, originalHeight: pdfH } = page.getOriginalSize();
904
1253
  const rendered = await page.render({
905
1254
  scale: OCR_RENDER_SCALE,
@@ -908,7 +1257,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
908
1257
  const { data: bgra, width: rw, height: rh } = rendered;
909
1258
  const rgba = bgraToRgba(bgra);
910
1259
  if (mode === "builtin") {
911
- const { rgba: upright } = deskewPage(rgba, rw, rh);
1260
+ const upright = vectorOps ? rgba : deskewPage(rgba, rw, rh).rgba;
912
1261
  const stats = { droppedLowConf: 0 };
913
1262
  const items = await engine.recognizePage(upright, rw, rh, stats);
914
1263
  if (stats.droppedLowConf > 0) {
@@ -919,6 +1268,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
919
1268
  });
920
1269
  }
921
1270
  const scale = rh / pdfH;
1271
+ if (vectorOps) return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, void 0, detectTables, vectorOps);
922
1272
  const ruling = detectRulingLines(upright, rw, rh, scale);
923
1273
  const extraLines = rulingToPdfLines(ruling, scale, pdfH);
924
1274
  return ocrItemsToBlocks(items, pageNo, pdfW, pdfH, scale, extraLines, detectTables);
@@ -937,7 +1287,7 @@ async function ocrOnePage(page, pageNo, mode, engine, warnings, detectTables) {
937
1287
  }
938
1288
  return [{ type: "paragraph", text: text.trim(), pageNumber: pageNo }];
939
1289
  }
940
- function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, detectTables = true) {
1290
+ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, detectTables = true, opList = { fnArray: [], argsArray: [] }) {
941
1291
  const norm = items.map((it) => {
942
1292
  const h = it.h / scale;
943
1293
  return {
@@ -953,7 +1303,7 @@ function ocrItemsToBlocks(items, pageNumber, pdfW, pdfH, scale, extraLines, dete
953
1303
  isHidden: false
954
1304
  };
955
1305
  });
956
- return extractPageBlocksWithLines(norm, pageNumber, { fnArray: [], argsArray: [] }, pdfW, pdfH, extraLines, detectTables);
1306
+ return extractPageBlocksWithLines(norm, pageNumber, opList, pdfW, pdfH, extraLines, detectTables);
957
1307
  }
958
1308
  async function tryImport2(name, loader) {
959
1309
  try {
@@ -999,4 +1349,4 @@ export {
999
1349
  runPdfOcr,
1000
1350
  ocrItemsToBlocks
1001
1351
  };
1002
- //# sourceMappingURL=chunk-4R67INKT.js.map
1352
+ //# sourceMappingURL=chunk-LFSQAMSM.js.map