@embedpdf/engine-services 3.0.0-next.4 → 3.0.0-next.5

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.cjs CHANGED
@@ -7062,23 +7062,26 @@ var RedactionApplier = class {
7062
7062
  //#region src/features/text/PageTextReader.ts
7063
7063
  /**
7064
7064
  * Per-page slow-path text reader. Acquires a `pagePtr` from the
7065
- * `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`),
7066
- * extracts the full UTF-16 buffer with `FPDFText_GetText`, decodes,
7067
- * and releases everything in reverse order.
7065
+ * `PagePtrPool`, opens a PDFium text page (`FPDFText_LoadPage`), extracts
7066
+ * the page text, and releases everything in reverse order.
7068
7067
  *
7069
- * Memory model:
7070
- * - `count` is in UTF-16 code units (FPDFText's "char").
7071
- * - The buffer needs `(count + 1) * 2` bytes (PDFium writes a
7072
- * trailing NUL terminator).
7073
- * - `mem.readU16String` walks the buffer up to the NUL and decodes
7074
- * to a JS string, faithfully preserving surrogate pairs.
7068
+ * Index spaces (see engine-core `text/charmap.ts`): `charCount` is the
7069
+ * CHARACTER space (`FPDFText_CountChars` — the space geometry runs tile);
7070
+ * `text` is the TEXT projection. The two diverge when a character
7071
+ * contributes zero text units (non-printing) or two (supplementary plane);
7072
+ * `charMap` anchors encode exactly those deviations.
7075
7073
  *
7076
- * Errors:
7077
- * - `EPDFDoc_LoadPageByObjectNumber` returning 0 is `NotFound`
7078
- * (raised by `PagePtrPool.acquire`).
7079
- * - `FPDFText_LoadPage` returning 0 is `RuntimeUnavailable` — should
7080
- * not happen on a healthy page; treat it as a runtime bug rather
7081
- * than user error.
7074
+ * Both come from the fork's batched extension calls, which share one
7075
+ * encoding authority (`FX_UTF16Encode`) so text and map can never
7076
+ * disagree:
7077
+ *
7078
+ * - `EPDFText_GetTextFull` — true UTF-16LE extraction; supplementary
7079
+ * characters arrive as surrogate pairs instead of being silently
7080
+ * dropped by `FPDFText_GetText`'s UCS-2 encoder. Called once with an
7081
+ * upper-bound buffer (2 units per character + NUL), so no measure
7082
+ * round-trip.
7083
+ * - `EPDFText_GetCharToTextMap` — the anchors, measure-then-fill. Zero
7084
+ * anchors is the common (identity) page and skips the fill call.
7082
7085
  */
7083
7086
  var PageTextReader = class {
7084
7087
  constructor(runtime, session) {
@@ -7087,7 +7090,7 @@ var PageTextReader = class {
7087
7090
  }
7088
7091
  read(pageObjectNumber, signal) {
7089
7092
  throwIfAborted(signal);
7090
- const { fn, mem } = this.runtime;
7093
+ const { fn } = this.runtime;
7091
7094
  const pool = this.session.pagePool();
7092
7095
  const pagePtr = pool.acquire(pageObjectNumber);
7093
7096
  try {
@@ -7096,10 +7099,18 @@ var PageTextReader = class {
7096
7099
  if (!textPagePtr) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `FPDFText_LoadPage returned null for page object ${pageObjectNumber}`);
7097
7100
  try {
7098
7101
  throwIfAborted(signal);
7099
- const charCount = fn.FPDFText_CountChars(textPagePtr);
7100
- return {
7101
- text: readPageText(fn, mem, textPagePtr, charCount),
7102
- charCount: Math.max(charCount, 0)
7102
+ const charCount = Math.max(fn.FPDFText_CountChars(textPagePtr), 0);
7103
+ const text = readTextFull(this.runtime, textPagePtr, charCount);
7104
+ const charMap = readCharMap(this.runtime, textPagePtr, pageObjectNumber);
7105
+ const violation = (0, _embedpdf_engine_core_runtime.charMapViolation)(charCount, text.length, charMap);
7106
+ if (violation !== null) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `char map invariant violated for page object ${pageObjectNumber}: ${violation}`);
7107
+ return charMap.length > 0 ? {
7108
+ text,
7109
+ charCount,
7110
+ charMap
7111
+ } : {
7112
+ text,
7113
+ charCount
7103
7114
  };
7104
7115
  } finally {
7105
7116
  fn.FPDFText_ClosePage(textPagePtr);
@@ -7109,11 +7120,43 @@ var PageTextReader = class {
7109
7120
  }
7110
7121
  }
7111
7122
  };
7112
- function readPageText(fn, mem, textPagePtr, charCount) {
7123
+ /**
7124
+ * Full-fidelity page text in one call: the buffer is sized at the maximum
7125
+ * possible expansion (every character a surrogate pair, plus the NUL), so
7126
+ * the returned requirement always fits and the measure round-trip is
7127
+ * skipped. The return value counts UTF-16 units INCLUDING the terminator.
7128
+ */
7129
+ function readTextFull(runtime, textPagePtr, charCount) {
7113
7130
  if (charCount <= 0) return "";
7114
- return withScratch(mem, (charCount + 1) * 2, (buf) => {
7115
- if (fn.FPDFText_GetText(textPagePtr, 0, charCount, buf) <= 0) return "";
7116
- return mem.readU16String(buf);
7131
+ const { fn, mem } = runtime;
7132
+ const bufUnits = charCount * 2 + 1;
7133
+ return withScratch(mem, bufUnits * 2, (buf) => {
7134
+ const written = fn.EPDFText_GetTextFull(textPagePtr, buf, bufUnits);
7135
+ if (written <= 1) return "";
7136
+ return decodeUtf16Le(mem.readBytes(buf, (written - 1) * 2));
7137
+ });
7138
+ }
7139
+ function decodeUtf16Le(bytes) {
7140
+ const units = new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength >> 1);
7141
+ let out = "";
7142
+ const CHUNK = 8192;
7143
+ for (let i = 0; i < units.length; i += CHUNK) out += String.fromCharCode(...units.subarray(i, Math.min(i + CHUNK, units.length)));
7144
+ return out;
7145
+ }
7146
+ /** Anchor struct layout: two little-endian i32s (see EPDF_CHAR_MAP_ANCHOR). */
7147
+ const ANCHOR_BYTES = 8;
7148
+ function readCharMap(runtime, textPagePtr, pageObjectNumber) {
7149
+ const { fn, mem } = runtime;
7150
+ const count = fn.EPDFText_GetCharToTextMap(textPagePtr, _embedpdf_engine_runtime.NULL_PTR, 0);
7151
+ if (count < 0) throw new _embedpdf_engine_core_runtime.EngineError(_embedpdf_engine_core_runtime.EngineErrorCode.RuntimeUnavailable, `EPDFText_GetCharToTextMap failed for page object ${pageObjectNumber}`);
7152
+ if (count === 0) return [];
7153
+ return withScratch(mem, count * ANCHOR_BYTES, (buf) => {
7154
+ fn.EPDFText_GetCharToTextMap(textPagePtr, buf, count);
7155
+ const bytes = mem.readBytes(buf, count * ANCHOR_BYTES);
7156
+ const words = new Int32Array(bytes.buffer, bytes.byteOffset, count * 2);
7157
+ const anchors = new Array(count);
7158
+ for (let i = 0; i < count; i++) anchors[i] = [words[i * 2], words[i * 2 + 1]];
7159
+ return anchors;
7117
7160
  });
7118
7161
  }
7119
7162
  //#endregion
@@ -8754,7 +8797,7 @@ function emptyResult(index, status) {
8754
8797
  //#endregion
8755
8798
  //#region src/features/search/internal/pageCorpusCache.ts
8756
8799
  /**
8757
- * Per-session, per-page search corpus: the page's extracted text plus its
8800
+ * Per-session, per-page search corpus: the page's text snapshot plus its
8758
8801
  * DEFAULT fold (the one literal queries with default options search).
8759
8802
  * This is the local engine's in-memory equivalent of the server's corpus
8760
8803
  * artifacts — same fold version, same shape, built lazily on first search
@@ -8779,19 +8822,20 @@ function acquirePageCorpus(runtime, session, pageObjectNumber, signal) {
8779
8822
  cache.set(session, pages);
8780
8823
  }
8781
8824
  const hit = pages.get(pageObjectNumber);
8782
- if (hit && hit.seq === seq) return hit.folded;
8783
- const text = new PageTextReader(runtime, session).read(pageObjectNumber, signal).text;
8784
- const folded = (0, _embedpdf_engine_core_runtime.foldText)(text);
8785
- pages.delete(pageObjectNumber);
8786
- pages.set(pageObjectNumber, {
8825
+ if (hit && hit.seq === seq) return hit;
8826
+ const snapshot = new PageTextReader(runtime, session).read(pageObjectNumber, signal);
8827
+ const entry = {
8787
8828
  seq,
8788
- folded
8789
- });
8829
+ folded: (0, _embedpdf_engine_core_runtime.foldText)(snapshot.text),
8830
+ snapshot
8831
+ };
8832
+ pages.delete(pageObjectNumber);
8833
+ pages.set(pageObjectNumber, entry);
8790
8834
  if (pages.size > MAX_CACHED_PAGES) {
8791
8835
  const oldest = pages.keys().next().value;
8792
8836
  if (oldest !== void 0) pages.delete(oldest);
8793
8837
  }
8794
- return folded;
8838
+ return entry;
8795
8839
  }
8796
8840
  //#endregion
8797
8841
  //#region src/features/search/internal/searchCursor.ts
@@ -8900,19 +8944,23 @@ var SearchReader = class {
8900
8944
  throwIfAborted(signal);
8901
8945
  const pon = order[scanned].pageObjectNumber;
8902
8946
  const corpus = acquirePageCorpus(this.runtime, this.session, pon, signal);
8947
+ const text = corpus.snapshot.text;
8903
8948
  let ranges;
8904
- if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(corpus.original, query);
8905
- else if (query.matchCase || query.matchDiacritics) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(corpus.original, (0, _embedpdf_engine_core_runtime.foldOptionsFor)(query)), query);
8906
- else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus, query);
8949
+ if (query.regex) ranges = (0, _embedpdf_engine_core_runtime.matchRegex)(text, query);
8950
+ else if (query.matchCase || query.matchDiacritics) ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)((0, _embedpdf_engine_core_runtime.foldText)(text, (0, _embedpdf_engine_core_runtime.foldOptionsFor)(query)), query);
8951
+ else ranges = (0, _embedpdf_engine_core_runtime.matchLiteral)(corpus.folded, query);
8907
8952
  if (ranges.length > 0) {
8908
8953
  const layout = (0, _embedpdf_engine_core_runtime.buildPageTextLayout)(new PageGeometryReader(this.runtime, this.session).read(pon, signal));
8909
- for (const range of ranges) matches.push({
8910
- pageObjectNumber: pon,
8911
- charStart: range.start,
8912
- charCount: range.length,
8913
- segments: (0, _embedpdf_engine_core_runtime.textSegmentsForRange)(layout, range.start, range.length),
8914
- ...mode === "full" ? { snippet: (0, _embedpdf_engine_core_runtime.buildSnippet)(corpus.original, range) } : {}
8915
- });
8954
+ for (const range of ranges) {
8955
+ const chars = (0, _embedpdf_engine_core_runtime.charRangeForTextOffsets)(corpus.snapshot, range.start, range.start + range.length);
8956
+ matches.push({
8957
+ pageObjectNumber: pon,
8958
+ charStart: chars.start,
8959
+ charCount: chars.end - chars.start,
8960
+ segments: (0, _embedpdf_engine_core_runtime.textSegmentsForRange)(layout, chars.start, chars.end - chars.start),
8961
+ ...mode === "full" ? { snippet: (0, _embedpdf_engine_core_runtime.buildSnippet)(text, range) } : {}
8962
+ });
8963
+ }
8916
8964
  }
8917
8965
  scanned++;
8918
8966
  pagesThisSlice++;