Font Color on PdfPCell increases the size of PDF file - itext

I have created a Font in this way.
Font tableHeaderFont = new Font(Font.HELVETICA, 8, Font.NORMAL, Color.WHITE);
and then using it inside PdfPCell with Phrase.
PdfPCell hcell = new PdfPCell(new Phrase("Column A", tableHeaderFont));
I am showing table header on every page.
Now if I drop the Font Color, it generates the PDF file of size 10 MB but when Color is provided it generates PDF file of size 24 MB. There are around 1400 page in PDF document.
Is there any better way to specify Font Color on PdfPCell level?
In addition to this, when I try to merge these pdf documents using PdfSmartCopy it takes around 4 GB memory usage.
I have tried both iText & OpenPDF.
Update:
iText 5.5:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
When I use the BaseColor class of iText 5+ then I am getting the same size of pdf file with or without Font Color.
Font tableHeaderFont = new Font(Font.HELVETICA, 8, Font.NORMAL, BaseColor.WHITE);
OpenPDF 1.3.20
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.20</version>
</dependency>
But OpenPDF doesn't come with BaseColor class so I have to use Color.WHITE here.
Font tableHeaderFont = new Font(Font.HELVETICA, 8, Font.NORMAL, Color.WHITE);
Do we have any alternative of BaseColor class in OpenPDF or iText4?
Update 2: Sample Use Case to reproduce the issue.
OpenPDF Impl: PDF File Size is around 15 MB
import com.lowagie.text.*;
import com.lowagie.text.Font;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import java.awt.*;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class TestOpenPDF {
public static void main(String[] args) throws FileNotFoundException {
Document document = new Document(PageSize.A4.rotate(), 10, 10, 10, 10);
Document.compress = false;
try {
PdfWriter.getInstance(document,
new FileOutputStream("AddBigTable_OpenPDF.pdf"));
document.open();
String[] bogusData = {"M0065920"};
int NumColumns = 1;
PdfPTable datatable = new PdfPTable(NumColumns);
datatable.getDefaultCell().setPadding(3);
datatable.getDefaultCell().setBorderWidth(2);
datatable.getDefaultCell().setHorizontalAlignment(
Element.ALIGN_CENTER);
Font tableHeaderFont = new Font(Font.HELVETICA, 20, Font.NORMAL, Color.WHITE);
PdfPCell header = new PdfPCell(new Phrase("Clock #", tableHeaderFont));
header.setBackgroundColor(Color.GRAY);
header.setPadding(3);
header.setBorderWidth(2);
header.setHorizontalAlignment(Element.ALIGN_CENTER);
datatable.addCell(header);
datatable.setHeaderRows(1); // this is the end of the table header
datatable.getDefaultCell().setBorderWidth(1);
for (int i = 1; i < 75000; i++) {
if (i % 2 == 1) {
datatable.getDefaultCell().setGrayFill(0.9f);
}
for (int x = 0; x < NumColumns; x++) {
datatable.addCell(bogusData[x]);
}
if (i % 2 == 1) {
datatable.getDefaultCell().setGrayFill(1);
}
}
document.add(datatable);
} catch (Exception de) {
de.printStackTrace();
}
document.close();
}
}
iText Impl: PDF File Size is around 8.5 MB
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class TestIText {
public static void main(String[] args) throws FileNotFoundException {
Document document = new Document(PageSize.A4.rotate(), 10, 10, 10, 10);
Document.compress = false;
try {
PdfWriter.getInstance(document,
new FileOutputStream("AddBigTable_iText.pdf"));
document.open();
String[] bogusData = {"M0065920"};
int NumColumns = 1;
PdfPTable datatable = new PdfPTable(NumColumns);
datatable.getDefaultCell().setPadding(3);
datatable.getDefaultCell().setBorderWidth(2);
datatable.getDefaultCell().setHorizontalAlignment(
Element.ALIGN_CENTER);
Font tableHeaderFont = FontFactory.getFont(FontFactory.HELVETICA, 20, Font.NORMAL, BaseColor.WHITE);
PdfPCell header = new PdfPCell(new Phrase("Clock #", tableHeaderFont));
header.setBackgroundColor(BaseColor.GRAY);
header.setPadding(3);
header.setBorderWidth(2);
header.setHorizontalAlignment(Element.ALIGN_CENTER);
datatable.addCell(header);
datatable.setHeaderRows(1); // this is the end of the table header
datatable.getDefaultCell().setBorderWidth(1);
for (int i = 1; i < 75000; i++) {
if (i % 2 == 1) {
datatable.getDefaultCell().setGrayFill(0.9f);
}
for (int x = 0; x < NumColumns; x++) {
datatable.addCell(bogusData[x]);
}
if (i % 2 == 1) {
datatable.getDefaultCell().setGrayFill(1);
}
}
document.add(datatable);
} catch (Exception de) {
de.printStackTrace();
}
document.close();
}
}

