Accessibility Overview

This sample shows how to create a tagged PDF using the Expense Report sample as a base.

The sample uses the info.title, tagged, displayTitle and lang properties to satisfy the basic Tagged PDF requirements when creating the instance of the PdfDocument class.

The sample uses the tag method to create tags and mark content and the addTag method to add tags to the logical document tree. The artifact method is used to mark decorative content as artifacts.

Note: Tagged PDF requires document version 1.4 or higher.

import 'bootstrap.css'; import './styles.css'; import * as wijmo from '@mescius/wijmo'; import * as pdf from '@mescius/wijmo.pdf'; import { getEmployees } from './data'; // var TableSection; (function (TableSection) { TableSection[TableSection["header"] = 0] = "header"; TableSection[TableSection["body"] = 1] = "body"; TableSection[TableSection["footer"] = 2] = "footer"; })(TableSection || (TableSection = {})); // document.readyState === 'complete' ? init() : window.onload = init; // function init() { document.querySelector('#btnExport').addEventListener('click', () => { let doc = new pdf.PdfDocument({ info: { title: 'Expense Report' }, tagged: true, displayTitle: true, lang: 'en-US', version: pdf.PdfVersion.v1_5, // The header will be automatically marked as a pagination artifact. header: { declarative: { text: 'Expense Report\t&[Page]\\&[Pages]', font: new pdf.PdfFont('times', 12), brush: '#bfc1c2' } }, lineGap: 2, pageSettings: { margins: { left: 36, right: 36, top: 36, bottom: 36 } }, ended: (_, args) => pdf.saveBlob(args.blob, 'Document.pdf') }); // getEmployees().forEach((employee, i, arr) => { drawEmployee(doc, employee); // if (i < arr.length - 1) { doc.addPage(); } }); // doc.end(); }); } // const ColWidth = 80, RowHeight = 18, ThinPen = new pdf.PdfPen('#000000', 0.5); // function drawEmployee(doc, employee) { let tot = employee.expenses.totals, expenses = employee.expenses.items.sort((a, b) => a.date.getTime() - b.date.getTime()), minDate = expenses[0].date, maxDate = expenses[expenses.length - 1].date, columns = [ { header: 'Date', binding: 'date', format: 'd' }, { header: 'Description', binding: 'description', format: 'c' }, { header: 'Hotel', binding: 'hotel', format: 'c' }, { header: 'Transport', binding: 'transport', format: 'c' }, { header: 'Meal', binding: 'meal', format: 'c' }, { header: 'Fuel', binding: 'fuel', format: 'c' }, { header: 'Misc', binding: 'misc', format: 'c' }, { header: 'Total', binding: 'total', format: 'c' } ], bold = new pdf.PdfFont('times', 10, 'normal', 'bold'); // // * draw captions * doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Purpose: ', null, null, { font: bold, continued: true }); doc.drawText(employee.purpose); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('From: ', 380, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(minDate, wijmo.DataType.String, 'd')); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('To: ', 470, 0, { font: bold, continued: true }); doc.drawText(wijmo.changeType(maxDate, wijmo.DataType.String, 'd')); })); // doc.moveDown(2); // let y = doc.y; // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Name: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.name); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Position: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.position); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('SSN: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.ssn); })); // y = doc.y; // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Department: ', 20, y, { font: bold, continued: true }); doc.drawText(employee.department); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Manager: ', 190, y, { font: bold, continued: true }); doc.drawText(employee.manager); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Employee ID: ', 360, y, { font: bold, continued: true }); doc.drawText(employee.id); })); // doc.moveDown(2); // // * draw table * doc.saveState(); // y = 0; let scale = doc.width / (columns.length * ColWidth), docY = doc.y; // if (scale > 1) { scale = 1; } // doc.scale(scale, scale, new wijmo.Point(0, docY)); doc.translate(0, docY); // let thead = doc.tag(pdf.PdfTagType.THead), tbody = doc.tag(pdf.PdfTagType.TBody), tfoot = doc.tag(pdf.PdfTagType.TFoot), table = doc.tag(pdf.PdfTagType.Table); // doc.addTag(table); table.add(thead); table.add(tbody); table.add(tfoot); // // header thead.add(renderRow(doc, TableSection.header, y, columns, (column) => column.header, null, bold, '#fad9cd')); y = RowHeight; // // body expenses.forEach(item => { tbody.add(renderRow(doc, TableSection.body, y, columns, (column) => item[column.binding], (column) => column.format)); y += RowHeight; }); // // footer let totRow = ['Total', '', tot.hotel, tot.transport, tot.meal, tot.fuel, tot.misc, tot.total]; tfoot.add(renderRow(doc, TableSection.footer, y, totRow, null, () => 'c', bold, '#fad9cd')); y += RowHeight; // doc.y = docY + y * scale; // doc.restoreState(); // doc.moveDown(2); // // * draw captions * doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Subtotal: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total - employee.advance, wijmo.DataType.String, 'c')); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Cash Advance: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(employee.advance, wijmo.DataType.String, 'c')); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Total: ', 400, doc.y, { font: bold, continued: true }); doc.drawText(wijmo.changeType(tot.total, wijmo.DataType.String, 'c')); })); // doc.moveDown(2); checkLineAvailable(doc); // y = doc.y; textWithPlaceholder(doc, 0, y, 'Employee signature:', 150); textWithPlaceholder(doc, 300, y, 'Date:', 75); // doc.moveDown(); checkLineAvailable(doc); // y = doc.y; textWithPlaceholder(doc, 0, y, 'Approved by:', 150); textWithPlaceholder(doc, 300, y, 'Date:', 75); } // function checkLineAvailable(doc) { if (doc.height - doc.y < doc.lineHeight() + doc.lineGap) { doc.addPage(); } } // function renderRow(doc, section, y, values, valueGetter, formatGetter, font, brush) { let trTag = doc.tag(pdf.PdfTagType.TR); // values.forEach((v, idx) => { let x = idx * ColWidth; // doc.artifact(() => doc.paths.rect(x, y, ColWidth, RowHeight).fill(brush || '#f4b19b'), { type: pdf.PdfArtifactType.Layout }); // let value = valueGetter != null ? valueGetter(v) : v || '', format = formatGetter != null ? formatGetter(v) : ''; // if (value !== 'Total') { value = wijmo.changeType(value, wijmo.DataType.String, format); } // trTag.add(doc.tag(section === TableSection.header ? pdf.PdfTagType.TH : pdf.PdfTagType.TD, doc.tag(pdf.PdfTagType.P, () => { doc.drawText(value, x + 3, y + 5, { font: font, height: RowHeight, width: ColWidth }); }))); }); // return trTag; } // function textWithPlaceholder(doc, x, y, text, placeholderWidth) { let sz; // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { sz = doc.drawText(text, x, y); })); // doc.artifact(() => { doc.paths .moveTo(x + sz.size.width, doc.y) .lineTo(x + sz.size.width + placeholderWidth, doc.y) .stroke(ThinPen); }); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Expense Report</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <!-- Export button --> <button class="btn btn-default" id="btnExport">Export</button> </div> </body> </html>
// export function getEmployees() { return [ { id: 'E892659', name: 'Robert King', department: 'Sales', position: 'Sales Representative', ssn: 'A37830', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1000, expenses: getExpenses() }, { id: 'E3667093', name: 'John Taylor', department: 'Sales', position: 'Sales Representative', ssn: 'A83745', manager: 'Andrew Fuller', purpose: 'On business', attachment: false, advance: 800, expenses: getExpenses() }, { id: 'E294989', name: 'Gregory Allen', department: 'Sales', position: 'Sales Representative', ssn: 'A23927', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1200, expenses: getExpenses() } ]; } // function getExpenses() { // [5; 10] let count = 5 + Math.round(Math.random() * 5), ret = { items: [], totals: { hotel: 0, transport: 0, fuel: 0, meal: 0, misc: 0, total: 0 } }, msPerDay = 1000 * 24 * 60 * 60, curDate = Date.now() - 60 * msPerDay; // for (let i = 0; i < count; i++) { let item = { date: new Date(curDate), description: 'Customer visit', hotel: 30 + Math.random() * 200, transport: 10 + Math.random() * 150, fuel: Math.random() * 50, meal: 30 + Math.random() * 170, misc: Math.random() * 220, total: 0 }; // item.total = item.hotel + item.transport + item.fuel + item.meal + item.misc; // ret.totals.fuel += item.fuel; ret.totals.hotel += item.hotel; ret.totals.meal += item.meal; ret.totals.misc += item.misc; ret.totals.total += item.total; ret.totals.transport += item.transport; // ret.items.push(item); // curDate += msPerDay * Math.round(Math.random() * 4); } // return ret; }
body { margin-bottom: 24px; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);