접근성(Accessibility) 개요

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

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

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

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

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

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wijmo from '@grapecity/wijmo'; import * as grid from '@grapecity/wijmo.grid'; import * as chart from '@grapecity/wijmo.chart'; import '@grapecity/wijmo.chart.render'; import * as pdf from '@grapecity/wijmo.pdf'; import * as gridPdf from '@grapecity/wijmo.grid.pdf'; // import { getEmployee } from './data'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { let employee = getEmployee(); // let flexGrid = new grid.FlexGrid('#flexGrid', { autoGenerateColumns: false, headersVisibility: grid.HeadersVisibility.Column, allowMerging: grid.AllowMerging.All, itemsSource: employee.expenses.items, columns: [ { header: 'Date', binding: 'date', format: 'd', minWidth: 80 }, { header: 'Hotel', binding: 'hotel', format: 'c' }, { header: 'Transport', binding: 'transport', format: 'c', minWidth: 80 }, { header: 'Meal', binding: 'meal', format: 'c' }, { header: 'Fuel', binding: 'fuel', format: 'c' }, { header: 'Misc', binding: 'misc', format: 'c' } ] }); // let flexPie = new chart.FlexPie('#flexPie', { itemsSource: ((totals) => [ { name: 'Hotel', value: totals.hotel }, { name: 'Transport', value: totals.transport }, { name: 'Meal', value: totals.meal }, { name: 'Fuel', value: totals.fuel }, { name: 'Misc', value: totals.misc } ])(employee.expenses.totals), binding: 'value', bindingName: 'name', innerRadius: 0.75, dataLabel: { content: '{value:c1}', position: chart.PieLabelPosition.Inside } }); // document.querySelector('#btnExport').addEventListener('click', () => { let doc = new pdf.PdfDocument({ info: { title: 'Expense Analysis 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 Analysis Report', font: new pdf.PdfFont('times', 12), brush: '#bfc1c2' } }, lineGap: 2, pageSettings: { margins: { left: 12, right: 12, top: 12, bottom: 12 } }, ended: (_, args) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); // drawText(doc); // drawEmployee(doc, flexGrid, flexPie, employee, () => { doc.artifact(() => drawWatermark(doc), { type: pdf.PdfArtifactType.Pagination }); doc.end(); }); }); } // function drawText(doc) { doc.addTag(doc.tag(pdf.PdfTagType.H1, () => { doc.drawText('What is an expense report?', undefined, undefined, { font: new pdf.PdfFont('times', 20, 'normal', 'bold') }); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText(`An expense report is a form of document that contains all the expenses that an individual has incurred as a result of the business operation. For example, if the owner of a business travels to another location for a meeting, the cost of travel, the meals, and all other expenses that he/she has incurred may be added to the expense report.`.replace(/\n/g, '')); })); } // function drawEmployee(doc, flexGrid, flexPie, employee, done) { let expenses = employee.expenses.items.sort((a, b) => a.date.getTime() - b.date.getTime()), minDate = expenses[0].date, maxDate = expenses[expenses.length - 1].date, bold = new pdf.PdfFont('times', 10, 'normal', 'bold'); // doc.moveDown(2); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Name: ', undefined, undefined, { font: bold, continued: true }); doc.drawText(employee.name); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('From: ', undefined, undefined, { font: bold, continued: true }); doc.drawText(wijmo.changeType(minDate, wijmo.DataType.String, 'd')); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('To: ', undefined, undefined, { 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('Expense details:', 0, y); })); // gridPdf.FlexGridPdfConverter.draw(flexGrid, doc, doc.width * 0.5, null, { styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' } } }); // flexPie.saveImageToDataUrl(chart.ImageFormat.Png, (url) => { doc.addTag(doc.tag(pdf.PdfTagType.Figure, [ doc.tag(pdf.PdfTagType.Caption, () => { doc.drawText('Total expenses by category:', doc.width * 0.5 + 20, y); }), () => { doc.drawImage(url, doc.width * 0.5 + 20, doc.y, { width: doc.width * 0.5 - 20 }); } ], { actual: 'The chart' })); // Finish the document. done(); }); } // function drawWatermark(doc) { let docX = doc.x, docY = doc.y, font = new pdf.PdfFont('times', 120), color = wijmo.Color.fromRgba(59, 59, 109, 0.05), pgc = new wijmo.Point(doc.width / 2, doc.height / 2), text = 'Wijmo', offs = 10; // doc.x = 0; doc.y = 0; // let sz = doc.measureText(text, font), szCx = sz.size.width / 2, szCy = sz.size.height / 2; // doc.saveState(); // doc.rotate(45, pgc); // doc.drawText(text, pgc.x - szCx, pgc.y - szCy, { font: font, brush: color, baseline: pdf.PdfTextBaseline.Top }); // doc.paths .rect(pgc.x - szCx - offs, pgc.y - szCy - offs, sz.size.width + 2 * offs, sz.size.height - offs) .stroke(new pdf.PdfPen(color, 10)); // doc.restoreState(); // doc.x = docX; doc.y = docY; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Expense analysis report</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></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 class="row"> <div class="col-lg-6 col-md-12"> <div id="flexGrid" class="grid"></div> </div> <div class="col-lg-6 col-md-12"> <div id="flexPie"></div> </div> </div> </div> </body> </html>
// export function getEmployee() { 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() }; } // 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; } .grid { max-height: 300px; } .col-lg-6 { margin: 6px 0 0 0; }
(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', '@grapecity/wijmo': 'npm:@grapecity/wijmo/index.js', '@grapecity/wijmo.input': 'npm:@grapecity/wijmo.input/index.js', '@grapecity/wijmo.styles': 'npm:@grapecity/wijmo.styles', '@grapecity/wijmo.cultures': 'npm:@grapecity/wijmo.cultures', '@grapecity/wijmo.chart': 'npm:@grapecity/wijmo.chart/index.js', '@grapecity/wijmo.chart.analytics': 'npm:@grapecity/wijmo.chart.analytics/index.js', '@grapecity/wijmo.chart.animation': 'npm:@grapecity/wijmo.chart.animation/index.js', '@grapecity/wijmo.chart.annotation': 'npm:@grapecity/wijmo.chart.annotation/index.js', '@grapecity/wijmo.chart.finance': 'npm:@grapecity/wijmo.chart.finance/index.js', '@grapecity/wijmo.chart.finance.analytics': 'npm:@grapecity/wijmo.chart.finance.analytics/index.js', '@grapecity/wijmo.chart.hierarchical': 'npm:@grapecity/wijmo.chart.hierarchical/index.js', '@grapecity/wijmo.chart.interaction': 'npm:@grapecity/wijmo.chart.interaction/index.js', '@grapecity/wijmo.chart.radar': 'npm:@grapecity/wijmo.chart.radar/index.js', '@grapecity/wijmo.chart.render': 'npm:@grapecity/wijmo.chart.render/index.js', '@grapecity/wijmo.chart.webgl': 'npm:@grapecity/wijmo.chart.webgl/index.js', '@grapecity/wijmo.chart.map': 'npm:@grapecity/wijmo.chart.map/index.js', '@grapecity/wijmo.gauge': 'npm:@grapecity/wijmo.gauge/index.js', '@grapecity/wijmo.grid': 'npm:@grapecity/wijmo.grid/index.js', '@grapecity/wijmo.grid.detail': 'npm:@grapecity/wijmo.grid.detail/index.js', '@grapecity/wijmo.grid.filter': 'npm:@grapecity/wijmo.grid.filter/index.js', '@grapecity/wijmo.grid.search': 'npm:@grapecity/wijmo.grid.search/index.js', '@grapecity/wijmo.grid.grouppanel': 'npm:@grapecity/wijmo.grid.grouppanel/index.js', '@grapecity/wijmo.grid.multirow': 'npm:@grapecity/wijmo.grid.multirow/index.js', '@grapecity/wijmo.grid.transposed': 'npm:@grapecity/wijmo.grid.transposed/index.js', '@grapecity/wijmo.grid.transposedmultirow': 'npm:@grapecity/wijmo.grid.transposedmultirow/index.js', '@grapecity/wijmo.grid.pdf': 'npm:@grapecity/wijmo.grid.pdf/index.js', '@grapecity/wijmo.grid.sheet': 'npm:@grapecity/wijmo.grid.sheet/index.js', '@grapecity/wijmo.grid.xlsx': 'npm:@grapecity/wijmo.grid.xlsx/index.js', '@grapecity/wijmo.grid.selector': 'npm:@grapecity/wijmo.grid.selector/index.js', '@grapecity/wijmo.grid.cellmaker': 'npm:@grapecity/wijmo.grid.cellmaker/index.js', '@grapecity/wijmo.nav': 'npm:@grapecity/wijmo.nav/index.js', '@grapecity/wijmo.odata': 'npm:@grapecity/wijmo.odata/index.js', '@grapecity/wijmo.olap': 'npm:@grapecity/wijmo.olap/index.js', '@grapecity/wijmo.rest': 'npm:@grapecity/wijmo.rest/index.js', '@grapecity/wijmo.pdf': 'npm:@grapecity/wijmo.pdf/index.js', '@grapecity/wijmo.pdf.security': 'npm:@grapecity/wijmo.pdf.security/index.js', '@grapecity/wijmo.viewer': 'npm:@grapecity/wijmo.viewer/index.js', '@grapecity/wijmo.xlsx': 'npm:@grapecity/wijmo.xlsx/index.js', '@grapecity/wijmo.undo': 'npm:@grapecity/wijmo.undo/index.js', '@grapecity/wijmo.interop.grid': 'npm:@grapecity/wijmo.interop.grid/index.js', '@grapecity/wijmo.touch': 'npm:@grapecity/wijmo.touch/index.js', '@grapecity/wijmo.cloud': 'npm:@grapecity/wijmo.cloud/index.js', '@grapecity/wijmo.barcode': 'npm:@grapecity/wijmo.barcode/index.js', '@grapecity/wijmo.barcode.common': 'npm:@grapecity/wijmo.barcode.common/index.js', '@grapecity/wijmo.barcode.composite': 'npm:@grapecity/wijmo.barcode.composite/index.js', '@grapecity/wijmo.barcode.specialized': 'npm:@grapecity/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);