Before getting started with Poppler, let’s first understand the PDF structure and different terminologies being used.
Annotations are PDF objects that enable user-clickable actions as well as contain text or other graphics and media. The following is a snippet of FreeText annotation created on a PDF page in Acrobat Reader. It is in the decompressed form:
87 0 obj % The FreeText Annot dictionary
/N 119 0 R % Indirect reference to the normal state XObject
/DA (0 G 0 g 0 Tc 0 Tw 100 Tz 24.45 TL 0 Ts 0 Tr /Cour 18 Tf)
Let’s grab the important terminologies regarding the font family and appearance:
- Appearance Stream: It is a form Xobject. A form Xobject is a reusable graphics object. It has a content stream which defines certain graphics. In the case of appearance stream, it is a self-content stream that shall be rendered inside the annotation rectangle. In the above snippet, AP string is the appearance stream.
- Default Appearance: The default appearance string (DA) shall be used in formatting the text. It contains any graphics state or text state operators needed to establish the graphics state parameters, such as text size and color, for displaying the field’s variable text.
Only operators that are allowed within text objects shall occur in this string. At a minimum, the string shall include a Tf (text font) operator along with its two operands, font, and size. The specified font value shall match a resource name in the Font entry of the default resource dictionary (the DR entry of the interactive form dictionary). A zero value for size means that the font shall be auto-sized: its size shall be computed as a function of the height of the annotation rectangle.
- Tf operator: In the above snippet, the DA string
/DA (0 G 0 g 0 Tc 0 Tw 100 Tz 24.45 TL 0 Ts 0 Tr /Cour 18 Tf)gives us the information about the font color and the font value and size. Tw, Tz, TL, Ts, Tr are the additional operators to fine-tune the FreeText annotation text.
/TheFont 18 Tf
| | “text font operator”
“font value”, must match a “resource name in the Font entry”
So in the above PDF snippet, the appearance stream (AP) is being provided inside the annotation dictionary.
/N 119 0 R
>> It is pointing to another object
119 0 which is shown below:
119 0 obj % The normal state XObjects stream dictionary
<code data-original-code=”/Resources </Resources << % Optional but strongly recommended; PDF 1.2
/Font << % The font dictionary
/Helv 556 0 R % The font definition! It's Helvetica Type1, see object 556 below. The tag /Helv can now be used by Tf operator in FreeText.
Here you can find the
/Resources key which contains the font dictionary with the /Helv font tag pointing to the object 556 0 below. Let’s look at the object
556 0 obj
/Encoding 1037 0 R
You can see the 4 essential key-value pairs in the above stream dictionary:
- /Type tells that it is a Font.
- /SubType tells about the font type. It’s one of Type0, Type1, MMType1, Type3, TrueType, CIDFontType0, CIDFontType2.
- /BaseFont tells the PostScript name, a platform-independent identifier for a font. Type1 fonts have the PostScript name as FontName field in the .afm file, and as /FontName in the *.pfb file TrueType fonts (*.ttf) fonts have the PostScript name as Id 6 in the “name table”.
- /Encoding tells about the encoding that the font supports. If WinAnsiEncoding, it supports all the Latin characters.
So in a nutshell, /AP takes precedence over /DA and points to the resource stream dictionary which contains the font dictionary telling about the font. In the above example, the annotation content is finally rendered using Helvetica glyphs.
But what if the AP string is missing? In this case, DA tells about the font family. Let’s begin with the interactive form dictionary aka AcroForm to understand how does DA string tells about the font!
AcroForm (Interactive form dictionary): It is a collection of fields for gathering information interactively from the user. AcroForm contains the default resource (DR) key and its value (of type dictionary) needs to include a Font key. The value of Font is the resource name and font to be used as a default font for displaying text in fields.
This is the case with Poppler that creates a FreeText annotation in which the AP string is missing and DA tells about the font family. The following diagram explains in a better way:
In the above example, instead of using base-14/standard font, we have used embedded font. These fonts are of TrueType and are embedded into the PDF document. We will discuss a bit about the font configuration in Poppler but before that, let’s look at the UML diagram of the font object graph prepared by my mentor Tobias Deiminger:
The diagram explains everything that we have discussed so far and is almost accurate.
Now we have different font configurations in Poppler viz. libfontconfig, win32 and generic for Linux, Windows and Android OS respectively. We have to take care of all but as I’m working on Kubuntu, my prime concern is libfontconfig.
Enough about the annotation and font in PDF. Let me tell you how did I explore the domain and the existing code in Poppler?
Poppler’s codebase is quite complex and as I was working on the project Okular, my domain was restricted to Poppler Qt5 frontend and the class AnnotFreeText in Poppler Core. After sending the patch for creating and parsing DA string in poppler-core, the load was shifted towards Annot.cc and after going through the codebase, I realized that poppler produces the in-memory appearance stream and sets the same font program for all the FreeText annotations in the current PDF document. The
createAnnotDrawFont functions are responsible but what we require is to create the DA string with a meaningful font tag and to write the DR string of AcroForm and the font dictionary into the document in the case of base-14 fonts so that the annotation font can be shown correctly by another document viewer.
Poppler needs to show the correct font when the AP exists and there should be a mechanism to call the C API of libfontconfig to get the font file for the non-standard font and to embed it into the document. All of these require a new API plan.
I have created a workaround patch https://bugs.freedesktop.org/attachment.cgi?id=140969 which is the work of my experiment to create different typewriter annotations with the different base-14 fonts on the same page in Okular. Neither it writes the font dictionary and DR string to the document nor it supports the embedded fonts. But it gives the idea and the intuition to carry forward the work towards writing the streams into the document and correctly displaying the fonts. It should also support the C API of libfontconfig in Linux and win32, and generic for the other font configurations to get the stream of the responsible font file.
I think the most difficult part is the different font configurations that we should use in Poppler and embedding the font file. Writing the correct stream to the document is another bigger challenge.