Hi there,
Been searching for a while to find a way to iterate through a PDF's objects, determine if the type of the object is a text and manipulate the font color in certain cases.
The best I've got until now is iterating the keys of a PdfDictionary returned by reader.getPageN().
I see some PRIndirectReference instances which I think are graphics and text, but I'm not sure how to go from here.
Is this at all possible?
iterate through a PDF's objects, determine if the type of the object is a text and manipulate the font color in certain cases.
First of all, what you are trying to achieve in general is not easy:
In the PDF you don't have something like a "text object" with some "text content" and "text properties" like font and color.
Instead you have a stream of instructions, among them font selection instructions (referencing a font object in the resources associated to the stream by name and a font size), color selection instructions (either for stroking or for filling outlines), text drawing instructions (with string arguments encoded according to the properties of the current font), and many other instructions.
The instructions for drawing text need not be near to the instructions selecting font or color, the font may be set at the start of the stream, the fill color somewhere in the middle while the text drawing instruction is at the end.
To get a first impression, you can use the com.lowagie.text.pdf.parser.PdfContentReaderTool. It expects up to three parameters, <pdf file> [<output file>|stdout] [<page num>], and displays the page content streams of one or all pages of the pdf file in question.
E.g. applied to the PDF HelloWorldMeta.pdf in the OpenPDF test resources you get:
==============Page 1====================
- - - - - Dictionary - - - - - -
(/Contents=Stream, /Type=/Page, /Resources=Dictionary, /Parent=Dictionary of type: /Pages, /MediaBox=[0, 0, 595, 842])
Subdictionary /Resources = (/ProcSet=[/PDF, /Text, /ImageB, /ImageC, /ImageI], /Font=Dictionary)
Subdictionary /Font = (/F1=Dictionary of type: /Font)
Subdictionary /F1 = (/Subtype=/Type1, /Type=/Font, /BaseFont=/Helvetica, /Encoding=/WinAnsiEncoding)
Subdictionary /Parent = (/Kids=[4 0 R], /Type=/Pages, /Count=1, /ITXT=1.2.12.SNAPSHOT)
- - - - - Content Stream - - - - - -
q
BT
36 806 Td
0 -18 Td
/F1 12 Tf
(Hello World)Tj
0 0 Td
ET
Q
- - - - - Text Extraction - - - - - -
Hello World
Here the font used is F1 at size 12, the color used is black by default, and the string drawn is "Hello World". This example looks deceptively easy, by the way...
If you want to dive into understanding and manipulating such streams, you can start by using the PdfReader method getPageContent to retrieve the content stream of a page and setPageContent to replace it. For processing you can use the classes PdfContentParser and PRTokeniser to parse the stream into separate instructions:
byte[] contentBytes = reader.getPageContent(pageNr);
PdfContentParser pdfContentParser = new PdfContentParser(new PRTokeniser(contentBytes));
List<PdfObject> operands = new ArrayList<>();
while (!pdfContentParser.parse(operands).isEmpty()) {
PdfLiteral operator = (PdfLiteral) operands.get(operands.size() - 1);
...
HANDLE THE INSTRUCTION WITH ARGUMENTS AND OPERATOR operands
...
}
(See e.g. com.lowagie.text.pdf.parser.PdfTextExtractor.processContent(...))
By the way, the com.lowagie.text.pdf.parser package contains code for text extraction, the classes therein analyze the instruction streams to determine their textual content. This code may serve as an inspiration for your project...
To understand the instructions, you should read the PDF specification ISO 32000. Adobe made a copy of part 1 (ISO 32000-1) publicly available for free at https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
You'll most likely want to study sections 7.8 (Content Streams and Resources), 8 (Graphics), and 9 (Text).
Concerning your question
Is this at all possible?
It is possible, but you have to study the specification first and then you have quite a bit of implementation ahead of you.
Thanks so much for this very detailed explanation! The instructions make perfect sense, but I never knew PDF's were structured this way. I will jump into the specs and will post the solution here if it works out. Thanks again!
Most helpful comment
First of all, what you are trying to achieve in general is not easy:
In the PDF you don't have something like a "text object" with some "text content" and "text properties" like font and color.
Instead you have a stream of instructions, among them font selection instructions (referencing a font object in the resources associated to the stream by name and a font size), color selection instructions (either for stroking or for filling outlines), text drawing instructions (with string arguments encoded according to the properties of the current font), and many other instructions.
The instructions for drawing text need not be near to the instructions selecting font or color, the font may be set at the start of the stream, the fill color somewhere in the middle while the text drawing instruction is at the end.
To get a first impression, you can use the
com.lowagie.text.pdf.parser.PdfContentReaderTool. It expects up to three parameters,<pdf file> [<output file>|stdout] [<page num>], and displays the page content streams of one or all pages of the pdf file in question.E.g. applied to the PDF HelloWorldMeta.pdf in the OpenPDF test resources you get:
Here the font used is F1 at size 12, the color used is black by default, and the string drawn is "Hello World". This example looks deceptively easy, by the way...
If you want to dive into understanding and manipulating such streams, you can start by using the
PdfReadermethodgetPageContentto retrieve the content stream of a page andsetPageContentto replace it. For processing you can use the classesPdfContentParserandPRTokeniserto parse the stream into separate instructions:(See e.g.
com.lowagie.text.pdf.parser.PdfTextExtractor.processContent(...))By the way, the
com.lowagie.text.pdf.parserpackage contains code for text extraction, the classes therein analyze the instruction streams to determine their textual content. This code may serve as an inspiration for your project...To understand the instructions, you should read the PDF specification ISO 32000. Adobe made a copy of part 1 (ISO 32000-1) publicly available for free at https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
You'll most likely want to study sections 7.8 (Content Streams and Resources), 8 (Graphics), and 9 (Text).
Concerning your question
It is possible, but you have to study the specification first and then you have quite a bit of implementation ahead of you.