From the snippets which are shared, I can grasp that iText 2 is used. If it is possible, it is better to reconsider such a choice, since iText 2 is more than 10 years old, potentially has a lot of functional and security issues and no longer maintained.
I recommend updating to iText 7. Although I do not have your code and thus could not prove that in your case setting color would not make the size of the PDF much bigger, I have created the following sample to demonstrate that there should no issue:
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outFileName));
Document doc = new Document(pdfDoc);
for (int i = 0; i < 10000; i++) {
doc.add(new Paragraph("Hello World")/*.setFontColor(ColorConstants.RED)*/);
}
doc.close();
The size of the resultant PDF, in which font color is enabled, is 161Kb; the size of the resultant PDF, in which font color is disabled, is 160Kb (that is fully expected, because enabling a color means writing the corresponding instruction to the PDF: there are 10k instructions there, hence 1Kb difference).

The issue has been fixed with latest version of OpenPDF 1.3.25. I was using the older version.

Related

saving modified data in pdf itextsharp

I have created a pdf using itextsharp which contains some editable fields, when the service is called the pdf is created. But the problem what I am facing is, if i am changing anything in the pdf and downloading it then the changes are not saved. Also i want to open the pdf in a new tab through server side.
Code what I am using is:
public static String[] LANGUAGES_gc = { "English", "Math", "Science" };
[HttpGet]
[ODataRoute("GetPdf")]
public void DownloadPDF()
{
HttpContext.Current.Response.ContentType = "application/pdf";
HttpContext.Current.Response.AddHeader("content-disposition", "inline;filename=Example.pdf");
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
Document doc = new Document(iTextSharp.text.PageSize.A4, 10f, 10f, 100f, 0f);
string pdfFilePath = HttpContext.Current.Server.MapPath(".") + "/PDFFiles";
PdfWriter wri = PdfWriter.GetInstance(doc, HttpContext.Current.Response.OutputStream);
doc.Open();
doc.AddAuthor("Test author");
doc.AddCreationDate();
PdfContentByte cb = wri.DirectContent;
Font _bf = new Font(Font.FontFamily.HELVETICA, 6);
PdfFormField _radioGroup = PdfFormField.CreateRadioButton(wri, true);
_radioGroup.FieldName = "language_gc";
Rectangle _rect;
RadioCheckField _radioG;
PdfFormField _radioField1;
PdfFormField field;
for (int i = 0; i < LANGUAGES_gc.Length; i++)
{
_rect = new Rectangle(46, 806 - i * 40, 60, 788 - i * 40);
_radioG = new RadioCheckField(wri, _rect, null, LANGUAGES_gc[i]);
_radioG.BackgroundColor = new GrayColor(0.8f);
_radioG.BorderColor = GrayColor.BLACK;
_radioG.CheckType = RadioCheckField.TYPE_CIRCLE;
_radioField1 = _radioG.RadioField;
_radioGroup.AddKid(_radioField1);
ColumnText.ShowTextAligned(cb, Element.ALIGN_LEFT, new Phrase(LANGUAGES_gc[i], new Font(Font.FontFamily.HELVETICA, 18)), 70, 790 - i * 40, 0);
}
/* Button */
_rect = new Rectangle(300, 806, 370, 788);
PushbuttonField button = new PushbuttonField(wri, _rect, "Buttons");
button.BackgroundColor = new GrayColor(0.75f);
button.BorderColor = GrayColor.GRAYBLACK;
button.BorderWidth = 1;
button.BorderStyle = PdfBorderDictionary.STYLE_BEVELED;
button.TextColor = GrayColor.GRAYBLACK ;
button.FontSize = 12;
button.Text = "Submit";
//button.Layout = PushbuttonField.LAYOUT_ICON_LEFT_LABEL_RIGHT;
button.ScaleIcon = PushbuttonField.SCALE_ICON_ALWAYS;
button.ProportionalIcon = true;
button.IconHorizontalAdjustment = 0;
field = button.Field;
field.Action = PdfAction.JavaScript("this.showButtonState()", wri);
wri.AddAnnotation(field);
//}
//return ms.ToArray();
/*----------------------------------------------------*/
wri.AddAnnotation(_radioGroup);
wri.AddAnnotation(button.Field);
cb = wri.DirectContent;
doc.Close();
HttpContext.Current.Response.Write(doc);
HttpContext.Current.Response.End();
}
Can someone suggest me the solution??
Let's make an analogy here, with a simpler file-type. Suppose you're showing the user a textfile. They open it in their native .txt app (notepad) and make some changes.
Do you expect their changes to be communicated automatically back to your server?
Or that their changes are magically propagated? Of course not.
And even if it would, this seems like functionality notepad would have to provide, rather than something the creator of the file should do.
Now, as it so happens, there is a particular standard of pdf documents that exists for precisely your use-case. Essentially, the document sets up a connection to a server, and synchronizes. However, this standard is rather obscure, not a lot of viewers support it.
To the best of my knowledge, no pdf library (including iText) supports making documents like that.

