Skip to content

Commit b66c26e

Browse files
committed
fix(file-parsers): keep text-free PDFs empty and count only pages that were read
Gate the truncation notice on the sanitized, trimmed body: a text-free multi-page PDF collapses to a lone separator, so the previous length check let a notice turn a document callers treat as empty into one that looks like it holds content. Also stop counting a page the budget cut off before it yielded anything, so the notice no longer reports one more page than was shown.
1 parent 3b5c766 commit b66c26e

2 files changed

Lines changed: 39 additions & 5 deletions

File tree

apps/sim/lib/file-parsers/pdf-parser.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,24 @@ function buildTextBombPdf(repeats: number): Buffer {
3333
Buffer.from('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>'),
3434
]
3535

36+
return assemblePdf(objects)
37+
}
38+
39+
/** Builds a PDF whose pages carry no content stream, so nothing is extractable. */
40+
function buildTextFreePdf(pageCount: number): Buffer {
41+
const pageIds = Array.from({ length: pageCount }, (_, i) => 3 + i)
42+
43+
return assemblePdf([
44+
Buffer.from('<< /Type /Catalog /Pages 2 0 R >>'),
45+
Buffer.from(
46+
`<< /Type /Pages /Kids [${pageIds.map((id) => `${id} 0 R`).join(' ')}] /Count ${pageCount} >>`
47+
),
48+
...pageIds.map(() => Buffer.from('<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>')),
49+
])
50+
}
51+
52+
/** Serializes numbered objects into a PDF with a matching xref table and trailer. */
53+
function assemblePdf(objects: Buffer[]): Buffer {
3654
const chunks: Buffer[] = [Buffer.from('%PDF-1.4\n')]
3755
const offsets: number[] = []
3856
let offset = chunks[0].length
@@ -88,4 +106,11 @@ describe('PdfParser', () => {
88106
expect(result.content).toContain('AAAA')
89107
expect(result.content).not.toContain('truncated')
90108
}, 30_000)
109+
110+
it('reports a multi-page PDF with no extractable text as empty', async () => {
111+
const result = await new PdfParser().parseBuffer(buildTextFreePdf(3))
112+
113+
expect(result.content.trim()).toBe('')
114+
expect(result.content).not.toContain('[...')
115+
}, 30_000)
91116
})

apps/sim/lib/file-parsers/pdf-parser.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,12 @@ async function extractTextWithinBudget(pdf: PdfDocumentProxy): Promise<BoundedEx
125125
const { text, used, completed } = await readPageWithinBudget(page, remainingChars, deadline)
126126

127127
remainingChars -= used
128-
pageTexts.push(text)
128+
129+
// A page the budget cut off before it yielded anything was never really
130+
// read, so it must not count toward `pagesRead` or add a blank separator.
131+
if (completed || text.length > 0) {
132+
pageTexts.push(text)
133+
}
129134
page.cleanup()
130135

131136
if (!completed) {
@@ -181,18 +186,22 @@ export class PdfParser implements FileParser {
181186
logger.warn(PDF_TRUNCATION_WARNING, { totalPages, pagesRead, textLength: text.length })
182187
}
183188

189+
const body = sanitizeTextForUTF8(text)
190+
184191
// Callers only ever read `content`, so without an inline notice a truncated
185-
// document is indistinguishable from a complete one. Empty text yields no
186-
// notice, so a text-free PDF still reports as empty rather than as a lone notice.
192+
// document is indistinguishable from a complete one. Tested after sanitizing
193+
// and against `trim`, because a text-free multi-page PDF collapses to a lone
194+
// separator — appending a notice to that would turn a document callers treat
195+
// as empty into one that looks like it holds content.
187196
const notice =
188-
truncated && text.length > 0
197+
truncated && body.trim().length > 0
189198
? truncationNotice(
190199
`PDF text truncated at parser limits, showing first ${pagesRead} of ${totalPages} pages`
191200
)
192201
: ''
193202

194203
return {
195-
content: sanitizeTextForUTF8(text + notice),
204+
content: body + notice,
196205
metadata: {
197206
pageCount: totalPages,
198207
source: 'unpdf',

0 commit comments

Comments
 (0)