Szablon PDF pozwala tworzyć/zmieniać klientowi widoki plików PDF bez konieczności zmian w kodzie. Użytkownik tworzy dowolny szablon i importuje go do systemu. Następnie tworzy pola na szablonie w których ma zostać wypełniona zmienna treść np. cena towarów na bonie, informacje na ulotce czy pola na recepcie. Zmienne do wypełnienia są tworzone przez programistę.
<!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>
Biblioteka zarządzająca rysowaniem danych to editing.js. Zawarte w niej są wszelkie funkcje takie jak:
W celu zapisu danych wykorzystana została mapa, przykładowa mapa z danymi pola:
{
"top":256,
"left":37,
"height":30,
"width":65,
"dataSourceCode":"pierwszy",
"dataSourcePath":"Price",
"sourceType":"defined",
"ownFieldType":"text",
"dataFontSize":"14",
"dataFontColor2":"#000000",
"dataFontType":"Helvetica",
"dataAlign":"0"
}
Przykład zwróconej danej w json dla parametru align:
{
"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"}
}
Widok listy pól jest definiowany za pomocą szablonu który w tym przypadku wygląda jak poniżej. Widok ten można dowolnie zmieniać - rozszerzać o nowe parametry lub je usuwać. W przypadku czcionki wykorzystano dodatkowy szablon.
<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>
W celu wygenerowania gotowego PDF z wypełnionymi danymi należy skorzystaliśmy ze strony z obiektem jPALIO typu Groovy. Na zaimportowany szablon PDF wstawiane są odpowiednie dane według współrzędnych które były ustalone w konfiguratorze. Przedstawiony jest tu sam zapis danych na PDF bez ich ładowania – należy je wcześniej pobrać do zmiennych.
//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()