Accessibility Overview (Angular)

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 Angular.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wijmo from '@mescius/wijmo'; import * as chart from '@mescius/wijmo.chart'; import '@mescius/wijmo.chart.render'; import * as grid from '@mescius/wijmo.grid'; import * as pdf from '@mescius/wijmo.pdf'; import * as gridPdf from '@mescius/wijmo.grid.pdf'; // import '@angular/compiler'; import { Component, Inject, enableProdMode, NgModule, ViewChild } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { DataService, IEmployee, IExpense } from './app.data'; import { WjGridModule } from '@mescius/wijmo.angular2.grid'; import { WjChartModule } from '@mescius/wijmo.angular2.chart'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { employee: IEmployee; totals: any[]; @ViewChild('flexGrid', { static: true }) flexGrid: grid.FlexGrid; @ViewChild('flexPie', { static: true }) flexPie: chart.FlexPie; // constructor(@Inject(DataService) private dataService: DataService) { this.employee = dataService.getEmployee(); // this.totals = ((totals: IExpense) => [ { 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 } ])(this.employee.expenses.totals); } // export() { 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: pdf.PdfDocument, args: pdf.PdfDocumentEndedEventArgs) => pdf.saveBlob(args.blob, 'FlexGrid.pdf') }); // this.drawText(doc); // this.drawEmployee(doc, this.flexGrid, this.flexPie, this.employee, () => { doc.artifact(() => this.drawWatermark(doc), { type: pdf.PdfArtifactType.Pagination }); doc.end(); }); } // drawText(doc: pdf.PdfDocument) { 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, '')); })); } // drawEmployee(doc: pdf.PdfDocument, flexGrid: grid.FlexGrid, flexPie: chart.FlexPie, employee: IEmployee, done: Function) { 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); })); y = doc.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: string) => { 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(); }); } // drawWatermark(doc: pdf.PdfDocument) { 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; } } // @NgModule({ imports: [WjGridModule, WjChartModule, BrowserModule], declarations: [AppComponent], providers: [DataService], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
<!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"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- 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> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="container-fluid"> <!-- Export button --> <button class="btn btn-default" (click)="export()">Export</button> <div class="row"> <div class="col-lg-6 col-md-12"> <wj-flex-grid class="grid" #flexGrid [autoGenerateColumns]="false" headersVisibility="Column" allowMerging="All" [itemsSource]="employee.expenses.items"> <wj-flex-grid-column header="Date" binding="date" format="d" [minWidth]="80"></wj-flex-grid-column> <wj-flex-grid-column header="Hotel" binding="hotel" format="c"></wj-flex-grid-column> <wj-flex-grid-column header="Transport" binding="transport" format="c" [minWidth]="80"></wj-flex-grid-column> <wj-flex-grid-column header="Meal" binding="meal" format="c"></wj-flex-grid-column> <wj-flex-grid-column header="Fuel" binding="fuel" format="c"></wj-flex-grid-column> <wj-flex-grid-column header="Misc" binding="misc" format="c"></wj-flex-grid-column> </wj-flex-grid> </div> <div class="col-lg-6 col-md-12"> <wj-flex-pie #flexPie [itemsSource]="totals" binding="value" bindingName="name" [innerRadius]="0.75"> <wj-flex-pie-data-label content="{value:c1}" position="Inside"></wj-flex-pie-data-label> </wj-flex-pie> </div> </div> </div>
import { Injectable } from '@angular/core'; // export interface IExpense { hotel: number; transport: number; fuel: number; meal: number; misc: number; total: number; } // export interface IExpenseDetails extends IExpense { date: Date; description: string; } // export interface IExpenses { items: IExpenseDetails[]; totals: IExpense; } // export interface IEmployee { id: string; name: string; department: string; position: string; ssn: string; manager: string; purpose: string; attachment: boolean; advance: number; expenses: IExpenses; } // @Injectable() export class DataService { getEmployee(): IEmployee { return { id: 'E892659', name: 'Robert King', department: 'Sales', position: 'Sales Representative', ssn: 'A37830', manager: 'Andrew Fuller', purpose: 'On business', attachment: true, advance: 1000, expenses: this.getExpenses() }; } // private getExpenses(): IExpenses { // [5; 10] let count = 5 + Math.round(Math.random() * 5), ret: IExpenses = { 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: IExpenseDetails = { 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) { SystemJS.config({ transpiler: './plugin-typescript.js', typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: 'node_modules/', meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'systemjs-plugin-css' } }, paths: { // paths serve as alias 'npm:': '' }, packageConfigPaths: [ '/node_modules/*/package.json', "/node_modules/@angular/*/package.json", "/node_modules/@mescius/*/package.json" ], map: { 'core-js': 'https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js', 'typescript': 'https://cdnjs.cloudflare.com/ajax/libs/typescript/5.2.2/typescript.min.js', "rxjs": "https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js", 'systemjs-plugin-css': 'https://cdn.jsdelivr.net/npm/systemjs-plugin-css@0.1.37/css.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', "@mescius/wijmo.angular2.chart.analytics": "npm:@mescius/wijmo.angular2.chart.analytics/index.js", "@mescius/wijmo.angular2.chart.animation": "npm:@mescius/wijmo.angular2.chart.animation/index.js", "@mescius/wijmo.angular2.chart.annotation": "npm:@mescius/wijmo.angular2.chart.annotation/index.js", "@mescius/wijmo.angular2.chart.finance.analytics": "npm:@mescius/wijmo.angular2.chart.finance.analytics/index.js", "@mescius/wijmo.angular2.chart.finance": "npm:@mescius/wijmo.angular2.chart.finance/index.js", "@mescius/wijmo.angular2.chart.hierarchical": "npm:@mescius/wijmo.angular2.chart.hierarchical/index.js", "@mescius/wijmo.angular2.chart.interaction": "npm:@mescius/wijmo.angular2.chart.interaction/index.js", "@mescius/wijmo.angular2.chart.radar": "npm:@mescius/wijmo.angular2.chart.radar/index.js", '@mescius/wijmo.angular2.chart.map': 'npm:@mescius/wijmo.angular2.chart.map/index.js', "@mescius/wijmo.angular2.chart": "npm:@mescius/wijmo.angular2.chart/index.js", "@mescius/wijmo.angular2.core": "npm:@mescius/wijmo.angular2.core/index.js", "@mescius/wijmo.angular2.gauge": "npm:@mescius/wijmo.angular2.gauge/index.js", "@mescius/wijmo.angular2.grid.detail": "npm:@mescius/wijmo.angular2.grid.detail/index.js", "@mescius/wijmo.angular2.grid.filter": "npm:@mescius/wijmo.angular2.grid.filter/index.js", "@mescius/wijmo.angular2.grid.grouppanel": "npm:@mescius/wijmo.angular2.grid.grouppanel/index.js", "@mescius/wijmo.angular2.grid.search": "npm:@mescius/wijmo.angular2.grid.search/index.js", "@mescius/wijmo.angular2.grid.multirow": "npm:@mescius/wijmo.angular2.grid.multirow/index.js", "@mescius/wijmo.angular2.grid.sheet": "npm:@mescius/wijmo.angular2.grid.sheet/index.js", '@mescius/wijmo.angular2.grid.transposed': 'npm:@mescius/wijmo.angular2.grid.transposed/index.js', '@mescius/wijmo.angular2.grid.transposedmultirow': 'npm:@mescius/wijmo.angular2.grid.transposedmultirow/index.js', "@mescius/wijmo.angular2.grid": "npm:@mescius/wijmo.angular2.grid/index.js", "@mescius/wijmo.angular2.input": "npm:@mescius/wijmo.angular2.input/index.js", "@mescius/wijmo.angular2.olap": "npm:@mescius/wijmo.angular2.olap/index.js", "@mescius/wijmo.angular2.viewer": "npm:@mescius/wijmo.angular2.viewer/index.js", "@mescius/wijmo.angular2.nav": "npm:@mescius/wijmo.angular2.nav/index.js", "@mescius/wijmo.angular2.directivebase": "npm:@mescius/wijmo.angular2.directivebase/index.js", '@mescius/wijmo.angular2.barcode.common': 'npm:@mescius/wijmo.angular2.barcode.common/index.js', '@mescius/wijmo.angular2.barcode.composite': 'npm:@mescius/wijmo.angular2.barcode.composite/index.js', '@mescius/wijmo.angular2.barcode.specialized': 'npm:@mescius/wijmo.angular2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', "@angular/common/http": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/http.mjs", "@angular/core": "https://cdn.jsdelivr.net/npm/@angular/core@16.2.6/fesm2022/core.mjs", "@angular/platform-browser": "https://cdn.jsdelivr.net/npm/@angular/platform-browser@16.2.6/fesm2022/platform-browser.mjs", "@angular/common": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/common.mjs", "@angular/compiler": "https://cdn.jsdelivr.net/npm/@angular/compiler@16.2.6/fesm2022/compiler.mjs", "@angular/forms": "https://cdn.jsdelivr.net/npm/@angular/forms@16.2.6/fesm2022/forms.mjs", "@angular/localize": "https://cdn.jsdelivr.net/npm/@angular/localize@16.2.6/fesm2022/localize.mjs", "@angular/platform-browser-dynamic": "https://cdn.jsdelivr.net/npm/@angular/platform-browser-dynamic@16.2.6/fesm2022/platform-browser-dynamic.mjs", }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: 'ts' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);