generator szablonów pdf

Generator szablonów PDF - wprowadzenie

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ę.

 

W procesie edycji należy skorzystać z bibliotek javascript:

  • moment.js
  • handlebars v2.0.0
  • fabric.js
  • PDFJS
  • pdf.worker.js

 

Przykładowa strona JPALIO z funkcjonalnościa edycji:


<!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:

  • ładowanie PDF
  • rysowanie nowym pól,
  • edycja pól,
  • usuwanie pól,
  • zapis danych

 

 

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"
}

 

 

Dane do formularzy pobierane są w postaci json z adresów url określonych w body:

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>

Generowanie PDF:

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()
 


Powrót