abb-opencode-local-rag 0.1.0
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/LICENSE +21 -0
- package/README.de.md +416 -0
- package/README.es.md +416 -0
- package/README.fr.md +416 -0
- package/README.md +491 -0
- package/README.pt-BR.md +416 -0
- package/README.zh-CN.md +416 -0
- package/dist/bin/install-skills.d.ts +20 -0
- package/dist/bin/install-skills.d.ts.map +1 -0
- package/dist/bin/install-skills.js +195 -0
- package/dist/bin/install-skills.js.map +1 -0
- package/dist/chunker/index.d.ts +24 -0
- package/dist/chunker/index.d.ts.map +1 -0
- package/dist/chunker/index.js +2 -0
- package/dist/chunker/index.js.map +1 -0
- package/dist/chunker/semantic-chunker.d.ts +97 -0
- package/dist/chunker/semantic-chunker.d.ts.map +1 -0
- package/dist/chunker/semantic-chunker.js +294 -0
- package/dist/chunker/semantic-chunker.js.map +1 -0
- package/dist/chunker/sentence-splitter.d.ts +28 -0
- package/dist/chunker/sentence-splitter.d.ts.map +1 -0
- package/dist/chunker/sentence-splitter.js +219 -0
- package/dist/chunker/sentence-splitter.js.map +1 -0
- package/dist/cli/common.d.ts +65 -0
- package/dist/cli/common.d.ts.map +1 -0
- package/dist/cli/common.js +138 -0
- package/dist/cli/common.js.map +1 -0
- package/dist/cli/delete.d.ts +8 -0
- package/dist/cli/delete.d.ts.map +1 -0
- package/dist/cli/delete.js +173 -0
- package/dist/cli/delete.js.map +1 -0
- package/dist/cli/file-collection.d.ts +2 -0
- package/dist/cli/file-collection.d.ts.map +1 -0
- package/dist/cli/file-collection.js +53 -0
- package/dist/cli/file-collection.js.map +1 -0
- package/dist/cli/ingest.d.ts +100 -0
- package/dist/cli/ingest.d.ts.map +1 -0
- package/dist/cli/ingest.js +363 -0
- package/dist/cli/ingest.js.map +1 -0
- package/dist/cli/list.d.ts +35 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +210 -0
- package/dist/cli/list.js.map +1 -0
- package/dist/cli/options.d.ts +100 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +241 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/cli/query.d.ts +24 -0
- package/dist/cli/query.d.ts.map +1 -0
- package/dist/cli/query.js +191 -0
- package/dist/cli/query.js.map +1 -0
- package/dist/cli/read-neighbors.d.ts +11 -0
- package/dist/cli/read-neighbors.d.ts.map +1 -0
- package/dist/cli/read-neighbors.js +224 -0
- package/dist/cli/read-neighbors.js.map +1 -0
- package/dist/cli/status.d.ts +8 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +80 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/sync.d.ts +8 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +244 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/cli-main.d.ts +12 -0
- package/dist/cli-main.d.ts.map +1 -0
- package/dist/cli-main.js +63 -0
- package/dist/cli-main.js.map +1 -0
- package/dist/embedder/index.d.ts +85 -0
- package/dist/embedder/index.d.ts.map +1 -0
- package/dist/embedder/index.js +284 -0
- package/dist/embedder/index.js.map +1 -0
- package/dist/features/list.d.ts +37 -0
- package/dist/features/list.d.ts.map +1 -0
- package/dist/features/list.js +40 -0
- package/dist/features/list.js.map +1 -0
- package/dist/features/sync.d.ts +207 -0
- package/dist/features/sync.d.ts.map +1 -0
- package/dist/features/sync.js +380 -0
- package/dist/features/sync.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/compute.d.ts +86 -0
- package/dist/ingest/compute.d.ts.map +1 -0
- package/dist/ingest/compute.js +177 -0
- package/dist/ingest/compute.js.map +1 -0
- package/dist/ingest/file.d.ts +27 -0
- package/dist/ingest/file.d.ts.map +1 -0
- package/dist/ingest/file.js +67 -0
- package/dist/ingest/file.js.map +1 -0
- package/dist/ingest/visual.d.ts +45 -0
- package/dist/ingest/visual.d.ts.map +1 -0
- package/dist/ingest/visual.js +234 -0
- package/dist/ingest/visual.js.map +1 -0
- package/dist/parser/docx-parser.d.ts +12 -0
- package/dist/parser/docx-parser.d.ts.map +1 -0
- package/dist/parser/docx-parser.js +328 -0
- package/dist/parser/docx-parser.js.map +1 -0
- package/dist/parser/html-parser.d.ts +18 -0
- package/dist/parser/html-parser.d.ts.map +1 -0
- package/dist/parser/html-parser.js +102 -0
- package/dist/parser/html-parser.js.map +1 -0
- package/dist/parser/index.d.ts +214 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +454 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/pdf-extract.d.ts +81 -0
- package/dist/parser/pdf-extract.d.ts.map +1 -0
- package/dist/parser/pdf-extract.js +112 -0
- package/dist/parser/pdf-extract.js.map +1 -0
- package/dist/parser/pdf-filter.d.ts +117 -0
- package/dist/parser/pdf-filter.d.ts.map +1 -0
- package/dist/parser/pdf-filter.js +528 -0
- package/dist/parser/pdf-filter.js.map +1 -0
- package/dist/parser/title-extractor.d.ts +69 -0
- package/dist/parser/title-extractor.d.ts.map +1 -0
- package/dist/parser/title-extractor.js +145 -0
- package/dist/parser/title-extractor.js.map +1 -0
- package/dist/pdf-visual/captioner.d.ts +16 -0
- package/dist/pdf-visual/captioner.d.ts.map +1 -0
- package/dist/pdf-visual/captioner.js +63 -0
- package/dist/pdf-visual/captioner.js.map +1 -0
- package/dist/pdf-visual/captioners/fast.d.ts +7 -0
- package/dist/pdf-visual/captioners/fast.d.ts.map +1 -0
- package/dist/pdf-visual/captioners/fast.js +103 -0
- package/dist/pdf-visual/captioners/fast.js.map +1 -0
- package/dist/pdf-visual/captioners/quality.d.ts +7 -0
- package/dist/pdf-visual/captioners/quality.d.ts.map +1 -0
- package/dist/pdf-visual/captioners/quality.js +127 -0
- package/dist/pdf-visual/captioners/quality.js.map +1 -0
- package/dist/pdf-visual/captioners/shared.d.ts +44 -0
- package/dist/pdf-visual/captioners/shared.d.ts.map +1 -0
- package/dist/pdf-visual/captioners/shared.js +104 -0
- package/dist/pdf-visual/captioners/shared.js.map +1 -0
- package/dist/pdf-visual/detector.d.ts +9 -0
- package/dist/pdf-visual/detector.d.ts.map +1 -0
- package/dist/pdf-visual/detector.js +234 -0
- package/dist/pdf-visual/detector.js.map +1 -0
- package/dist/pdf-visual/index.d.ts +13 -0
- package/dist/pdf-visual/index.d.ts.map +1 -0
- package/dist/pdf-visual/index.js +45 -0
- package/dist/pdf-visual/index.js.map +1 -0
- package/dist/pdf-visual/renderer.d.ts +9 -0
- package/dist/pdf-visual/renderer.d.ts.map +1 -0
- package/dist/pdf-visual/renderer.js +177 -0
- package/dist/pdf-visual/renderer.js.map +1 -0
- package/dist/pdf-visual/types.d.ts +62 -0
- package/dist/pdf-visual/types.d.ts.map +1 -0
- package/dist/pdf-visual/types.js +32 -0
- package/dist/pdf-visual/types.js.map +1 -0
- package/dist/server/error-utils.d.ts +79 -0
- package/dist/server/error-utils.d.ts.map +1 -0
- package/dist/server/error-utils.js +148 -0
- package/dist/server/error-utils.js.map +1 -0
- package/dist/server/index.d.ts +258 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1104 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/list-scanner.d.ts +52 -0
- package/dist/server/list-scanner.d.ts.map +1 -0
- package/dist/server/list-scanner.js +72 -0
- package/dist/server/list-scanner.js.map +1 -0
- package/dist/server/tool-definitions.d.ts +8 -0
- package/dist/server/tool-definitions.d.ts.map +1 -0
- package/dist/server/tool-definitions.js +181 -0
- package/dist/server/tool-definitions.js.map +1 -0
- package/dist/server/tool-input.d.ts +37 -0
- package/dist/server/tool-input.d.ts.map +1 -0
- package/dist/server/tool-input.js +216 -0
- package/dist/server/tool-input.js.map +1 -0
- package/dist/server/types.d.ts +331 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +3 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server-main.d.ts +46 -0
- package/dist/server-main.d.ts.map +1 -0
- package/dist/server-main.js +242 -0
- package/dist/server-main.js.map +1 -0
- package/dist/utils/base-dirs.d.ts +212 -0
- package/dist/utils/base-dirs.d.ts.map +1 -0
- package/dist/utils/base-dirs.js +422 -0
- package/dist/utils/base-dirs.js.map +1 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +53 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/limits.d.ts +26 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +28 -0
- package/dist/utils/limits.js.map +1 -0
- package/dist/utils/list-sources.d.ts +47 -0
- package/dist/utils/list-sources.d.ts.map +1 -0
- package/dist/utils/list-sources.js +50 -0
- package/dist/utils/list-sources.js.map +1 -0
- package/dist/utils/raw-data-utils.d.ts +131 -0
- package/dist/utils/raw-data-utils.d.ts.map +1 -0
- package/dist/utils/raw-data-utils.js +255 -0
- package/dist/utils/raw-data-utils.js.map +1 -0
- package/dist/utils/scan.d.ts +126 -0
- package/dist/utils/scan.d.ts.map +1 -0
- package/dist/utils/scan.js +221 -0
- package/dist/utils/scan.js.map +1 -0
- package/dist/utils/scope-match.d.ts +43 -0
- package/dist/utils/scope-match.d.ts.map +1 -0
- package/dist/utils/scope-match.js +87 -0
- package/dist/utils/scope-match.js.map +1 -0
- package/dist/utils/sensitive-path.d.ts +23 -0
- package/dist/utils/sensitive-path.d.ts.map +1 -0
- package/dist/utils/sensitive-path.js +91 -0
- package/dist/utils/sensitive-path.js.map +1 -0
- package/dist/utils/sync-path-key.d.ts +20 -0
- package/dist/utils/sync-path-key.d.ts.map +1 -0
- package/dist/utils/sync-path-key.js +33 -0
- package/dist/utils/sync-path-key.js.map +1 -0
- package/dist/vectordb/index.d.ts +168 -0
- package/dist/vectordb/index.d.ts.map +1 -0
- package/dist/vectordb/index.js +619 -0
- package/dist/vectordb/index.js.map +1 -0
- package/dist/vectordb/search-filters.d.ts +39 -0
- package/dist/vectordb/search-filters.d.ts.map +1 -0
- package/dist/vectordb/search-filters.js +136 -0
- package/dist/vectordb/search-filters.js.map +1 -0
- package/dist/vectordb/types.d.ts +196 -0
- package/dist/vectordb/types.d.ts.map +1 -0
- package/dist/vectordb/types.js +224 -0
- package/dist/vectordb/types.js.map +1 -0
- package/package.json +105 -0
- package/skills/mcp-local-rag/SKILL.md +308 -0
- package/skills/mcp-local-rag/references/cli-reference.md +175 -0
- package/skills/mcp-local-rag/references/html-ingestion.md +78 -0
- package/skills/mcp-local-rag/references/query-optimization.md +57 -0
- package/skills/mcp-local-rag/references/result-refinement.md +56 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Profile-agnostic helpers shared by every `captioners/*` profile.
|
|
2
|
+
//
|
|
3
|
+
// `stripControlChars` + `postProcess` are the post-generation pipeline
|
|
4
|
+
// documented in the captioner contract: control-char stripping, whitespace
|
|
5
|
+
// trim, empty → `null`, length cap with ellipsis. Both `fast` and `quality`
|
|
6
|
+
// profiles run the captioner output through the same pipeline so caption
|
|
7
|
+
// chunk shape is independent of profile.
|
|
8
|
+
//
|
|
9
|
+
// `VLM_DTYPE`, `buildModelLoadOptions`, `createModelLoader`, and
|
|
10
|
+
// `decodePngToRawImage` are the profile-agnostic load/decode mechanics. The
|
|
11
|
+
// model-class choice, prompt, processor call shape, and generation options
|
|
12
|
+
// stay per-profile (that is where `fast` and `quality` genuinely diverge).
|
|
13
|
+
import { RawImage } from '@huggingface/transformers';
|
|
14
|
+
/**
|
|
15
|
+
* ONNX quantization variant shared by both captioner profiles. Pinned to the
|
|
16
|
+
* smallest viable variant; production has no user-facing knob.
|
|
17
|
+
*/
|
|
18
|
+
const VLM_DTYPE = 'q4';
|
|
19
|
+
/**
|
|
20
|
+
* Build the `from_pretrained` option objects (processor + model) with the
|
|
21
|
+
* pinned dtype and resolved device. transformers.js declares `dtype` as a
|
|
22
|
+
* literal union; cast through `unknown` to widen-to-string-then-back.
|
|
23
|
+
*/
|
|
24
|
+
export function buildModelLoadOptions(resolvedDevice) {
|
|
25
|
+
const dtypeOpt = { dtype: VLM_DTYPE };
|
|
26
|
+
const modelOpt = { dtype: VLM_DTYPE, device: resolvedDevice };
|
|
27
|
+
return { dtypeOpt, modelOpt };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Lazy model loader shared by both profiles. Encapsulates the
|
|
31
|
+
* pending→ok/failed state machine and the identical load-failure wrapping
|
|
32
|
+
* (`Captioner load failed (modelName=..., device=...)`). The per-profile
|
|
33
|
+
* `load` callback owns the model-class choice and receives the shared option
|
|
34
|
+
* objects. On first `ensureLoaded()` the model loads; subsequent calls return
|
|
35
|
+
* the cached pair; a prior failure re-throws the same wrapped error.
|
|
36
|
+
*/
|
|
37
|
+
export function createModelLoader(modelName, resolvedDevice, load) {
|
|
38
|
+
let loaded = null;
|
|
39
|
+
let state = { kind: 'pending' };
|
|
40
|
+
return {
|
|
41
|
+
async ensureLoaded() {
|
|
42
|
+
if (state.kind === 'ok' && loaded)
|
|
43
|
+
return loaded;
|
|
44
|
+
if (state.kind === 'failed')
|
|
45
|
+
throw state.cause;
|
|
46
|
+
try {
|
|
47
|
+
loaded = await load(buildModelLoadOptions(resolvedDevice));
|
|
48
|
+
state = { kind: 'ok' };
|
|
49
|
+
return loaded;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const original = err instanceof Error ? err : new Error(String(err));
|
|
53
|
+
const wrapped = new Error(`Captioner load failed (modelName=${modelName}, device=${resolvedDevice}): ${original.message}`, { cause: original });
|
|
54
|
+
state = { kind: 'failed', cause: wrapped };
|
|
55
|
+
throw wrapped;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Decode PNG bytes to a `RawImage`. `Blob` accepts `Uint8Array` directly (the
|
|
62
|
+
* renderer returns `Uint8Array` from `Pixmap.asPNG()`), but the `BlobPart`
|
|
63
|
+
* type omits `Uint8Array<ArrayBufferLike>` due to SharedArrayBuffer subtyping;
|
|
64
|
+
* cast through `unknown`. Profiles needing a fixed input size resize the result.
|
|
65
|
+
*/
|
|
66
|
+
export async function decodePngToRawImage(pngBytes) {
|
|
67
|
+
const blob = new Blob([pngBytes], { type: 'image/png' });
|
|
68
|
+
return RawImage.fromBlob(blob);
|
|
69
|
+
}
|
|
70
|
+
/** Maximum caption length in characters; longer captions are truncated with an ellipsis. */
|
|
71
|
+
const MAX_CAPTION_LENGTH = 1000;
|
|
72
|
+
/**
|
|
73
|
+
* Strip C0 (U+0000–U+001F) and C1 (U+007F–U+009F) control characters from the
|
|
74
|
+
* input, except `\n` (U+000A) and `\t` (U+0009) which are kept verbatim.
|
|
75
|
+
*/
|
|
76
|
+
function stripControlChars(input) {
|
|
77
|
+
let out = '';
|
|
78
|
+
for (let i = 0; i < input.length; i++) {
|
|
79
|
+
const code = input.charCodeAt(i);
|
|
80
|
+
if (code === 0x09 || code === 0x0a) {
|
|
81
|
+
out += input[i];
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (code <= 0x1f)
|
|
85
|
+
continue;
|
|
86
|
+
if (code >= 0x7f && code <= 0x9f)
|
|
87
|
+
continue;
|
|
88
|
+
out += input[i];
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Apply the post-generation processing rules. Returns the final caption or
|
|
94
|
+
* `null` when the result is empty after stripping.
|
|
95
|
+
*/
|
|
96
|
+
export function postProcess(decoded) {
|
|
97
|
+
const stripped = stripControlChars(decoded).trim();
|
|
98
|
+
if (stripped.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
if (stripped.length > MAX_CAPTION_LENGTH)
|
|
101
|
+
return `${stripped.slice(0, MAX_CAPTION_LENGTH)}…`;
|
|
102
|
+
return stripped;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/pdf-visual/captioners/shared.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,uEAAuE;AACvE,2EAA2E;AAC3E,4EAA4E;AAC5E,yEAAyE;AACzE,yCAAyC;AACzC,EAAE;AACF,iEAAiE;AACjE,4EAA4E;AAC5E,2EAA2E;AAC3E,2EAA2E;AAE3E,OAAO,EAAmB,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAErE;;;GAGG;AACH,MAAM,SAAS,GAAG,IAAI,CAAA;AAKtB;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,cAAsB;IAI1D,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,SAAS,EAAgC,CAAA;IACnE,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAG1D,CAAA;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAA;AAC/B,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAiB,EACjB,cAAsB,EACtB,IAA8E;IAE9E,IAAI,MAAM,GAAuB,IAAI,CAAA;IACrC,IAAI,KAAK,GAAuB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;IAEnD,OAAO;QACL,KAAK,CAAC,YAAY;YAChB,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAA;YAChD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC,KAAK,CAAA;YAC9C,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC,CAAA;gBAC1D,KAAK,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;gBACtB,OAAO,MAAM,CAAA;YACf,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,QAAQ,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;gBACpE,MAAM,OAAO,GAAG,IAAI,KAAK,CACvB,oCAAoC,SAAS,YAAY,cAAc,MAAM,QAAQ,CAAC,OAAO,EAAE,EAC/F,EAAE,KAAK,EAAE,QAAQ,EAAE,CACpB,CAAA;gBACD,KAAK,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;gBAC1C,MAAM,OAAO,CAAA;YACf,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAoB;IAC5D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,QAAkC,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;IAClF,OAAO,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AAChC,CAAC;AAED,4FAA4F;AAC5F,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAE/B;;;GAGG;AACH,SAAS,iBAAiB,CAAC,KAAa;IACtC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAChC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACnC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAA;YACf,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,IAAI,IAAI;YAAE,SAAQ;QAC1B,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;YAAE,SAAQ;QAC1C,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAA;IAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,IAAI,QAAQ,CAAC,MAAM,GAAG,kBAAkB;QAAE,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAA;IAC5F,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Document as MupdfDocument } from 'mupdf';
|
|
2
|
+
import type { DetectedVisualRegion } from './types.js';
|
|
3
|
+
interface DetectorPage {
|
|
4
|
+
pageNum: number;
|
|
5
|
+
stextJson: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare function detectVisualRegions(pages: DetectorPage[], doc: MupdfDocument): DetectedVisualRegion[];
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/pdf-visual/detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,OAAO,CAAA;AAGtD,OAAO,KAAK,EAAE,oBAAoB,EAA8B,MAAM,YAAY,CAAA;AAmBlF,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;CACnB;AAoND,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,GAAG,EAAE,aAAa,GACjB,oBAAoB,EAAE,CA0BxB"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import * as mupdf from 'mupdf';
|
|
2
|
+
const MIN_IMAGE_BLOCK_WIDTH = 80;
|
|
3
|
+
const MIN_IMAGE_BLOCK_HEIGHT = 80;
|
|
4
|
+
const MIN_IMAGE_BLOCK_AREA_RATIO = 0.01;
|
|
5
|
+
const MAX_EFFECTIVE_AREA_RATIO = 0.85;
|
|
6
|
+
const IMAGE_MAX_AREA_RATIO_THRESHOLD = 0.1;
|
|
7
|
+
const IMAGE_TOTAL_AREA_RATIO_THRESHOLD = 0.15;
|
|
8
|
+
const MAX_CORNER_LOGO_AREA_RATIO = 0.03;
|
|
9
|
+
const CORNER_LOGO_EDGE_BAND_RATIO = 0.15;
|
|
10
|
+
const MIN_VECTOR_WIDTH = 20;
|
|
11
|
+
const MIN_VECTOR_HEIGHT = 20;
|
|
12
|
+
const MIN_VECTOR_AREA_RATIO = 0.0005;
|
|
13
|
+
const VECTOR_STROKE_COUNT_THRESHOLD = 5;
|
|
14
|
+
const CROP_PADDING_RATIO = 0.08;
|
|
15
|
+
const MIN_CROP_PADDING = 12;
|
|
16
|
+
const GROUP_GAP = 6;
|
|
17
|
+
const GROUP_GAP_PAGE_RATIO = 0.01;
|
|
18
|
+
function areaOf(rect) {
|
|
19
|
+
return Math.max(0, rect[2] - rect[0]) * Math.max(0, rect[3] - rect[1]);
|
|
20
|
+
}
|
|
21
|
+
function clampRect(rect, bounds) {
|
|
22
|
+
return [
|
|
23
|
+
Math.max(bounds[0], Math.min(bounds[2], rect[0])),
|
|
24
|
+
Math.max(bounds[1], Math.min(bounds[3], rect[1])),
|
|
25
|
+
Math.max(bounds[0], Math.min(bounds[2], rect[2])),
|
|
26
|
+
Math.max(bounds[1], Math.min(bounds[3], rect[3])),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
function unionRects(rects) {
|
|
30
|
+
const first = rects[0];
|
|
31
|
+
if (!first)
|
|
32
|
+
return null;
|
|
33
|
+
let [x0, y0, x1, y1] = first;
|
|
34
|
+
for (const rect of rects.slice(1)) {
|
|
35
|
+
x0 = Math.min(x0, rect[0]);
|
|
36
|
+
y0 = Math.min(y0, rect[1]);
|
|
37
|
+
x1 = Math.max(x1, rect[2]);
|
|
38
|
+
y1 = Math.max(y1, rect[3]);
|
|
39
|
+
}
|
|
40
|
+
return [x0, y0, x1, y1];
|
|
41
|
+
}
|
|
42
|
+
function padRect(rect, pageBounds) {
|
|
43
|
+
const xPad = Math.max(MIN_CROP_PADDING, (rect[2] - rect[0]) * CROP_PADDING_RATIO);
|
|
44
|
+
const yPad = Math.max(MIN_CROP_PADDING, (rect[3] - rect[1]) * CROP_PADDING_RATIO);
|
|
45
|
+
return clampRect([rect[0] - xPad, rect[1] - yPad, rect[2] + xPad, rect[3] + yPad], pageBounds);
|
|
46
|
+
}
|
|
47
|
+
function blockRect(block) {
|
|
48
|
+
if (typeof block !== 'object' || block === null)
|
|
49
|
+
return null;
|
|
50
|
+
const bbox = block.bbox;
|
|
51
|
+
if (typeof bbox !== 'object' || bbox === null)
|
|
52
|
+
return null;
|
|
53
|
+
const { x, y, w, h } = bbox;
|
|
54
|
+
if (![x, y, w, h].every((value) => typeof value === 'number' && Number.isFinite(value))) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return [x, y, x + w, y + h];
|
|
58
|
+
}
|
|
59
|
+
function getBlocks(stextJson) {
|
|
60
|
+
if (typeof stextJson !== 'object' || stextJson === null)
|
|
61
|
+
return [];
|
|
62
|
+
const blocks = stextJson.blocks;
|
|
63
|
+
return Array.isArray(blocks) ? blocks : [];
|
|
64
|
+
}
|
|
65
|
+
function isLikelyCornerLogo(rect, pageBounds, areaRatio) {
|
|
66
|
+
if (areaRatio > MAX_CORNER_LOGO_AREA_RATIO)
|
|
67
|
+
return false;
|
|
68
|
+
const pageWidth = pageBounds[2] - pageBounds[0];
|
|
69
|
+
const pageHeight = pageBounds[3] - pageBounds[1];
|
|
70
|
+
const xBand = pageWidth * CORNER_LOGO_EDGE_BAND_RATIO;
|
|
71
|
+
const yBand = pageHeight * CORNER_LOGO_EDGE_BAND_RATIO;
|
|
72
|
+
const nearHorizontalEdge = rect[0] <= pageBounds[0] + xBand || rect[2] >= pageBounds[2] - xBand;
|
|
73
|
+
const nearVerticalEdge = rect[1] <= pageBounds[1] + yBand || rect[3] >= pageBounds[3] - yBand;
|
|
74
|
+
return nearHorizontalEdge && nearVerticalEdge;
|
|
75
|
+
}
|
|
76
|
+
function collectRasterRects(stextJson, pageBounds) {
|
|
77
|
+
const pageArea = areaOf(pageBounds);
|
|
78
|
+
if (pageArea <= 0)
|
|
79
|
+
return [];
|
|
80
|
+
const rects = [];
|
|
81
|
+
for (const block of getBlocks(stextJson)) {
|
|
82
|
+
if (typeof block !== 'object' || block === null)
|
|
83
|
+
continue;
|
|
84
|
+
if (block.type !== 'image')
|
|
85
|
+
continue;
|
|
86
|
+
const rawRect = blockRect(block);
|
|
87
|
+
if (!rawRect)
|
|
88
|
+
continue;
|
|
89
|
+
const rect = clampRect(rawRect, pageBounds);
|
|
90
|
+
const width = rect[2] - rect[0];
|
|
91
|
+
const height = rect[3] - rect[1];
|
|
92
|
+
const ratio = areaOf(rect) / pageArea;
|
|
93
|
+
if (width >= MIN_IMAGE_BLOCK_WIDTH &&
|
|
94
|
+
height >= MIN_IMAGE_BLOCK_HEIGHT &&
|
|
95
|
+
ratio >= MIN_IMAGE_BLOCK_AREA_RATIO &&
|
|
96
|
+
ratio <= MAX_EFFECTIVE_AREA_RATIO &&
|
|
97
|
+
!isLikelyCornerLogo(rect, pageBounds, ratio)) {
|
|
98
|
+
rects.push(rect);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return rects;
|
|
102
|
+
}
|
|
103
|
+
function rasterQualifies(rects, pageBounds) {
|
|
104
|
+
const pageArea = areaOf(pageBounds);
|
|
105
|
+
const ratios = rects.map((rect) => areaOf(rect) / pageArea);
|
|
106
|
+
return (Math.max(0, ...ratios) >= IMAGE_MAX_AREA_RATIO_THRESHOLD ||
|
|
107
|
+
ratios.reduce((sum, ratio) => sum + ratio, 0) >= IMAGE_TOTAL_AREA_RATIO_THRESHOLD);
|
|
108
|
+
}
|
|
109
|
+
function collectVectorStrokeRects(page, pageNum, pageBounds) {
|
|
110
|
+
const pageArea = areaOf(pageBounds);
|
|
111
|
+
const rects = [];
|
|
112
|
+
const device = new mupdf.Device({
|
|
113
|
+
strokePath(path, stroke, ctm) {
|
|
114
|
+
try {
|
|
115
|
+
const raw = path.getBounds(stroke, ctm);
|
|
116
|
+
if (!raw.every(Number.isFinite))
|
|
117
|
+
return;
|
|
118
|
+
const rect = clampRect([raw[0], raw[1], raw[2], raw[3]], pageBounds);
|
|
119
|
+
const width = rect[2] - rect[0];
|
|
120
|
+
const height = rect[3] - rect[1];
|
|
121
|
+
const ratio = areaOf(rect) / pageArea;
|
|
122
|
+
if (width >= MIN_VECTOR_WIDTH &&
|
|
123
|
+
height >= MIN_VECTOR_HEIGHT &&
|
|
124
|
+
ratio >= MIN_VECTOR_AREA_RATIO &&
|
|
125
|
+
ratio <= MAX_EFFECTIVE_AREA_RATIO) {
|
|
126
|
+
rects.push(rect);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
console.warn(`detector: stroke bounds failed on page ${pageNum}: ${message}`);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
page.run(device, mupdf.Matrix.identity);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
140
|
+
console.warn(`detector: vector scan failed on page ${pageNum}: ${message}`);
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
device.close();
|
|
145
|
+
}
|
|
146
|
+
return rects;
|
|
147
|
+
}
|
|
148
|
+
function axisGap(a0, a1, b0, b1) {
|
|
149
|
+
if (a1 < b0)
|
|
150
|
+
return b0 - a1;
|
|
151
|
+
if (b1 < a0)
|
|
152
|
+
return a0 - b1;
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
function groupRects(rects, pageBounds) {
|
|
156
|
+
const gap = Math.max(GROUP_GAP, Math.min(pageBounds[2] - pageBounds[0], pageBounds[3] - pageBounds[1]) * GROUP_GAP_PAGE_RATIO);
|
|
157
|
+
const remaining = new Set(rects.map((_, index) => index));
|
|
158
|
+
const groups = [];
|
|
159
|
+
while (remaining.size > 0) {
|
|
160
|
+
const seed = remaining.values().next().value;
|
|
161
|
+
remaining.delete(seed);
|
|
162
|
+
const indices = [seed];
|
|
163
|
+
for (let cursor = 0; cursor < indices.length; cursor += 1) {
|
|
164
|
+
const current = rects[indices[cursor]];
|
|
165
|
+
for (const candidateIndex of remaining) {
|
|
166
|
+
const candidate = rects[candidateIndex];
|
|
167
|
+
if (axisGap(current[0], current[2], candidate[0], candidate[2]) <= gap &&
|
|
168
|
+
axisGap(current[1], current[3], candidate[1], candidate[3]) <= gap) {
|
|
169
|
+
remaining.delete(candidateIndex);
|
|
170
|
+
indices.push(candidateIndex);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
groups.push(indices.map((index) => rects[index]));
|
|
175
|
+
}
|
|
176
|
+
return groups;
|
|
177
|
+
}
|
|
178
|
+
function pageRegions(pageRecord, page, pageBounds) {
|
|
179
|
+
const rasterRects = collectRasterRects(pageRecord.stextJson, pageBounds);
|
|
180
|
+
let groups;
|
|
181
|
+
let evidence;
|
|
182
|
+
if (rasterQualifies(rasterRects, pageBounds)) {
|
|
183
|
+
groups = groupRects(rasterRects, pageBounds);
|
|
184
|
+
evidence = 'raster';
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const vectorRects = collectVectorStrokeRects(page, pageRecord.pageNum, pageBounds);
|
|
188
|
+
if (vectorRects.length < VECTOR_STROKE_COUNT_THRESHOLD)
|
|
189
|
+
return [];
|
|
190
|
+
groups = groupRects(vectorRects, pageBounds);
|
|
191
|
+
evidence = 'vector';
|
|
192
|
+
}
|
|
193
|
+
return groups.flatMap((group) => {
|
|
194
|
+
const union = unionRects(group);
|
|
195
|
+
if (!union)
|
|
196
|
+
return [];
|
|
197
|
+
const bbox = padRect(union, pageBounds);
|
|
198
|
+
return [
|
|
199
|
+
{
|
|
200
|
+
pageNum: pageRecord.pageNum,
|
|
201
|
+
bbox,
|
|
202
|
+
evidence,
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
export function detectVisualRegions(pages, doc) {
|
|
208
|
+
const pending = [];
|
|
209
|
+
for (const pageRecord of pages) {
|
|
210
|
+
let page = null;
|
|
211
|
+
try {
|
|
212
|
+
page = doc.loadPage(pageRecord.pageNum - 1);
|
|
213
|
+
const raw = page.getBounds();
|
|
214
|
+
const bounds = [raw[0], raw[1], raw[2], raw[3]];
|
|
215
|
+
if (areaOf(bounds) > 0)
|
|
216
|
+
pending.push(...pageRegions(pageRecord, page, bounds));
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
220
|
+
console.warn(`detector: page scan failed on page ${pageRecord.pageNum}: ${message}`);
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
page?.destroy?.();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
pending.sort((left, right) => left.pageNum - right.pageNum || left.bbox[1] - right.bbox[1] || left.bbox[0] - right.bbox[0]);
|
|
227
|
+
const nextIndex = new Map();
|
|
228
|
+
return pending.map((region) => {
|
|
229
|
+
const detectionIndex = nextIndex.get(region.pageNum) ?? 0;
|
|
230
|
+
nextIndex.set(region.pageNum, detectionIndex + 1);
|
|
231
|
+
return { ...region, detectionIndex };
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/pdf-visual/detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Document as MupdfDocument } from 'mupdf';
|
|
2
|
+
import type { Captioner, DetectedVisualRegion, ProcessedVisualRegion } from './types.js';
|
|
3
|
+
export { createCaptioner } from './captioner.js';
|
|
4
|
+
export { detectVisualRegions } from './detector.js';
|
|
5
|
+
export { renderPdfPage, renderPdfRendition } from './renderer.js';
|
|
6
|
+
export type { DetectedVisualRegion, ImageRendition, ProcessedVisualRegion, } from './types.js';
|
|
7
|
+
export { VlmError } from './types.js';
|
|
8
|
+
export interface ProcessVisualRegionsOptions {
|
|
9
|
+
captioner?: Captioner;
|
|
10
|
+
includeImages?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function processVisualRegions(regions: DetectedVisualRegion[], doc: MupdfDocument, options: ProcessVisualRegionsOptions): Promise<ProcessedVisualRegion[]>;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pdf-visual/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,OAAO,CAAA;AAGtD,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAUxF,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AACjE,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,qBAAqB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAmClC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// `pdf-visual` region orchestration and public surface. Region processing
|
|
2
|
+
// isolates caption and rendition failures so one crop cannot discard another.
|
|
3
|
+
import { renderPdfPage, renderPdfRendition } from './renderer.js';
|
|
4
|
+
// Public surface re-exports. The dispatch sites in `src/cli/ingest.ts` and
|
|
5
|
+
// `src/server/index.ts` reach the visual-mode
|
|
6
|
+
// implementation exclusively through `await import('../pdf-visual/index.js')`
|
|
7
|
+
// so default-mode ingest does not load visual dependencies. Keeping every
|
|
8
|
+
// public symbol re-exported here means those sites never need to know the
|
|
9
|
+
// internal module layout.
|
|
10
|
+
// Re-export ordering below is alphabetical by source module to match Biome's
|
|
11
|
+
// `organizeImports` rule (`./captioner` → `./detector` → `./renderer` → `./types`).
|
|
12
|
+
export { createCaptioner } from './captioner.js';
|
|
13
|
+
export { detectVisualRegions } from './detector.js';
|
|
14
|
+
export { renderPdfPage, renderPdfRendition } from './renderer.js';
|
|
15
|
+
export { VlmError } from './types.js';
|
|
16
|
+
export async function processVisualRegions(regions, doc, options) {
|
|
17
|
+
const processed = [];
|
|
18
|
+
for (const region of regions) {
|
|
19
|
+
let caption = null;
|
|
20
|
+
let rendition;
|
|
21
|
+
if (options.captioner) {
|
|
22
|
+
try {
|
|
23
|
+
const pngBytes = await renderPdfPage(doc, region.pageNum, region.bbox);
|
|
24
|
+
caption = await options.captioner.caption(pngBytes, region.pageNum);
|
|
25
|
+
if (caption === null) {
|
|
26
|
+
console.warn(`VLM caption empty for page ${region.pageNum}, visual ${region.detectionIndex}; proceeding without caption`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
console.warn(`VLM caption failed for page ${region.pageNum}, visual ${region.detectionIndex}; proceeding without caption`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (options.includeImages) {
|
|
34
|
+
try {
|
|
35
|
+
rendition = await renderPdfRendition(doc, region.pageNum, region.bbox, region.evidence);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
console.warn(`PDF rendition failed for page ${region.pageNum}, visual ${region.detectionIndex}; proceeding without image`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
processed.push({ ...region, caption, ...(rendition ? { rendition } : {}) });
|
|
42
|
+
}
|
|
43
|
+
return processed;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pdf-visual/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAI9E,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAGjE,2EAA2E;AAC3E,8CAA8C;AAC9C,8EAA8E;AAC9E,0EAA0E;AAC1E,0EAA0E;AAC1E,0BAA0B;AAC1B,6EAA6E;AAC7E,oFAAoF;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAMjE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAOrC,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAA+B,EAC/B,GAAkB,EAClB,OAAoC;IAEpC,MAAM,SAAS,GAA4B,EAAE,CAAA;IAC7C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,OAAO,GAAkB,IAAI,CAAA;QACjC,IAAI,SAA6C,CAAA;QAEjD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;gBACtE,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;gBACnE,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;oBACrB,OAAO,CAAC,IAAI,CACV,8BAA8B,MAAM,CAAC,OAAO,YAAY,MAAM,CAAC,cAAc,8BAA8B,CAC5G,CAAA;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,IAAI,CACV,+BAA+B,MAAM,CAAC,OAAO,YAAY,MAAM,CAAC,cAAc,8BAA8B,CAC7G,CAAA;YACH,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,SAAS,GAAG,MAAM,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;YACzF,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,IAAI,CACV,iCAAiC,MAAM,CAAC,OAAO,YAAY,MAAM,CAAC,cAAc,4BAA4B,CAC7G,CAAA;YACH,CAAC;QACH,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC7E,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Document as MupdfDocument } from 'mupdf';
|
|
2
|
+
import type { ImageRendition, VisualBBox, VisualEvidence, VisualImageMimeType } from './types.js';
|
|
3
|
+
import { VlmError } from './types.js';
|
|
4
|
+
export { VlmError };
|
|
5
|
+
export declare function renderPdfPage(doc: MupdfDocument, pageNum: number, cropRect: VisualBBox): Promise<Uint8Array>;
|
|
6
|
+
export declare function renderPdfRendition(doc: MupdfDocument, pageNum: number, cropRect: VisualBBox, evidence: VisualEvidence): Promise<ImageRendition>;
|
|
7
|
+
/** Bound a Mammoth-extracted PNG/JPEG using the same query payload limits as PDF crops. */
|
|
8
|
+
export declare function renderImageRendition(bytes: Uint8Array, sourceMimeType: VisualImageMimeType): ImageRendition;
|
|
9
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/pdf-visual/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,IAAI,aAAa,EAAE,MAAM,OAAO,CAAA;AAItD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AACjG,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,OAAO,EAAE,QAAQ,EAAE,CAAA;AAsFnB,wBAAsB,aAAa,CACjC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,UAAU,CAAC,CAYrB;AA+CD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,cAAc,CAAC,CAkBzB;AA2BD,2FAA2F;AAC3F,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,UAAU,EACjB,cAAc,EAAE,mBAAmB,GAClC,cAAc,CAqBhB"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as mupdf from 'mupdf';
|
|
2
|
+
import { MAX_VISUAL_RENDITION_BYTES } from '../utils/limits.js';
|
|
3
|
+
import { VlmError } from './types.js';
|
|
4
|
+
export { VlmError };
|
|
5
|
+
const RENDER_DPI = 200;
|
|
6
|
+
const BASE_SCALE = RENDER_DPI / 72;
|
|
7
|
+
const CAPTION_LONG_EDGE_MAX = 4096;
|
|
8
|
+
const CAPTION_PIXEL_MAX = 16_777_216;
|
|
9
|
+
const RENDITION_LONG_EDGE_MAX = 1024;
|
|
10
|
+
const RENDITION_TARGET_BYTES = 256 * 1024;
|
|
11
|
+
const JPEG_QUALITIES = [82, 72, 62, 52];
|
|
12
|
+
const RENDITION_EDGES = [1024, 896, 768, 640, 512, 384, 256];
|
|
13
|
+
function cropDimensions(cropRect) {
|
|
14
|
+
const width = cropRect[2] - cropRect[0];
|
|
15
|
+
const height = cropRect[3] - cropRect[1];
|
|
16
|
+
if (![...cropRect, width, height].every(Number.isFinite) || width <= 0 || height <= 0) {
|
|
17
|
+
throw new Error('Invalid crop rectangle');
|
|
18
|
+
}
|
|
19
|
+
return { width, height };
|
|
20
|
+
}
|
|
21
|
+
function scaleForLongEdge(cropRect, longEdge) {
|
|
22
|
+
const { width, height } = cropDimensions(cropRect);
|
|
23
|
+
return Math.min(BASE_SCALE, longEdge / Math.max(width, height));
|
|
24
|
+
}
|
|
25
|
+
function captionScale(cropRect) {
|
|
26
|
+
const { width, height } = cropDimensions(cropRect);
|
|
27
|
+
return Math.min(BASE_SCALE, CAPTION_LONG_EDGE_MAX / Math.max(width, height), Math.sqrt(CAPTION_PIXEL_MAX / (width * height)));
|
|
28
|
+
}
|
|
29
|
+
function renderCrop(page, cropRect, scale, consume) {
|
|
30
|
+
const { width: sourceWidth, height: sourceHeight } = cropDimensions(cropRect);
|
|
31
|
+
const width = Math.max(1, Math.floor(sourceWidth * scale));
|
|
32
|
+
const height = Math.max(1, Math.floor(sourceHeight * scale));
|
|
33
|
+
const pixmap = new mupdf.Pixmap(mupdf.ColorSpace.DeviceRGB, [0, 0, width, height], false);
|
|
34
|
+
let device = null;
|
|
35
|
+
try {
|
|
36
|
+
const matrix = [scale, 0, 0, scale, -cropRect[0] * scale, -cropRect[1] * scale];
|
|
37
|
+
device = new mupdf.DrawDevice(matrix, pixmap);
|
|
38
|
+
pixmap.clear(255);
|
|
39
|
+
page.run(device, mupdf.Matrix.identity);
|
|
40
|
+
return consume(pixmap);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
try {
|
|
44
|
+
device?.close();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
pixmap.destroy?.();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function encodePixmap(pixmap, mimeType, quality = 82) {
|
|
52
|
+
const bytes = mimeType === 'image/png' ? pixmap.asPNG() : pixmap.asJPEG(quality);
|
|
53
|
+
return {
|
|
54
|
+
bytes,
|
|
55
|
+
mimeType,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function encodePixmapCandidates(pixmap, mimeType) {
|
|
59
|
+
const renditions = [];
|
|
60
|
+
const qualities = mimeType === 'image/jpeg' ? JPEG_QUALITIES : [82];
|
|
61
|
+
for (const quality of qualities) {
|
|
62
|
+
const rendition = encodePixmap(pixmap, mimeType, quality);
|
|
63
|
+
renditions.push(rendition);
|
|
64
|
+
if (rendition.bytes.byteLength <= RENDITION_TARGET_BYTES)
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
return renditions;
|
|
68
|
+
}
|
|
69
|
+
export async function renderPdfPage(doc, pageNum, cropRect) {
|
|
70
|
+
let page = null;
|
|
71
|
+
try {
|
|
72
|
+
page = doc.loadPage(pageNum - 1);
|
|
73
|
+
return renderCrop(page, cropRect, captionScale(cropRect), (pixmap) => pixmap.asPNG());
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error instanceof VlmError)
|
|
77
|
+
throw error;
|
|
78
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
79
|
+
throw new VlmError('Failed to render PDF page', { cause, pageNum });
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
page?.destroy?.();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function preferredMime(evidence) {
|
|
86
|
+
return evidence === 'raster' ? 'image/jpeg' : 'image/png';
|
|
87
|
+
}
|
|
88
|
+
function renditionEdges(sourceLongEdge) {
|
|
89
|
+
const initialLongEdge = Math.min(RENDITION_LONG_EDGE_MAX, sourceLongEdge);
|
|
90
|
+
return [...new Set(RENDITION_EDGES.map((edge) => Math.min(edge, initialLongEdge)))].filter((edge) => edge >= 1);
|
|
91
|
+
}
|
|
92
|
+
function selectForMime(edges, mimeType, renderAtEdge) {
|
|
93
|
+
let withinHardLimit = null;
|
|
94
|
+
for (const edge of edges) {
|
|
95
|
+
for (const rendition of renderAtEdge(edge, mimeType)) {
|
|
96
|
+
if (rendition.bytes.byteLength <= MAX_VISUAL_RENDITION_BYTES && withinHardLimit === null) {
|
|
97
|
+
withinHardLimit = rendition;
|
|
98
|
+
}
|
|
99
|
+
if (rendition.bytes.byteLength <= RENDITION_TARGET_BYTES)
|
|
100
|
+
return rendition;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return withinHardLimit;
|
|
104
|
+
}
|
|
105
|
+
function selectBoundedRendition(sourceLongEdge, preferred, renderAtEdge) {
|
|
106
|
+
const edges = renditionEdges(sourceLongEdge);
|
|
107
|
+
const rendition = selectForMime(edges, preferred, renderAtEdge);
|
|
108
|
+
if (rendition)
|
|
109
|
+
return rendition;
|
|
110
|
+
if (preferred === 'image/png') {
|
|
111
|
+
const jpeg = selectForMime(edges, 'image/jpeg', renderAtEdge);
|
|
112
|
+
if (jpeg)
|
|
113
|
+
return jpeg;
|
|
114
|
+
}
|
|
115
|
+
throw new Error('Rendition exceeds the encoded-size limit');
|
|
116
|
+
}
|
|
117
|
+
export async function renderPdfRendition(doc, pageNum, cropRect, evidence) {
|
|
118
|
+
let page = null;
|
|
119
|
+
try {
|
|
120
|
+
page = doc.loadPage(pageNum - 1);
|
|
121
|
+
const { width, height } = cropDimensions(cropRect);
|
|
122
|
+
const nativeLongEdge = Math.max(1, Math.floor(Math.max(width, height) * BASE_SCALE));
|
|
123
|
+
return selectBoundedRendition(nativeLongEdge, preferredMime(evidence), (edge, mimeType) => renderCrop(page, cropRect, scaleForLongEdge(cropRect, edge), (pixmap) => encodePixmapCandidates(pixmap, mimeType)));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof VlmError)
|
|
127
|
+
throw error;
|
|
128
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
129
|
+
throw new VlmError('Failed to render PDF rendition', { cause, pageNum });
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
page?.destroy?.();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function renderImageAtSize(image, width, height, preserveAlpha, consume) {
|
|
136
|
+
const pixmap = new mupdf.Pixmap(mupdf.ColorSpace.DeviceRGB, [0, 0, width, height], preserveAlpha);
|
|
137
|
+
let device = null;
|
|
138
|
+
try {
|
|
139
|
+
pixmap.clear(preserveAlpha ? 0 : 255);
|
|
140
|
+
device = new mupdf.DrawDevice(mupdf.Matrix.identity, pixmap);
|
|
141
|
+
device.fillImage(image, [width, 0, 0, height, 0, 0], 1);
|
|
142
|
+
device.close();
|
|
143
|
+
device = null;
|
|
144
|
+
return consume(pixmap);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
try {
|
|
148
|
+
device?.close();
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
pixmap.destroy?.();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Bound a Mammoth-extracted PNG/JPEG using the same query payload limits as PDF crops. */
|
|
156
|
+
export function renderImageRendition(bytes, sourceMimeType) {
|
|
157
|
+
let image = null;
|
|
158
|
+
try {
|
|
159
|
+
const loadedImage = new mupdf.Image(bytes);
|
|
160
|
+
image = loadedImage;
|
|
161
|
+
const sourceWidth = loadedImage.getWidth();
|
|
162
|
+
const sourceHeight = loadedImage.getHeight();
|
|
163
|
+
if (sourceWidth <= 0 || sourceHeight <= 0)
|
|
164
|
+
throw new Error('Image has invalid dimensions');
|
|
165
|
+
const sourceLongEdge = Math.max(sourceWidth, sourceHeight);
|
|
166
|
+
return selectBoundedRendition(sourceLongEdge, sourceMimeType, (edge, mimeType) => {
|
|
167
|
+
const scale = edge / sourceLongEdge;
|
|
168
|
+
const width = Math.max(1, Math.round(sourceWidth * scale));
|
|
169
|
+
const height = Math.max(1, Math.round(sourceHeight * scale));
|
|
170
|
+
return renderImageAtSize(loadedImage, width, height, mimeType === 'image/png', (pixmap) => encodePixmapCandidates(pixmap, mimeType));
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
image?.destroy?.();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=renderer.js.map
|