Mixing text and images causes incorrect vertical positioning

Using iTextSharp version 5.5.8 (same bug existed in 5.5.7), there's an unpleasant bug when you add images to Chapters and Sections - the images and the section headings start out OK but quickly become offset relative to each other.
The PDF generated from the following code starts out correctly, it says "Section 1" and below it is the image. The next section ("Section 2") has a little of the image overlapping the section text, the next section is even worse, etc. I think it's the text that's mal-positioned, not the image.
Is this a known iTextSharp bug?
static Document m_doc = null;
static BaseFont m_helvetica = null;
static Font m_font = null;
static PdfWriter m_writer = null;
static Image m_image = null;
static void Main(string[] args)
{
m_doc = new Document(PageSize.LETTER);
m_helvetica = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
m_font = new Font(m_helvetica, 10.0f);
m_writer = PdfWriter.GetInstance(m_doc, new FileStream("Output.pdf", FileMode.Create));
m_writer.StrictImageSequence = true;
m_doc.Open();
m_doc.Add(new Chunk("Created by iTextSharp version " + new iTextSharp.text.Version().GetVersion, m_font));
Chapter chapter = new Chapter("Chapter 1", 1);
chapter.TriggerNewPage = false;
if (m_image == null)
{
m_image = Image.GetInstance(new Uri("https://pbs.twimg.com/profile_images/2002307628/Captura_de_pantalla_2012-03-17_a_la_s__22.14.48.png"));
m_image.ScaleAbsolute(100, 100);
}
for (int i = 0; i < 5; i++)
{
Section section = chapter.AddSection(18, "Section " + (i + 1));
section.Add(new Chunk(" ", m_font));
section.Add(m_image);
}
m_doc.Add(chapter);
m_doc.Close();
}
From the documentation for the Java version:
A Section is a part of a Document containing other Sections, Paragraphs, List and/or Tables.
Further looking at the Add() method in the C# source we see:
Adds a Paragraph, List, Table or another Section
Basically, instead of a Chunk use a Paragraph. So instead of this
section.Add(new Chunk(" ", m_font));
Use this:
section.Add(new Paragraph(new Chunk(" ", m_font)));
Or even just this:
section.Add(new Paragraph(" ", m_font));

