Instead I get a nice blog post about a knotty little problem caused by someone's abuse of semantic markup for style purposes. Shows that HTML doesn't have a monopoly on that issue. PDFs are already bad enough as a format for extracting data from, we don't need dumb HTML-inspired tricks on top of that.
Nice job finding the issue and explaining it clearly, OP. Thanks for an interesting writeup.
Did they try one method, and what was the feedback?
I’d be cautious about making “non-black means invisible” the general rule. A possible middle ground between raw text extraction and full OCR would be to render the page, map each extracted glyph’s bounding box back to the rendered pixels, and discard glyphs that have almost no contrast with their local background.
That would preserve the exact embedded text for visible characters while using rasterization only to determine visibility.
Has anyone tried this kind of hybrid approach?
It's the ground truth, and it's not like it's more complex than parsing a pdf, at this point, the technology for OCR might even be better than PDF parsing, which is full of accidental instead of natural complexity.
So we have two identically formatted piece of data, where only the text colour offers a clue on what's real and what isn't. Absolutely insane.
Another thing to consider is OCR works well for English but not so well for other languages.
I should have made it clear in the blog post, but the first thing I do when processing a PDF is I identify the document_type. Then I run special rules for that document_type.
So the "non-black means invisible" rule only applies when processing this document_type.
OCR of a digital image generated from those PDF draw commands only operates on the visible portions that are rendered.
Another case to be considered .. FOI requests for documents from "pain in the arse" government departments or law firms can result in "digital documents" that are deliberately generated to be difficult to process (go figure) .. eg: filled with circular and spaghetti draw commands that "print correctly" but are a nightmare horror show to parse.
( "technically compliant" is a legally compliant "fuck you" )

A few days ago I got this email from a customer. Something about all the amounts coming out as negative. I converted their PDF.


The customer was right, all amounts were coming through as negative. Pretty weird. Interestingly the column headers end with minus sign characters. I then opened up the PDF in PDFSnake to see how the text is encoded. Let’s look at how the values “$110.00-” and “$1,527.57” are encoded.

PDFs are made up of a series of commands. Let’s walk through the commands that appear in this segment.
BT: Begin Text. Tells the PDF renderer to enter into text rendering mode and to save the previous graphics state. Only text operators are legal when inside a Begin Text and End Text block.
ET: End Text. Tells the PDF to exit text rendering mode and restore the graphics state to what it was before the BT command.
Tf: Set text font and size.
Td: Move text position.
Tj: Show text.
g: Set grey level for non-stroking operations.
G: Set grey level for stroking operations.
The above PDF commands could be summarise as “Draw the text ($110.00). Set the grey level to 0. Draw the text (-)”

We’ve seen all these commands before and the structure is similar. However there is one key difference. The gray level is set to 0.878 when drawing the minus sign character. The gray level of the minus sign is the same gray level as the background colour. This makes the minus sign invisible. The character is there, but it is not visible and that’s why it comes through when my code processes this PDF.
I don’t know, but I can have a guess.

They used trailing minus signs in this PDF. Trailing minus signs make right alignment a bit difficult, because they didn’t just want basic right alignment, they wanted right alignment for the number elements ignoring the minus sign. One way you can achieve this is by always leaving space for the minus sign. But how wide is the minus sign? It depends on the font size, maybe it’s hard to predict. So instead they put in an invisible minus sign for postive values.
Great the PDF looks good, but extracting data from it is a pain. They could avoid all this if they used leading minus signs, but for some reason they wanted trailing minus signs. Okay so now we know why this problem is occuring, how can we get this PDF to convert properly. I can see two solutions.
Instead of reading the text elements in the PDF I could convert the PDF into a series of PNG files and then run OCR software on the PNG files. This should work although it has some downsides. OCRing isn’t 100% accurate. It works pretty well but there’s always a chance that characters are incorrectly recognised. OCRing is significantly slower.
For this statement type only black coloured text is relevant. Text elements that are set to any other colour can be treated as invisible. This should work, however there’s one problem. When extracting text in Bank Statement Converter, I do not store the colour of the text. So I’ll need to add in code to do this. It shouldn’t be too hard though.