MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / extractTextFromStream

Function extractTextFromStream

src/tools/filesystem/pdf-read.ts:137–192  ·  view source on GitHub ↗

* Extract text from a PDF content stream. The stream contains a sequence of * operators in PostScript-ish syntax. We care about: * - (literal string) Tj → show literal * - Tj → show hex-encoded literal * - [array] TJ → show array of strings with kerning

(stream: string)

Source from the content-addressed store, hash-verified

135 * or MacRoman encoding) this works fine.
136 */
137function extractTextFromStream(stream: string): string {
138 const out: string[] = [];
139
140 // Process line-by-line-ish: scan for text operators
141 // (literal) Tj | (literal) ' | (literal) "
142 const singleStringRe = /\(((?:\\\)|\\\(|\\\\|[^()\\]|\\[0-9]{1,3}|\\[nrtbf])*)\)\s*(Tj|'|")/g;
143 // <hex> Tj
144 const hexStringRe = /<([0-9A-Fa-f\s]+)>\s*Tj/g;
145 // [arrays] TJ
146 const arrayRe = /\[([^\]]*)\]\s*TJ/g;
147 // T* and ET = line/block end
148 const newlineRe = /\bT\*\b/g;
149 const blockEndRe = /\bET\b/g;
150
151 // We want to walk the stream in order, mixing all operators. Easiest:
152 // tokenize by scanning forward and detecting which kind of op is next.
153 // For simplicity here we do passes and merge — this loses some ordering
154 // precision but works for most documents.
155
156 // Pass 1: literals
157 let m: RegExpExecArray | null;
158 const tokens: Array<{ pos: number; text: string }> = [];
159 singleStringRe.lastIndex = 0;
160 while ((m = singleStringRe.exec(stream))) {
161 tokens.push({ pos: m.index, text: decodePdfLiteral(m[1]!) + (m[2] === "'" || m[2] === '"' ? '\n' : '') });
162 }
163 hexStringRe.lastIndex = 0;
164 while ((m = hexStringRe.exec(stream))) {
165 tokens.push({ pos: m.index, text: decodeHex(m[1]!) });
166 }
167 arrayRe.lastIndex = 0;
168 while ((m = arrayRe.exec(stream))) {
169 const arrContent = m[1]!;
170 // Pull out (literals) inside the array; ignore numeric kerning offsets
171 const inner: string[] = [];
172 const innerRe = /\(((?:\\\)|\\\(|\\\\|[^()\\]|\\[0-9]{1,3}|\\[nrtbf])*)\)|<([0-9A-Fa-f\s]+)>/g;
173 let n: RegExpExecArray | null;
174 while ((n = innerRe.exec(arrContent))) {
175 if (n[1] !== undefined) inner.push(decodePdfLiteral(n[1]));
176 else if (n[2]) inner.push(decodeHex(n[2]));
177 }
178 tokens.push({ pos: m.index, text: inner.join('') });
179 }
180 newlineRe.lastIndex = 0;
181 while ((m = newlineRe.exec(stream))) {
182 tokens.push({ pos: m.index, text: '\n' });
183 }
184 blockEndRe.lastIndex = 0;
185 while ((m = blockEndRe.exec(stream))) {
186 tokens.push({ pos: m.index, text: '\n' });
187 }
188
189 tokens.sort((a, b) => a.pos - b.pos);
190 for (const t of tokens) out.push(t.text);
191 return out.join('');
192}
193
194function decodePdfLiteral(s: string): string {

Callers 1

executeMethod · 0.85

Calls 3

decodePdfLiteralFunction · 0.85
decodeHexFunction · 0.85
pushMethod · 0.45

Tested by

no test coverage detected