Introduction
Some Mako operations, particularly those operating on large documents, can take a long time. In an interactive application, evidence of progress by means of UI element such as a progress bar or even just a “spinner” of some kind reassures the user that something is happening and that the application has not hung.
Mako provides these classes to enable implementation of such mechanisms:
Class | Description |
---|
IProgressMonitor
| An abstract class encapsulating both an IProgressTick and an IAbort so that the caller can monitor the progress and/or abort the processing. |
IProgressTick
| An abstract class allowing a callback to monitor the progress of a task. The callback can either pass an integer (from 0 to a maximum value) or a float (0.0 to 1.0) which represents the progress of the task. |
IAbort
| A simple class to signal an abort in processing. |
In principle, adding progress monitoring involves these steps:
Create a progress tick call back function
Create an IProgressTick
referencing the call back function
Creating an IProgressMonitor
with the IProgressTick
instance and an IAbort
instance
Attach the progress monitor to the process to be monitored by passing in the IProgressMonitor
instance
Thereafter, the call back function will be called on a regular ‘tick’, the number of which is determined by the work being done. The interval of the ticks can be controlled, and a maximum number can be specified, so that as the process continues, the tick will incrementally approach the maximum. The tick call back can be an integer or a float.
The call back can also interrupt the process by signalling an abort. This is shown in the accompanying page that looks in more detail at monitoring transforms.
Sample Code
To help you understand how to implement progress monitoring in your development, we have created sample code, attached, that does the following:
✅Demonstrates the Use of IProgressMonitor
, IProgressTick
, and IAbort
✅ Multiple Progress Monitors for Different Stages
✅ Encapsulation of Progress Handling (ProgressHandler
Class)
ProgressHandler::create()
simplifies instantiation.
The tick()
method demonstrates periodic updates.
The callbackInt()
method links progress updates back to the ProgressHandler
.
Code Examples
Examples are provided in C++ & C#.
C++
CPP
/* -----------------------------------------------------------------------
* <copyright file="ProgressMonitoring.cpp" company="Global Graphics Software Ltd">
* Copyright (c) 2025 Global Graphics Software Ltd. All rights reserved.
* </copyright>
* <summary>
* This example is provided on an "as is" basis and without warranty of any kind.
* Global Graphics Software Ltd. does not warrant or make any representations
* regarding the use or results of use of this example.
* </summary>
* -----------------------------------------------------------------------
*/
#include <iostream>
#include <memory>
#include <jawsmako/jawsmako.h>
#include <jawsmako/pdfinput.h>
#include <jawsmako/customtransform.h>
using namespace JawsMako;
using namespace EDL;
class ConverterParams
{
public:
std::wstring m_inputFilePath;
eFileFormat m_inputFileFormat;
std::wstring m_outputFilePath;
eFileFormat m_outputFileFormat;
IProgressMonitorPtr m_customTransformProgressMonitor;
IProgressMonitorPtr m_transformProgressMonitor;
IProgressMonitorPtr m_transformChainProgressMonitor;
void setInputPath(const std::wstring& filePath)
{
m_inputFilePath = filePath;
m_inputFileFormat = formatFromPath(filePath);
}
void setOutputPath(const std::wstring& filePath)
{
m_outputFilePath = filePath;
m_outputFileFormat = formatFromPath(filePath);
}
// Determine the associated format for a given path from the file extension
static eFileFormat formatFromPath(const std::wstring& path)
{
// Get the extension in lower case
if (path.size() < 3)
{
// Cannot determine the extension if there isn't one!
std::string message("Cannot determine file extension for path ");
throw std::length_error(message);
}
size_t extensionPosition = path.find_last_of('.');
if (extensionPosition != String::npos)
{
std::wstring extension = path.substr(extensionPosition);
std::transform(extension.begin(), extension.end(), extension.begin(), towlower);
if (extension == L".xps")
return eFFXPS;
if (extension == L".pdf")
return eFFPDF;
if (extension == L".svg")
return eFFSVG;
if (extension == L".ps")
return eFFPS;
if (extension == L".eps")
return eFFEPS;
if (extension == L".pcl")
return eFFPCL5;
if (extension == L".pxl")
return eFFPCLXL;
if (extension == L".ijp")
return eFFIJPDS;
if (extension == L".zip")
return eFFPPML;
if (extension == L".oxps")
return eFFOXPS;
}
std::string message("Unsupported file type for path ");
throw std::invalid_argument(message);
}
};
class ProgressHandler
{
// This section should be customised
public:
explicit ProgressHandler(std::string info = "") : m_info(std::move(info)), m_tickCount(0) {}
void tick(uint32_t currentCount, uint32_t maxCount)
{
m_tickCount++;
printf("%s : %d\n", m_info.c_str(), m_tickCount);
fflush(stdout);
}
// Everything below this part can be reused
IProgressTickPtr m_progressTick;
static std::shared_ptr<ProgressHandler> create(const std::string& info)
{
std::shared_ptr<ProgressHandler> progressHandler = std::make_shared<ProgressHandler>(info);
progressHandler->m_progressTick = IProgressTick::create(callbackInt, progressHandler.get());
return progressHandler;
}
protected:
static void callbackInt(void* priv, uint32_t currentCount, uint32_t maxCount)
{
ProgressHandler* progressHandler = (ProgressHandler*)priv;
progressHandler->tick(currentCount, maxCount);
}
private:
std::string m_info;
int m_tickCount;
};
class CEmptyTransformImplementation : public ICustomTransform::IImplementation
{
public:
CEmptyTransformImplementation(const IJawsMakoPtr& jawsMako)
{
m_jawsMako = jawsMako;
}
protected:
IJawsMakoPtr m_jawsMako;
};
static void output_iterateByPage(const ConverterParams& cvtParams,
const IJawsMakoPtr& jawsMako,
const IDocumentAssemblyPtr& assembly,
const IOutputPtr& output)
{
IRAInputStreamPtr reader;
IRAOutputStreamPtr writer;
jawsMako->getTempStore()->createTemporaryReaderWriterPair(reader, writer);
const IOutputWriterPtr outputWriter = output->openWriter(assembly, writer);
// Create the transform
CEmptyTransformImplementation implementation(jawsMako);
ITransformPtr customTransform = ICustomTransform::create(jawsMako, &implementation);
customTransform->setProgressMonitor(cvtParams.m_customTransformProgressMonitor);
IColorConverterTransformPtr ccTransform = IColorConverterTransform::create(jawsMako);
IDOMColorSpacePtr deviceCmyk = IDOMColorSpaceDeviceCMYK::create(jawsMako);
ccTransform->setTargetSpace(deviceCmyk);
ccTransform->setProgressMonitor(cvtParams.m_transformProgressMonitor);
ITransformChainPtr transformChain;
{
IColorConverterTransformPtr colorConverter = IColorConverterTransform::create(jawsMako);
transformChain = ITransformChain::create(jawsMako, cvtParams.m_transformChainProgressMonitor);
transformChain->pushTransform(colorConverter);
}
for (int docNo = 0; ; docNo++)
{
if (!assembly->documentExists(docNo))
{
// No more documents exist
break;
}
IDocumentPtr document = assembly->getDocument(docNo);
outputWriter->beginDocument(document);
int pageStart = 0;
constexpr int pageEnd = 1; // process one page only
for (int pageIndex = pageStart; pageIndex < pageEnd; pageIndex++)
{
if (!document->pageExists(pageIndex))
{
// No more pages in this document.
break;
}
IPagePtr page = document->getPage(pageIndex);
// Test progress monitor in ICustomTransform
{
customTransform->transformPage(page);
}
// Test progress monitor in a single ITransform
{
IDOMFixedPagePtr fixedPage = page->getContent();
bool changed = false;
ccTransform->transform(fixedPage, changed);
}
// Test progress monitor in a transform chain
{
IDOMFixedPagePtr fixedPage = page->getContent();
bool changed = false;
transformChain->transform(fixedPage, changed);
}
outputWriter->writePage(page);
page->release();
}
outputWriter->endDocument();
}
outputWriter->finish();
IOutputStream::copy(reader, IOutputStream::createToFile(jawsMako, cvtParams.m_outputFilePath.c_str()));
}
static void usage(const std::wstring& progName)
{
wprintf(L"%s <input> <output>", progName.c_str());
fflush(stdout);
}
#ifdef _WIN32
int wmain(int argc, wchar_t* argv[])
#else
int main(int argc, char* argv[])
#endif
{
try
{
ConverterParams cvtParams;
std::wstring progName = argv[0];
if (argc != 3)
{
usage(progName);
return 1;
}
cvtParams.setInputPath(argv[1]);
cvtParams.setOutputPath(argv[2]);
// Create our JawsMako instance.
const IJawsMakoPtr jawsMako = IJawsMako::create();
IJawsMako::enableAllFeatures(jawsMako);
std::shared_ptr<ProgressHandler> inputProgressHandler = ProgressHandler::create("input");
std::shared_ptr<ProgressHandler> outputProgressHandler = ProgressHandler::create("output");
std::shared_ptr<ProgressHandler> customTransformProgressHandler = ProgressHandler::create("customtransform");
std::shared_ptr<ProgressHandler> transformProgressHandler = ProgressHandler::create("transform");
std::shared_ptr<ProgressHandler> transformChainProgressHandler = ProgressHandler::create("transformChain");
IAbortPtr abort = IAbort::create();
IProgressMonitorPtr inputProgressMonitor = IProgressMonitor::create(inputProgressHandler->m_progressTick, abort);
IProgressMonitorPtr outputProgressMonitor = IProgressMonitor::create(outputProgressHandler->m_progressTick, abort);
cvtParams.m_customTransformProgressMonitor = IProgressMonitor::create(customTransformProgressHandler->m_progressTick, abort);
cvtParams.m_transformProgressMonitor = IProgressMonitor::create(transformProgressHandler->m_progressTick, abort);
cvtParams.m_transformChainProgressMonitor = IProgressMonitor::create(transformChainProgressHandler->m_progressTick, abort);
// Create input pointer
IInputPtr input = IInput::create(jawsMako, cvtParams.m_inputFileFormat, inputProgressMonitor);
// Create output pointer and set optional parameters
const IOutputPtr output = IOutput::create(jawsMako, cvtParams.m_outputFileFormat, outputProgressMonitor);
// Get the assembly from the input.
const IDocumentAssemblyPtr assembly = input->open(cvtParams.m_inputFilePath.c_str());
output_iterateByPage(cvtParams, jawsMako, assembly, output);
}
catch (IError& e)
{
String errorFormatString = getEDLErrorString(e.getErrorCode());
std::wcerr << L"Exception thrown: " << e.getErrorDescription(errorFormatString) << std::endl;
#ifdef _WIN32
// On windows, the return code allows larger numbers, and we can return the error code
return static_cast<int>(e.getErrorCode());
#else
// On other platforms, the exit code is masked to the low 8 bits. So here we just return
// a fixed value.
return 1;
#endif
}
catch (std::exception& e)
{
std::wcerr << L"std::exception thrown: " << e.what() << std::endl;
return 1;
}
return 0;
}
C#
C#
/* -----------------------------------------------------------------------
* <copyright file="ProgressMonitoringCS.cs" company="Global Graphics Software Ltd">
* Copyright (c) 2025 Global Graphics Software Ltd. All rights reserved.
* </copyright>
* <summary>
* This example is provided on an "as is" basis and without warranty of any kind.
* Global Graphics Software Ltd. does not warrant or make any representations
* regarding the use or results of use of this example.
* </summary>
* -----------------------------------------------------------------------
*/
using JawsMako;
using static JawsMako.jawsmakoIF_csharp;
namespace ProgressMonitoringCS;
class ConverterParams
{
public string? m_inputFilePath;
public eFileFormat m_inputFileFormat;
public string? m_outputFilePath;
public eFileFormat m_outputFileFormat;
public IProgressMonitor? m_customTransformProgressMonitor;
public IProgressMonitor? m_transformProgressMonitor;
public IProgressMonitor? m_transformChainProgressMonitor;
public ConverterParams()
{
m_inputFilePath = null;
m_outputFilePath = null;
}
public void setInputPath(string filePath)
{
m_inputFilePath = filePath;
m_inputFileFormat = formatFromPath(filePath);
}
public void setOutputPath(string filePath)
{
m_outputFilePath = filePath;
m_outputFileFormat = formatFromPath(filePath);
}
static eFileFormat formatFromPath(String path)
{
var ext = Path.GetExtension(path);
switch (ext.ToLower())
{
case ".pdf":
return eFileFormat.eFFPDF;
case ".xps":
return eFileFormat.eFFXPS;
case ".pcl":
return eFileFormat.eFFPCL5;
case ".pxl":
return eFileFormat.eFFPCLXL;
case ".ps":
return eFileFormat.eFFPS;
case ".svg":
return eFileFormat.eFFSVG;
case ".ijp":
return eFileFormat.eFFIJPDS;
case ".eps":
return eFileFormat.eFFEPS;
}
return eFileFormat.eFFUnknown;
}
}
class ProgressTick : IProgressTickIntCallback
{
public int m_tickCount;
public string m_info;
public ProgressTick(string info)
{
m_info = info;
m_tickCount = 0;
}
public override void tick(uint tickNo, uint maxTick)
{
m_tickCount++;
Console.Out.WriteLine($"{m_info} {m_tickCount}");
}
};
class ProgressTickHandler
{
public IProgressTick? m_progressTick;
private IProgressTickIntCallback? m_callback;
ProgressTickHandler()
{
m_progressTick = null;
m_callback = null;
}
public static ProgressTickHandler create(IProgressTickIntCallback callback)
{
var handler = new ProgressTickHandler();
handler.m_callback = callback;
handler.m_progressTick = IProgressTick.create(callback.getCallbackFunc(), callback.getPriv());
return handler;
}
};
class CEmptyTransformImplementation : ICustomTransform.IImplementation
{
public CEmptyTransformImplementation(IJawsMako jawsMako)
{
m_jawsMako = jawsMako;
}
protected IJawsMako m_jawsMako;
};
class ProgressMonitor
{
// Print usage
static void Usage(string app)
{
Console.Write($"Usage: {app} <source file> <output file>");
}
static void output_iterateByPage(ConverterParams cvtParams,
IJawsMako jawsMako,
IDocumentAssembly assembly,
IOutput output)
{
var (reader, writer) = jawsMako.getTempStore().createTemporaryReaderWriterTuple();
var outputWriter = output.openWriter(assembly, writer);
// Create the transform
var implementation = new CEmptyTransformImplementation(jawsMako);
ITransform customTransform = ICustomTransform.create(jawsMako, implementation);
customTransform.setProgressMonitor(cvtParams.m_customTransformProgressMonitor);
var ccTransform = IColorConverterTransform.create(jawsMako);
IDOMColorSpace deviceCmyk = IDOMColorSpaceDeviceCMYK.create(jawsMako);
ccTransform.setTargetSpace(deviceCmyk);
ccTransform.setProgressMonitor(cvtParams.m_transformProgressMonitor);
ITransformChain transformChain;
{
var colorConverter = IColorConverterTransform.create(jawsMako);
transformChain = ITransformChain.create(jawsMako, cvtParams.m_transformChainProgressMonitor);
transformChain.pushTransform(colorConverter);
}
for (uint docNo = 0; ; docNo++)
{
if (!assembly.documentExists(docNo))
{
// No more documents exist
break;
}
var document = assembly.getDocument(docNo);
outputWriter.beginDocument(document);
var pageStart = 0;
var pageEnd = -1;
pageEnd = 1; // process one page only
for (var pageNo = pageStart; (pageEnd < 0) || (pageNo < pageEnd); pageNo++)
{
if (!document.pageExists((uint)pageNo))
{
// No more pages in this document.
break;
}
var page = document.getPage((uint)pageNo);
// Test progress monitor in ICustomTransform
{
customTransform.transformPage(page);
}
// Test progress monitor in a single ITransform
{
var fixedPage = page.getContent();
var changed = false;
IDOMNode node = fixedPage;
ccTransform.transform(node, ref changed);
}
// Test progress monitor in a transform chain
{
var fixedPage = page.getContent();
var changed = false;
transformChain.transform(fixedPage, ref changed);
}
outputWriter.writePage(page);
page.release();
}
outputWriter.endDocument();
}
outputWriter.finish();
IOutputStream.copy(reader, IOutputStream.createToFile(jawsMako, cvtParams.m_outputFilePath));
}
static int Main(string[] args)
{
try
{
var progName = AppDomain.CurrentDomain.FriendlyName;
if (args.Length != 2)
{
Usage(progName);
return 1;
}
var cvtParams = new ConverterParams();
cvtParams.setInputPath(args[0]);
cvtParams.setOutputPath(args[1]);
var jawsMako = IJawsMako.create();
IJawsMako.enableAllFeatures(jawsMako);
var factory = jawsMako.getFactory();
var inputProgressTickHandler = ProgressTickHandler.create(new ProgressTick("input"));
var outputProgressTickHandler = ProgressTickHandler.create(new ProgressTick("output"));
var customTransformProgressHandler = ProgressTickHandler.create(new ProgressTick("customtransform"));
var transformProgressHandler = ProgressTickHandler.create(new ProgressTick("transform"));
var transformChainProgressHandler = ProgressTickHandler.create(new ProgressTick("transformChain"));
var abort = IAbort.create();
var inputProgressMonitor = IProgressMonitor.create(inputProgressTickHandler.m_progressTick, abort);
var outputProgressMonitor = IProgressMonitor.create(outputProgressTickHandler.m_progressTick, abort);
cvtParams.m_customTransformProgressMonitor = IProgressMonitor.create(customTransformProgressHandler.m_progressTick, abort);
cvtParams.m_transformProgressMonitor = IProgressMonitor.create(transformProgressHandler.m_progressTick, abort);
cvtParams.m_transformChainProgressMonitor = IProgressMonitor.create(transformChainProgressHandler.m_progressTick, abort);
var input = IInput.create(jawsMako, cvtParams.m_inputFileFormat, inputProgressMonitor);
var output = IOutput.create(jawsMako, cvtParams.m_outputFileFormat, outputProgressMonitor);
// Get the assembly from the input.
var assembly = input.open(cvtParams.m_inputFilePath);
output_iterateByPage(cvtParams, jawsMako, assembly, output);
output?.Dispose();
assembly?.Dispose();
output?.Dispose();
input.Dispose();
factory?.Dispose();
inputProgressMonitor?.Dispose();
outputProgressMonitor?.Dispose();
abort?.Dispose();
jawsMako.Dispose();
}
catch (MakoException e)
{
getEDLErrorString(e.m_errorCode);
Console.WriteLine("Exception thrown: " + e.m_msg);
return 1;
}
catch (Exception e)
{
Console.WriteLine($"Exception thrown: {e}");
}
return 0;
}
}