접근성(Accessibility) 개요

이 샘플은 경비 보고서 샘플을 기반으로 태그가 지정된 PDF를 만드는 방법을 보여줍니다.

샘플은 PdfDocument 클래스의 인스턴스를 생성할 때, 기본 Tagged PDF 요구 사항을 충족하기 위해 info.title, tagged, displayTitlelang 속성을 사용합니다.

샘플에서는 tag 메서드를 사용하여 태그를 만들고 콘텐츠를 표시하고, addTag 메서드를 사용하여 문서 트리에 해당 태그들을 추가합니다.

artifact 매서드는 콘텐츠를 꾸밀때 사용한 요소를 아티팩트로 표시하는 데 사용됩니다.

참고: 태그가 지정된 PDF에는 문서 버전 1.4 이상이 필요합니다.

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);