POC:
Generovanie PDF z XML
Milan Herda 04/2023
Problém
- stal som sa vlastníkom technológie "PDF server"
- bolo mi povedané, že treba vymyslieť lepšie riešenie
Problém
- viacero služieb potrebuje generovať PDF
- aplikácia chce vygenerovať PDF, ale nechce si to programovať sama
- rôzne aplikácie chcú využívať rovnakú šablónu
Pokus č. 1
"PDF server"
Samostatná REST služba naprogramovaná v JS s využitím knižnice pdfmake
Pokus č. 1
"PDF server"
Výhody:
- ✅ jednotný výzor naprieč aplikáciami
- ✅ aplikácia potrebuje vedieť iba názov šablóny a dáta, ktoré chce vložiť
Nevýhody:
- ❌ aplikácia nemá v rukách šablónu
- ❌ kvôli zmene šablóny treba upraviť a nasadiť PDF server
- ❌ PDF server vygeneruje iba taký typ dokumentu, k akému má šablónu
- ❌ okrem dát musíme posielať aj preklady a číselníky
- ❌ silné previazanie na CV
Možnosti
Bez serveru
Každá aplikácia si PDF generuje po svojom. PDF generátor je poskytovaný ako balíček. Šablóny môžu byť v balíčku tiež.
1.
2.
Server bez šablón
Server neobsahuje žiadne šablóny a klient mu posiela dáta a aj definíciu šablóny.
3.
Server so šablónami
Klient pošle na PDF server názov šablóny a dáta, ktoré chce do nej vložiť.
Ako generovať PDF a mať to štandardizované alebo aspoň čiastočne centralizované?
Možnosť č. 1
Bez serveru
Výhody:
- ✅ aplikácia používa pripravený generátor
- ✅ aplikácia môže použiť pripravenú alebo aj vlastnú šablónu
Nevýhody:
- ❌ aplikácia musí byť v rovnakom jazyku, ako generátor
- ❌ treba vymyslieť, ako definovať šablónu v rôznych jazykoch
- ❌ každá aplikácia si musí vymýšľať vlastnú architektúru
Možnosť č. 2
Server bez šablón
Výhody:
- ✅ aplikácia má šablónu vo svojej správe
- ✅ zmena šablóny neovplyvňuje PDF server
- ✅ PDF server vygeneruje hocijaký typ dokumentu
Nevýhody:
- ❌ ťažšie dosiahnuť skutočne jednotný výzor naprieč aplikáciami (požiadavka rovnakých šablón)
- ❌ treba vymyslieť, ako definovať šablónu v rôznych jazykoch
Možnosť č. 3
Server so šablónami
Výhody:
- ✅ jednotný výzor naprieč aplikáciami
- ✅ aplikácia potrebuje vedieť ina názov šablóny a dáta, ktoré chce vložiť
Nevýhody:
- ❌ aplikácia nemá v rukách šablónu
- ❌ kvôli zmene šablóny treba upraviť a nasadiť PDF server
- ❌ PDF server vygeneruje iba taký typ dokumentu, k akému má šablónu
Problém
- viacero služieb potrebuje generovať PDF
- aplikácia chce vygenerovať PDF, ale nechce si to programovať sama
- rôzne aplikácie chcú využívať rovnakú šablónu
Problém
- viacero služieb potrebuje generovať PDF
- aplikácia chce vygenerovať PDF, ale nechce si to programovať sama
- rôzne aplikácie chcú využívať rovnakú šablónu
Naozaj?
Možnosti
Bez serveru
Každá aplikácia si PDF generuje po svojom. PDF generátor je poskytovaný ako balíček. Šablóny môžu byť v balíčku tiež.
1.
2.
Server bez šablón
Server neobsahuje žiadne šablóny a klient mu posiela dáta a aj definíciu šablóny.
3.
Server so šablónami
Klient pošle na PDF server názov šablóny a dáta, ktoré chce do nej vložiť.
Ako generovať PDF a mať to štandardizované alebo aspoň čiastočne centralizované?
🏆
🏆
Proof of Concept
Generovanie PDF na základe XML
Proof of Concept
Generovanie PDF na základe šablóny zapísanej pomocou XML
Pozrite sa na
repozitár a dokumentáciu
- všetci poznajú
- ľahko sa generuje v každom jazyku
- extra jednoducho sa generuje v PHP pomocou twig-u
- ľahké mapovanie na príkazy knižnice pdfmake
Prečo XML?
Ako môže vyzerať XML
<?xml version='1.0' standalone='yes'?>
<document>
<images>
<image-url
name="logo"
src="https://www.monkeyuser.com/assets/images/2017/62-design-patterns-bureaucracy.png"
/>
<image-url
name="cover"
src="https://www.monkeyuser.com/assets/images/2020/177-devs-journey.png"
/>
</images>
<content>
<text fontSize="18" color="grey">Hello World!</text>
<image name="cover" width="250" />
<text pageBreak="before" fontSize="16" color="blue">Table Example</text>
<table>
<tr>
<td>Bunka 1</td>
<td>Bunka 2</td>
</tr>
<tr>
<td>Bunka 3</td>
<td><image name="logo" fit="[500, 500]"/></td>
</tr>
</table>
</content>
</document>
Použitie 1: Konverzia v konzole
yarn ts-node generate-from-file.ts --xml=test.xml --pdf=/tmp/a.pdf
Skript generate-from-file.ts
function readXmlFile(xmlFile: string) {
const buffer = fs.readFileSync(xmlFile);
return buffer.toString();
}
async function main() {
const { xmlFile, pdfFile } = parseCommandLineArguments();
const xml = readXmlFile(xmlFile);
const pdfCommands = await createPdfFromXmlString(xml);
printPdf(pdfCommands, pdfFile);
}
main();
Použitie 2: Konverzia cez REST API
yarn server
XML="
<?xml version='1.0' standalone='yes'?>
<document>
<content>
<text fontSize='18' color='blue'>Hello World</text>
<qr>Have a nice day!</qr>
</content>
</document>
"
XML=`echo $XML | jq -Rsa`
curl -X POST http://127.0.0.1:8080/pdf/create/moj-zivotopis.pdf \
-H 'Content-Type: application/json' \
-d "{\"xml\": $XML}"
Kód skriptu server.ts
import { pipePdfToResponse } from './js/server';
app.post('/pdf/create/:filename', async (req, res) => {
res.status(200);
try {
const fonts = {
Roboto: {
normal: './node_modules/@fontsource/roboto/files/roboto-all-700-normal.woff',
bold: './node_modules/@fontsource/roboto/files/roboto-all-900-normal.woff',
italics: './node_modules/@fontsource/roboto/files/roboto-all-700-italic.woff',
bolditalics: './node_modules/@fontsource/roboto/files/roboto-all-900-italic.woff',
},
};
pipePdfToResponse(
req.body.xml,
fonts,
res,
req.params.filename
);
} catch (e: any) {
// ...
}
});
Použitie 3: Konverzia na webe
yarn dev
Kód pre web
import showWithPdfjs from './js/browser';
const xml = `...`;
const fonts = {
Roboto: {
normal: `${document.location.protocol}//${document.location.host}/node_modules/@fontsource/roboto/files/roboto-all-700-normal.woff`,
bold: `${document.location.protocol}//${document.location.host}/node_modules/@fontsource/roboto/files/roboto-all-900-normal.woff`,
italics: `${document.location.protocol}//${document.location.host}/node_modules/@fontsource/roboto/files/roboto-all-700-italic.woff`,
bolditalics: `${document.location.protocol}//${document.location.host}/node_modules/@fontsource/roboto/files/roboto-all-900-italic.woff`,
},
};
async function main() {
await showWithPdfjs(
xml,
<HTMLCanvasElement>document.getElementById('canvas'),
fonts,
'./node_modules/pdfjs-dist/build/pdf.worker.js',
1
);
}
main();
- API pre jednotlivé použitia ešte nie je ustálená
Disclaimer
- použitie ako RESTový mikroservice (server bez šablón)
- použitie ako konzolový skript (bez serveru)
- použitie ako knižnica vo webstránke (bez serveru)
Videli sme
Čo všetko vieme do PDF vložiť?
Nájdeme v dokumentácii
Text
<?xml version='1.0' standalone='yes'?>
<document>
<content>
Hello World
<text bold='true' fontSize='18' color='blue'>Farebný Hello World</text>
<text
decoration='underline'
decorationStyle='wavy'
decorationColor='green'
>
Zelená vlnovka
</text>
</content>
</document>
# PRESENTING CODE
Stĺpce a stacky
<?xml version='1.0' standalone='yes'?>
<document>
<content>
<columns>
<stack>
<text>Prvý riadok</text>
<text>Druhý riadok</text>
<text>Tretí riadok</text>
</stack>
<text
width='100'
bold='true'
fontSize='18'
color='blue'
>Farebný Hello World</text>
<text
width='50'
decoration='underline'
decorationStyle='wavy'
decorationColor='green'
>Zelená vlnovka</text>
</columns>
</content>
</document>
# PRESENTING CODE
Obrázky
<?xml version='1.0' standalone='yes'?>
<document>
<images>
<image-url name='logo' src='https://www.monkeyuser.com/assets/images/2017/62-design-patterns-bureaucracy.png' />
<image-url name='cover' src='https://www.monkeyuser.com/assets/images/2020/177-devs-journey.png' />
</images>
<content>
<image name='logo' width='250' />
<image name='cover' fit='[200, 200]' />
</content>
</document>
# PRESENTING CODE
SVG obrázky
<?xml version='1.0' standalone='yes'?>
<document>
<content>
<svg-wrapper width='15'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='30'
height='30'
viewBox='0 0 30 30'
>
<g transform='translate(-517 -1486)'><g transform='translate(517 1486)'><circle cx='15' cy='15' r='15' fill='#f7af26'/></g><g transform='translate(524 1493)'><path d='M14,1H2A2,2,0,0,0,0,3v.4L8,7.9l8-4.4V3A2,2,0,0,0,14,1Z' fill='#fff'/><path d='M7.5,9.9,0,5.7V13a2,2,0,0,0,2,2H14a2,2,0,0,0,2-2V5.7L8.5,9.9A1.243,1.243,0,0,1,7.5,9.9Z' fill='#fff'/></g></g>
</svg>
</svg-wrapper>
</content>
</document>
# PRESENTING CODE
Zoznamy
<?xml version='1.0' standalone='yes'?>
<document>
<content>
<ul type='disc'>
<text>Prvá položka</text>
<text>Druhá položka</text>
<ul type='circle'>
<text>Podpoložka 1</text>
<text>Podpoložka 2</text>
</ul>
</ul>
<ol type='lower-alpha' start='5'>
<text>Prvá položka</text>
<text>Druhá položka</text>
<text>Tretia položka</text>
</ol>
</content>
</document>
# PRESENTING CODE
Tabuľky
<?xml version='1.0' standalone='yes'?>
<document>
<content>
<table widths='["30%", "*"]'>
<tr>
<td borderColor='["red", "green", "blue", "violet"]'>Bunka 1</td>
<td fillColor='gold'>Bunka 2</td>
</tr>
<tr>
<td colspan='2'>Bunka 3</td>
<td></td>
</tr>
<tr>
<td rolspan='2'>Bunka 5</td>
<td>Bunka 6</td>
</tr>
<tr>
<td></td>
<td>Bunka 8</td>
</tr>
</table>
</content>
</document>
# PRESENTING CODE
QR kódy
<?xml version='1.0' standalone='yes'?>
<document>
<content>
<qr>Hello World!</qr>
<qr foreground='red' background='gold'>Hello World!</qr>
<qr fit='50'>Hello World!</qr>
</content>
</document>
# PRESENTING CODE
Tabuľka obsahu
<?xml version='1.0' standalone='yes'?>
<document>
<content>
<toc id='main'>Obsah</toc>
<text marginTop='50' tocItem='main' bold='true'>Nadpis 1</text>
<text>Lorem Ipsum...</text>
<text tocItem='main' bold='true'>Nadpis 2</text>
<text>Lorem Ipsum...</text>
<text tocItem='main' tocMargin='[20, 0, 0, 0]' bold='true'>Podnadpis 2.2</text>
<text>Lorem Ipsum...</text>
</content>
</document>
# PRESENTING CODE
Vieme toho viac
- upratovanie kódu a písanie testov
- vyriešenie problému s fontami
- NPM balíček
- prenos existujúcej CV šablóny do twigu?
Ďalšie kroky
Ďakujem za pozornosť
POC: generovanie PDF z XML
By Milan Herda
POC: generovanie PDF z XML
Výsledok proof-of-concept projektu, v ktorom bol cieľ zistiť, či vieme na základe XML šablóny generovať ľubovoľné PDF
- 250