Some test text!

Loading...
Guides

Semantic Compare in Java

Overview

PDFTron's semantic comparison feature enables the visualization of textual differences between two related PDF documents. The text processing is based on natural reading order, highlighting the differences as colored annotations.

The comparison is always performed between two versions of a document. The older version is called the Before file (document 1), while the new version is the After file (document 2).

A difference is defined as a consecutive block of text that was inserted, deleted or modified. Differences always come in pairs. When some text is deleted from the Before side, a corresponding placeholder is inserted into the After side to indicate the position where the text was deleted from.

Similarly, when some text is inserted into the After side, a corresponding placeholder is generated for the Before side to help identify its location of insertion.

Finally, when content is modified, the difference comes out as a pair of annotations consisting of a deletion on the Before side and an insertion on the After side.

These difference annotations are labeled with a unique identifier, so they can be paired up side-by-side at the application level. We'll learn more about this later.

When entire lines are inserted or deleted, the placeholder at the opposite end is pictured as a horizontal line.

Usage

The HighlightTextDiff method takes two PDF documents as input, one being the Before (1) and the other one being the After (2) document. It compares them to find any differences, then overlays the highlight annotations on top of the input documents, which can in turn be saved to files.

// Start with a PDFDoc (open source documents to compare)
using (PDFDoc doc1 = new PDFDoc("compare_before.pdf"))
using (PDFDoc doc2 = new PDFDoc("compare_after.pdf"))
{
    // Create an options object
    TextDiffOptions options = new TextDiffOptions();

    // Compare and highlight text differences in doc1 and doc2
    PDFDoc.HighlightTextDiff(doc1, doc2, options);

    // Save highlighted PDFs
    doc1.Save("diff_before.pdf", SDFDoc.SaveOptions.e_incremental);
    doc2.Save("diff_after.pdf", SDFDoc.SaveOptions.e_incremental);
}
// Start with a PDFDoc (open source documents to compare)
PDFDoc doc1("compare_before.pdf");
PDFDoc doc2("compare_after.pdf");

// Create an options object
TextDiffOptions options;

// Compare and highlight text differences in doc1 and doc2
PDFDoc::HighlightTextDiff(doc1, doc2, &options);

// Save highlighted PDFs
doc1.Save("diff_before.pdf", pdftron::SDF::SDFDoc::e_incremental);
doc2.Save("diff_after.pdf", pdftron::SDF::SDFDoc::e_incremental);
// Start with a PDFDoc (open source documents to compare)
PDFDoc doc1 = new PDFDoc("compare_before.pdf");
PDFDoc doc2 = new PDFDoc("compare_after.pdf");

// Create an options object
TextDiffOptions options = new TextDiffOptions();

// Compare and highlight text differences in doc1 and doc2
PDFDoc.highlightTextDiff(doc1, doc2, options);

// Save highlighted PDFs
doc1.save("diff_before.pdf", SDFDoc.SaveMode.INCREMENTAL, null);
doc2.save("diff_after.pdf", SDFDoc.SaveMode.INCREMENTAL, null);

// Dispose PDFDoc objects
doc1.close();
doc2.close();
async function main() {
    // Start with a PDFDoc (open source documents to compare)
    const doc1 = await PDFNet.PDFDoc.createFromURL("compare_before.pdf");
    const doc2 = await PDFNet.PDFDoc.createFromURL("compare_after.pdf");

    // Create an options object
    const options = await PDFNet.PDFDoc.createTextDiffOptions();

    // Compare and highlight text differences in doc1 and doc2
    await doc1.highlightTextDiff(doc2, options);
}
PDFNet.runWithCleanup(main);
' Start with a PDFDoc (open source documents to compare)
Using doc1 As New PDFDoc("compare_before.pdf"),
    doc2 As New PDFDoc("compare_after.pdf")

    ' Create an options object
    Dim options As New TextDiffOptions

    ' Compare and highlight text differences in doc1 and doc2
    PDFDoc.HighlightTextDiff(doc1, doc2, options)

    ' Save highlighted PDFs
    doc1.Save("diff_before.pdf", pdftron.SDF.SDFDoc.SaveOptions.e_incremental)
    doc2.Save("diff_after.pdf", pdftron.SDF.SDFDoc.SaveOptions.e_incremental)
End Using

