Skip to content

Instantly share code, notes, and snippets.

@mako-team
Last active June 3, 2023 08:37
Show Gist options
  • Save mako-team/d471cd43c882fbfe3a87c25a6977d2c6 to your computer and use it in GitHub Desktop.
Save mako-team/d471cd43c882fbfe3a87c25a6977d2c6 to your computer and use it in GitHub Desktop.
This example code demonstrates using Mako to (1) parse PJL (as may be found in PCL, PCL/XL and PostScript) to discover information about the job such as a duplex setting and (2) getting information from print tickets that Mako populates as it interprets the document content. Optionally is can save the content to a PDF. It also demonstrates a tec…
/*
* Copyright (C) 2020-2023 Global Graphics Software Ltd. All rights reserved
*
* Simple sample application to report on job parameters in PJL-wrapped PCL, PCL/XL, PostScript or PDF, using Mako APIs.
*/
#include <algorithm>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <jawsmako/jawsmako.h>
#include <jawsmako/psinput.h>
#include <jawsmako/pcl5input.h>
#include <jawsmako/pclxlinput.h>
#include <jawsmako/pdfinput.h>
#include <jawsmako/pdfoutput.h>
#include <filesystem>
using namespace JawsMako;
using namespace EDL;
void GetAttributes(const IPJLParserPtr& pjlParser)
{
IPJLParser::CPjlAttributeVect attributes = pjlParser->getAttributes("SET", "DUPLEX");
if (attributes.size() != 0)
{
// Take the last-seen one if there are multiples
RawString value = attributes[attributes.size() - 1].value;
// Case insensitive
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
if (value == "on")
{
// What is the direction?
String duplexMode = L"TwoSidedLongEdge";
// Look for Binding direction in the PJL.
attributes = pjlParser->getAttributes("SET", "BINDING");
if (attributes.size() != 0)
{
// Take the last-seen one if there are multiples
value = attributes[attributes.size() - 1].value;
// Case insensitive
std::transform(value.begin(), value.end(), value.begin(), ::tolower);
if (value == "longedge")
{
duplexMode = L"TwoSidedLongEdge";
}
else if (value == "shortedge")
{
duplexMode = L"TwoSidedShortEdge";
}
}
std::wcout << "Duplex mode: " << duplexMode << std::endl;
}
else
std::wcout << "Duplex mode: OFF" << std::endl;
}
}
static bool ReportJobTicketItem(const IDOMJobTkNodePtr& node)
{
// Get name (without namespace prefix)
EDLQName qName = node->getQName();
std::wcout << L" Parameter " << qName.getName() << L" \tValue ";
const IDOMJobTkNode::eDOMJobTkNodeType nodeType = node->getJobTkNodeType();
switch (nodeType) // NOLINT(clang-diagnostic-switch-enum)
{
case IDOMJobTkNode::eDOMJobTkPTNodeParameterInit:
{
// Report value
const IDOMJobTkValuePtr jobTkValue = node->getChildValue();
const PValue pVal = jobTkValue->getValue();
switch (pVal.getType()) // NOLINT(clang-diagnostic-switch-enum)
{
case PValue::T_UNASSIGNED:
std::wcout << L"** not available **" << std::endl;
break;
case PValue::T_INT:
std::wcout << pVal.getInt32() << std::endl;
break;
case PValue::T_STRING:
std::wcout << pVal.getString() << std::endl;
break;
case PValue::T_QNAME:
std::wcout << pVal.getQName().getName() << std::endl;
break;
default:
std::wcout << L"** value not available **" << std::endl;
}
}
break;
case IDOMJobTkNode::eDOMJobTkPTNodeFeature:
{
// Report option
IDOMJobTkNodePtr childNode = edlobj2IDOMJobTkNode(node->getFirstChild());
if (childNode)
{
if (childNode->getJobTkNodeType() == IDOMJobTkNode::eDOMJobTkPTNodeOption)
{
qName = childNode->getQName();
std::wcout << qName.getName() << std::endl;
}
return true;
}
std::wcout << L"** not available **" << std::endl;
return false;
}
default:
std::wcout << L"Node type " << nodeType << std::endl;
}
return true;
}
struct sTestFile
{
String filePath;
eFileFormat type;
sTestFile(String f, eFileFormat e);
};
sTestFile::sTestFile(String f, eFileFormat e) : filePath(f), type(e)
{
}
class CTBCPState
{
public:
CTBCPState()
{
lastChar = 0;
}
public:
CEDLSimpleBuffer buffer;
uint8 lastChar;
IInputStreamPtr prnStream;
};
// Modify stream to deal with Adobe's tagged binary communication protocol (TBCP) codes
static int tbcpRead(void *pPriv, void *pBuff, unsigned int len, unsigned int *pLenRead, int *pEof)
{
CTBCPState *state = (CTBCPState *) pPriv;
*pEof = 0;
if (state->buffer.size() < len)
{
state->buffer.resize(len);
}
const int32 readAmount = state->prnStream->read(&state->buffer[0], (int32) len);
if (readAmount == 0)
{
*pEof = 1;
}
else if (readAmount < 0)
{
return -1;
}
else
{
uint8 *outPtr = (uint8 *) pBuff;
*pLenRead = 0;
for (int32 i = 0; i < readAmount; i++)
{
if (state->lastChar == 0x01)
{
// XOR this character with 0x40
*outPtr++ = (state->buffer[i] ^ 0x40);
(*pLenRead)++;
state->lastChar = 0;
}
else if (state->buffer[i] == 0x01)
{
// Note and skip the next
state->lastChar = state->buffer[i];
}
else
{
// Copy the byte
*outPtr++ = state->buffer[i];
(*pLenRead)++;
}
}
}
return 0;
}
int main(int argc, char* argv[])
{
if (argc < 1)
{
std::wcout << L"Usage: makopjlreporter <path\\to\\folder\\of\\testfiles> [-c]" << std::endl;
std::wcout << L" Specify -c to also convert to PDF (in a folder named PDF)" << std::endl;
return 1;
}
// Was a second parameter specified?
bool convertToPDF = false;
if (argc > 2)
{
U8String param = argv[2];
std::transform(param.begin(), param.end(), param.begin(), tolower);
convertToPDF = param == "-c";
}
// Find all files in the folder specified as the first argument. Does not recurse into folders.
std::vector <sTestFile> testFiles;
for (const auto& entry : std::filesystem::directory_iterator(argv[1]))
{
if (entry.is_regular_file())
testFiles.emplace_back(sTestFile(entry.path().c_str(), eFFUnknown));
}
try
{
// Create JawsMako instance
const IJawsMakoPtr jawsMako = IJawsMako::create(".");
IJawsMako::enableAllFeatures(jawsMako);
// *** Example 1: Process the PJL header only ***
for (auto& testFile : testFiles)
{
// Get the stream ready
IInputPushbackStreamPtr prnStream = IInputStream::createPushbackStream(jawsMako, IInputStream::createFromFile(jawsMako,
testFile.filePath));
IPJLParserPtr pjlParser = IPJLParser::create(jawsMako);
// Open the stream
if (!prnStream->open())
{
throw std::runtime_error("Could not open input stream");
}
std::wcout << testFile.filePath << L": ";
IPJLParser::ePjlResult pjlResult;
// Repeatedly parse PJL until we get to the end of the stream
try {
while ((pjlResult = pjlParser->parse(prnStream)) != IPJLParser::ePREndOfFile)
{
switch (pjlResult)
{
case IPJLParser::ePREnterPostScript:
{
testFile.type = eFFPS;
GetAttributes(pjlParser);
break;
}
case IPJLParser::ePREnterPclXl:
{
testFile.type = eFFPCLXL;
GetAttributes(pjlParser);
break;
}
case IPJLParser::ePREnterPcl:
{
testFile.type = eFFPCL5;
GetAttributes(pjlParser);
break;
}
case IPJLParser::ePREnterPdf:
{
testFile.type = eFFPDF;
GetAttributes(pjlParser);
break;
}
case IPJLParser::ePREndOfFile:
break;
}
// If we have hit PCL, then break out
if (pjlResult != IPJLParser::ePREndOfFile)
break;
}
// Reached end of PJL
if (testFile.type == eFFPCL5)
std::cout << "PCL5 ";
if (testFile.type == eFFPCLXL)
std::cout << "PCL/XL ";
if (testFile.type == eFFPS)
std::cout << "PostScript ";
if (testFile.type == eFFPDF)
std::cout << "PDF ";
std::wcout << "End of PJL" << std::endl;
}
catch (IError& e)
{
uint32 x = e.getErrorCode();
if (x == 124)
continue;
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
}
}
// *** Example 2: Process the print tickets ***
// Look at each test file
for (sTestFile testFile : testFiles)
{
// Open the stream.
// The PJL parser requires a stream that implements the IInputPushbackStream
// interface, as it needs to sniff content to do its job. We can overlay
// this on a standard file stream.
IInputPushbackStreamPtr prnStream = IInputStream::createPushbackStream(jawsMako, IInputStream::createFromFile(jawsMako, testFile.filePath));
// Create our PJL Parser, PCL/5, PCL/XL and PS inputs
IPJLParserPtr pjlParser = IPJLParser::create(jawsMako);
IPCLXLInputPtr xlInput = IPCLXLInput::create(jawsMako);
IPCL5InputPtr pcl5Input = IPCL5Input::create(jawsMako);
IPSInputPtr psInput = IPSInput::create(jawsMako);
IPDFInputPtr pdfInput = IPDFInput::create(jawsMako);
// Normally the PCLXL and PCL5 inputs will process PJL themselves. We want
// to take control, so we use them unencapsulated.
xlInput->enableUnencapsulatedMode(true);
pcl5Input->enableUnencapsulatedMode(true);
// The PostScript input does not handle PJL itself, but we still do not
// want it to open the input stream. The API however is the same.
psInput->enableUnencapsulatedMode(true);
// The PDF input does not have an enableUnencapsulatedMode method,
// but we can do the same by using an stream type that detects a UEL
// (Universal End of Language) tag. See code below.
// Open the stream
if (!prnStream->open())
{
throw std::runtime_error("Could not open input stream");
}
// Create an output for saving the file as a PDF
IPDFOutputPtr output = IPDFOutput::create(jawsMako);
IInputPtr input;
// Count of the distinct document assemblies contained in the stream
uint32 documentAssemblyIndex = 0;
// Now start parsing until we run out of input, beginning in PJL mode
IPJLParser::ePjlResult pjlResult;
try {
// Repeatedly parse PJL until we get to the end of the stream
while ((pjlResult = pjlParser->parse(prnStream)) != IPJLParser::ePREndOfFile)
{
IDocumentAssemblyPtr assembly;
// Class to hold buffer and some state information
CTBCPState tbcpState;
// We have a PCL/XL, PCL5e or PostScript stream. Open.
switch (pjlResult)
{
case IPJLParser::ePREnterPclXl:
input = xlInput;
assembly = input->open(prnStream);
break;
case IPJLParser::ePREnterPcl:
input = pcl5Input;
assembly = input->open(prnStream);
break;
case IPJLParser::ePREnterPostScript:
{
input = psInput;
tbcpState.lastChar = 0;
tbcpState.prnStream = prnStream;
IInputStreamPtr psStream = IInputStream::createFromUserReadFunc(jawsMako, tbcpRead, static_cast<void *>(&tbcpState));
assembly = input->open(psStream);
break;
}
case IPJLParser::ePREnterPdf:
{
input = pdfInput;
IInputStreamPtr pdfStream = IInputStream::createUelStream(jawsMako, prnStream);
assembly = input->open(pdfStream);
break;
}
case IPJLParser::ePREndOfFile:
break;
}
// Increment count
documentAssemblyIndex++;
// Open this portion of the stream.
{
// Get assembly-level print ticket
std::wcout << L"\nFile " << testFile.filePath << L":" << std::endl;
std::wcout << L" Assembly-level print ticket:" << std::endl;
IDOMJobTkPtr jobTicket = assembly->getJobTicket();
if (jobTicket)
{
IDOMJobTkContentPtr jobTicketContent = jobTicket->getContent();
IDOMJobTkNodePtr node = jobTicketContent->getRootNode();
while (node)
{
ReportJobTicketItem(node);
node = edlobj2IDOMJobTkNode(node->getNextSibling());
}
}
else
std::wcout << L" ** None found **" << std::endl;
}
// Look at each document within the document assembly (always only one for PS or PDF)
for (uint32 documentIndex = 0; documentIndex < assembly->getNumDocuments(); documentIndex++)
{
IDocumentPtr document = assembly->getDocument(documentIndex);
uint32 numPages = document->getNumPages();
if (numPages > 0)
{
std::wcout << L" Document " << documentIndex + 1 << L" of " << assembly->getNumDocuments() << L":" << std::endl;
// Get document-level print ticket
std::wcout << L" Document-level print ticket:" << std::endl;
IDOMJobTkPtr jobTicket = document->getJobTicket();
if (jobTicket)
{
IDOMJobTkContentPtr jobTicketContent = jobTicket->getContent();
IDOMJobTkNodePtr node = jobTicketContent->getRootNode();
while (node)
{
ReportJobTicketItem(node);
node = edlobj2IDOMJobTkNode(node->getNextSibling());
}
}
else
std::wcout << L" ** None found **" << std::endl;
// Look at each page
for (uint32 pageIndex = 0; pageIndex < numPages; pageIndex++)
{
std::wcout << L" Page-level print ticket for page " << pageIndex + 1 << " of " << numPages << L":" << std::endl;
IPagePtr page = document->getPage(pageIndex);
IDOMJobTkPtr pageJobTicket = page->getJobTicket();
if (pageJobTicket)
{
IDOMJobTkContentPtr pageJobTicketContent = pageJobTicket->getContent();
IDOMJobTkNodePtr node = pageJobTicketContent->getRootNode();
while (node)
{
ReportJobTicketItem(node);
node = edlobj2IDOMJobTkNode(node->getNextSibling());
}
}
else
std::wcout << L" ** None found **" << std::endl;
}
}
// Write the document out to a 'PDF' folder. Each document assembly (and the documents
// within it) are written as separate PDFs, with a filename to suit
if (convertToPDF)
{
std::filesystem::path path = testFile.filePath;
auto filename = path.stem().string() + "_" + path.extension().string().substr(1) + "_";
std::string outFolder = path.remove_filename().append("PDF\\").string();
auto outFile = outFolder + filename + std::to_string(documentAssemblyIndex);
outFile += documentIndex > 0 ? "_" + std::to_string(documentIndex + 1) + ".pdf" : ".pdf";
std::cout << char(0x1b) << "[38;5;11mWriting " << outFile << char(0x1b) << "[0m" << std::endl;
std::filesystem::create_directory(outFolder);
if (documentIndex > 0)
{
const auto pdfAssembly = IDocumentAssembly::create(jawsMako);
pdfAssembly->appendDocument(document->clone());
output->writeAssembly(pdfAssembly, outFile.c_str());
}
else
{
output->writeAssembly(assembly, outFile.c_str());
}
}
}
}
}
catch (IError& e)
{
// PJL exhausted?
if (e.getErrorCode() == 124)
continue;
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 (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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment