More Flexible OutlineHandler Customization in pdfHTML
Introduction
We're pleased to announce a significant improvement in the customization of OutlineHandler within the pdfHTML add-on for iText Core. This update simplifies the customization of outline headings, which allows users to customize the handling of outline headings to their liking with only minimal effort required.
What is OutlineHandler
The OutlineHandler (Java/.NET) in iText’s pdfHTML add-on is a component responsible for generating document outlines, also known as bookmarks or table of contents (TOC), in PDF files. These outlines provide users with a convenient way to navigate through the document's structure, allowing them to quickly locate and access specific sections or chapters.
The OutlineHandler parses the input HTML data and identifies designated headings within the content, such as chapters, sections, or subsections. It then creates corresponding outline entries in the PDF document, linking them to the relevant sections within the content, enabling users to quickly jump to specific sections.
What has changed?
In the previous OutlineHandler behavior, the prioritization of outline entries was purely tag-based. This means that the OutlineHandler identified structural divisions within the content based solely on HTML tags, such as <h1>
, <h2>
, <h3>
, etc. For more details on this approach, see the section of the pdfHTML e-book linked below:
While this approach provided a basic level of outline generation, it lacked flexibility in accommodating custom document structures or markup conventions beyond HTML tags.
In contrast, the new behavior in pdfHTML 5.0.4 introduces the option for out-of-the-box class-based outline handling, as well as fully custom outline handling. This means that users now have the flexibility to define any custom logic within their HTML documents or application code to identify outline headings. By overriding key functions within the OutlineHandler, such as OutlineHandler#addOutlineAndDestToDocument
and OutlineHandler#setDestinationToElement
, users can specify how these custom classes or markup should be interpreted and mapped to outline entries in the resulting PDF document.
Code Samples
The newly introduced functionality is shown below. First is the out-of-the-box outline handling, and then the customized behavior:
Class-Based Outline Handling
public void classBasedOutlineExample() throws IOException {
String inFile = SOURCE_FOLDER + "htmlForClassBasedOutline.html";
String outFile = DESTINATION_FOLDER + "pdfWithClassBasedOutline.pdf";
Map<String, Integer> priorityMappings = new HashMap<>();
priorityMappings.put("heading1", 1);
priorityMappings.put("heading2", 2);
OutlineHandler handler = OutlineHandler.createHandler(new ClassOutlineMarkExtractor())
.putAllMarksPriorityMappings(priorityMappings);
HtmlConverter.convertToPdf(new File(inFile), new File(outFile),
new ConverterProperties().setOutlineHandler(handler));
}
public virtual void ClassBasedOutlineExample() {
String inFile = SOURCE_FOLDER + "htmlForClassBasedOutline.html";
String outFile = DESTINATION_FOLDER + "pdfWithClassBasedOutline.pdf";
IDictionary<String, int?> priorityMappings = new Dictionary<String, int?>();
priorityMappings.Put("heading1", 1);
priorityMappings.Put("heading2", 2);
OutlineHandler handler = OutlineHandler.CreateHandler(new ClassOutlineMarkExtractor()).PutAllMarksPriorityMappings
(priorityMappings);
HtmlConverter.ConvertToPdf(new FileInfo(inFile), new FileInfo(outFile), new ConverterProperties().SetOutlineHandler
(handler));
}
Fully Custom Outline Handling
public void overrideOutlineHandlerExample() throws IOException {
String inFile = SOURCE_FOLDER + "htmlForChangedOutlineHandler.html";
String outFile = DESTINATION_FOLDER + "changedOutlineHandlerDoc.pdf";
OutlineHandler handler = new ChangedOutlineHandler();
HtmlConverter.convertToPdf(new File(inFile), new File(outFile),
new ConverterProperties().setOutlineHandler(handler));
}
public static class ChangedOutlineHandler extends OutlineHandler {
@Override
protected OutlineHandler addOutlineAndDestToDocument(ITagWorker tagWorker, IElementNode element, ProcessorContext context) {
String markName = markExtractor.getMark(element);
if (null != tagWorker && hasMarkPriorityMapping(markName) && context.getPdfDocument() != null
&& "customMark".equals(element.getAttribute("class"))) {
int level = (int) getMarkPriorityMapping(markName);
if (null == currentOutline) {
currentOutline = context.getPdfDocument().getOutlines(false);
}
PdfOutline parent = currentOutline;
while (!levelsInProcess.isEmpty() && level <= levelsInProcess.getFirst()) {
parent = parent.getParent();
levelsInProcess.pop();
}
PdfOutline outline = parent.addOutline(generateOutlineName(element));
String destination = generateUniqueDestinationName(element);
PdfAction action = PdfAction.createGoTo(destination);
outline.addAction(action);
destinationsInProcess.push(new Tuple2<String, PdfDictionary>(destination, action.getPdfObject()));
levelsInProcess.push(level);
currentOutline = outline;
}
return this;
}
public ChangedOutlineHandler(){
markExtractor = new TagOutlineMarkExtractor();
putMarkPriorityMapping(TagConstants.H1, 1);
putMarkPriorityMapping(TagConstants.H2, 2);
putMarkPriorityMapping(TagConstants.H3, 3);
putMarkPriorityMapping(TagConstants.H4, 4);
putMarkPriorityMapping(TagConstants.H5, 5);
putMarkPriorityMapping(TagConstants.H6, 6);
}
}
public virtual void OverrideOutlineHandlerTest() {
String inFile = SOURCE_FOLDER + "htmlForChangedOutlineHandler.html";
String outFile = DESTINATION_FOLDER + "changedOutlineHandlerDoc.pdf";
OutlineHandler handler = new OutlineHandlerTest.ChangedOutlineHandler();
HtmlConverter.ConvertToPdf(new FileInfo(inFile), new FileInfo(outFile), new ConverterProperties().SetOutlineHandler
(handler));
}
public class ChangedOutlineHandler : OutlineHandler {
protected internal override OutlineHandler AddOutlineAndDestToDocument(ITagWorker tagWorker, IElementNode
element, ProcessorContext context) {
String markName = markExtractor.GetMark(element);
if (null != tagWorker && HasMarkPriorityMapping(markName) && context.GetPdfDocument() != null && "customMark"
.Equals(element.GetAttribute("class"))) {
int level = (int)GetMarkPriorityMapping(markName);
if (null == currentOutline) {
currentOutline = context.GetPdfDocument().GetOutlines(false);
}
PdfOutline parent = currentOutline;
while (!levelsInProcess.IsEmpty() && level <= levelsInProcess.JGetFirst()) {
parent = parent.GetParent();
levelsInProcess.JRemoveFirst();
}
PdfOutline outline = parent.AddOutline(GenerateOutlineName(element));
String destination = GenerateUniqueDestinationName(element);
PdfAction action = PdfAction.CreateGoTo(destination);
outline.AddAction(action);
destinationsInProcess.AddFirst(new Tuple2<String, PdfDictionary>(destination, action.GetPdfObject()));
levelsInProcess.AddFirst(level);
currentOutline = outline;
}
return this;
}
public ChangedOutlineHandler() {
markExtractor = new TagOutlineMarkExtractor();
PutMarkPriorityMapping(TagConstants.H1, 1);
PutMarkPriorityMapping(TagConstants.H2, 2);
PutMarkPriorityMapping(TagConstants.H3, 3);
PutMarkPriorityMapping(TagConstants.H4, 4);
PutMarkPriorityMapping(TagConstants.H5, 5);
PutMarkPriorityMapping(TagConstants.H6, 6);
}
}
Resources
htmlForChangedOutlineHandler.html