The PDF template allows you to create and change views of PDF files without changing the application code. The application user creates any template and imports it to the system. Next creates fields on the template in which the variable content is to be filled. It can be anything: the price of goods on the voucher, information on the leaflet or fields on the prescription.
Variables to be filled in are created by the programmer based on fields entered by the user.
<!doctype html>
<html>
<head lang="pl">
<title>Szablon</title>
<link href="$page.url("editing.css")" rel="stylesheet">
<script src="$media.simpleUrl("jquery-1.11.0.min.js")" type="text/javascript"></script>
$//załącznie wymaganych bibliotek:
<script src="$page.url("jpalio.js.pdfjs.build.pdf.js")" type="text/javascript"></script>
…
<script src="editing.js" type="text/javascript"></script>
</head>
<body>
data-file-url="$#{fileMeta.url}" $//adres url szablonu pdf
data-save-document-template-definition-url="$#{saveDocumentTemplateDefinitionUrl}" $//adres url do zapisu danych
data-load-start-state-url="$#{loadStartStateUrl}" $//adres url z aktualnie zapisanymi danymi
data-data-source-info-url="$#{dataSourceInfoUrl}" $// adres url ze źródłami danych.
data-data-font-color-url="$#{dataFontColorUrl}" $//adres url zmienne
data-data-font-size-url="$#{dataFontSizeUrl}" $//adres url zmienne
data-data-font-type-url="$#{dataFontTypeUrl}" $//adres url zmienne
data-data-align-url="$#{dataAlign}"> $//adres url zmienne
<div class="button-container">
<button id="drawRectangle" name="rectangle">Rysuj</button>
<button id="drawOneClickRectangle" name="oneClickRectangle">Prostokąt</button>
<button id="deleteFigure" name="deleteFigure">Usuń</button>
<button id="saveFields" name="save">Zapisz</button>
<a href="$#{documentFilledWithTestDataUrl}">Testowy dokument</a>
</div>
<div id="status"></div>
<div class="main-section">
<div class="canvas-section">
<canvas id="the-canvas"></canvas>
</div>
<div class="sidebar">
<h2>List pól</h2>
<div>
<label>Źródło danych</label>
<select id="dataSource">
$=(@dataSource, (Object[])null)
$for(@dataSource, (List)$dataSourcesList,{
<option value=$@dataSource[0]>$@dataSource[1]</option>
})
</select>
</div>
<div id="fields-container">
</div>
</div>
</div>
$*fieldsContainerPosition $//szablon pól z czcionką (przykład poniżej)
$*descriptionOfDocumentPosition $//szablon pól (przykład poniżej)
</body>
</html>
The library for drawing data is editing.js with following functions on board:
A map format can be used to save data, below you will find a field position data map example:
{
"top":256,
"left":37,
"height":30,
"width":65,
"dataSourceCode":"pierwszy",
"dataSourcePath":"Price",
"sourceType":"defined",
"ownFieldType":"text",
"dataFontSize":"14",
"dataFontColor2":"#000000",
"dataFontType":"Helvetica",
"dataAlign":"0"
}
An example of the returned data in json for the align parameter:
{
"fields":{
"7":"ALIGN_BASELINE",
"6":"ALIGN_BOTTOM",
"1":"ALIGN_CENTER",
"3":"ALIGN_JUSTIFIED",
"8":"ALIGN_JUSTIFIED_ALL",
"0":"ALIGN_LEFT",
"5":"ALIGN_MIDDLE",
"2":"ALIGN_RIGHT",
"4":"ALIGN_TOP"}
}
The field list view is defined using a template. This view can be freely modified with new parameters - an additional template was used to manage fonts.
<script type="text/x-handlebars-template" id="tmpl-descriptionOfDocumentPosition">
<section class="position{{#if documentPosition.selected}} selected{{/if}}">
<header>
<label class="first">nazwa</label>
<input type="text" name="name" value="{{documentPosition.name}}">
</header>
<section class="description">
<div><label class="first">treść</label></div>
<div><div class="source">
<input hidden type="radio" name="sourceType-{{documentPosition.uuid}}" id="sourceType-{{documentPosition.uuid}}-defined" value="defined" {{#is documentPosition.sourceType "defined"}}checked{{/is}}>
<label for="sourceType-{{documentPosition.uuid}}-defined">źródło</label>
<select name="dataSourcePath">
{{#each fields}}
<option value="{{@key}}" {{#is @key ../documentPosition.dataSourcePath}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
</div>
<div class="own" hidden>
<input type="radio" name="sourceType-{{documentPosition.uuid}}" id="sourceType-{{documentPosition.uuid}}-own" value="own" {{#is documentPosition.sourceType "own"}}checked{{/is}}>
<label for="sourceType-{{documentPosition.uuid}}-own">własna</label>
<select name="ownFieldType">
{{#each ownTypes}}
<option value="{{@key}}" {{#is @key ../documentPosition.ownFieldType}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
</div>
</div>
<div>
<select name="dataFontSize">
{{#each fieldsSize}}
<option value="{{@key}}" {{#is @key ../documentPosition.dataFontSize}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
<input type="color" name="dataFontColor2" value="{{documentPosition.dataFontColor2}}">
<select name="dataFontType" style="width: 150px;">
{{#each fieldsTypes}}
<option value="{{@key}}" {{#is @key ../documentPosition.dataFontType}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
<select name="dataAlign">
{{#each fieldsAlign}}
<option value="{{@key}}" {{#is @key ../documentPosition.dataAlign}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
</div>
</section>
<section class="style">
<h4>Czcionka<h4>
<label>rozmiar</label>
<input type="text" name="fontSize">
</section>
<aside hidden>
<a class="description">Opis</a>
<a class="style">Styl</a>
</aside>
</section>
</script>
<script type="text/x-handlebars-template" id="tmpl-fieldsContainerPosition">
<div class="position">
<input type="text" name="name" value="{{name}}" style="width: 100px;">
<select name="dataSourcePath">
{{#each fields}}
<option value="{{@key}}" {{#is @key ../dataSourcePath}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
<select name="dataFontSize">
{{#each fieldsSize}}
<option value="{{@key}}" {{#is @key ../dataFontSize}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
<select name="dataFontColor">
{{#each fieldsColors}}
<option value="{{@key}}" {{#is @key ../dataFontColor}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
<select name="dataFontType" style="width: 150px;">
{{#each fieldsTypes}}
<option value="{{@key}}" {{#is @key ../dataFontType}}selected{{/is}}>{{this}}</option>
{{/each}}
</select>
</div>
</script>
In order to generate a ready PDF with completed data, we have used the site with the jPALIO Groovy object. The imported PDF template inserts the appropriate data according to the coordinates that were set in the configurator. The data is presented here as PDF data without loading it - it must be downloaded to variables beforehand.
//ID szablonu pdf
Long documentBackgroundFileId = CouponTemplateManager.getCouponTemplateId(supplier.getSupplierId(), languageCode)
// lista pozycji do wypełnienia
java.util.List<DocumentField> fieldsList = DocumentFieldDao.list("PDF_TEMPLATES_id = ? ",[documentBackgroundFileId] as Object[])
// pobranie szablonu
byte[] pdfRawTemplate = CouponTemplateManager.getRawCouponTemplate(supplier.getSupplierId(), languageCode)
//Wypełnienie mapy danymi do podstawienia w PDF
DocumentField firstField = fieldsList[0]
Map<String, String> dataToFill = ["CouponNumber":"${cn}",
"Price":"${FormatUtils.formatCurrency(price)}",
"Currency":"${currency}",
"Subject":"${subject,replaceAll("\\<.*?>","")}",
"PosName":"${posName}",
….
"Description":"${description().replaceAll("\\<.*?>","")}",
"PicContent":"PicContent",
"SupplierLogo":"SupplierLogo"]
//fieldsList
PdfReader pdfReader = new PdfReader((byte[])pdfRawTemplate)
Rectangle orginalPageSize = pdfReader.getPageSize(pageNumber);
CoordinatesCalculator cc = new CoordinatesCalculator(orginalPageSize, pdfReader.getCropBox(pageNumber))
Document pdfDocument = new Document(orginalPageSize);
ByteArrayOutputStream output = new ByteArrayOutputStream();
//use PdfWriter
PdfWriter pdfWriter = PdfWriter.getInstance(pdfDocument, output);
pdfDocument.open()
PdfContentByte canvas = pdfWriter.getDirectContentUnder();
PdfImportedPage pdfPage = pdfWriter.getImportedPage(pdfReader, pageNumber)
canvas.addTemplate(pdfPage , 0f, 0f)
FontFactory.registerDirectories()
fieldsList.each{field->
Map<String, Object> definition = field.getDefinitionMap()
String d = definition["dataSourcePath"]
//dla obrazów
if(d=="Barcode2d" || d=="PicContent"){
Image img = null
if(d=="Barcode2d"){
img = barcode2dData //zmienną należy wcześniej załadować danymi
}else if(d=="PicContent" && posPicContent != null){
img = posPicContentData //zmienną należy wcześniej załadować danymi
}
if(img!=null){
img.setAbsolutePosition(cc.calcX(definition["left"]as float), cc.calcY(definition["top"]+(definition["height"]) as float));
img.scaleAbsolute( definition["width"] as float, definition["height"] as float);
pdfDocument.add(img)
}
}else{
//Dla tekstów
ColumnText ct = new ColumnText(canvas);
Long fontSize = palio.toLong(definition["dataFontSize"])
if(fontSize == null){fontSize = 10}
String fontColor2 = definition["dataFontColor2"]
if(fontColor2 == null){fontColor2 = "#000000"}
String fontType = definition["dataFontType"]
if(fontType == null){fontType = "Times-Roman"}
String alignType = definition["dataAlign"]
if(alignType == null){alignType = "1"}
String df = dataToFill?.get(definition["dataSourcePath"])
if(df == "null"){df =""}
df+=errText
BaseFont helvetica = BaseFont.createFont(fontType, BaseFont.CP1250, BaseFont.EMBEDDED);
Font helvetica16=new Font(helvetica,fontSize, Font.NORMAL, Color.decode((String)fontColor2));
Phrase myText = new Phrase(df, helvetica16);
ct.setSimpleColumn(
myText,
cc.calcX(definition["left"] as float),
cc.calcY(definition["top"]+definition["height"] as float),
cc.calcX(definition["left"] + definition["width"] as float),
cc.calcY(definition["top"] as float),
fontSize,
alignType as int
);
ct.go();
}
}
pdfDocument.close()
return output.toByteArray()