Note that HighlightTextDiff is a static method within PDFDoc. The method returns the number of differences found, where each contiguous block of change is considered a single difference. If the two documents are identical, the function returns 0 and no annotations are added.

When the input PDFs already contain annotations or widgets, they are first flattened. When the function finishes, all annotations represent actual differences of text.

Facing Pages

It is easy to see that the more words and paragraphs you keep inserting into the document, the longer the After version will become compared to the Before PDF. You can reach a point where page N in Before is no longer matching up with page N in After. If you were to display Before and After next to each other, you could start displaying unrelated content, and that can be quite confusing.

Yet in certain situations, you can assume that pages generally line up between Before and After, especially with short documents with few changes between them.

We actually offer two separate APIs, AppendTextDiff and HighlightTextDiff. Depending on the situation, one might be preferable to the other.

Consider that you have two versions of an input document, Before (yellow paper) and After (cyan paper):

The AppendTextDiff function generates a single output document where the Before and After pages are merged in an alternating order (page 1 of each, followed by page 2 of each, and so on). In this case, you are only saving a single output file, and you can use a single WebViewer in Double Page mode (also known as Two Page View in Acrobat), so that the same page numbers from the two versions are next to each other.

This is most suitable when you know that Before and After have approximately the same number of pages and the differences are on the small side. When one document is longer than the other, blank filler pages are automatically inserted, so that the last few pages will have a blank pair.

The advantage is that it's easy to use a single WebViewer and switch it into Double Page view, and a single output file contains both versions in a compact format. However, the Before and After sides can't be scrolled independently, and the left and right pages could get out of sync very soon.

The HighlightTextDiff function treats the two inputs independently, and inserts the highlights directly into the Before and After documents. In this case, you are saving two output files that require two WebViewer controls side-by-side. This way the two documents can scroll independently, so that insertions and deletions line up perfectly, even when the page numbers are way out of sync.

We've already seen sample code for HighlightTextDiff, here are some samples for AppendTextDiff:

// Start with a PDFDoc (open source documents to compare)
using (PDFDoc output = new PDFDoc())
using (PDFDoc doc1 = new PDFDoc("compare_before.pdf"))
using (PDFDoc doc2 = new PDFDoc("compare_after.pdf"))
{
    // Create an options object
    TextDiffOptions options = new TextDiffOptions();

    // Compare and highlight text differences in doc1 and doc2
    output.AppendTextDiff(doc1, doc2, options);

    // Save highlighted PDF
    output.Save("diff.pdf", SDFDoc.SaveOptions.e_incremental);
}
// Start with a PDFDoc (open source documents to compare)
PDFDoc output;
PDFDoc doc1("compare_before.pdf");
PDFDoc doc2("compare_after.pdf");

// Create an options object
TextDiffOptions options;

// Compare and highlight text differences in doc1 and doc2
output.AppendTextDiff(doc1, doc2, &options);

// Save highlighted PDF
output.Save("diff.pdf", pdftron::SDF::SDFDoc::e_incremental);
// Start with a PDFDoc (open source documents to compare)
PDFDoc output = new PDFDoc();
PDFDoc doc1 = new PDFDoc("compare_before.pdf");
PDFDoc doc2 = new PDFDoc("compare_after.pdf");

// Create an options object
TextDiffOptions options = new TextDiffOptions();

// Compare and highlight text differences in doc1 and doc2
output.appendTextDiff(doc1, doc2, options);

// Save highlighted PDF
output.save("diff.pdf", SDFDoc.SaveMode.INCREMENTAL, null);

// Dispose PDFDoc objects
doc1.close();
doc2.close();
output.close();
async function main() {
    // Start with a PDFDoc (open source documents to compare)
    const output = await PDFNet.PDFDoc.create();
    const doc1 = await PDFNet.PDFDoc.createFromURL("compare_before.pdf");
    const doc2 = await PDFNet.PDFDoc.createFromURL("compare_after.pdf");

    // Create an options object
    const options = await PDFNet.PDFDoc.createTextDiffOptions();

    // Compare and highlight text differences in doc1 and doc2
    await output.appendTextDiff(doc1, doc2, options);
}
PDFNet.runWithCleanup(main);
' Start with a PDFDoc (open source documents to compare)
Using doc1 As New PDFDoc("compare_before.pdf"),
    doc2 As New PDFDoc("compare_after.pdf"),
    output As New PDFDoc(),

    ' Create an options object
    Dim options As New TextDiffOptions

    ' Compare and highlight text differences in doc1 and doc2
    output.AppendTextDiff(doc1, doc2, options)

    ' Save highlighted PDF
    output.Save("diff.pdf", pdftron.SDF.SDFDoc.SaveOptions.e_incremental)
