Introduction
XML Worker is an add-on for iText®. It allows developers to convert XML files to PDF documents in a programmer-friendly way. As a proof of concept, we're shipping XML Worker with simple XHTML to PDF functionality, taking into account the styles stored in a CCS2 file. This functionality has been tested on straightforward HTML created with WYSIWYG editors such a TinyMCE and CKEditor.
Simple XHTML/CSS2 to PDF conversion was one of the "most wanted" features that emerged from a user survey, but there's more: with XML Worker, it's possible to parse all kinds of XML, provided you write a specific implementation for that type of XML, or: you can contact our consultancy department to make you an offer.
topDefault configuration for HTML
XML Worker uses HTML TagProcessors in the HtmlPipeline to convert HTML to PDF.
The default configuration uses the following settings:
- XML Worker will look for CSS styles in the head tag (external or internal), and for styles in individual tags (e.g.
style="margin:15px"). If no styles are defined, the default CSS of Firefox 4 is applied. - XML Worker will automatically create bookmarks for header tags
h1toh6. - XML Worker will only add pictures to the document if they are defined using a fully qualified URL.
Let's take a look at a first example.
topExample using the default setup
Parsing HTML using the default setup is as easy as creating a PDF in five steps:
- Create a
Documentobject - Get a
PdfWriterinstance. - Open the
Document - Invoke
XMLWorkerHelper.getInstance().parseXHtml() - Close the
Document
Let's take a look at a code snippet that converts the walden.html file to PDF.
In this snippet we use the XMLWorkerHelper class and its parseXHtml() method to do all the work:
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream("results/walden1.pdf"));
document.open();
XMLWorkerHelper.getInstance().parseXHtml(writer, document,
HTMLParsingDefault.class.getResourceAsStream("/html/walden.html"));
document.close();
see HTMLParsingDefault and the resulting PDF walden1.pdf
The HTML was taken from project Gutenberg. It's a book by H.D. Thoreau: Walden, or Life in the Woods.
When we look at the first page that is generated by iText, we see that something went wrong: the first lines on the HTML result in a line of gibberish. What went wrong and how can we fix it?
topParsing HTML into a list of Element objects
When iText parses an XML file, it interprets the different tags and, whenever possible, iText will create a corresponding Element object.
Suppose you're not interested in creating PDF, but you just want parse the HTML into a list of iText Element objects.
XMLWorkerHelper.getInstance().parseXHtml(new ElementHandler() {
public void add(final Writable w) {
if (w instanceof WritableElement) {
List<Element> elements = ((WritableElement)w).elements();
// write class names of elements to file
}
}
}, HTMLParsingToList.class.getResourceAsStream("/html/walden.html"));
see HTMLParsingToList and the resulting PDF objects.txt
Let's take a look at the first handful of objects.
com.itextpdf.tool.xml.html.head.Title$1 com.itextpdf.text.Chunk com.itextpdf.text.Paragraph com.itextpdf.tool.xml.html.Header$1 com.itextpdf.text.Paragraph
The first object is a Title header. It will result in a bookmark. The first real Element is a Chunk.
This is the chunk of text between the <pre> tags in the HTML file:
<pre> The Project Gutenberg EBook of Walden, and On The Duty Of Civil Disobedience, by Henry David Thoreau This eBook is for the use of anyone anywhere at no cost and with almost no restrictions whatsoever. ... <pre>
This snippet is converted to a Chunk, and a Chunk is an Element that doesn't have a leading of its own, hence the gibberish: all the lines between the <pre> tags are written on top of each other, until the first <p> tag is encountered, resulting in a Paragraph object.
Fixing the leading problem
When no content has been added to a Document, the initial leading is 0.
As soon as a Paragraph is added to the Document, the default leading changes into the leading of that Paragraph.
In our first example, we started by adding a Chunk object, and all of its content was written on the first line because the initial leading is 0.
We can avoid this problem by setting an initial leading. In the next example, we set the initial leading to 12.5 point.
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream("results/xmlworker/walden2.pdf"));
writer.setInitialLeading(12.5f);
document.open();
XMLWorkerHelper.getInstance().parseXHtml(writer, document,
HTMLParsingDefault2.class.getResourceAsStream("/html/walden.html"));
document.close();
see HTMLParsingDefault2 and the resulting PDF walden2.pdf
The content inside the <pre> tags in the HTML file is now legible in the PDF.
Understanding XML Worker
In the previous examples, we've used the XMLWorkerHelper class and its parseXHtml() method to parse the HTML to PDF or to a list of Element objects. Now suppose that this class and method didn't exist. What would we have to do?
First you need an instance of the XMLWorker class. With this instance you can create an XMLParser. Finally use its parse() to parse an HTML file.
Let's take a look at an example, and examine every step.
topParsing HTML step by step
The following snippet shows what happens in step 4 of the PDF creation process in more detail.
HtmlPipelineContext htmlContext = new HtmlPipelineContext();
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
CSSResolver cssResolver =
XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
Pipeline<?> pipeline =
new CssResolverPipeline(cssResolver,
new HtmlPipeline(htmlContext,
new PdfWriterPipeline(document, writer)));
XMLWorker worker = new XMLWorker(pipeline, true);
XMLParser p = new XMLParser(worker);
p.parse(HTMLParsingProcess.class.getResourceAsStream("/html/walden.html"));
see HTMLParsingProcess and the resulting PDF walden3.pdf
Let's do a buttom-up examination of this snippet.
HTML inputAs you can see, we parse the HTML as an InputStream. We could also have used a Reader object to read the HTML file.
The XMLParser class expects an implementation of the XMLParserListener interface. XMLWorker is such an implementation. Another implementation (ParserListenerWriter) was written for debugging purposes.
The XMLWorker constructor expects two parameters: a Pipeline<?> and a boolean indicating whether or not the XML should be treated as HTML. If true, all tags will be converted to lowercase and whitespace used to indent the HTML syntax will be ignored.
Internally, XMLWorker creates Tag objects that are processed using implementations of the TagProcessor interface (for instance com.itextpdf.tool.xml.html.Anchor is the tag processor for the <a>-tag).
In this case, we're parsing XHTML and CSS to PDF; we define the Pipeline<?> as a chain of three Pipeline implementations:
- a
CssResolverPipeline, - an
HtmlPipeline, and - a
PdfWriterPipeline.
You create the first pipeline passing the second one as a parameter; the second pipeline is instantiated passing the third as a parameter; and so on.
Pipeline<?> pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));
The PdfWriterPipeline marks the end of the pipeline: it creates the PDF document.
The style of your HTML document is probably defined using Cascading Style Sheets (CSS). The CSSResolverPipeline is responsible for adding the correct CSS Properties to each Tag that is created by XMLWorker. Without a CssResolverPipeline, the document would be parsed without style.
The CssResolverPipeline constructor needs a CssResolver instance. The getDefaultCssResolver() method in the XMLWorkerHelper class provides a default CssResolver:
CSSResolver cssResolver = XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
The boolean parameter indicates whether or not the default.css (shipped with XML Worker) should be added to the resolver.
Next in line, is the HtmlPipeline. Its constructor expects an HtmlPipelineContext.
HtmlPipelineContext htmlContext = new HtmlPipelineContext(); htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
Using the setTagFactory() method of the HtmlPipelineContext, you can configure how the HtmlPipeline should interpret the tags encountered by the parser. We've created a default implementation of the TagProcessorFactory interface for parsing HTML. It can be obtained using the getHtmlTagProcessorFactory() method in the Tags class.
If you want to parse other types of XML, you'll need to implement your own Pipeline implementations, for instance an SvgPipeline.
This is the end of the pipeline. The PdfWriterPipeline constructor expects the Document and a PdfWriter instance you've created in step 1 and 2 of the PDF creation process.
In some cases, using the default configuration won't be sufficient, and you'll need to configure XML Worker yourself. This is the case if you want to parse HTML with images and links.
topChanging the default configuration
Let's take a look at another HTML file: thoreau.html. If we use the default configuration to create the corresponding PDF, we detect some problems in the resulting PDF thoreau0.pdf:
- We've defined a different font in the
bodytag (style="font-family: Nimbus Roman No9 L,Times New Roman"), but the font in the resulting PDF is Helvetica. - There are three images in the HTML document, but none of them show up in the PDF.
- There are three links in the HTML document, but only the links to another http-site work, the local link is broken.
Let's find out how to fix these problems by looking at the following example:
FontFactory.registerDirectories();
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream("results/xmlworker/thoreau1.pdf"));
document.open();
HtmlPipelineContext htmlContext = new HtmlPipelineContext();
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
htmlContext.setImageProvider(new AbstractImageProvider() {
public String getImageRootPath() {
return "src/main/resources/html/";
}
});
htmlContext.setLinkProvider(new LinkProvider() {
public String getLinkRoot() {
return "http://tutorial.itextpdf.com/src/main/resources/html/";
}
});
CSSResolver cssResolver =
XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
Pipeline<?> pipeline =
new CssResolverPipeline(cssResolver,
new HtmlPipeline(htmlContext,
new PdfWriterPipeline(document, writer)));
XMLWorker worker = new XMLWorker(pipeline, true);
XMLParser p = new XMLParser(worker);
p.parse(HTMLParsingProcess.class.getResourceAsStream("/html/thoreau.html"));
document.close();
See HTMLParsingImagesLinks1.java and the resulting PDF thoreau1.pdf
As you can see, we don't need a special XML Worker configuration to fix the font problem.
topRegistering fonts
In the style attribute of the body tag of thoreau.html, we've told the HTML renderer that we prefer the font Nimbus Roman No9 L (a font you can usually find on Linux distributions).
If that font can't be found, we want the font to be Times New Roman (a font that is usually distributed with Windows).
If that font isn't found, the default font can be used (which is what happened in thoreau0.pdf).
XML Worker uses the FontFactory class to retrieve fonts. Initially, this class is only aware of the standard Type 1 fonts.
If we want iText to find Nimbus Roman No9 L or Times New Roman, we need to register these fonts (see section 11.4.1 of iText in Action Second Edition).
In this example, we just register the "usual suspects":
FontFactory.registerDirectories();
On Windows, this method will find the font files times.ttf, timesbd.ttf, timesbi.ttf, and timesi.ttf. iText will use fonts these to render all the text in the HTML. On Linux, iText will use the Type1 font stored in n021004l.afm/n021004l.pfb and use it whenever a regular font is needed. Unfortunately, it will be more difficult to find the corresponding bold, italic and bold italic font. Choose your font wisely if you want to avoid this problem.
topAdding an ImageProvider
We've used a relative path to our images in thoreau.html:
<img src="img/Henry_David_Thoreau_1861.jpg" />
If the HTML file you're parsing is stored in a directory that is different from the working directory, iText won't be able to create Image objects.
We have to supply an implementation of the ImageProvider interface that tells iText what to do if an img tag is encountered.
This interface has the following methods:
Image retrieve(final String src);String getImageRootPath();void store(String src, Image img);void reset();
You can write your own class implementing these four methods, or you can subclass AbstractImageProvider.
It is preferred to do the latter.
XML Worker will use the store() method of the AbstractImageProvider class to cache all the Image objects that are encountered in a Map.
These objects will be reused when the retrieve() method is called for an image with the same src.
If you don't cache images, your PDF will be bloated. The same image bits and bytes will be written to the PDF more than once.
The reset() method clears the cache; it is used when an ImageProvider is cloned.
Finally, the getImageRootPath() method isn't implemented. You have to implement it yourself, as is done in the following snippet:
htmlContext.setImageProvider(new AbstractImageProvider() {
public String getImageRootPath() {
return "src/main/resources/html/";
}
});
The relative path from our workdir to our thoreau.html file is "src/main/resources/html/".
By using this ImageProvider in the HtmlPipelineContext, relative paths in the src attribute of an img tag will be adapted.
iText will add src/main/resources/html/ to the src attribute of the tag (e.g. img/Henry_David_Thoreau_1861.jpg), resulting in the path src/main/resources/html/img/Henry_David_Thoreau_1861.jpg.
This path is valid relative to the working directory.
Adding a LinkProvider
It makes perfect sense to create a PDF with http links that open an URL in a browser window.
It's more delicate to add a relative link to a PDF document. If the document is downloaded and consulted off line,
the relative link requires that the document that is referred to is present on your system at the correct location.
Looking at thoreau1.pdf, we see
that there's a link to the walden.html file
that is in the same folder as thoreau.html file.
However, the PDF we're creating is written to a different directory. If we want the link to work, we need to change the base URL.
This can be done by implementing LinkProvider, an interface with a single method: getLinkRoot(),
and adding it to the HtmlPipelineContext using the setLinkProvider() method.
htmlContext.setLinkProvider(new LinkProvider() {
public String getLinkRoot() {
return "http://tutorial.itextpdf.com/src/main/resources/html/";
}
});
In this case, the relative link <a href="walden.html">Walden</a> is changed into an absolute link to
http://tutorial.itextpdf.com/src/main/resources/html/walden.html.
Congratulations! You can now parse HTML. Now let's take a look at ways to extend XML Worker so that you can also parse other XML documents.
topExtending the XMLWorker
Depending on the nature of your XML file, you can either write your own Pipeline implementations,
or you can extend the HtmlPipeline by adding your own TagProcessor classes.
Let's start by extending the HtmlPipeline.
How to extend the HtmlPipeline class
We've already configured a HtmlPipeline by changing the HtmlPipelineContext.
We've defined an ImageProvider and a LinkProvider and applied it using the
setImageProvider() and setLinkProvider() method, but there's more.
Each time a new XMLWorker/XmlParser is started with the same HtmlPipeline,
the context is cloned using some defaults. You can change these defaults with the following methods:
- The
charSet()method change the character set - The
setPageSize()method changess the default page size (which is A4) - The
autoBookmark()method enables or disables the automatic creation of bookmarks. The default is: enabled (true). - The
setAcceptUnknown()method should XML Worker accept unknown tags? The default value istrue. - The
setRootTags()method by defaultbodyanddivare set as root tags. This affects the margin calculations.
In previous examples, we've also used the setTagFactory() method.
We can completely change the way HtmlPipeline interprets tags by creating a custom TagProcessorFactory.
XMLWorker creates Tag objects that contains attributes, styles and a hierarchy (one parent, zero or more children).
HtmlPipeline transforms these Tags into com.itextpdf.text.Element objects with the help of TagProcessors.
You can find a series of precanned TagProcessor implementations in the com.itextpdf.tool.xml.html package.
The default TagProcessorFactory can be obtained from the Tags class, using the getHtmlTagProcessorFactory() method.
Not all tags are enabled by default. Some tags are linked to the DummyTagProcessor (a processor that doesn't do anything), other tags result in a TagProcessor with a very specific implementation.
You can extend the HtmlPipeline by adding your own TagProcessor implementations to the TagProcessorFactory with
the addProcessor() method. This will either replace the default functionality of already supported tags,
or add functionality for new tags.
Suppose that you have HTML code in which you've used a custom tag that should trigger a call to a database, for example a <userdata> tag.
XMLWorker will detect this tag and pass it to the HtmlPipeline.
As a result, HtmlPipeline looks for the appropriate TagProcessor in its HtmlPipelineContext.
You can implement the TagProcessor interface or extend the AbstractTagProcessor class
in such a way that it performs a database query, adding its ResultSet to the Document
in the form of a (list of) Element object(s). You should prefer extending AbstractTagProcessor,
as this class comes with precanned page-break-before, page-break-after, and fontsize handling.
Note that your TagProcessor can use CSS if you introduced a CssResolverPipeline before each pipeline that wants to apply styles.
The CssResolverPipeline is responsible for setting the right CSS properties on each tag.
This pipeline requires a CSSResolver that contains your css file.
Let's take a look at the StyleAttrCssResolver that is shipped with XML Worker.
The StyleAttrCSSResolver explained
The major function of a CSSResolver is detecting the right CSS for a given tag.
The StyleAttrCssResolver uses the following criteria:
- All inheritable CSS from the parent tag is added to the current tag.
- All
CssFileare checked for rules applying on the given tag in the order they were added to theCSSResolver. Rules defined on the same property are overridden. - Finally any CSS found in the tag's
styleattribute is added to the tags CSS.
Note that CssFiles can be added to a CSSResolver at any time.
When adding a CssFile to the StyleAttrCssResolver, it's used by
the resolving process immediately. There's no method to remove a CssFile in the
CSSResolver interface, but that doesn't mean you can't add such a method in your
custom implementation.
You can provide inheritance rules for the StyleAttrCssResolver class
with the setCssInheritanceRules() method. By default, the DefaultCssInheritanceRules
are used, but you can always write your own implementation of the CssInheritanceRules interface,
for instance if you don't want certain CSS properties to be inherited from a tag.
All of this is very interesting if your XML is very similar to HTML and if your styles are defined using CSS.
But if your XML is completely different, and if you need to produce content that doesn't fit in iText's Element
objects, you'll need to write your own Pipeline.
Write your own Pipeline
Pipelines were introduced to separate Tag processing from content received from the XMLWorker.
Different pipelines will result in different actions. Creating PDF is only one of the many possible actions.
If you need functionality that goes beyond HTML to PDF rendering, you need to implement the Pipeline
interface.
public interface Pipeline<T extends CustomContext> {
Pipeline init(final WorkerContext context) throws PipelineException;
Pipeline open(WorkerContext context, Tag t, ProcessObject po) throws PipelineException;
Pipeline content(WorkerContext context, Tag t, byte[] content, ProcessObject po) throws PipelineException;
Pipeline close(WorkerContext context, Tag t, ProcessObject po) throws PipelineException;
Pipeline getNext();
}
For your convenience, the AbstractPipeline already implements all this method.
It's always a good idea to write subclass. This allows you to inherit all the default behavior,
so that you only have to implement the open(), content(), and close() methods.
These methods are called when XMLWorker detects that a tag is opened, when it
detects content inside a tag, and when it detects that a tag is closed.
XMLWorker
passes a Tag object (containing the name of the tag, attributes, styles, its parent,
and its children) as well as a ProcessObject to these methods. In the case of the content()
method, you also get a byte array with whatever was found in-between tags.
This lifecycle of such a ProcessObject object starts in the first pipeline that is encountered
and it ends in the final pipeline. It contains a list of Writable objects.
Writable is used as a marker interface, allowing you to pass virtually
anything from one pipeline to another. For instance the PdfWriterPipeline expects
the ProcessObject to contain lists of WritableElements.
These contain lists of Element object that can be added to a document.
In the HTML to PDF implementation, the HtmlPipeline add Element objects
to a WritableElement and puts them in the ProcessObject that is passed on to the
PdfWriterPipeline.
The WorkerContext lives as long as the parsing is going on, the context can be filled with CustomContext implementations used by pipelines. This way pipelines can access a CustomContext of another pipeline. In the existing pipelines this is done in the init method which is called by the XMLParsers various parse methods.
Please consult the source code of the existing pipelines for inspiration when writing your
own Pipeline implementation.
References
Before you start coding, please consult the requirements to find out if you're using the correct version of iText and XML Worker. Not all HTML tags and CSS styles are supported. See the tables "HTML Tag Support" and "CSS Support" for more information. Note that we've also added support for some styles that are specific for XML Worker.
topRequirements
- A basic understanding of iText®
- Java 1.5
- itextpdf-5.1.1
- xmlworker-1.0.1
- HTML with correctly nested tags is required, e.g. <br> is not allowed
- Valid CSS files
HTML Tag Support
| Tag | Comment | Supported Attributes |
|---|---|---|
| html | ignored | |
| head | ignored | |
| title | if a document is available, the title is set with document.addTitle(title). |
|
| meta | parses http-equiv="Content-Type" and the charset | http-equiv, content |
| script | ignored | |
| style | parsed and added to css processing | |
| link | css is parsed and added to global styles | type, href |
| body | direct content in body is added | |
| a | supported | href, name |
| br | supported | |
| div | direct content in div is added | |
| h1 to h6 | supported | |
| p | supported | |
| span | supported | |
| img | supported | src, width, height |
| hr | supported | |
| ul, ol, li | supported | |
| dfn, dl, dt | supported | |
| table | supported, including nested tables, but behavior is not a 100% tested. | width, border, cellspacing, cellpadding |
| tr | supported | |
| td, th | supported | width, rowspan, colspan |
| thead, tfoot, tbody | supported | |
| caption | caption element of a table is supported | |
| sub | supported | |
| sup | supported | |
| small, big | supported | |
| b, strong | supported | |
| u, ins | supported | |
| i, cite, em, var, dfn, address | supported | |
| pre, tt, code, kbd, samp | supported | |
| s, strike, del | supported |
CSS Support
n = not supported, f = fully supported, s = semi supported, ' ' = not applicable
| Property The CSS property (CSS2/3) |
Text CSS properties applicable on text |
tables CSS properties applicable on tables (table, td, tr) |
list CSS properties applicable on list (ul, ol, li) |
image CSS properties applicable on images (img) |
|---|---|---|---|---|
| background | s | s | n | n |
| background-attachment | n | n | n | |
| background-color | n | f | n | |
| background-image | n | n | n | |
| background-position | n | n | n | |
| background-repeat | n | n | n | |
| border | n | f | n | n |
| border-bottom | n | f | n | n |
| border-bottom-color | n | f | n | n |
| border-bottom-style | n | s, only solid supported | n | n |
| border-bottom-width | n | f | n | n |
| border-color | n | f | n | n |
| border-collapse | f | |||
| border-left | n | f | n | n |
| border-left-color | n | f | n | n |
| border-left-style | n | s, only solid supported | n | n |
| border-left-width | n | f | n | n |
| border-right | n | f | n | n |
| border-right-color | n | f | n | n |
| border-right-style | n | s, only solid supported | n | n |
| border-right-width | n | f | n | n |
| border-spacing | f | |||
| border-style | n | s, only solid supported | n | n |
| border-top | n | f | n | n |
| border-top-color | n | f | n | n |
| border-top-style | n | s, only solid supported | n | n |
| border-top-width | n | f | n | n |
| border-width | n | f | n | n |
| bottom | n | n | n | n |
| caption-side | f | |||
| clear | n | n | n | n |
| clip | n | n | n | n |
| color | f | |||
| content | n | n | n | n |
| counter-increment | n | n | n | |
| counter-reset | n | n | n | |
| cursor | n | n | n | |
| direction | n | n | n | |
| display | n | n | n | n |
| empty-cells | f | |||
| float | n | n | n | n |
| font | f | |||
| font-family | f | |||
| font-size | f | |||
| font-style | f | |||
| font-variant | n | |||
| font-weight | f | |||
| height | n | f | n | |
| left | n | n | n | |
| letter-spacing | f | |||
| line-height | f | |||
| list-style | f | |||
| list-style-image | f | |||
| list-style-position | f | |||
| list-style-type | f | |||
| margin | f | f | s (no left and right on li) | n |
| margin-bottom | f | f | f | n |
| margin-left | f | f | s (not on li) | n |
| margin-right | f | f | s (not on li) | n |
| margin-top | f | f | f | |
| max-height | n | n | n | |
| max-width | n | n | n | |
| min-height | n | n | n | |
| min-width | n | n | n | |
| orphans | n | n | n | |
| outline | n | n | n | |
| outline-color | n | n | n | |
| outline-style | n | n | n | |
| outline-width | n | n | n | |
| overflow | n | n | n | |
| padding | f | f | s | n |
| padding-bottom | f | f | f | |
| padding-left | f | f | s (not on li) | |
| padding-right | f | f | s (not on li) | |
| padding-top | f | f | f | |
| page-break-after | s - only value always | s - only value always | s - only value always | s - only value always |
| page-break-before | s - only value always | s - only value always | s - only value always | s - only value always |
| page-break-inside | n | n | n | |
| position | n | n | n | n |
| quotes | n | n | n | |
| right | n | n | n | n |
| table-layout | n, always auto. Table and cell width settings are used though, if not wider than the page width (default is A4 portrait). | |||
| text-align | f | |||
| text-decoration | f | |||
| text-indent | f | |||
| text-shadow | n | |||
| text-transform | n | |||
| top | n | n | n | n |
| unicode-bidi | n | n | n | |
| vertical-align | f | f | n | |
| visibility | n | n | n | n |
| white-space | n | n | n | |
| widows | n | n | n | |
| width | n | f, table and cell width settings are used if not wider than the page width (default is A4 portrait). | n | |
| word-spacing | n | n | n | |
| z-index | n | n | n |
XMLWorker Specific CSS
page-break-beforeandpage-break-afterare supported but only with valuealways. When usingpage-break-beforeandpage-break-afterinside tags that are one element in PDF (like lists or tables) the outcome of adding a new page is unpredictable.- Parsed HTML tables can be spread over several pages. To implement repeating headers and/or footers, set
repeat-header:yesrepeat-footer:yes
tableas css style.