Accessibility Overview (React)

This sample shows how to create a tagged PDF using the Expense Analysis 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.

This example uses React.

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import ReactDOM from 'react-dom/client'; import React, { useRef, useState } from 'react'; import useEvent from 'react-use-event-hook'; import '@grapecity/wijmo.chart.render'; import * as wjcCore from '@grapecity/wijmo'; import * as wjcGrid from '@grapecity/wijmo.react.grid'; import * as wjChart from '@grapecity/wijmo.chart'; import * as wjChartModule from '@grapecity/wijmo.react.chart'; import * as gridPdf from '@grapecity/wijmo.grid.pdf'; import * as pdf from '@grapecity/wijmo.pdf'; import './app.css'; import { getEmployee } from './data'; function App() { const [employee, setEmployee] = useState(getEmployee()); const [totals, setTotals] = useState([ { name: 'Hotel', value: employee.expenses.totals.hotel }, { name: 'Transport', value: employee.expenses.totals.transport }, { name: 'Meal', value: employee.expenses.totals.meal }, { name: 'Fuel', value: employee.expenses.totals.fuel }, { name: 'Misc', value: employee.expenses.totals.misc } ]); const flexGridRef = useRef(null); const flexPieRef = useRef(null); const employeeRef = useRef(null); const initializeGrid = useEvent((ctl) => { flexGridRef.current = ctl; }); const initializePie = useEvent((ctl) => { flexPieRef.current = ctl; }); const exportPDF = () => { 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: (sender, args) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); drawText(doc); drawEmployee(doc, flexGridRef.current, flexPieRef.current, employee, () => { doc.artifact(() => drawWatermark(doc), { type: pdf.PdfArtifactType.Pagination }); doc.end(); }); }; const 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, '')); })); }; const 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(wjcCore.changeType(minDate, wjcCore.DataType.String, 'd')); })); // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('To: ', undefined, undefined, { font: bold, continued: true }); doc.drawText(wjcCore.changeType(maxDate, wjcCore.DataType.String, 'd')); })); // doc.moveDown(2); let y = doc.y; // doc.addTag(doc.tag(pdf.PdfTagType.P, () => { doc.drawText('Expense details:', 0, y); })); y = doc.y; // gridPdf.FlexGridPdfConverter.draw(flexGrid, doc, doc.width * 0.5, undefined, { styles: { cellStyle: { backgroundColor: '#ffffff', borderColor: '#c6c6c6' }, altCellStyle: { backgroundColor: '#f9f9f9' }, groupCellStyle: { font: { weight: 'bold' }, backgroundColor: '#dddddd' }, headerCellStyle: { backgroundColor: '#eaeaea' } } }); // flexPie.saveImageToDataUrl(wjChart.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(); }); }; const drawWatermark = (doc) => { let docX = doc.x, docY = doc.y, font = new pdf.PdfFont('times', 120), color = wjcCore.Color.fromRgba(59, 59, 109, 0.05), pgc = new wjcCore.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; }; return (<div className="container-fluid"> {/* Export button */} <button className="btn btn-default" onClick={exportPDF}>Export</button> <div className="row"> <div className="col-lg-6 col-md-12"> {/* FlexGrid */} <wjcGrid.FlexGrid className="grid" autoGenerateColumns={false} headersVisibility="Column" allowMerging="All" itemsSource={employee.expenses.items} initialized={initializeGrid}> <wjcGrid.FlexGridColumn header="Date" binding="date" format="d" minWidth={80}/> <wjcGrid.FlexGridColumn header="Hotel" binding="hotel" format="c"/> <wjcGrid.FlexGridColumn header="Transport" binding="transport" format="c" minWidth={80}/> <wjcGrid.FlexGridColumn header="Meal" binding="meal" format="c"/> <wjcGrid.FlexGridColumn header="Fuel" binding="fuel" format="c"/> <wjcGrid.FlexGridColumn header="Misc" binding="misc" format="c"/> </wjcGrid.FlexGrid> </div> <div className="col-lg-6 col-md-12"> <wjChartModule.FlexPie binding="value" bindingName="name" innerRadius={0.75} initialized={initializePie} itemsSource={totals}> <wjChartModule.FlexChartDataLabel content='{value:c1}'/> </wjChartModule.FlexPie> </div> </div> </div>); } const container = document.getElementById('app'); if (container) { const root = ReactDOM.createRoot(container); root.render(<App />); }
<!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 id="app"></div> </body> </html>
.grid { max-height: 300px; } .col-lg-6 { margin: 6px 0 0 0; }
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; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: 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.grid.immutable': 'npm:@grapecity/wijmo.grid.immutable/index.js', '@grapecity/wijmo.touch': 'npm:@grapecity/wijmo.touch/index.js', '@grapecity/wijmo.cloud': 'npm:@grapecity/wijmo.cloud/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.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', "@grapecity/wijmo.react.chart.analytics": "npm:@grapecity/wijmo.react.chart.analytics/index.js", "@grapecity/wijmo.react.chart.animation": "npm:@grapecity/wijmo.react.chart.animation/index.js", "@grapecity/wijmo.react.chart.annotation": "npm:@grapecity/wijmo.react.chart.annotation/index.js", "@grapecity/wijmo.react.chart.finance.analytics": "npm:@grapecity/wijmo.react.chart.finance.analytics/index.js", "@grapecity/wijmo.react.chart.finance": "npm:@grapecity/wijmo.react.chart.finance/index.js", "@grapecity/wijmo.react.chart.hierarchical": "npm:@grapecity/wijmo.react.chart.hierarchical/index.js", "@grapecity/wijmo.react.chart.interaction": "npm:@grapecity/wijmo.react.chart.interaction/index.js", "@grapecity/wijmo.react.chart.radar": "npm:@grapecity/wijmo.react.chart.radar/index.js", "@grapecity/wijmo.react.chart": "npm:@grapecity/wijmo.react.chart/index.js", "@grapecity/wijmo.react.core": "npm:@grapecity/wijmo.react.core/index.js", '@grapecity/wijmo.react.chart.map': 'npm:@grapecity/wijmo.react.chart.map/index.js', "@grapecity/wijmo.react.gauge": "npm:@grapecity/wijmo.react.gauge/index.js", "@grapecity/wijmo.react.grid.detail": "npm:@grapecity/wijmo.react.grid.detail/index.js", "@grapecity/wijmo.react.grid.filter": "npm:@grapecity/wijmo.react.grid.filter/index.js", "@grapecity/wijmo.react.grid.grouppanel": "npm:@grapecity/wijmo.react.grid.grouppanel/index.js", '@grapecity/wijmo.react.grid.search': 'npm:@grapecity/wijmo.react.grid.search/index.js', "@grapecity/wijmo.react.grid.multirow": "npm:@grapecity/wijmo.react.grid.multirow/index.js", "@grapecity/wijmo.react.grid.sheet": "npm:@grapecity/wijmo.react.grid.sheet/index.js", '@grapecity/wijmo.react.grid.transposed': 'npm:@grapecity/wijmo.react.grid.transposed/index.js', '@grapecity/wijmo.react.grid.transposedmultirow': 'npm:@grapecity/wijmo.react.grid.transposedmultirow/index.js', '@grapecity/wijmo.react.grid.immutable': 'npm:@grapecity/wijmo.react.grid.immutable/index.js', "@grapecity/wijmo.react.grid": "npm:@grapecity/wijmo.react.grid/index.js", "@grapecity/wijmo.react.input": "npm:@grapecity/wijmo.react.input/index.js", "@grapecity/wijmo.react.olap": "npm:@grapecity/wijmo.react.olap/index.js", "@grapecity/wijmo.react.viewer": "npm:@grapecity/wijmo.react.viewer/index.js", "@grapecity/wijmo.react.nav": "npm:@grapecity/wijmo.react.nav/index.js", "@grapecity/wijmo.react.base": "npm:@grapecity/wijmo.react.base/index.js", '@grapecity/wijmo.react.barcode.common': 'npm:@grapecity/wijmo.react.barcode.common/index.js', '@grapecity/wijmo.react.barcode.composite': 'npm:@grapecity/wijmo.react.barcode.composite/index.js', '@grapecity/wijmo.react.barcode.specialized': 'npm:@grapecity/wijmo.react.barcode.specialized/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'react': 'npm:react/umd/react.production.min.js', 'react-dom': 'npm:react-dom/umd/react-dom.production.min.js', 'react-dom/client': 'npm:react-dom/umd/react-dom.production.min.js', 'redux': 'npm:redux/dist/redux.min.js', 'react-redux': 'npm:react-redux/dist/react-redux.min.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', "react-use-event-hook": "npm:react-use-event-hook/dist/esm/useEvent.js", }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);