End Using

Highlight Colors

Designers will be happy to learn that the Before and After annotation colors are customizable. Both the RGB value and the opacity can be adjusted. An opacity of 1.0 (100% opaque) can be too dark in combination with certain colors, in which case 0.5 (50% semi-transparent) may work better. An opacity value of 0.0 (full transparency) is completely invisible, so it makes sense to stay above values of 0.15.

The colors can be configured via the TextDiffOptions object. SetColorA and SetOpacityA control the Before document's annotation color. SetColorB and SetOpacityB adjust the After document's annotation color. Finally, the options object is passed to HighlightTextDiff as the third argument.

// Create an options object
TextDiffOptions options = new TextDiffOptions();

// Before color is 100% red, 25% opacity
options.SetColorA(new ColorPt(1.0, 0.0, 0.0));
options.SetOpacityA(0.25);

// After color is 100% blue, 25% opacity
options.SetColorB(new ColorPt(0.0, 0.0, 1.0));
options.SetOpacityB(0.25);

// Compare and highlight text differences
PDFDoc.HighlightTextDiff(doc1, doc2, options);
// Create an options object
TextDiffOptions options;

// Before color is 100% red, 25% opacity
options.SetColorA(ColorPt(1.0, 0.0, 0.0));
options.SetOpacityA(0.25);

// After color is 100% blue, 25% opacity
options.SetColorB(ColorPt(0.0, 0.0, 1.0));
options.SetOpacityB(0.25);

// Compare and highlight text differences
PDFDoc::HighlightTextDiff(doc1, doc2, &options);
// Create an options object
TextDiffOptions options = new TextDiffOptions();

// Before color is 100% red, 25% opacity
options.setColorA(new ColorPt(1.0, 0.0, 0.0));
options.setOpacityA(0.25);

// After color is 100% blue, 25% opacity
options.setColorB(new ColorPt(0.0, 0.0, 1.0));
options.setOpacityB(0.25);

// Compare and highlight text differences
PDFDoc.highlightTextDiff(doc1, doc2, options);
async function main() {
    // Create an options object
    const options = await PDFNet.PDFDoc.createTextDiffOptions();

    // Before color is 100% red, 25% opacity
    options.setColorA({R: 1, G: 0, B: 0});
    options.setOpacityA(0.25);

    // After color is 100% blue, 25% opacity
    options.setColorB({R: 0, G: 0, B: 1});
    options.setOpacityB(0.25);

    // Compare and highlight text differences
    await doc1.highlightTextDiff(doc2, options);
}
PDFNet.runWithCleanup(main);
' Create an options object
Dim options As New TextDiffOptions

' Before color is 100% red, 25% opacity
options.SetColorA(New ColorPt(1.0, 0.0, 0.0))
options.SetOpacityA(0.25)

' After color is 100% blue, 25% opacity
options.SetColorB(New ColorPt(0.0, 0.0, 1.0))
options.SetOpacityB(0.25)

' Compare and highlight text differences
PDFDoc.HighlightTextDiff(doc1, doc2, options)

Exclusion Zones

Sometimes the need arises to exclude certain areas from text differencing. Most typically, headers and footers can disrupt the flow of the logical content, which often shows up as fake differences. Another example may be an advertisement that should not be a part of the actual content, either.

For those reasons the semantic comparison API offers a way of setting up exclusion zones where text is not considered for comparison, so any differences will be ignored.

// Create an options object
TextDiffOptions options = new TextDiffOptions();

// Exclude footer area from page 1
RectCollection exclusion = new RectCollection();
exclusion.AddRect(new Rect(0, 0, 612, 72));
options.AddIgnoreZonesForPage(exclusion, 1);

// Compare and highlight text differences
PDFDoc.HighlightTextDiff(doc1, doc2, options);
}
// Create an options object
TextDiffOptions options;

// Exclude footer area from page 1
RectCollection exclusion;
exclusion.AddRect(Rect(0, 0, 612, 72));
options.AddIgnoreZonesForPage(exclusion, 1);

