This post reflects what I have done so far to support Type 1 fonts in my implementation of the PDF Viewer SDK for Android. There are still things I don't understand and a few questions I have, so I hope others can help answer them.
According to
Wikipedia
:
Standard Type 1 Fonts (Standard 14 Fonts)
Fourteen typefaces, known as the standard 14 fonts, have a special significance in PDF documents:
Times (v3) (in regular, italic, bold, and bold italic)
Courier (in regular, oblique, bold and bold oblique)
Helvetica (v3) (in regular, oblique, bold and bold oblique)
Symbol
Zapf Dingbats
These fonts are sometimes called the base fourteen fonts. These fonts, or suitable substitute fonts with the same metrics, must always be available in all PDF readers and so need not be embedded in a PDF. PDF viewers must know about the metrics of these fonts. Other fonts may be substituted if they are not embedded in a PDF.
Basically, many programs that generate PDFs expect that PDF readers should be able to render PDFs that do not include the Type 1 fonts embedded in them. So instead they reference the font without embedding it.
The above quote from Wikipedia makes it sound like the PDF Viewer SDK should automatically support the Type 1 fonts without any extra work on my part:
These fonts, or suitable substitute fonts with the same metrics, must always be available in all PDF readers and so need not be embedded in a PDF.
Unfortunately it does not (yet?).
In my app that uses the PDF Viewer SDK to render PDFs, this unfortunately means if a user opens a PDF that does not have one of the Type 1 fonts embedded in it, that text is displayed incorrectly and the user is frustrated.
So, my main question is, how can I support these Type 1 fonts? Here's what I've done so far:
First,
Times New Roman
and
Courier
are fonts that must be licensed to be distributed. However there are free alternatives to these fonts that are "metrically compatible", for example
Tinos
and
Cousine
, which I am currently using.
Helvetica
must also be licensed, however
Roboto
, which is the default font on Android, is similar to Helvetica, and seems to be a good fallback when Helvetica is not included. Apparently Arial is also similar to Helvetica, and there is a free font metrically compatible with Arial called
Arimo
.
I'm not really sure about Symbol and Zapf Dingbats.
Now, how do we add these fonts to the PDF Viewer SDK?
1. Add the font ttf files to the assets directory in the PDF Viewer SDK project. I created a subfolder called "fonts", so my ttf files are in "assets/fonts/" (e.g. "assets/fonts/Tinos-Regular.ttf").
2. Create a method in Global.java that copies the font file out of the assets to another location (e.g. your app data folder). For example:
private static String copyFontFromAssets(Context context, String fontName) {
// Make sure font directory exists
File fontDir = new File(context.getFilesDir(), "fonts");
fontDir.mkdirs();
// Only copy font if it doesn't already exist
File fontFile = new File(fontDir, fontName);
if (!fontFile.exists()) {
try {
InputStream src;
FileOutputStream dst = new FileOutputStream(fontFile);
src = context.getAssets().open("fonts/" + fontName);
int read;
byte buf[] = new byte[4096];
while ((read = src.read(buf)) > 0) {
dst.write(buf, 0, read);
}
src.close();
dst.close();
} catch (Exception e) {
}
}
return fontFile.getAbsolutePath();
}
3. In between fontfileListStart() and fontfileListEnd(), call copyFontFromAssets() and add the path to the font file list. For example:
fontfileListStart();
// ...system fonts here...
fontfileListAdd(copyFontFromAssets(app, "Tinos-Regular.ttf"));
fontfileListAdd(copyFontFromAssets(app, "Tinos-Bold.ttf"));
fontfileListAdd(copyFontFromAssets(app, "Tinos-Italic.ttf"));
fontfileListAdd(copyFontFromAssets(app, "Tinos-BoldItalic.ttf"));
fontfileListAdd(copyFontFromAssets(app, "Cousine-Regular.ttf"));
fontfileListAdd(copyFontFromAssets(app, "Cousine-Bold.ttf"));
fontfileListAdd(copyFontFromAssets(app, "Cousine-Italic.ttf"));
fontfileListAdd(copyFontFromAssets(app, "Cousine-BoldItalic.ttf"));
fontfileListEnd();
4. After fontfileListEnd() and before setDefaultFont(), call fontfileMapping() to map font names to the fonts you added. For example:
// Add font mapping for Times New Roman. "Times" automatically matches to "Times-Roman", "TimesNewRoman", but not "Times New Roman".
if (!fontfileMapping("Times", "Tinos")) {
Log.d("PDF", "Failed to map Times");
}
if (!fontfileMapping("Times New Roman", "Tinos")) {
Log.d("PDF", "Failed to map Times New Roman");
}
// Add font mapping for Times New Roman Bold
if (!fontfileMapping("Times-Bold", "Tinos-Bold")) {
Log.d("PDF", "Failed to map Times-Bold");
}
if (!fontfileMapping("Times New Roman,Bold", "Tinos-Bold")) {
Log.d("PDF", "Failed to map Times New Roman,Bold");
}
// Add font mapping for Times New Roman Italic
if (!fontfileMapping("Times-Italic", "Tinos-Italic")) {
Log.d("PDF", "Failed to map Times-Italic");
}
if (!fontfileMapping("Times New Roman,Italic", "Tinos-Italic")) {
Log.d("PDF", "Failed to map Times New Roman,Italic");
}
// Add font mapping for Times New Roman BoldItalic
if (!fontfileMapping("Times-BoldItalic", "Tinos-BoldItalic")) {
Log.d("PDF", "Failed to map Times-BoldItalic");
}
if (!fontfileMapping("Times New Roman,BoldItalic", "Tinos-BoldItalic")) {
Log.d("PDF", "Failed to map Times New Roman,BoldItalic");
}
// Add font mapping for Courier
if (!fontfileMapping("Courier", "Cousine")) {
Log.d("PDF", "Failed to map Courier");
}
if (!fontfileMapping("Courier New", "Cousine")) {
Log.d("PDF", "Failed to map Courier New");
}
// Add font mapping for Courier Bold
if (!fontfileMapping("Courier-Bold", "Cousine-Bold")) {
Log.d("PDF", "Failed to map Courier-Bold");
}
if (!fontfileMapping("Courier New,Bold", "Cousine-Bold")) {
Log.d("PDF", "Failed to map Courier New,Bold");
}
// Add font mapping for Courier Italic
if (!fontfileMapping("Courier-Italic", "Cousine-Italic")) {
Log.d("PDF", "Failed to map Courier-Italic");
}
if (!fontfileMapping("Courier New,Italic", "Cousine-Italic")) {
Log.d("PDF", "Failed to map Courier New,Italic");
}
// Add font mapping for Courier BoldItalic
if (!fontfileMapping("Courier-BoldItalic", "Cousine-BoldItalic")) {
Log.d("PDF", "Failed to map Courier-BoldItalic");
}
if (!fontfileMapping("Courier New,BoldItalic", "Cousine-BoldItalic")) {
Log.d("PDF", "Failed to map Courier New,BoldItalic");
}
5. That's it. If you want to print out the list of face names, you can do the following:
int faceCount = getFaceCount();
for (int i = 0; i < faceCount; i++) {
Log.d("PDF", "Face Name: " + getFaceName(i));
}
I found that I get a "null" face name for every call to fontfileMapping().
Questions I still have:
1. Why doesn't PDF Viewer SDK support the Type 1 fonts out of the box? Is it a font licensing limitation, or something else?
2. What
is supported in PDF Viewer SDK out of the box? I see that the following files are included in the PDF Viewer SDK: rdf008, rdf013, cmaps1, cmaps2, umaps1, umaps2. These files are set in Global.java via load_std_font() and setCMapsPath(). What are these? Do these have anything to do with the Type 1 fonts, or some other font support?
3. Is there already support in PDF Viewer SDK for Symbol and Zapf Dingbats fonts? If not, is there a recommendation for how to add support?
4. Is my example above the "correct" way to add support for Times and Courier?
5. Am I missing any font mappings for Times and Courier? I had to use trial and error to determine the font mappings I included in the example above. Is there a list of font names somewhere for referencing fonts in a PDF?
6. What kind of matching does fontfileMapping() do on the first parameter (map_name)? For example, passing in "Times" appears to match both "Times-Roman" and "TimesNewRoman", but not "Times New Roman".
7. Should getFaceName() include a "null" for every call to fontfileMapping()?
Thanks,
Andrew