PDF is a popular format for creating documents and reports as it's highly customisable and preserves important details.
This demo shows how to use chart.export() to create PDF reports with custom layouts, fonts and embedded KeyLines charts in vector quality.
Tailor-made layouts
Different documents may have different layout or text requirements, which is why the doc option gives you full control over the appearance of the exported PDF.
Passing an instance of PDFDocument to doc allows you to customise the page layout using the methods provided by PDFKit, such as text(), font() and list().
Customised fonts
The fonts option tackles the limited default font support in PDF by letting you embed your own fonts. Optionally, you can also use the specify separate font files for 'regular' and 'bold' versions using the weight property.
To minimise the size of the output file the export will only embed fonts that were used in the exported image.
Before you start
KeyLines uses external dependencies to run PDF export:
- PDFKit
- SVG-to-PDFKit (for PDFs containing SVGs)
See Adding dependencies for more details and guidance.
Note that some viewers and in-browser viewers may not fully support viewing PDF. For example, we do not recommend using Firefox built-in PDF viewer.
Key functions used:
import KeyLines from "keylines";
import PDFDocument from "pdfkit/js/pdfkit.standalone";
import SVGtoPDF from "svg-to-pdfkit";
import { data, defaultStyle, lorem, initZoomIds } from "./data";
// KeyLines' PDF export uses PDFKit and SVG-to-PDFKit from the global scope
window.PDFDocument = PDFDocument;
window.SVGtoPDF = SVGtoPDF;
const margin = 0.4 * 72; // 72dpi so this is 0.4inches
const iconFonts = {
"Material Icons": { src: "/fonts/MaterialIcons/MaterialIcons-Regular.ttf" },
};
const ralewayFontSrcs = [
{ name: "Raleway-Bold", src: "/fonts/Raleway/Raleway-Bold.ttf" },
{ name: "Raleway-Regular", src: "/fonts/Raleway/Raleway-Regular.ttf" },
{ name: "Raleway-Italic", src: "/fonts/Raleway/Raleway-Italic.ttf" },
];
const timesNewFont = {
regular: "Times-Roman",
bold: "Times-Bold",
italic: "Times-Italic",
};
const helveticaFont = {
regular: "Helvetica",
bold: "Helvetica-Bold",
italic: "Helvetica-Oblique",
};
const ralewayFont = {
regular: "Raleway-Regular",
bold: "Raleway-Bold",
italic: "Raleway-Italic",
};
let chart;
function downloadPdf(pdfUrl) {
// Create the link to download the image
const snapshotLink = document.createElement("a");
snapshotLink.download = `chart-export.pdf`;
snapshotLink.href = pdfUrl;
snapshotLink.click();
// Important - remember to revoke the url afterwards to free it from browser memory
URL.revokeObjectURL(pdfUrl);
}
async function addExportedChartAndCaption(
width,
height,
fontFamily,
captionText,
doc,
) {
doc.font(fontFamily.italic, 10);
const captionHeight = doc.heightOfString(captionText);
// add chart
await chart.export({
type: "pdf",
doc,
fonts: iconFonts,
extents: "view",
fitTo: {
width,
height: height - captionHeight,
},
});
// add caption
doc.moveDown().text(captionText, { align: "center", width });
}
async function loadAndRegisterFonts(fontSrcs, doc) {
let error = false;
for (let i = 0; i < fontSrcs.length; i++) {
const font = fontSrcs[i];
const response = await fetch(font.src);
if (!response.ok) {
console.warn(`Unable to load font ${font.name}`);
error = true;
}
const fontBuffer = await response.arrayBuffer();
doc.registerFont(font.name, fontBuffer);
}
return !error;
}
async function generateLandscapeReport(doc, docTitle, captionText) {
const fontsLoaded = await loadAndRegisterFonts(ralewayFontSrcs, doc);
const fontFamily = fontsLoaded ? ralewayFont : helveticaFont;
// Add title
doc.font(fontFamily.bold, 35).text(docTitle, { align: "center" });
// Allowing a margin separation between them,
// calculate the remaining space for the chart & bullet points
const chartWidth = ((doc.page.width - 3 * margin) * 2) / 3;
const contentHeight = doc.page.height - margin - doc.y;
const chartHeight = (contentHeight * 4) / 5;
// Split the padding equally above and below the main content
doc.y += (contentHeight - chartHeight) / 2;
const contentTop = doc.y;
await addExportedChartAndCaption(
chartWidth,
chartHeight,
fontFamily,
captionText,
doc,
);
// Add the bullet points to the right of the chart
doc.x += chartWidth + margin;
doc.y = contentTop;
doc.font(fontFamily.regular, 16);
for (let i = 0; i < 4; i++) {
doc.list([lorem[4]]).moveDown();
}
}
async function generatePortraitReport(doc, docTitle, captionText) {
const fontFamily = timesNewFont;
// Add title
doc.font(fontFamily.bold, 20).text(docTitle, { align: "center" });
const contentWidth = doc.page.width - 2 * margin;
const chartHeight = (doc.page.height - margin - doc.y) * 0.5;
await addExportedChartAndCaption(
contentWidth,
chartHeight,
fontFamily,
captionText,
doc,
);
doc.moveDown(2);
// Add columns of text
doc.font(fontFamily.regular, 13);
doc.text(lorem[3], { align: "justify", columns: 2 });
}
async function exportReport() {
const orientation = document.querySelector(
'input[name="orientation"]:checked',
).value;
const docTitle = document.getElementById("title").value;
const captionText = document.getElementById("caption").value;
// Create the PDF documnet
const doc = new PDFDocument({ layout: orientation, margin });
const reportGenerator =
orientation === "landscape"
? generateLandscapeReport
: generatePortraitReport;
await reportGenerator(doc, docTitle, captionText);
const docData = [];
doc.on("data", docData.push.bind(docData));
doc.on("end", () => {
downloadPdf(
URL.createObjectURL(new Blob(docData, { type: "application/pdf" })),
);
});
doc.end();
}
async function startKeyLines() {
const options = {
backColour: "#F0F8FF",
selectedNode: { b: "#111", bw: 5, fbc: "#333", fc: "white" },
iconFontFamily: "Material Icons",
imageAlignment: defaultStyle.imageAlignment,
handMode: true,
logo: { u: "/public/images/Logo.png" },
};
chart = await KeyLines.create({ container: "klchart", options });
data.items.forEach((item) => {
if (item.type === "node") {
const kind = item.d.kind;
const icon = defaultStyle.kindIcons[kind];
item.fi = {
t: icon,
c: defaultStyle.nodeColours[kind],
};
}
});
await chart.load(data);
await chart.layout("organic", { fit: false });
chart.zoom("fit", { animate: true, time: 500, ids: initZoomIds });
document.getElementById("exportDoc").addEventListener("click", exportReport);
}
async function loadFontsAndStart() {
// wait for Material icons to load before starting keylines
document.fonts.load("24px 'Material Icons'").then(startKeyLines);
}
window.addEventListener("DOMContentLoaded", loadFontsAndStart);export const data = {
type: "LinkChart",
items: [
{
type: "link",
id: "252-250",
id1: "252",
id2: "250",
w: 3,
off: 0,
},
{
type: "link",
id: "293-291",
id1: "293",
id2: "291",
w: 3,
off: 0,
},
{
type: "link",
id: "252-249",
id1: "252",
id2: "249",
w: 3,
off: 0,
},
{
type: "link",
id: "293-290",
id1: "293",
id2: "290",
w: 3,
off: 0,
},
{
type: "link",
id: "253-252",
id1: "253",
id2: "252",
w: 3,
off: 0,
},
{
type: "link",
id: "294-293",
id1: "294",
id2: "293",
w: 3,
off: 0,
},
{
type: "link",
id: "447-294",
id1: "447",
id2: "294",
w: 3,
off: 0,
},
{
type: "link",
id: "476-253",
id1: "476",
id2: "253",
w: 3,
off: 0,
},
{
type: "link",
id: "663-253",
id1: "663",
id2: "253",
w: 3,
off: 0,
},
{
type: "link",
id: "447-3",
id1: "447",
id2: "3",
w: 3,
off: 0,
},
{
type: "link",
id: "476-3",
id1: "476",
id2: "3",
w: 3,
off: 0,
},
{
type: "link",
id: "663-3",
id1: "663",
id2: "3",
w: 3,
off: 0,
},
{
type: "link",
id: "445-447",
id1: "445",
id2: "447",
w: 3,
off: 0,
},
{
type: "link",
id: "446-447",
id1: "446",
id2: "447",
w: 3,
off: 0,
},
{
type: "link",
id: "469-476",
id1: "469",
id2: "476",
w: 3,
off: 0,
},
{
type: "link",
id: "470-476",
id1: "470",
id2: "476",
w: 3,
off: 0,
},
{
type: "link",
id: "471-476",
id1: "471",
id2: "476",
w: 3,
off: 0,
},
{
type: "link",
id: "472-476",
id1: "472",
id2: "476",
w: 3,
off: 0,
},
{
type: "link",
id: "473-476",
id1: "473",
id2: "476",
w: 3,
off: 0,
},
{
type: "link",
id: "474-476",
id1: "474",
id2: "476",
w: 3,
off: 0,
},
{
type: "link",
id: "475-476",
id1: "475",
id2: "476",
w: 3,
off: 0,
},
{
type: "link",
id: "655-663",
id1: "655",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "656-663",
id1: "656",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "657-663",
id1: "657",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "658-663",
id1: "658",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "659-663",
id1: "659",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "660-663",
id1: "660",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "661-663",
id1: "661",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "662-663",
id1: "662",
id2: "663",
w: 3,
off: 0,
},
{
type: "link",
id: "447-292",
id1: "447",
id2: "292",
w: 3,
off: 0,
},
{
type: "link",
id: "476-251",
id1: "476",
id2: "251",
w: 3,
off: 0,
},
{
type: "link",
id: "663-251",
id1: "663",
id2: "251",
w: 3,
off: 0,
},
{
type: "link",
id: "447-437",
id1: "447",
id2: "437",
w: 3,
off: 0,
},
{
type: "link",
id: "476-460",
id1: "476",
id2: "460",
w: 3,
off: 0,
},
{
type: "link",
id: "476-464",
id1: "476",
id2: "464",
w: 3,
off: 0,
},
{
type: "link",
id: "663-654",
id1: "663",
id2: "654",
w: 3,
off: 0,
},
{
type: "link",
id: "447-441",
id1: "447",
id2: "441",
w: 3,
off: 0,
},
{
type: "link",
id: "447-444",
id1: "447",
id2: "444",
w: 3,
off: 0,
},
{
type: "link",
id: "476-468",
id1: "476",
id2: "468",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-445",
id1: "3",
id2: "445",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-446",
id1: "3",
id2: "446",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-469",
id1: "3",
id2: "469",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-470",
id1: "3",
id2: "470",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-471",
id1: "3",
id2: "471",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-472",
id1: "3",
id2: "472",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-473",
id1: "3",
id2: "473",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-474",
id1: "3",
id2: "474",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-475",
id1: "3",
id2: "475",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-655",
id1: "3",
id2: "655",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-656",
id1: "3",
id2: "656",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-657",
id1: "3",
id2: "657",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-658",
id1: "3",
id2: "658",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-659",
id1: "3",
id2: "659",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-660",
id1: "3",
id2: "660",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-661",
id1: "3",
id2: "661",
w: 3,
off: 0,
},
{
type: "link",
id: "garage-damages-3-662",
id1: "3",
id2: "662",
w: 3,
off: 0,
},
{
type: "link",
id: "people-252-460",
id1: "252",
id2: "460",
w: 3,
off: 0,
},
{
type: "link",
id: "people-252-464",
id1: "252",
id2: "464",
w: 3,
off: 0,
},
{
type: "link",
id: "people-252-468",
id1: "252",
id2: "468",
w: 3,
off: 0,
},
{
type: "link",
id: "people-252-654",
id1: "252",
id2: "654",
w: 3,
off: 0,
},
{
type: "link",
id: "people-293-437",
id1: "293",
id2: "437",
w: 3,
off: 0,
},
{
type: "link",
id: "people-293-441",
id1: "293",
id2: "441",
w: 3,
off: 0,
},
{
type: "link",
id: "people-293-444",
id1: "293",
id2: "444",
w: 3,
off: 0,
},
{
type: "link",
id: "people-437-441",
id1: "437",
id2: "441",
w: 3,
off: 0,
},
{
type: "link",
id: "people-437-444",
id1: "437",
id2: "444",
w: 3,
off: 0,
},
{
type: "link",
id: "people-441-444",
id1: "441",
id2: "444",
w: 3,
off: 0,
},
{
type: "link",
id: "people-460-464",
id1: "460",
id2: "464",
w: 3,
off: 0,
},
{
type: "link",
id: "people-460-468",
id1: "460",
id2: "468",
w: 3,
off: 0,
},
{
type: "link",
id: "people-464-468",
id1: "464",
id2: "468",
w: 3,
off: 0,
},
{
type: "link",
id: "person-garage-3-252",
id1: "3",
id2: "252",
w: 3,
off: 0,
},
{
type: "link",
id: "person-garage-3-293",
id1: "3",
id2: "293",
w: 3,
off: 0,
},
{
type: "node",
id: "3",
t: "Torphy - Ebert",
d: {
kind: "garage",
},
c: "rgba(255,255,255,1)",
b: "rgba(0,107,95,1)",
x: -1497.4758377075195,
y: -1202.4347877502441,
},
{
type: "node",
id: "249",
t: "0065 Cordia Throughway, Vivienneton",
d: {
kind: "address",
},
c: "rgba(255,255,255,1)",
b: "rgba(164,39,104,1)",
x: -1466.7487869262695,
y: -454.68124771118164,
},
{
type: "node",
id: "250",
t: "055 9497 2435",
d: {
kind: "telephone",
},
c: "rgba(255,255,255,1)",
b: "rgba(164,39,104,1)",
x: -1550.6120681762695,
y: -432.18075942993164,
},
{
type: "node",
id: "251",
t: "QWE09EE",
d: {
kind: "vehicle",
},
c: "rgba(255,255,255,1)",
b: "rgba(127,203,104,1)",
x: -1475.888557434082,
y: -870.048038482666,
},
{
type: "node",
id: "252",
t: "Ellen Larkin",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1564.0012283325195,
y: -686.4468421936035,
},
{
type: "node",
id: "253",
t: "Policy 910",
d: {
kind: "policy",
},
c: "rgba(255,255,255,1)",
b: "rgba(31,120,180,1)",
x: -1450.9795608520508,
y: -805.0684852600098,
},
{
type: "node",
id: "290",
t: "2253 Desiree Drive, Lake Cadeside",
d: {
kind: "address",
},
c: "rgba(255,255,255,1)",
b: "rgba(164,39,104,1)",
x: -1216.8798904418945,
y: -1862.4563331604004,
},
{
type: "node",
id: "291",
t: "0313 652 3599",
d: {
kind: "telephone",
},
c: "rgba(255,255,255,1)",
b: "rgba(164,39,104,1)",
x: -1267.6861038208008,
y: -1925.0965309143066,
},
{
type: "node",
id: "292",
t: "ASD984R",
d: {
kind: "vehicle",
},
c: "rgba(255,255,255,1)",
b: "rgba(127,203,104,1)",
x: -1759.520393371582,
y: -1844.9278297424316,
},
{
type: "node",
id: "293",
t: "Ahmad Lemke",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1414.8723831176758,
y: -1728.5325050354004,
},
{
type: "node",
id: "294",
t: "Policy 961",
d: {
kind: "policy",
},
c: "rgba(255,255,255,1)",
b: "rgba(31,120,180,1)",
x: -1520.4905471801758,
y: -1879.7627296447754,
},
{
type: "node",
id: "437",
t: "August Fay DVM",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1417.4956741333008,
y: -1924.5281105041504,
},
{
type: "node",
id: "441",
t: "Lora Nitzsche",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1518.036994934082,
y: -1976.313877105713,
},
{
type: "node",
id: "444",
t: "Yesenia Schinner",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1610.619026184082,
y: -1909.568515777588,
},
{
type: "node",
id: "445",
t: "Damage 129",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1532.5708084106445,
y: -1479.2611122131348,
},
{
type: "node",
id: "446",
t: "Damage 294",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1633.2661209106445,
y: -1465.8401832580566,
},
{
type: "node",
id: "447",
t: "Claim 451",
d: {
kind: "claim",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,47,63,1)",
x: -1573.300666809082,
y: -1687.7504615783691,
},
{
type: "node",
id: "460",
t: "Nya Reilly",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1756.694465637207,
y: -634.4872779846191,
},
{
type: "node",
id: "464",
t: "Linda Kub",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1857.9077224731445,
y: -635.5139503479004,
},
{
type: "node",
id: "468",
t: "Lori Lueilwitz",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1722.832649230957,
y: -535.9418678283691,
},
{
type: "node",
id: "469",
t: "Damage 468",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1617.355110168457,
y: -1022.916675567627,
},
{
type: "node",
id: "470",
t: "Damage 586",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1537.0869216918945,
y: -971.7761421203613,
},
{
type: "node",
id: "471",
t: "Damage 638",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1706.604377746582,
y: -1131.728801727295,
},
{
type: "node",
id: "472",
t: "Damage 581",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1773.801399230957,
y: -1051.3435401916504,
},
{
type: "node",
id: "473",
t: "Damage 602",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1769.793586730957,
y: -1156.489854812622,
},
{
type: "node",
id: "474",
t: "Damage 771",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1695.019905090332,
y: -1060.3865242004395,
},
{
type: "node",
id: "475",
t: "Damage 927",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1814.770149230957,
y: -1105.2457389831543,
},
{
type: "node",
id: "476",
t: "Claim 503",
d: {
kind: "claim",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,47,63,1)",
x: -1709.5419998168945,
y: -877.073184967041,
},
{
type: "node",
id: "654",
t: "Marietta Turner Jr.",
d: {
kind: "person",
},
c: "rgba(255,255,255,1)",
b: "rgba(166,116,186,1)",
x: -1324.6194534301758,
y: -770.9541358947754,
},
{
type: "node",
id: "655",
t: "Damage 712",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1364.6671829223633,
y: -1311.3736000061035,
},
{
type: "node",
id: "656",
t: "Damage 708",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1211.3150100708008,
y: -1058.8240699768066,
},
{
type: "node",
id: "657",
t: "Damage 233",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1188.2261428833008,
y: -1203.4983215332031,
},
{
type: "node",
id: "658",
t: "Damage 912",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1270.8091506958008,
y: -1193.0131721496582,
},
{
type: "node",
id: "659",
t: "Damage 500",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1183.8325271606445,
y: -1130.365379333496,
},
{
type: "node",
id: "660",
t: "Damage 120",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
x: -1288.408576965332,
y: -1298.7427940368652,
},
{
type: "node",
id: "661",
t: "Damage 855",
d: {
kind: "damage",
},
c: "rgba(255,255,255,1)",
b: "rgba(255,134,21,1)",
// ...truncated 96 lines<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PDF Reports</title>
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/keylines.css">
<link
rel="stylesheet"
type="text/css"
href="@ci/theme/kl/css/minimalsdk.css"
>
<link
rel="stylesheet"
type="text/css"
href="@ci/theme/kl/css/sdk-layout.css"
>
<link rel="stylesheet" type="text/css" href="@ci/theme/kl/css/demo.css">
<link
rel="stylesheet"
type="text/css"
href="@ci/theme/kl/css/examples-chrome.css"
>
<link
rel="stylesheet"
type="text/css"
href="https://fonts.googleapis.com/icon?family=Material+Icons"
>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="chart-wrapper demo-cols">
<div id="lhs" class="flex1">
<div
id="lhsVisual"
class="toggle-content is-visible"
style="width: 100%; height: 100%"
>
<div id="klchart" class="klchart"></div>
</div>
</div>
<div class="rhs citext" id="demorhscontent">
<div class="tab-content-panel">
<div id="controlsTab" class="toggle-content is-visible tabcontent">
<p>
Position the chart as desired, then export it as a high resolution
image into a PDF report.
</p>
<form id="rhsForm" autocomplete="off" onsubmit="return false;">
<div class="cicontent">
<h4 style="padding-top: 10px">Report template</h4>
<label class="radio inline"
><input
type="radio"
name="orientation"
value="portrait"
style="opacity: 0"
checked
id="reportPortrait"
><svg
width="90"
height="90"
viewBox="0 0 254 254"
xmlns="https://www.w3.org/2000/svg"
>
<g
transform="matrix(.7882 0 0 .7882 43.634 16.84)"
stroke="#333"
>
<rect
width="227.118"
height="276.425"
x="-.885"
y="-1.457"
ry="0"
transform="matrix(.9382 0 0 1 2.275 2.953)"
fill="#fff"
stroke-width="2.732"
></rect>
<path
d="M38.907 21.684h138.879"
fill="none"
stroke-width="7.695"
></path>
<g
transform="translate(-5.82 -.49)"
fill="none"
stroke-width="1.587"
>
<circle cx="74.326" cy="49.435" r="10.134"></circle>
<circle cx="107.659" cy="86.68" r="10.134"></circle>
<circle cx="76.962" cy="125.051" r="10.134"></circle>
<circle cx="157.114" cy="87.107" r="10.134"></circle>
<path
d="M80.324 58.103l20.118 21.92M101.4 94.846L83.76 117.17M117.743 87.226l28.915.464"
stroke-width="1.465"
></path>
</g>
<g fill="#333" stroke-width="2.908">
<path
d="M15.162 151.11h91.604M15.162 180.392h91.604M15.162 219.435h91.604M15.162 209.674h91.604M15.162 229.196h91.604M15.162 248.717h91.604M15.162 238.956h91.604M15.162 258.478h91.604M15.162 160.87h91.604M15.162 190.152h91.604M15.162 170.63h91.604M15.162 199.913h91.604"
transform="matrix(.9382 0 0 1 2.275 2.953)"
></path>
<path
d="M15.162 151.11h91.604M15.162 180.392h91.604M15.162 219.435h91.604M15.162 209.674h91.604M15.162 229.196h91.604M15.162 248.717h91.604M15.162 238.956h91.604M15.162 258.478h91.604M15.162 160.87h91.604M15.162 190.152h91.604M15.162 170.63h91.604M15.162 199.913h91.604"
transform="matrix(.9382 0 0 1 99.414 2.953)"
></path>
</g>
</g>
</svg></label
><label class="radio inline"
><input
type="radio"
name="orientation"
value="landscape"
style="opacity: 0"
id="reportLandscape"
><svg
width="90"
height="90"
viewBox="0 0 254 254"
xmlns="https://www.w3.org/2000/svg"
>
<g stroke="#333">
<rect
width="294.776"
height="212.949"
x="-.885"
y="-1.457"
ry="0"
fill="#fff"
stroke-width="2.732"
transform="matrix(.74636 0 0 .79553 17.99 43.724)"
></rect>
<path
d="M70.836 58.272h109.465"
fill="none"
stroke-width="6.0651207199999995"
></path>
<g
transform="matrix(.79553 0 0 .79553 -1.755 57.632)"
fill="none"
stroke-width="1.587"
>
<circle cx="74.326" cy="49.435" r="10.134"></circle>
<circle cx="107.659" cy="86.68" r="10.134"></circle>
<circle cx="76.962" cy="125.051" r="10.134"></circle>
<circle cx="157.114" cy="87.107" r="10.134"></circle>
<path
d="M80.324 58.103l20.118 21.92M101.4 94.846L83.76 117.17M117.743 87.226l28.915.464"
stroke-width="1.465"
></path>
</g>
<g fill="#333" stroke-width="2.24021248">
<path
d="M154.765 84.25h68.37M154.765 96.452h68.37M154.765 90.351h68.37M154.765 102.553h68.37"
></path>
<path
d="M154.765 119.622h68.37M154.765 131.824h68.37M154.765 125.723h68.37M154.765 137.925h68.37"
></path>
<path
d="M154.765 154.994h68.37M154.765 167.196h68.37M154.765 161.095h68.37M154.765 173.297h68.37"
></path>
</g>
</g>
</svg></label
>
<h4>Page title</h4>
<input
id="title"
type="text"
style="height: 30px"
value="Investigative report"
>
<h4>Figure caption</h4>
<textarea id="caption" type="text" cols="40" rows="4">
Policy 910 has two claims, each with an unusually high number of damage reports</textarea><input
class="btn"
type="button"
value="Export"
style="width: 100%"
id="exportDoc"
>
</div>
</form>
</div>
</div>
</div>
</div>
<script type="module" src="./code.js"></script>
</body>
</html>/* --- custompdf.css --- */
#caption {
border: 1px solid rgba(179, 179, 179, 0.705);
padding-left: 7px;
padding-top: 8px;
resize: none;
}
[type="radio"] + svg {
background-color: rgba(179, 179, 179, 0.521);
border: 1px solid rgba(179, 179, 179, 0.705);
margin-left: -18px;
margin-right: 10px;
}
[type="text"] {
font-family: muli;
width: calc(100% - 6px);
margin: 8px 3px 10px 3px;
}
[type="radio"]:checked + svg {
border: 2px solid #009968;
}
[type="radio"]:hover + svg {
border: 2px solid #00c980;
}