// Compare and highlight text differences
PDFDoc::HighlightTextDiff(doc1, doc2, &options);
// Create an options object
TextDiffOptions options = new TextDiffOptions();

// Exclude footer area from page 1
RectCollection exclusion = new RectCollection();
exclusion.addRect(new Rect(0, 0, 612, 72));
options.addIgnoreZonesForPage(exclusion, 1);

// Compare and highlight text differences
PDFDoc.highlightTextDiff(doc1, doc2, options);
async function main() {
    // Create an options object
    const options = await PDFNet.PDFDoc.createTextDiffOptions();

    // Exclude footer area from page 1
    const exclusion = [{ x1: 0, y1: 0, x2: 612, y2: 72 }];
    options.addIgnoreZonesForPage(exclusion, 1);

    // Compare and highlight text differences
    await doc1.highlightTextDiff(doc2, options);
}
PDFNet.runWithCleanup(main);
' Create an options object
Dim options As New TextDiffOptions

' Exclude footer area from page 1
Dim exclusion As New RectCollection()
exclusion.AddRect(New Rect(0, 0, 612, 72))
options.AddIgnoreZonesForPage(exclusion, 1)

' Compare and highlight text differences
PDFDoc.HighlightTextDiff(doc1, doc2, options)

Note that rectangles use PDF coordinates (measured in points, 1 point = 1/72 inch; origin is the page's bottom-left corner).

Annotation Metadata

We mentioned earlier that differences always come in pairs, and each carries a unique numeric identifier starting at the number 1 by increments of 1. Highlight annotations sharing the same identifier in both documents correspond to each other.

In addition, each annotation also carries information about the type of difference it represents, which can be either insertion, deletion or edit.

Usually each difference is represented by a single annotation object, which may consist of one or more rectangles. However, in certain situations an insertion or deletion may wrap across page boundaries. In those cases a single difference can consist of more than one highlight annotation, one per page, all instances sharing the same identifier and difference type.

The identifier and type information are stored as metadata within the annotation object under two custom keys:

  • TextDiffID: a unique number shared between the two PDF documents.
  • TextDiffType: may be either insert, delete or edit.

The easiest way to retrieve this information is via the Annot.GetCustomData method:

// Get page 1
Page page1 = doc1.GetPage(1);
// Get first annotation
Annot annot1 = page1.GetAnnot(0);
// Get custom data TextDiffID
string id = annot1.GetCustomData("TextDiffID");
// Get custom data TextDiffType
string type = annot1.GetCustomData("TextDiffType");
// Get page 1
Page page1 = doc1.GetPage(1);
// Get first annotation
Annot annot1 = page1.GetAnnot(0);
// Get custom data TextDiffID
UString id = annot1.GetCustomData("TextDiffID");
// Get custom data TextDiffType
UString type = annot1.GetCustomData("TextDiffType");
// Get page 1
Page page1 = doc1.getPage(1);
// Get first annotation
Annot annot1 = page1.getAnnot(0);
// Get custom data TextDiffID
String id = annot1.getCustomData("TextDiffID");
// Get custom data TextDiffType
String type = annot1.getCustomData("TextDiffType");
async function main() {
    // Get page 1
    const page1 = await doc1.getPage(1);
    // Get first annotation
    const annot1 = await page1.getAnnot(0);
    // Get custom data TextDiffID
    const id = await annot1.getCustomData("TextDiffID");
    // Get custom data TextDiffType
    const type = await annot1.getCustomData("TextDiffType");
}
PDFNet.runWithCleanup(main);
' Get page 1
Dim page1 As Page = doc1.GetPage(1)
' Get first annotation
Dim annot1 As Annot = page1.GetAnnot(0)
' Get custom data TextDiffID
Dim id As String = annot1.GetCustomData("TextDiffID")
' Get custom data TextDiffType
Dim type As String = annot1.GetCustomData("TextDiffType")

Note that the identifier comes out as a string, but it can be interpreted as a number.

Get the answers you need: Support

Upcoming Webinar: SDK Features Preview and Live Run-Through | July 14, 2022 at 11 am PT

PDFTron SDK

The Platform

NEW

© 2022 PDFTron Systems Inc. All rights reserved.

Privacy

Terms of Use