This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Report-Engine
Setup
Starting with version 3.14 we use Prince for report generation. Our Prince docker images are hosted with VSHN and require basic auth credentials to access.
To create a report, we write Freemarker, which gets transformed to HTML, in our backend. Afterwards, the HTML, together with any used fonts, are sent to our service that runs Prince. The service calls Prince, turning the HTML into a PDF, which then gets streamed back to our backend, where we deliver it to the user.
Credentials
Credentials to access these containers are stored in Bitwarden. Developers must configure these credentials for
local development using the application.properties shown below.
Configuration
The following application.properties are available to configure the Prince pdf generation.
Name |
Default |
Description |
|---|---|---|
nice2.conversion.pdf.reportservice.url |
The url of the report service pod |
|
nice2.conversion.pdf.reportservice.username |
The username used to access prince. See Bitwarden. |
|
nice2.conversion.pdf.reportservice.password |
The password used to access Prince. See Bitwarden. |
|
nice2.conversion.pdf.keepTemporaryFiles |
false |
A property that may be set if you want to keep temporary files. This is very useful for debugging purposes. |
Differences to old engines
Positioning
Setting position: absolute or position: fixed works differently in
Prince than one might expect. Both place an element at a fixed position on the
current page, they do not get repeated. Default positioning (static) works
as in regular websites, Prince just moves elements to new pages when necessary.
Migrations
From WKHTMLTOPDF → PrinceXML
What needs to be done:
#headerand#footerselectors no longer work → rewrite to use the new classes.headerand.footerdisplay: nonein headers and footers no longer works → switch to the new configuration.first-pageand.note-first-pageno longer work. If they were used for something other than showing or hiding elements, they need to be rewritten to#header-first-pageand#header-not-first-page
Example CSS WKHTMLTOPDF:
#header {
#phsg_logo {
position: fixed;
top: 24mm;
right: @base-document-margin-right;
}
&.not-first-page {
#phsg_logo {
display: none;
}
}
}
#footer {
#pagination,
#phsg_footer_address {
position: absolute;
top: 14mm;
font-size: 7.5pt;
}
#phsg_footer_address {
left: @base-document-margin-left;
width: @page-width - (@base-document-margin-left + @base-document-margin-right);
}
&.first-page {
#pagination {
display: none;
}
}
&.not-first-page {
#phsg_footer_address {
display: none;
}
}
}
Example Contribution WKHTMLTOPDF:
@Bean
public CorporateDesignContribution phsgCorrespondence215CorporateDesign() {
CorporateDesignContribution bean = new CorporateDesignContribution();
bean.setUniqueId("phsg_correspondence_2_15");
bean.setLabelTextResourceKey("corporatedesign.phsg_correspondence_2_15");
bean.setLess(findModelResource("corporatedesign/phsg_correspondence_2_15.less"));
bean.setHeaderElements(List.of(TemplateSnippetContribution.withUniqueId("phsg_logo")));
bean.setFooterElements(List.of(TemplateSnippetContribution.withUniqueId("phsg_footer_address"), TemplateSnippetContribution.withUniqueId("pagination")));
return bean;
}
Migrated CSS:
.header {
#phsg_logo {
position: fixed;
top: 24mm;
right: @base-document-margin-right;
}
}
.footer {
#pagination,
#phsg_footer_address {
position: absolute;
top: 14mm;
font-size: 7.5pt;
}
#phsg_footer_address {
left: @base-document-margin-left;
width: @page-width - (@base-document-margin-left + @base-document-margin-right);
}
}
Migrated Contribution
@Bean
public CorporateDesignContribution phsgCorrespondence215CorporateDesign() {
CorporateDesignContribution bean = new CorporateDesignContribution();
bean.setUniqueId("phsg_correspondence_2_15");
bean.setLabelTextResourceKey("corporatedesign.phsg_correspondence_2_15");
bean.setLess(findModelResource("corporatedesign/phsg_correspondence_2_15.less"));
bean.setHeaderElements(new MarginElementContribution("phsg_logo", true, false)); // First page only
bean.setFooterElements(new MarginElementContribution("phsg_footer_address", true, false), // First page only
new MarginElementContribution("pagination", false, true)); // Following pages only
return bean;
}
From Paged.js & Gotenberg
What needs to be done:
#headerand#footerselectors no longer work → rewrite to use the new classes.headerand.footerdisplay: nonein headers and footers no longer works → switch to the new configuration@page :nth(n+2)no longer works. If it was used for something other than showing or hiding elements, it needs to be rewritten to#header-first-pageand#header-not-first-pagecheck vertical alignment
Prince calculates the reserved place for absolutely positioned elements as well as headers and footers differently, so you may need to adjust
padding-topof the top most element ortopof any absolutely positioned elementssee usages of
@finance-content-margin-topfor example from finance reportsoften times, simply setting
padding-topon.document-wrappercan be enough
.document-wrapper { padding-top: @correspondence-document-padding-top - @header-height; }
Example CSS Paged.JS / Gotenberg:
#header {
#profil_logo {
position: absolute;
top: 13mm;
right: @base-document-margin-right;
svg {
width: 33mm;
height: auto;
}
}
}
// hide logo on every page after the first one
@page :nth(n+2) {
#profil_logo {
display: none;
}
}
Example Contribution Paged.JS / Gotenberg:
@Bean
public CorporateDesignContribution profilCorrespondence314CorporateDesign() {
CorporateDesignContribution bean = new CorporateDesignContribution();
bean.setUniqueId("profil_correspondence_314");
bean.setLabelTextResourceKey("corporatedesign.profil_correspondence_314");
bean.setLess(findModelResource("corporatedesign/profil_correspondence_314.less"));
bean.setHeaderElements(List.of(TemplateSnippetContribution.withUniqueId("profil_logo")));
bean.setFooterElements(List.of(TemplateSnippetContribution.withUniqueId("inqualis_logo"), TemplateSnippetContribution.withUniqueId("profil_person"), TemplateSnippetContribution.withUniqueId("pagination")));
return bean;
}
Migrated CSS:
.header {
#profil_logo {
position: absolute;
top: 13mm;
right: @base-document-margin-right;
svg {
width: 33mm;
height: auto;
}
}
}
Migrated Contribution:
@Bean
public CorporateDesignContribution profilCorrespondence314CorporateDesign() {
CorporateDesignContribution bean = new CorporateDesignContribution();
bean.setUniqueId("profil_correspondence_314");
bean.setLabelTextResourceKey("corporatedesign.profil_correspondence_314");
bean.setLess(findModelResource("corporatedesign/profil_correspondence_314.less"));
bean.setHeaderElements(new MarginElementContribution("profil_logo", true, false)); // This controls that it only appears on the first page!
bean.setFooterElements(new MarginElementContribution("inqualis_logo"), new MarginElementContribution("profil_person"), new MarginElementContribution("pagination"));
return bean;
}