Printable documents (PDF)
Roadmap Phase 21. The optional tesseraql-pdf module adds a pdf codec behind the standard
file-codec SPI: any query-export or file-export route can stream a printable document
instead of a tabular file. Putting the jar on the classpath is the whole install
(ServiceLoader, design ch. 28, 47). PDF is output-only: file-import rejects it
(TQL-LD-2830).
A printable route
Section titled “A printable route”version: tesseraql/v1id: users.printkind: routerecipe: query-export
security: auth: bearer policy: users.read
sql: file: print.sql
export: format: pdf filename: users.pdf template: print.html # colocated with the route; omit for the built-in grid columns: - { name: name, header: 氏名 } - { name: status, header: 状態 }Without a template: the built-in grid lays the rows out as a plain A4 table with repeating
column headers and Page n / total numbering — useful for ad-hoc listings. With a template,
the document is fully app-designed.
file-export works the same way for asynchronous extraction ({path}/{transferId}/file
downloads the finished document), including after: follow-up statements.
Print templates
Section titled “Print templates”A print template is an app-authored XHTML file (well-formed XML, .html), colocated with the
route and rendered through the standard template engine (design ch. 12) before PDF conversion.
The model is:
rows— the query rows, values already formatted per the column mappings (locale, time zone,format:patterns — the same rules as CSV output)columns—{ name, header }per declared columnfontFamilies— the app’s font families as a CSSfont-familylist, for templates that do not name fonts themselves
Page-oriented CSS drives the print layout:
<html xmlns:th="http://www.thymeleaf.org"><head> <style> @page { size: A4; margin: 20mm 15mm; @bottom-center { content: "Page " counter(page) " / " counter(pages); font-size: 8pt; } } body { font-family: 'TesseraQL Sample Gothic'; font-size: 10pt; } thead { display: table-header-group; } /* repeat column headers on every page */ </style></head><body> <h1>利用者一覧</h1> <table> <thead><tr><th th:each="column : ${columns}" th:text="${column.header}">h</th></tr></thead> <tbody> <tr th:each="row : ${rows}"><td th:text="${row.name}">name</td></tr> </tbody> </table></body></html>Templates are app-authored and confined: the template must live inside the app home
(TQL-LD-2832), every url(...) it references resolves only to files inside the app home,
and the network is never fetched during an export. Data interpolates with th:text, escaped
by default.
Fonts (CJK included)
Section titled “Fonts (CJK included)”Fonts under the app home’s fonts/ directory (*.ttf, *.otf) embed automatically, each
registered under the family name carried in the font itself — so
font-family: 'Noto Sans JP' works as soon as NotoSansJP-Regular.ttf is in fonts/.
Registration order is file-name order, deterministic across machines. The examples ship
TesseraQL Sample Gothic, a small renamed Noto Sans JP glyph subset (OFL 1.1, see
examples/user-admin-app/fonts/README.md); real applications should ship complete fonts.
Deterministic output
Section titled “Deterministic output”Rendered documents are normalized so exports stay reproducibility-friendly (design ch. 48):
the producer is fixed to TesseraQL, creation/modification dates and XMP metadata are
dropped, and the trailer /ID derives from a fixed seed. The same rows through the same
template yield byte-identical PDFs.
Engine and licensing
Section titled “Engine and licensing”The renderer is openhtmltopdf (LGPL),
adopted at the design ch. 50 decision point for its full page-oriented CSS support. The LGPL
dependency never leaks into applications that do not print: tesseraql-pdf is an opt-in
module - no runtime module depends on it, and without the jar a format: pdf route fails
loudly at build time (TQL-LD-2801). Inside the module the engine sits behind the
PdfEngine ServiceLoader SPI with the tesseraql.pdf.engine system property (default
openhtml), so a replacement stack can ship as a drop-in jar without touching the codec.
Testing and coverage
Section titled “Testing and coverage”A suite case that exercises the route’s extraction SQL proves the data the document renders:
- name: the printable user list extraction returns sato sql: file: web/api/users/print/print.sql params: {} expect: rowCount: 1 rows: - name: satoThe document coverage kind declares every route exporting a printable document and counts
it covered when a suite case exercises one of its SQL artifacts; gate it with
coverage.thresholds.document.
| Code | Severity | Meaning |
|---|---|---|
TQL-YAML-1005 |
error | a pdf export declares workbook-only options (sheet:, startCell:) |
TQL-YAML-1006 |
error | the pdf template is not an .html file, or is missing |
Error codes
Section titled “Error codes”| Code | Meaning |
|---|---|
TQL-LD-2830 |
PDF is output-only; file-import cannot read it |
TQL-LD-2831 |
rendering failed (bad template markup, engine error) |
TQL-LD-2832 |
the template lies outside the app resource root |
TQL-LD-2833 |
tesseraql.pdf.engine names no available engine |
TQL-LD-2834 |
a font under fonts/ cannot be read |