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
IPDFOutputclass has long supported incremental save withsetEnableIncrementalOutput(), for example:
C++ | C# |
|---|---|
CPP
|
C#
|
Mako 7.3.0 introduces three new
IPDFInputAPIs:
API | Purpose |
|---|---|
| Returns the number of incremental saves found in a given PDF input stream. |
| Opens an incremental PDF stream, returning the |
| Returns a list of |
💪 Usage
Each API function has variants that will take an
IInputStream,U8StringorStringIncremental 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
// 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
// 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
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");
}
☑️ 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.