How to Create Valid PDF / A File with iText and XMLWorker (HTML to PDF / A Process)
I am currently developing a method that will take HTML input and convert it to a valid PDF / A file. I know how to programmatically build a valid PDF / A file using iText (link: http://itextsupport.com/download/pdfa3.html ), but I cannot create a valid PDF / File using HTML as input and using XMLWorker to convert this input to PDF file. The problem I have in mind right now has to do with the requirement for built-in PDF / A fonts. I always get this exception:
Exception on stream "main" com.itextpdf.text.pdf.PdfAConformanceException: All fonts must be embedded. This is not: Helvetica
I am trying to force which fonts the HTML will use using a CSS file and I register the fonts I want to use in the output PDF via the XMLWorkerFontProvider class, but it seems that I am doing something wrong because the exception is commented above always thrown away.
What else do I need for XMLWorker to use fonts registered through the XMLWorkerFontProvider class? I want to avoid using the default Helvetica font in every HTML element present in the input.
Below is the code I am using for testing:
style.css (just one line):
* { font: normal 100% Arial, sans-serif !important; }
Main.java:
package com.itextpdf;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.ICC_Profile;
import com.itextpdf.text.pdf.PdfAConformanceLevel;
import com.itextpdf.text.pdf.PdfAWriter;
import com.itextpdf.tool.xml.XMLWorker;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.itextpdf.tool.xml.css.CssFile;
import com.itextpdf.tool.xml.css.StyleAttrCSSResolver;
import com.itextpdf.tool.xml.html.CssAppliers;
import com.itextpdf.tool.xml.html.CssAppliersImpl;
import com.itextpdf.tool.xml.html.Tags;
import com.itextpdf.tool.xml.parser.XMLParser;
import com.itextpdf.tool.xml.pipeline.css.CSSResolver;
import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline;
import com.itextpdf.tool.xml.pipeline.end.PdfWriterPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
public class Main {
/**
* @param args
*/
public static void main(String[] args) {
StringBuffer buf = new StringBuffer();
buf.append("<!DOCTYPE html>");
buf.append("<html>");
buf.append("<head>");
buf.append("<title>Test</title>");
buf.append("</head>");
buf.append("<body>");
buf.append("<p>This is a test</p>");
buf.append("</body>");
buf.append("</html>");
OutputStream file = null;
Document document = null;
PdfAWriter writer = null;
try {
file = new FileOutputStream(new File("C:\\Users\\amartin\\Desktop\\Test.pdf"));
document = new Document();
writer = PdfAWriter.getInstance(document, file, PdfAConformanceLevel.PDF_A_1B);
// Create XMP metadata. It a PDF/A requirement.
writer.createXmpMetadata();
document.open();
// Set output intent. PDF/A requirement.
ICC_Profile icc = ICC_Profile.getInstance(new FileInputStream("./src/main/resources/com/itextpdf/sRGB Color Space Profile.icm"));
writer.setOutputIntents("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
// CSS
CSSResolver cssResolver = new StyleAttrCSSResolver();
CssFile cssFile = XMLWorkerHelper.getCSS(new FileInputStream("./css/style.css"));
cssResolver.addCss(cssFile);
XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider();
fontProvider.register("./fonts/arial.ttf");
fontProvider.register("./fonts/sans-serif.ttf");
fontProvider.addFontSubstitute("lowagie", "garamond");
CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
// Pipelines
PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer);
HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);
XMLWorker worker = new XMLWorker(css, true);
XMLParser p = new XMLParser(worker);
Reader reader = new StringReader(buf.toString());
p.parse(reader);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (document != null && document.isOpen())
document.close();
try {
if (file != null)
file.close();
} catch (IOException e) {}
if (writer != null && !writer.isCloseStream())
writer.close();
}
}
}
change:
Responding to Bruno, I extended the FontFactoryImp class by overriding the getFont () method (the one that has all the arguments). It calls the System.out.println function like this:
System.out.println("=fontname: " + fontname + " =encoding: " + encoding + " =embedded : " + embedded + " =size: " + size + " =style: " + style + " =BaseColor: " + color)
and then calls the parent.getFont () method with the same arguments. The only result I can see is this:
= fontname: null = encoding: Cp1252 = embedded: true = size: -1.0 = style: -1 = BaseColor: null = fontname: null = encoding: Cp1252 = embedded: true = size: -1.0 = style: -1 = BaseColor : null
and the exception inserted before this code.
source to share
Based on the feedback you are submitting to System.out
, it seems that XML Worker is not collecting the font family you want to use.
Please specify the font family as follows:
font-family: "Arial"
Using a font in CSS might work, but it's tricky. I think iText sees normal
and interprets it as Use default font.
source to share
The complete code that makes this example work is as follows:
style.css:
* {
font-family: "Arial";
font-style: normal;
}
Main.java:
package com.itextpdf;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.ICC_Profile;
import com.itextpdf.text.pdf.PdfAConformanceLevel;
import com.itextpdf.text.pdf.PdfAWriter;
import com.itextpdf.tool.xml.XMLWorker;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.itextpdf.tool.xml.css.CssFile;
import com.itextpdf.tool.xml.css.StyleAttrCSSResolver;
import com.itextpdf.tool.xml.html.CssAppliers;
import com.itextpdf.tool.xml.html.CssAppliersImpl;
import com.itextpdf.tool.xml.html.Tags;
import com.itextpdf.tool.xml.parser.XMLParser;
import com.itextpdf.tool.xml.pipeline.css.CSSResolver;
import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline;
import com.itextpdf.tool.xml.pipeline.end.PdfWriterPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
public class Main {
public static void main(String[] args) {
StringBuffer buf = new StringBuffer();
String title = "Test";
// Sample HTML content.
buf.append("<!DOCTYPE html>");
buf.append("<html>");
buf.append("<head>");
buf.append("<title>" + title + "</title>");
buf.append("</head>");
buf.append("<body>");
buf.append("<p>This is a test</p>");
buf.append("</body>");
buf.append("</html>");
OutputStream file = null;
Document document = null;
PdfAWriter writer = null;
try {
file = new FileOutputStream(new File("C:\\Users\\amartin\\Desktop\\Test.pdf"));
document = new Document();
writer = PdfAWriter.getInstance(document, file, PdfAConformanceLevel.PDF_A_1B);
// Avoid discrepances between document title and XMP metadata information.
document.addTitle(title);
// Create XMP metadata. It a PDF/A requirement.
writer.createXmpMetadata();
document.open();
// Set output intent. PDF/A requirement.
ICC_Profile icc = ICC_Profile.getInstance(new FileInputStream("./src/main/resources/com/itextpdf/sRGB Color Space Profile.icm"));
writer.setOutputIntents("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
// CSS stylesheet.
CSSResolver cssResolver = new StyleAttrCSSResolver();
CssFile cssFile = XMLWorkerHelper.getCSS(new FileInputStream("./css/style.css"));
cssResolver.addCss(cssFile);
MyFontProvider fontProvider = new MyFontProvider();
fontProvider.register("./fonts/arial.ttf");
/* DEBUG
System.out.println("Fonts present in " + fontProvider.getClass().getName());
Set<String> registeredFonts = fontProvider.getRegisteredFonts();
for (String font : registeredFonts)
System.out.println(font);
*/
CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
// Pipelines.
PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer);
HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);
XMLWorker worker = new XMLWorker(css, true);
XMLParser p = new XMLParser(worker);
Reader reader = new StringReader(buf.toString());
p.parse(reader);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (document != null && document.isOpen())
document.close();
try {
if (file != null)
file.close();
} catch (IOException e) {}
if (writer != null && !writer.isCloseStream())
writer.close();
}
}
}
MyFontProvider.java:
package com.itextpdf;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactoryImp;
public class MyFontProvider extends FontFactoryImp {
@Override
public Font getFont(String fontname, String encoding, boolean embedded,
float size, int style, BaseColor color) {
System.out.println("=fontname: " + fontname + " =encoding: " + encoding + " =embedded : " + embedded + " =size: " + size + " =style: " + style + " =BaseColor: " + color);
return super.getFont(fontname, encoding, embedded, size, style, color);
}
}
Thanks again, Bruno. I am very happy to receive your help here :)
source to share