Embedding fonts flattening a document with XFAFlattener

I'm flattening an existing document with XFA_Worker. Is there a way to force fonts to be embedded in the flattened document?
using iText = iTextSharp.text;
using iTextPDF = iTextSharp.text.pdf;
using iTextImage = iTextSharp.text.Image;
using iTextReader = iTextSharp.text.pdf.PdfReader;
using iTextWriter = iTextSharp.text.pdf.PdfWriter;
reader = new iTextReader(tempFileDirectory + "\\" + "orig_" + tmpFileName);
iTextPDF.AcroFields form = reader.AcroFields;
iTextPDF.XfaForm xfa = form.Xfa;
if (xfa.XfaPresent)
{
iText.Document document = new iText.Document();
iTextWriter writer = iTextWriter.GetInstance(document,
new FileStream(tempFileDirectory + "\\" + tmpFileName, FileMode.Create));
XFAFlattener xfaf = new XFAFlattener(document, writer);
xfaf.Flatten(reader);
document.Close();
writer.Close();
}
For a specific project, I have HTML that requires a regular font and a bold font that are embedded. The requirements for the PDF are also very strict: I am not allowed to use any font other than OpenSans and OpenSans bold, and the text has to be stored as UNICODE (no simple fonts allowed).
To meet these requirements, I wrote the following implementation of the FontProvider interface:
class MyFontProvider implements FontProvider {
protected BaseFont regular;
protected BaseFont bold;
public MyFontProvider() throws DocumentException, IOException {
regular = BaseFont.createFont("resources/fonts/OpenSans-Regular.ttf", BaseFont.IDENTITY_H, true);
bold = BaseFont.createFont("resources/fonts/OpenSans-Bold.ttf", BaseFont.IDENTITY_H, true);
}
public boolean isRegistered(String fontname) {
return true;
}
public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color) {
Font font;
switch (style) {
case Font.BOLD:
font = new Font(bold, size);
break;
default:
font = new Font(regular, size);
}
font.setColor(color);
return font;
}
}
I create two BaseFont instances in the constructor. In the getFont() method, I ignore the fontname, encoding and embedded parameters. I will always return embedded OpenSans using Identity-H as "encoding". I do apply the font size, font color, and the style (but only in case the style is set to bold; in all other cases, I use the regular font). You can adapt this FontProvider any way you like, but in my case, these were strict requirements for the project.
Obviously, I need the "long" version of the XML Worker code because I now need to declare MyFontProvider to the HTML pipeline:
// CSS
CSSResolver cssResolver = new StyleAttrCSSResolver();
CssFile cssFile = XMLWorkerHelper.getCSS(new FileInputStream(CSS));
cssResolver.addCss(cssFile);
// HTML
CssAppliers cssAppliers = new CssAppliersImpl(new MyFontProvider());
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);
// XML Worker
XMLWorker worker = new XMLWorker(css, true);
XMLParser p = new XMLParser(worker);
p.parse(new ByteArrayInputStream(invoice));
Every time XML Worker needs a font, it will call MyFontProvider and the getFont() method will return no other font than embedded OpenSans or OpenSans-Bold.
The same principle exists for XFA Flattener where you can also work with CssAppliers and FontProvider implementations. As XFA Worker is a closed source product, which means that you are either using the trial version or that you are a customer of iText Group. In both cases, you should contact iText Group directly if this answer doesn't fix your problem.

