Skip to main content
Skip table of contents

Incremental save rollback using IPDFInput

📌 Overview

When a PDF is saved incrementally, changes are appended to the end of the file, and pointers to the original content are updated to reference the new content. An incremental save is usually faster than rewriting the entire file, and the original content remains in the PDF. The only downside is that each incremental save will grow the size of the PDF.

ℹ️ Key Concepts

  • Mako’s IPDFOutput class has long supported incremental save with setEnableIncrementalOutput(), for example:

C++

C#

CPP
IJawsMakoPtr jawsMako = IJawsMako::create();
...
const IPDFOutputPtr pdfOutput = IPDFOutput::create(jawsMako);
pdfOutput->setEnableIncrementalOutput(true);
C#
using var jawsMako = IJawsMako.create();
...
var pdfOutput = IPDFOutput.create(jawsMako);
pdfOutput.setEnableIncrementalOutput(true);
  • Mako 7.3.0 introduces three new IPDFInput APIs:

API

Purpose

IPDFInput::getNumIncrementalSaves()

Returns the number of incremental saves found in a given PDF input stream.

IPDFInput::openIncremental()

Opens an incremental PDF stream, returning the IDocumentAssembly representing the contents.

IPDFInput::getIncrementalSaves()

Returns a list of IRAInputStreamobjects representing each incremental save found in a given PDF input stream.

💪 Usage

  • Each API function has variants that will take an IInputStream, U8String or String

  • Incremental saves are added in order of the last save. For example, to obtain the PDF as it was before the last save, use:

    • openIncremental(filename.pdf, 0)

    • or use the first element in the array returned by getIncrementalSaves()

  • The new APIs will work with incrementally-saved PDFs from any other producer, not just Mako

  • There is no metadata associated with an incremental save

    • If there is a requirement to store information about an incremental save, for example the date and time, it must be added as custom metadata

⌨️ Complete Example

This sample generates PDFs, each with an increasing number of incremental saves, and also a single PDF with multiple streams each representing an incremental save. It then shows how to interrogate the available version of the PDF, allowing the choice of version to be saved as a separate PDF. In addition, it shows how to add custom metadata that can be used to further identify a specific version. Finally, the ILayout class is used to generate the PDF content. A ZIP file is attached with the test files referenced by this code. The full code is available at IncrementalSave.cpp.

Method 1: Write multiple files to disk

CPP
// Create a 10-page document, incrementally saving after each page is added
for (uint32 pageIndex = 0; pageIndex < 20; pageIndex++)
{
    document->appendPage(getPageContent(mako, pageIndex));

    // Add a date stamp
    addMetadata(mako, document);

    // Write the PDF
    U8String outputFile = std::format(R"(TestPDF_{:03}.pdf)", pageIndex + 1).c_str();
    pdfOutput->writeAssembly(assembly, outputFile);
    
    // Reopen: output becomes input
    assembly = pdfInput->open(outputFile);
    document = assembly->getDocument();
}

Method 2: Use a shared stream

CPP
// Create a 10-page document, incrementally saving after each page is added
IRAInputStreamPtr reader;
IRAOutputStreamPtr writer;
for (uint32 pageIndex = 0; pageIndex < 20; pageIndex++)
{
    // ReaderWriter we'll use to test this.
    mako->getTempStore()->createTemporaryReaderWriterPair(reader, writer);

    // Add content
    document->appendPage(getPageContent(mako, pageIndex));

    // Add a date stamp
    addMetadata(mako, document);

    // Write the PDF
    pdfOutput->writeAssembly(assembly, writer);

    // Reopen: output becomes input
    assembly = pdfInput->open(reader);
    document = assembly->getDocument();
}

// Now write the final PDF to disk
IOutputStream::copy(reader, IOutputStream::createToFile(mako, "TestPDF.pdf"));

Now look at the created doc

CPP
const auto inputFilePath = "TestPDF.pdf";
const auto numSaves = pdfInput->getNumIncrementalSaves(inputFilePath);

// Get all of them
const CIRAInputStreamVect incrementalSaves = pdfInput->getIncrementalSaves(inputFilePath);

// Display their metadata, if present
for (uint32 i = 0; i < numSaves; i++)
{
    const auto isDocument = pdfInput->open(incrementalSaves[i])->getDocument();
    std::cout << "Increment " << i + 1 << " - Date stamp: " << getMetadata(isDocument);
}

if (numSaves > 1)
{
    // Which incremental save?
    std::cout << "\nChoose one to save as a separate PDF: ";
    uint32 saveIndex;
    std::wcin >> saveIndex;
    saveIndex--;
    if (saveIndex < 1)
        saveIndex = 0;
    if (saveIndex > numSaves - 1)
        saveIndex = numSaves - 1;

    // Save the stream as is, which will be quick
    IOutputStream::copy(incrementalSaves[saveIndex], IOutputStream::createToFile(mako, "TestOut1.pdf"));

    // Reload as an assembly and rewrite, allowing the incremental saves to be removed.
    const auto inAssembly = pdfInput->openIncremental(inputFilePath, saveIndex);
    clearMetadata(inAssembly->getDocument());
    pdfOutput->setEnableIncrementalOutput(false);
    pdfOutput->writeAssembly(inAssembly, "TestOut2.pdf");
}

TestFiles.zip

☑️ Conclusion

The IPDFInput class in Mako provides powerful tools for managing incremental saves in PDF documents. By utilizing the new APIs introduced in Mako 7.3.0, users can efficiently track and manage changes, allowing for precise control over document versions. The provided example demonstrates how to generate PDFs with incremental saves and how to access specific versions.

📚 Additional Resources

If you need additional help, see our API documentation for detailed information on class/method usage, or raise a support ticket via our customer portal.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.