Green Screens Web Terminal support virtual spool printer with multiple drivers. The most interesting drivers are RAW and PDF. Why RAW driver we will describe it little bit later, for now let's focus on PDF driver.
When virtual printer is started on specific OUTQ and when operator release spool file, PDF driver will convert SCS spool file into PDF document. But here story does not end.
Our PDF printing engine support report overlays (background images) and scripting through powerful custom JavaScript engine.
The basics
Each spool file have attributes, one of them is USER DATA. Usually, this attribute contains program name that generated spool file or some unique value that identifies spool file on OUTQ. When spool file is released, virtual printer will pickup spool attributes and then it will look up for template directory named as USER DATA value.
Another options is to set template name manually from web terminal before releasing spool file.
NOTE: All template folders are located on server at [USER]\io.greenscreens\templates.
TIP
If there are multiple server nodes in cluster, one can create shared template folder and create links under [USER]\io.greenscreens\
Overlays
Clients might require different backgrounds for different spool files. Sometimes spool files have different orientation or different backgrounds for different pages are required. Having this is mind we have enabled declarative configuration mechanism which enables multiple backgrounds for a single spool file.
Declaration is based on report and page properties. Those are (in order of preference):
- default (0)
- even (1)
- odd (1)
- portrait (2)
- landscape (2)
- first (3)
- last (3)
- page number (4)
To create multi background support, create background templates named as those properties.
- Each template has selection weight. Higher number means, that template will apply.
- If default is defined, all pages without other higher weighted templates will have default background.
- If page is named as a page number, only that page will have declared background from numbered template.
- Numbered template with highest weight will override other backgrounds.
- 1.pdf will override first.pdf, landscape and portrait will override even or odd etc.
- If image and PDF templates are declared, both will be applied independently
- Image template will be applied first, PDF overly will be applied last
For example: If we have 10 page report, and declared default.pdf, first.pdf, portrait.pdf and 4.pdf will add background to pages with following rules:
- 1st page will have first.pdf background
- 4th page will have 4.pdf background
- Portrait pages will have portrait.pdf
- Remaining pages will have default.pdf
If default.png is declared, all pages will also contain default.png background.
NOTE: For HTML reporting, create templates as images in one of available formats: gif, png, jpg and name templates with proper extension.
Scripting
Scripting engine is modern version of JavaScript created in Java through Java Nashorn project. This is next generation of ultrafast JavaScript engine which will compile JavaScript code
into Java Virtual Machine instruction set making script execution fast. Additionally, it is possible to mix Java classes and standard JavaScript which opens very powerful possibilities.
How does it work?
Create script.jsx and save it under templates/[USER_DATA] folder. When spool file is released, virtual printer will determine which template script to use. During spool processing, various callback functions inside script will be called (if function is defined). Those functions are like triggered events. Except functions, there are also some global objects available to current script context like PDF page, PDF Document in process etc...
Here is the list of all available objects and functions.
Global objects
- IControls controls
- SpoolAttributes attributes
- PDDocument document - current document
- PDPageContentStream stream - current document stream (do no close it)
- PDPage page - current page in use
- String outq - name of printer output queue
Event Functions
- function onInit() {}
- function onStartReport() {}
- function onStartPage() {}
- function onEndPage() {}
- function onLine(page, line, position, overprint, text) {}
- function onEndReport(File file) {}
SpoolAttributes methods
- float getCpi() {}
- float getLpi() {}
- float getPageLength() {}
- float getPageWidth() {}
- int getOwerflow() {}
- int getSpoolNumber() {}
- String getCreateionDate() {}
- String getCreationTime() {}
- String getJobName() {}
- String getJobNumber() {}
- String getJobUser() {}
- String getOutq() {}
- String getSpoolName() {}
- String getSystem() {}
- String getUserData() {}
- String getUserDefinedData() {}
- String getUserDefinedOptions() {}
- String getUserDefinedText() {}
IControls methods
- boolean isNewLine();
- boolean isPageSet();
- float getFontSize();
- float getMarginBottomInPoints();
- float getMarginLeftInPoints();
- float getMarginRightInPoints();
- float getMarginTopInPoints();
- float getPageHeightInPoints();
- float getPageWidthInPoints();
- int cpiToPoint(int cpi);
- int getCurrentLine();
- int getLineSize();
- int pointToCpi(int point);
- int pointToTwip(int point);
- int twipToPoint(int twip);
- short getCharactersPerInch();
- short getCharactersPerLine();
- short getLinesPerInch();
- short getLinesPerPage();
Finally let's show some script
/*
*
* Copyright (C) 2015, 2016 Green Screens Ltd.
*
* Barcode generator library - API
* https://zxing.github.io/zxing/apidocs/
*
* PDF generator library API
* https://pdfbox.apache.org/docs/2.0.0/javadocs/
*
* NOTE: For barcode detection testing use
* https://play.google.com/store/apps/details?id=mobi.pdf417
*
*/
var File = Java.type('java.io.File');
var HashMap = Java.type('java.util.HashMap');
// reference to Java barcode and PDF library classes
var BarcodeFormat = Java.type('com.google.zxing.BarcodeFormat');
var MultiFormatWriter = Java.type('com.google.zxing.MultiFormatWriter');
var MatrixToImageWriter = Java.type('com.google.zxing.client.j2se.MatrixToImageWriter');
var JPEGFactory = Java.type('org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory');
var EncodeHintType = Java.type('com.google.zxing.EncodeHintType');
var PDImageXObject = Java.type('org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject');
var PDFont = Java.type('org.apache.pdfbox.pdmodel.font.PDFont');
var PDType1Font = Java.type('org.apache.pdfbox.pdmodel.font.PDType1Font');
var PDRectangle = Java.type('org.apache.pdfbox.pdmodel.common.PDRectangle');
var Matrix = Java.type('org.apache.pdfbox.util.Matrix');
// other local variables
var ADDR = 'This is demo text that will be encoded into PDF417 barcode';
var PAGE_WIDTH = 635; // custom width
var repeat = 0;
/*
* event function
* called when new virtual printer is initialized
*/
function onInit() {
print(outq);
print(template);
}
/*
* event function
* called when new PDF is started
*/
function onStartReport() {
print(attributes);
print(controls);
}
/*
* event function
* called when PDF is saved to disk
*
* @file - java.io.File - location of PDF
*/
function onEndReport(file) {
print('Location: ' + file.getAbsolutePath());
}
/*
* event function
* called when new page is created
*/
function onStartPage() {
var pageSize = page.getMediaBox();
// add logo to the top center
createLogo();
// add background overlay
centerRotate45(stream, pageSize, "SPECIMEN");
// add footer
centerFooter(stream, pageSize, "© 2016., Green Screens Ltd.");
// create template frame
createFrame();
// reset font color to black
stream.setNonStrokingColor(0);
}
/*
* event function
* called when page rendering is about to end
*/
function onEndPage() {
// render barcode on page number 2
if (document.getNumberOfPages() === 2) {
print('BARCODE VALUE: ' + ADDR);
create2Dbarcode();
// magenta color in R=0xff, G=0x00, B=0xff
stream.setNonStrokingColor(0xff, 0, 0xff);
writeText(5, 30, PDType1Font.TIMES_BOLD, 16, "DEMO TEXT");
}
}
/*
* event function
* Called on every line before added to PDF.
* For performance, remove it if not used.
*
* @page int - current page
* @line int - current line
* @positionfloat - vertical position in points on PDF page from bottom right
* @overprint boolean - true if repeating
* @text string
* @return - string or boolean
* - if string, line will be replaced with returned value
* - if boolean and value is true, line will be skipped
*/
function onLine(page, line, position, overprint, text) {
if (overprint) {
repeat++;
} else {
repeat = 0;
}
// move TITLE to the left to not to print over centered logo
if (line === 1 && repeat === 1) {
text = text.replace(' ', '');
}
if (line > 8) {
stream.setNonStrokingColor(0.2);
if (text.startsWith(' ')) {
stream.setNonStrokingColor(120, 120, 120);
}
if (text.contains('CPF')) {
// blue color
stream.setNonStrokingColor(255, 0, 0);
}
} else {
stream.setNonStrokingColor(0);
}
// process text on 2nd page from 15nth line
if (page === 2 && line > 15) {
//print(text);
// remove spaces and dots
var tmp = text.replace(/[ \.]/g, '');
// check if it is url address and save it for later
if (tmp.startsWith('http')) {
ADDR = tmp;
// blue color
stream.setNonStrokingColor(0, 0, 255);
}
}
// log every line
//print(page + ':' + line + ':' + text);
return text.replace('QSYS', 'QDEMO');
}
// internal script function - use PDF reader to get POINT locations
function createFrame() {
var pageSize = page.getMediaBox();
var h = pageSize.getHeight();
stream.setStrokingColor(32, 32, 32);
stream.setLineWidth(0.5);
//stream.addRect(2, h - 90, PAGE_WIDTH, 72);
stream.addRect(2, 25, PAGE_WIDTH, h - 40);
stream.closeAndStroke();
stream.setStrokingColor(1);
// 1st vertical head line
stream.moveTo(414, h - 34);
stream.lineTo(414, h - 74);
stream.closeAndStroke();
// 2nd vertical head line
stream.moveTo(246, h - 34);
stream.lineTo(246, h - 74);
stream.closeAndStroke();
// 1st horizontal line
stream.moveTo(3, h - 34);
stream.lineTo(PAGE_WIDTH, h - 34);
stream.closeAndStroke();
// 2nd horizontal line
stream.moveTo(3, h - 74);
stream.lineTo(PAGE_WIDTH, h - 74);
stream.closeAndStroke();
// 3rd horizontal line
stream.moveTo(3, h - 90);
stream.lineTo(PAGE_WIDTH, h - 90);
stream.closeAndStroke();
// vertical body line
stream.setStrokingColor(0.25);
stream.moveTo(168, h - 90);
stream.lineTo(168, 25);
stream.closeAndStroke();
}
// internal script function
function createLogo() {
var margin = 5;
var scale = 0.24;
var pageSize = page.getMediaBox();
var logo = new File(template, "logo.png");
var img = PDImageXObject.createFromFileByExtension(logo, document);
var width = img.getWidth() * scale;
var height = img.getHeight() * scale;
/*
var posX = pageSize.getWidth() - width - margin;
var posY = pageSize.getHeight() - height - margin;
*/
var posX = (PAGE_WIDTH - width - margin) / 2;
var posY = pageSize.getHeight() - height - margin - 12;
stream.drawImage(img, posX, posY, width, height);
}
// internal script function
function create2Dbarcode() {
// dimensions in points
var pageSize = page.getMediaBox();
// barcode dimensions in pixel
var bMargin = 2;
var bWidth = 200;
var bHeight = 150;
// barcode position in points; 1px = 0.75pt
//var bPosX = pageSize.getWidth() - bWidth * 0.75;
var bPosX = PAGE_WIDTH - bWidth * 0.75;
var bPosY = 30;
// barcode surrounding white space
var hints = new HashMap();
hints.put(EncodeHintType.MARGIN, bMargin);
// create barcode as bitmap matric
var wtr = new MultiFormatWriter();
var bitMatrix = wtr.encode(ADDR, BarcodeFormat.PDF_417, bWidth, bHeight, hints);
var buffImg = MatrixToImageWriter.toBufferedImage(bitMatrix);
// convert bitmap matrix to JPEG
var image = JPEGFactory.createFromImage(document, buffImg);
// and draw JPEG image to PDF page
stream.drawImage(image, bPosX, bPosY);
}
// internal script function
function writeText(x, y, font, size, text) {
stream.beginText();
stream.setFont(font, size);
stream.newLineAtOffset(x, y);
stream.showText(text);
stream.endText();
}
// internal script function
function centerFooter(contentStream, pageSize, message) {
var fontSize = 8.0;
var font = PDType1Font.HELVETICA;
var textWidth = (font.getStringWidth(message) / 1000) * fontSize;
var textHeight = (font.getFontDescriptor().getCapHeight() / 1000) * fontSize;
var centeredXPosition = (PAGE_WIDTH - textWidth) / 2;
var centeredYPosition = (5 + textHeight);
contentStream.beginText();
contentStream.setNonStrokingColor(0x99, 0x99, 0x99);
contentStream.setFont(font, fontSize);
contentStream.newLineAtOffset(centeredXPosition, centeredYPosition);
contentStream.showText(message);
contentStream.endText();
}
// internal script function
function centerRotate45(contentStream, pageSize, message) {
var fontSize = 96.0;
var font = PDType1Font.HELVETICA;
var ratio = Math.sqrt(2);
var textWidth = font.getStringWidth(message) / 1000 * fontSize;
// text is rotated by 45 degres, we need to calculate length of translated x
// diagonal of square = x * sqrt(2), translated x' = x / sqrt(2)
var centeredXPosition = (pageSize.getWidth() - (textWidth / ratio)) / 2;
var centeredYPosition = (pageSize.getHeight() - (textWidth / ratio)) / 2;
var matrix = Matrix.getRotateInstance(-7 * Math.PI * 0.25, centeredXPosition, centeredYPosition);
contentStream.beginText();
//contentStream.setNonStrokingColor(0xf5, 0xf5, 0xf5);
contentStream.setNonStrokingColor(0.95);
contentStream.setFont(font, fontSize);
contentStream.setTextMatrix(matrix);
contentStream.showText(message);
contentStream.endText();
}
And resulting PDF will look like shown on images below. Note frames, colored text, barcode and logo image.
RAW Driver
RAW driver is pass through meaning raw spool data in EBCDIC encoding will be downloaded to the browser. But, what is the use of it? We provide simple scripting and testing desktop application for offline spool scripts testing how they will look like before releasing script to the server. This is much easier that constantly redeploying and reloading templates on the server. Once when script is ready for deployment, we can upload it to the Green Screens Terminal server.