How do I change the weight of a simulated bold font using itext

I am using the iText library to generate text. I am loading the Arial Unicode MS font which does not contain a bold style so iText is simulating the bold. This works fine, but the weight of the bold font appears too heavy compared with text generated using the Java API or even using Microsoft Word.
I tried to get the weight from the FontDescriptor, but the value returned is always 0.0
float weight = font.getBaseFont().getFontDescriptor(BaseFont.FONT_WEIGHT, fontSize);
Is there a way I can change the weight of a simulated bold font?
As an addendum to #Chris' answer: You do not need to construct those Object[]s as there is a Chunk convenience method:
BaseFont arialUnicodeMs = BaseFont.createFont("c:\\Windows\\Fonts\\ARIALUNI.TTF", BaseFont.WINANSI, BaseFont.EMBEDDED);
Font arial12 = new Font(arialUnicodeMs, 12);
Paragraph p = new Paragraph();
for (int i = 1; i < 100; i++)
{
Chunk chunk = new Chunk(String.valueOf(i) + " ", arial12);
chunk.setTextRenderMode(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, i/100f, null);
p.add(chunk);
}
document.add(p);
results in
EDIT
Sorry, I just realized after posting this that you're using iText but my answer is for iTextSharp. You should, however, be able to use most of the code below. I've updated the source code link to reference the appropriate Java source.
Bold simulation (faux bold) is done by drawing the text with a stroke. When iText is asked to draw bold text with a non-bold font it defaults to applying a stroke with a width of of the font's size divided by 30. You can see this in the current source code here. The magic part is setting the chunk's text rendering mode to a stroke of your choice:
//.Net code
myChunk.Attributes[Chunk.TEXTRENDERMODE] = new Object[] { PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, MAGIC_NUMBER_HERE, null };
//Java code
myChunk.attributes.put(Chunk.TEXTRENDERMODE, new Object[]{Integer.valueOf(PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE), MAGIC_NUMBER_HERE, null});
Knowing that you can just apply the same logic but using your weight preference. The sample below creates four chunks, the first normal, the second faux-bold, the third ultra-heavy faux-bold and the fourth ultra-lite faux-bold.
//.Net code below but should be fairly easy to convert to Java
//Path to our PDF
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
//Path to our font
var ff = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "ARIALUNI.TTF");
//Normal document setup, nothing special here
using (var fs = new FileStream(testFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (var doc = new Document()) {
using (var writer = PdfWriter.GetInstance(doc, fs)) {
doc.Open();
//Register our font
FontFactory.Register(ff, "Arial Unicode MS");
//Declare a size to use throughout the demo
var size = 20;
//Get a normal and a faux-bold version of the font
var f = FontFactory.GetFont("Arial Unicode MS", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, size, iTextSharp.text.Font.NORMAL);
var fb = FontFactory.GetFont("Arial Unicode MS", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, size, iTextSharp.text.Font.BOLD);
//Create a normal chunk
var cNormal = new Chunk("Hello ", f);
//Create a faux-bold chunk
var cFauxBold = new Chunk("Hello ", fb);
//Create an ultra heavy faux-bold
var cHeavy = new Chunk("Hello ", f);
cHeavy.Attributes = new Dictionary<string, object>();
cHeavy.Attributes[Chunk.TEXTRENDERMODE] = new Object[] { PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, size / 10f, null };
//Create a lite faux-bold
var cLite = new Chunk("Hello ", f);
cLite.Attributes = new Dictionary<string, object>();
cLite.Attributes[Chunk.TEXTRENDERMODE] = new Object[] { PdfContentByte.TEXT_RENDER_MODE_FILL_STROKE, size / 50f, null };
//Add to document
var p = new Paragraph();
p.Add(cNormal);
p.Add(cFauxBold);
p.Add(cHeavy);
p.Add(cLite);
doc.Add(p);
doc.Close();
}
}
}

iTextSharp does not render header/footer when using element generated by XmlWorkerHelper

I am trying to add header/footer to a PDF whose content is otherwise generated by XMLWorkerHelper. Not sure if it's a placement issue but I can't see the header/footer.
This is in an ASP.NET MVC app using iTextSharp and XmlWorker packages ver 5.4.4 from Nuget.
Code to generate PDF is as follows:
private byte[] ParseXmlToPdf(string html)
{
XhtmlToListHelper xhtmlHelper = new XhtmlToListHelper();
Document document = new Document(PageSize.A4, 30, 30, 90, 90);
MemoryStream msOutput = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(document, msOutput);
writer.PageEvent = new TextSharpPageEventHelper();
document.Open();
var htmlContext = new HtmlPipelineContext(null);
htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
var cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
cssResolver.AddCssFile(HttpContext.Server.MapPath("~/Content/themes/mytheme.css"), true);
var pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));
var worker = new XMLWorker(pipeline, true);
var parser = new XMLParser();
parser.AddListener(worker);
using (TextReader sr = new StringReader(html))
{
parser.Parse(sr);
}
//string text = "Some Random Text";
//for (int k = 0; k < 8; ++k)
//{
// text += " " + text;
// Paragraph p = new Paragraph(text);
// p.SpacingBefore = 8f;
// document.Add(p);
//}
worker.Close();
document.Close();
return msOutput.ToArray();
}
Now instead of using these three lines
using (TextReader sr = new StringReader(html))
{
parser.Parse(sr);
}
if I comment them out and uncomment the code to add a random Paragraph of text (commented in above sample), I see the header/footer along with the random text.
What am I doing wrong?
The EventHandler is as follows:
public class TextSharpPageEventHelper : PdfPageEventHelper
{
public Image ImageHeader { get; set; }
public override void OnEndPage(PdfWriter writer, Document document)
{
float cellHeight = document.TopMargin;
Rectangle page = document.PageSize;
PdfPTable head = new PdfPTable(2);
head.TotalWidth = page.Width;
PdfPCell c = new PdfPCell(ImageHeader, true);
c.HorizontalAlignment = Element.ALIGN_RIGHT;
c.FixedHeight = cellHeight;
c.Border = PdfPCell.NO_BORDER;
head.AddCell(c);
c = new PdfPCell(new Phrase(
DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + " GMT",
new Font(Font.FontFamily.COURIER, 8)
));
c.Border = PdfPCell.TOP_BORDER | PdfPCell.RIGHT_BORDER | PdfPCell.BOTTOM_BORDER | PdfPCell.LEFT_BORDER;
c.VerticalAlignment = Element.ALIGN_BOTTOM;
c.FixedHeight = cellHeight;
head.AddCell(c);
head.WriteSelectedRows(
0, -1, // first/last row; -1 flags all write all rows
0, // left offset
// ** bottom** yPos of the table
page.Height - cellHeight + head.TotalHeight,
writer.DirectContent
);
}
}
Feeling angry and stupid for having lost more than two hours of my life to Windows 8.1! Apparently the built in PDF Reader app isn't competent enough to show 'Headers'/'Footers'. Installed Foxit Reader and opened the output and it shows the Headers allright!
I guess I should try with Adobe Reader too!!!
UPDATES:
I tried opening the generated PDF in Adobe Acrobat reader and finally saw some errors. This made me look at the HTML that I was sending to the XMLWorkerHelper more closely. The structure of the HTML had <div>s inside <table>s and that was tripping up the generated PDF. I cleaned up the HTML to ensure <table> inside <table> and thereafter the PDF came out clean in all readers.
Long story short, the code above is fine, but if you are testing for PDF's correctness, have Adobe Acrobat Reader handy at a minimum. Other readers behave unpredictably as in either not reporting errors or incorrectly rendering the PDF.