├── .editorconfig ├── .github ├── CONTRIBUTING.md └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.json ├── build-examples.js ├── build-vfs.js ├── build ├── fonts │ ├── Roboto.js │ └── Roboto │ │ ├── Roboto-Italic.ttf │ │ ├── Roboto-Medium.ttf │ │ ├── Roboto-MediumItalic.ttf │ │ └── Roboto-Regular.ttf ├── pdfmake.js ├── pdfmake.js.map ├── pdfmake.min.js ├── pdfmake.min.js.map ├── standard-fonts │ ├── Courier.js │ ├── Helvetica.js │ ├── Symbol.js │ ├── Times.js │ └── ZapfDingbats.js └── vfs_fonts.js ├── dev-playground ├── README.md ├── package.json ├── public │ ├── ace.js │ ├── index.html │ ├── mode-javascript.js │ ├── pdfmake.css │ ├── playground.js │ ├── samples │ │ ├── attachments │ │ ├── basics │ │ ├── columns │ │ ├── images │ │ ├── inline-styling │ │ ├── lists │ │ ├── margins │ │ ├── named-styles │ │ ├── style-overrides │ │ ├── svgs │ │ └── tables │ ├── theme-monokai.js │ └── worker-javascript.js └── server.js ├── eslint.config.mjs ├── examples ├── absolute.js ├── attachments.js ├── background.js ├── basics.js ├── columns_simple.js ├── fonts │ ├── Roboto-Italic.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-MediumItalic.ttf │ ├── Roboto-Regular.ttf │ └── sampleImage.jpg ├── images.js ├── links.js ├── lists.js ├── margins.js ├── pageReference.js ├── pdfa.js ├── pdfs │ ├── absolute.pdf │ ├── attachments.pdf │ ├── background.pdf │ ├── basics.pdf │ ├── columns_simple.pdf │ ├── images.pdf │ ├── links.pdf │ ├── lists.pdf │ ├── margins.pdf │ ├── named_styles.pdf │ ├── named_styles_with_overrides.pdf │ ├── pageReference.pdf │ ├── pdfa.pdf │ ├── qrCode.pdf │ ├── relative.pdf │ ├── sections.pdf │ ├── security.pdf │ ├── standardfonts.pdf │ ├── styling_inlines.pdf │ ├── styling_properties.pdf │ ├── svgs.pdf │ ├── tables.pdf │ ├── textDecorations.pdf │ ├── toc.pdf │ ├── vectors.pdf │ └── watermark.pdf ├── qrCode.js ├── relative.js ├── sections.js ├── security.js ├── standardfonts.js ├── styling_inlines.js ├── styling_named_styles.js ├── styling_named_styles_with_overrides.js ├── styling_properties.js ├── svgs.js ├── tables.js ├── textDecorations.js ├── toc.js ├── vectors.js └── watermark.js ├── fonts ├── Roboto.js └── Roboto │ ├── Roboto-Italic.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-MediumItalic.ttf │ └── Roboto-Regular.ttf ├── package.json ├── src ├── 3rd-party │ ├── svg-to-pdfkit.js │ └── svg-to-pdfkit │ │ ├── LICENSE │ │ └── source.js ├── DocMeasure.js ├── DocPreprocessor.js ├── DocumentContext.js ├── ElementWriter.js ├── LayoutBuilder.js ├── Line.js ├── OutputDocument.js ├── OutputDocumentServer.js ├── PDFDocument.js ├── PageElementWriter.js ├── PageSize.js ├── Printer.js ├── Renderer.js ├── SVGMeasure.js ├── StyleContextStack.js ├── TableProcessor.js ├── TextBreaker.js ├── TextDecorator.js ├── TextInlines.js ├── URLResolver.js ├── base.js ├── browser-extensions │ ├── OutputDocumentBrowser.js │ ├── URLBrowserResolver.js │ ├── fonts │ │ └── Roboto.js │ ├── index.js │ ├── pdfMake.js │ ├── standard-fonts │ │ ├── Courier.js │ │ ├── Helvetica.js │ │ ├── Symbol.js │ │ ├── Times.js │ │ └── ZapfDingbats.js │ └── virtual-fs-cjs.js ├── columnCalculator.js ├── helpers │ ├── node.js │ ├── tools.js │ └── variableType.js ├── index.js ├── qrEnc.js ├── standardPageSizes.js ├── tableLayouts.js └── virtual-fs.js ├── standard-fonts ├── Courier.js ├── Helvetica.js ├── Symbol.js ├── Times.js └── ZapfDingbats.js ├── tests ├── browser │ └── polyfills.spec.js ├── fonts │ ├── Roboto-Italic.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-MediumItalic.ttf │ ├── Roboto-Regular.ttf │ └── sampleImage.jpg ├── integration │ ├── alignment.spec.js │ ├── background.spec.js │ ├── basics.spec.js │ ├── columns.spec.js │ ├── images.spec.js │ ├── integrationTestHelper.js │ ├── lists.spec.js │ ├── svgs.spec.js │ └── tables.spec.js └── unit │ ├── DocMeasure.spec.js │ ├── DocPreprocessor.spec.js │ ├── DocumentContext.spec.js │ ├── ElementWriter.spec.js │ ├── LayoutBuilder.spec.js │ ├── Line.spec.js │ ├── Node-interface.spec.js │ ├── PDFDocument.spec.js │ ├── PageElementWriter.spec.js │ ├── Printer.spec.js │ ├── SVGMeasure.spec.js │ ├── StyleContextStack.spec.js │ ├── TableProcessor.spec.js │ ├── TextBreaker.spec.js │ ├── TextDecorator.spec.js │ ├── TextInlines.spec.js │ ├── columnCalculator.spec.js │ └── helpers │ ├── node.spec.js │ ├── tools.spec.js │ └── variableType.spec.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = tab 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [{*.json,*.yml}] 14 | indent_style = space 15 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for your interest in contributing to this library! 2 | 3 | ## Using the issue tracker 4 | 5 | The issue tracker on GitHub is the preferred channel for bug reports, feature requests, and discussions about pdfmake library. 6 | 7 | Bug reports require example for reproduce issue runnable on [playground](http://pdfmake.org/playground.html) or https://jsfiddle.net/ or a similar service. 8 | 9 | ## Pull requests 10 | 11 | **Please do not commit changes in `build` folder.** 12 | 13 | **Please do not commit changes in `package.json` file if is not related to the pull request changes.** 14 | 15 | Use [EditorConfig](https://editorconfig.org/) in your IDE for keep code style. Do not make code style changes unless it is related to the pull request. 16 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macos-latest] 17 | node-version: [18.x, 20.x, 22.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm install 26 | - run: npm test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | doc 4 | .DS_Store 5 | .idea 6 | *.iml 7 | *.ipr 8 | *.iws 9 | nbproject 10 | package-lock.json 11 | yarn.lock 12 | /js 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .idea/ 3 | dev-playground/ 4 | docs/ 5 | examples/ 6 | tests/ 7 | build/fonts/ 8 | package-lock.json 9 | yarn.lock 10 | .editorconfig 11 | eslint.config.mjs 12 | build-fonts.js 13 | build-examples.js 14 | babel.config.json 15 | webpack.config.js 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.0-beta.18 - 2025-05-09 4 | 5 | - Added `section` node 6 | - Fixed crash that occurred when using automatic page height 7 | - Fixed text overflow with some non-wrappable texts 8 | 9 | ## 0.3.0-beta.17 - 2025-04-29 10 | 11 | - Fixed DoS via repeatedly redirect URL in file embedding 12 | 13 | ## 0.3.0-beta.16 - 2025-04-26 14 | 15 | - Update pdfkit to 0.17.0 16 | - Update Roboto font (version 3.011) 17 | - Fixed URL resolving for same URL in browser 18 | - Fixed sharing URL resolver for not available URLs 19 | 20 | ## 0.3.0-beta.15 - 2025-01-01 21 | 22 | - Reverted to the original `pdfkit` package, moving away from `@foliojs-fork` 23 | - Update pdfkit to 0.16.0 24 | - Fixed a potential issue in the minimized library when detecting the orientation of JPEG images 25 | 26 | ## 0.3.0-beta.14 - 2024-12-23 27 | 28 | - Fixed big size pdfmake bundle for browser 29 | 30 | ## 0.3.0-beta.13 - 2024-12-15 31 | 32 | - Update pdfkit to 0.15.2 33 | - Fixed speed in Node.js if is fetching URL for image or font redirected 34 | - Fixed aspect ratio for image with exif orientation tag 35 | - Fixed font size calculation for watermark if is page orientation is changed 36 | 37 | ## 0.3.0-beta.12 - 2024-11-03 38 | 39 | - Added support PDF/A and PDF/UA (see [documentation](https://pdfmake.github.io/docs/0.3/document-definition-object/pdfa/)) 40 | - Added support `link`, `linkToPage` and `linkToDestination` for SVG 41 | - Update pdfkit to 0.15.1 42 | - Fixed bug with how page breaks provoked by cells with rowspan were handled 43 | - Fixed find where previous cell started with row span and col span combination 44 | - Fixed calculating correctly the 'y' at the end of a rowSpan with dontBreakRows 45 | 46 | ## 0.3.0-beta.11 - 2024-10-09 47 | 48 | - Fixed drawing top horizontal line of the table with page break 49 | 50 | ## 0.3.0-beta.10 - 2024-09-22 51 | 52 | - Drop support Internet Explorer 11 (Microsoft will not support from 2022) 53 | - Minimal supported version Node.js 18 LTS 54 | - Update Roboto font (version 3.010) 55 | - Fixed page break in a column group 56 | - Fixed saving margins in an unbreakable block 57 | - Fixed fillColor items in unbreakable blocks 58 | - Fixed calculating correctly the 'y' at the end of a rowSpan with dontBreakRows 59 | - Fixed margins (top/bottom) of nodes and row height are considered for breaking page 60 | - Fixed margins after page break 61 | - Fixed margins of nodes with relativePosition or absolutePosition are ignored and don't interfere with the regular flow of the layout 62 | 63 | ## 0.3.0-beta.9 - 2024-08-09 64 | 65 | - Fixed and validates input values headerRows and keepWithHeaderRows 66 | - Fixed numbering nested ordered lists 67 | - Speed up StyleContextStack.autopush() for large tables 68 | - Fixed widths of table columns with percentages 69 | - Fixed storing the correct context in the ending cell of a row span when there were nested column groups (columns or tables) 70 | 71 | ## 0.3.0-beta.8 - 2024-03-07 72 | 73 | - Removed unused brfs dependency 74 | 75 | ## 0.3.0-beta.7 - 2024-01-01 76 | 77 | - Minimal supported version Node.js 16 LTS 78 | - Added padding option for QR code 79 | - Allow the document language to be specified 80 | - Fixed cover image size inside table 81 | - Fixed "Cannot read properties of undefined (reading 'bottomMost')" if table contains too few rows 82 | - Fixed invalid source-maps in built js file 83 | 84 | ## 0.3.0-beta.6 - 2023-11-09 85 | 86 | - Update pdfkit to 0.14.0 87 | - Update Roboto font (version 3.008) 88 | 89 | ## 0.3.0-beta.5 - 2023-02-19 90 | 91 | - Fixed document buffer size. Node.js 18+ allow max 1 GiB. 92 | 93 | ## 0.3.0-beta.4 - 2022-12-17 94 | 95 | - Minimal supported version Node.js 14 LTS 96 | - Fixed theoretical vulnerability CVE-2022-46161 (**It was never part of version released as npm package or cdnjs or bower or packagist!**) 97 | 98 | ## 0.3.0-beta.3 - 2022-10-09 99 | 100 | - Updated Roboto font (version 3.005) 101 | - Fixed calculating auto page height 102 | - Fixed TrueType Collection loading from URL 103 | - Fixed refetching fonts from URL 104 | 105 | ## 0.3.0-beta.2 - 2022-04-01 106 | 107 | - Attachments embedding 108 | - Support passing headers to request for loading font files and images via URL addresses 109 | 110 | ## 0.3.0-beta.1 - 2022-01-01 111 | 112 | - Port code base to ES6+ 113 | - Unify interface for node and browser **(breaking change)** 114 | - All methods return promise instead of using callback **(breaking change)** 115 | - Change including virtual font storage in client-side **(breaking change)** 116 | - Change parameters of `pageBreakBefore` function **(breaking change)** 117 | - Support for loading font files and images via URL adresses (https:// or http:// protocol) in Node.js (client and server side now) 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 bpampuch 4 | 2016-2025 liborm85 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pdfmake [![Node.js CI][githubactions_img]][githubactions_url] [![GitHub][github_img]][github_url] [![npm][npm_img]][npm_url] [![CDNJS][cdnjs_img]][cndjs_url] 2 | 3 | [githubactions_img]: https://github.com/bpampuch/pdfmake/actions/workflows/node.js.yml/badge.svg?branch=master 4 | [githubactions_url]: https://github.com/bpampuch/pdfmake/actions 5 | 6 | [github_img]: https://img.shields.io/github/release/bpampuch/pdfmake.svg?colorB=0E7FBF 7 | [github_url]: https://github.com/bpampuch/pdfmake/releases/latest 8 | 9 | [npm_img]: https://img.shields.io/npm/v/pdfmake.svg?colorB=0E7FBF 10 | [npm_url]: https://www.npmjs.com/package/pdfmake 11 | 12 | [cdnjs_img]: https://img.shields.io/cdnjs/v/pdfmake.svg?colorB=0E7FBF 13 | [cndjs_url]: https://cdnjs.com/libraries/pdfmake 14 | 15 | 16 | PDF document generation library for server-side and client-side in pure JavaScript. 17 | 18 | Check out [the playground](http://bpampuch.github.io/pdfmake/playground.html) and [examples](https://github.com/bpampuch/pdfmake/tree/master/examples). 19 | 20 | #### This is unstable master branch for version 0.3.x, for stable use version 0.2.x see [branch 0.2](https://github.com/bpampuch/pdfmake/tree/0.2) or older version 0.1.x see [branch 0.1](https://github.com/bpampuch/pdfmake/tree/0.1). 21 | 22 | ### Features 23 | 24 | * line-wrapping, 25 | * text-alignments (left, right, centered, justified), 26 | * numbered and bulleted lists, 27 | * tables and columns 28 | * auto/fixed/star-sized widths, 29 | * col-spans and row-spans, 30 | * headers automatically repeated in case of a page-break, 31 | * images and vector graphics, 32 | * convenient styling and style inheritance, 33 | * page headers and footers: 34 | * static or dynamic content, 35 | * access to current page number and page count, 36 | * background-layer, 37 | * page dimensions and orientations, 38 | * margins, 39 | * document sections, 40 | * custom page breaks, 41 | * font embedding, 42 | * support for complex, multi-level (nested) structures, 43 | * table of contents, 44 | * helper methods for opening/printing/downloading the generated PDF, 45 | * setting of PDF metadata (e.g. author, subject). 46 | 47 | ## Documentation 48 | 49 | **Documentation URL: https://pdfmake.github.io/docs/** 50 | 51 | Source of documentation: https://github.com/pdfmake/docs **Improvements are welcome!** 52 | 53 | ## Building from sources 54 | 55 | using npm: 56 | ``` 57 | git clone https://github.com/bpampuch/pdfmake.git 58 | cd pdfmake 59 | npm install 60 | npm run build 61 | ``` 62 | 63 | using yarn: 64 | ``` 65 | git clone https://github.com/bpampuch/pdfmake.git 66 | cd pdfmake 67 | yarn 68 | yarn run build 69 | ``` 70 | 71 | ## License 72 | MIT 73 | 74 | ## Authors 75 | * [@bpampuch](https://github.com/bpampuch) (founder) 76 | * [@liborm85](https://github.com/liborm85) 77 | 78 | pdfmake is based on a truly amazing library [pdfkit](https://github.com/devongovett/pdfkit) (credits to [@devongovett](https://github.com/devongovett)). 79 | 80 | Thanks to all contributors. 81 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": "18.12" 8 | }, 9 | "loose": true 10 | } 11 | ] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /build-examples.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const exec = require('child_process').exec; 3 | 4 | var errCount = 0; 5 | var position = 0; 6 | process.chdir('examples'); 7 | 8 | const items = fs.readdirSync('.'); 9 | const files = items.filter(file => file.substring(file.length - 3, file.length) === '.js'); 10 | 11 | files.forEach(function (file) { 12 | exec(`node ${file}`, function (err, stdout, stderr) { 13 | position++; 14 | console.log('FILE: ', file, ` (${position}/${files.length})`); 15 | console.log(stdout); 16 | 17 | if (stderr) { 18 | errCount++; 19 | console.error(stderr); 20 | } else if (err) { 21 | errCount++; 22 | console.error(err); 23 | } 24 | 25 | if (position === files.length) { 26 | if (errCount) { 27 | console.error('Errors count: ', errCount); 28 | } 29 | } 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /build-vfs.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const vfsBefore = "var vfs = "; 4 | const vfsAfter = "; var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this; if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addVirtualFileSystem !== 'undefined') { _global.pdfMake.addVirtualFileSystem(vfs); } if (typeof module !== 'undefined') { module.exports = vfs; }"; 5 | const sourcePath = process.argv[2]; 6 | const vfsFilename = process.argv[3] ? process.argv[3] : './build/vfs_fonts.js'; 7 | 8 | var vfs = {}; 9 | 10 | if (sourcePath === undefined) { 11 | console.error('Usage: node build-vfs.js path [filename]'); 12 | console.log(''); 13 | console.log('Parameters:'); 14 | console.log(' path Source path with fonts.'); 15 | console.log(' filename Optional. Output vfs file. Default: ./build/vfs_fonts.js'); 16 | console.log(''); 17 | console.log('Examples:'); 18 | console.log(' node build-vfs.js "examples/fonts"'); 19 | console.log(' node build-vfs.js "examples/fonts" "./build/vfs_fonts.js"'); 20 | return; 21 | } 22 | 23 | if (!fs.existsSync(sourcePath)) { 24 | console.error('Source path "' + sourcePath + '" not found.'); 25 | return; 26 | } 27 | 28 | console.log('Source path:', sourcePath); 29 | console.log(''); 30 | 31 | var files = fs.readdirSync(sourcePath); 32 | 33 | files.forEach(function (file) { 34 | var fileBase64 = fs.readFileSync(sourcePath + '/' + file).toString('base64'); 35 | console.log('FILE:', file); 36 | 37 | vfs[file] = fileBase64; 38 | }); 39 | 40 | const vfsFileContent = vfsBefore + JSON.stringify(vfs, null, 2) + vfsAfter; 41 | fs.writeFileSync(vfsFilename, vfsFileContent); 42 | 43 | console.log(''); 44 | console.log('Builded ' + files.length + ' files to ' + vfsFilename + '.'); 45 | -------------------------------------------------------------------------------- /build/fonts/Roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/build/fonts/Roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /build/fonts/Roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/build/fonts/Roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /build/fonts/Roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/build/fonts/Roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /build/fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/build/fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /dev-playground/README.md: -------------------------------------------------------------------------------- 1 | ## Dev Playground 2 | 3 | Start from pdfmake ROOT directory with nodemon if you want server to restart with every change you make in pdfmake/src: 4 | 5 | ``` 6 | cd dev-playground 7 | npm install # or: yarn 8 | npm install -g nodemon 9 | cd .. 10 | nodemon ./dev-playground/server.js 11 | ``` 12 | -------------------------------------------------------------------------------- /dev-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pdfmake-server-playground", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "body-parser": "^1.20.2", 6 | "express": "^4.18.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dev-playground/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 |
33 |
34 | 35 | 38 |
39 |
40 | 41 |
42 |
43 |
44 |

Playground does not make too much sense when horizontal resolution is below 300px

45 |
46 |
47 |
48 |
49 | 50 | 51 |
52 |
53 | 54 | 55 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /dev-playground/public/pdfmake.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Open Sans', sans-serif; 3 | color: #666; 4 | background: white !important; 5 | } 6 | .navbar { 7 | margin-bottom: 0; 8 | background-color: white; 9 | border-bottom: 0; 10 | } 11 | #top.navbar-default a { 12 | color: #0074c1; 13 | } 14 | #top.navbar-default .navbar-nav a{ 15 | text-transform: uppercase; 16 | } 17 | #top.navbar-default li:hover { 18 | background: rgb(249,249,249); 19 | } 20 | h2 { 21 | font-weight: 300; 22 | color: #0074c1; 23 | font-size: 24px; 24 | } 25 | .keyDescription { 26 | padding-bottom: 80px; 27 | } 28 | 29 | h4 { 30 | opacity: 0.9; 31 | } 32 | .subtitle { 33 | color: white; 34 | margin-top: 30px; 35 | } 36 | .header, .playground { 37 | background: #0074c1; 38 | color: white; 39 | } 40 | .header p { 41 | opacity: 0.7; 42 | font-weight: 300; 43 | font-size: 18px; 44 | } 45 | h1 { 46 | font-size: 36px; 47 | font-weight: 300; 48 | } 49 | 50 | .playground ul { 51 | list-style: none; 52 | padding: 0; 53 | margin-left: -8px; 54 | margin-bottom: 0; 55 | } 56 | 57 | .playground li { 58 | display: none; 59 | padding: 8px; 60 | text-transform: uppercase; 61 | cursor: pointer; 62 | font-size: 12px; 63 | text-overflow: ellipsis; 64 | } 65 | 66 | .playground li:hover { 67 | background: rgba(0,0,0,0.1); 68 | } 69 | 70 | #pdfeViewer { 71 | background-color: #404040; 72 | } 73 | 74 | @media(min-width: 768px) { 75 | h1 { 76 | font-size: 60px; 77 | } 78 | .header { 79 | padding-top: 20px; 80 | padding-bottom: 40px; 81 | margin-bottom: 30px; 82 | height: 400px; 83 | } 84 | .header p { 85 | font-size: 24px; 86 | line-height: 1.4; 87 | } 88 | .page h1 { 89 | font-size: 40px; 90 | } 91 | h2 { 92 | font-size: 30px; 93 | 94 | } 95 | 96 | .subtitle { 97 | margin-top: 90px; 98 | } 99 | } 100 | .pdf { 101 | font-weight: 300; 102 | } 103 | .make { 104 | font-weight: 600 105 | } 106 | 107 | .playgroundBody .content { 108 | display: none; 109 | } 110 | 111 | 112 | #editor { 113 | position: absolute; 114 | top: 0px; 115 | right: 50%; 116 | left: 0; 117 | width: 50%; 118 | height: 100%; 119 | } 120 | 121 | 122 | #stats { 123 | display: none; 124 | float: right; 125 | position: relative; 126 | top: 6px; 127 | opacity: 0.5; 128 | } 129 | #printThis { 130 | float: right; 131 | top:0; 132 | } 133 | 134 | 135 | @media(min-width: 300px) and (max-width: 1000px) { 136 | .playground .container li { 137 | display: inline-block; 138 | } 139 | 140 | #exampleList li { 141 | width: 60px; 142 | overflow: hidden; 143 | text-overflow: ellipsis; 144 | } 145 | 146 | .playgroundBody .content { 147 | position: fixed; 148 | display: block; 149 | bottom: 0; 150 | left: 0; 151 | right: 0; 152 | top: 83px; 153 | } 154 | 155 | #editor { 156 | position: absolute; 157 | right: 0; 158 | left: 0; 159 | width: 100%; 160 | height: 50%; 161 | top: 0; 162 | bottom: 50%; 163 | } 164 | 165 | #pdfV { 166 | left: 0; 167 | right: 0; 168 | bottom: 0; 169 | top: 50%; 170 | height: 50%; 171 | width: 100%; 172 | position: absolute; 173 | } 174 | 175 | .playgroundBody .notEnoughSpace, #stats { 176 | display: none; 177 | } 178 | } 179 | 180 | 181 | .subpage { 182 | margin-top: 50px; 183 | margin-bottom: 100px; 184 | } 185 | .subpage h1 { 186 | font-size: 48px; 187 | color: #0074c1; 188 | } 189 | 190 | @media(max-width: 767px) { 191 | .banner { 192 | display: none; 193 | } 194 | 195 | .subpage { 196 | margin-top: 40px; 197 | } 198 | .subpage h1 { 199 | font-size: 30px; 200 | font-weight: 400; 201 | } 202 | } 203 | 204 | @media(min-width: 1001px) { 205 | .playground .container li { 206 | display: inline-block; 207 | } 208 | .playgroundBody .content { 209 | position: fixed; 210 | display: block; 211 | bottom: 0; 212 | left: 0; 213 | right: 0; 214 | top: 83px; 215 | } 216 | 217 | #pdfV { 218 | left: 50%; 219 | width: 50%; 220 | height: 100%; 221 | right: 0; 222 | top: 0; 223 | bottom: 0; 224 | position: absolute; 225 | } 226 | .playgroundBody .notEnoughSpace { 227 | display: none; 228 | } 229 | 230 | #stats { 231 | display: block; 232 | } 233 | } 234 | 235 | #pdfeViewer { 236 | position: absolute; 237 | top: 0px; 238 | left: 50%; 239 | bottom: 0; 240 | right: 0; 241 | } 242 | 243 | #toolbarViewerRight, #sidebarToggle, .toolbarButtonSpacer, #viewFind { 244 | display:none; 245 | } 246 | 247 | #toolbarViewerLeft { 248 | width: 100% 249 | } 250 | 251 | .playground { 252 | position: relative; 253 | } 254 | 255 | .banner small { 256 | color: white; 257 | opacity: 0.4; 258 | } 259 | -------------------------------------------------------------------------------- /dev-playground/public/playground.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('pdfmake', ['ngRoute']); 2 | 3 | app.config(function ($routeProvider) { 4 | $routeProvider 5 | .when('/', { 6 | templateUrl: 'index.html', 7 | controller: 'PlaygroundController' 8 | }) 9 | .otherwise({ redirectTo: '/' }); 10 | }); 11 | -------------------------------------------------------------------------------- /dev-playground/public/samples/basics: -------------------------------------------------------------------------------- 1 | content: [ 2 | 'First paragraph', 3 | 'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines' 4 | ] 5 | -------------------------------------------------------------------------------- /dev-playground/public/samples/inline-styling: -------------------------------------------------------------------------------- 1 | content: [ 2 | { 3 | text: 'This is a header (whole paragraph uses the same header style)\n\n', 4 | style: 'header' 5 | }, 6 | { 7 | text: [ 8 | 'It is however possible to provide an array of texts ', 9 | 'to the paragraph (instead of a single string) and have ', 10 | {text: 'a better ', fontSize: 15, bold: true}, 11 | 'control over it. \nEach inline can be ', 12 | {text: 'styled ', fontSize: 20}, 13 | {text: 'independently ', italics: true, fontSize: 40}, 14 | 'then.\n\n' 15 | ] 16 | }, 17 | {text: 'Mixing named styles and style-overrides', style: 'header'}, 18 | { 19 | style: 'bigger', 20 | italics: false, 21 | text: [ 22 | 'We can also mix named-styles and style-overrides at both paragraph and inline level. ', 23 | 'For example, this paragraph uses the "bigger" style, which changes fontSize to 15 and sets italics to true. ', 24 | 'Texts are not italics though. It\'s because we\'ve overriden italics back to false at ', 25 | 'the paragraph level. \n\n', 26 | 'We can also change the style of a single inline. Let\'s use a named style called header: ', 27 | {text: 'like here.\n', style: 'header'}, 28 | 'It got bigger and bold.\n\n', 29 | 'OK, now we\'re going to mix named styles and style-overrides at the inline level. ', 30 | 'We\'ll use header style (it makes texts bigger and bold), but we\'ll override ', 31 | 'bold back to false: ', 32 | {text: 'wow! it works!', style: 'header', bold: false}, 33 | '\n\nMake sure to take a look into the sources to understand what\'s going on here.' 34 | ] 35 | } 36 | ], 37 | styles: { 38 | header: { 39 | fontSize: 18, 40 | bold: true 41 | }, 42 | bigger: { 43 | fontSize: 15, 44 | italics: true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dev-playground/public/samples/margins: -------------------------------------------------------------------------------- 1 | content: [ 2 | { 3 | stack: [ 4 | 'This header has both top and bottom margins defined', 5 | {text: 'This is a subheader', style: 'subheader'}, 6 | ], 7 | style: 'header' 8 | }, 9 | { 10 | text: [ 11 | 'Margins have slightly different behavior than other layout properties. They are not inherited, unlike anything else. They\'re applied only to those nodes which explicitly ', 12 | 'set margin or style property.\n', 13 | ] 14 | }, 15 | { 16 | text: 'This paragraph (consisting of a single line) directly sets top and bottom margin to 20', 17 | margin: [0, 20], 18 | }, 19 | { 20 | stack: [ 21 | {text: [ 22 | 'This line begins a stack of paragraphs. The whole stack uses a ', 23 | {text: 'superMargin', italics: true}, 24 | ' style (with margin and fontSize properties).', 25 | ] 26 | }, 27 | {text: ['When you look at the', {text: ' document definition', italics: true}, ', you will notice that fontSize is inherited by all paragraphs inside the stack.']}, 28 | 'Margin however is only applied once (to the whole stack).' 29 | ], 30 | style: 'superMargin' 31 | }, 32 | { 33 | stack: [ 34 | 'I\'m not sure yet if this is the desired behavior. I find it a better approach however. One thing to be considered in the future is an explicit layout property called inheritMargin which could opt-in the inheritance.\n\n', 35 | { 36 | fontSize: 15, 37 | text: [ 38 | 'Currently margins for ', 39 | /* the following margin definition doesn't change anything */ 40 | {text: 'inlines', margin: 20}, 41 | ' are ignored\n\n' 42 | ], 43 | }, 44 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.\n', 45 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.\n', 46 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.\n', 47 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.\n', 48 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.\n', 49 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.\n', 50 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.\n', 51 | ], 52 | margin: [0, 20, 0, 0], 53 | alignment: 'justify' 54 | } 55 | ], 56 | styles: { 57 | header: { 58 | fontSize: 18, 59 | bold: true, 60 | alignment: 'right', 61 | margin: [0, 190, 0, 80] 62 | }, 63 | subheader: { 64 | fontSize: 14 65 | }, 66 | superMargin: { 67 | margin: [20, 0, 40, 0], 68 | fontSize: 15 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /dev-playground/public/samples/named-styles: -------------------------------------------------------------------------------- 1 | content: [ 2 | { 3 | text: 'This is a header, using header style', 4 | style: 'header' 5 | }, 6 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam.\n\n', 7 | { 8 | text: 'Subheader 1 - using subheader style', 9 | style: 'subheader' 10 | }, 11 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 12 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 13 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.\n\n', 14 | { 15 | text: 'Subheader 2 - using subheader style', 16 | style: 'subheader' 17 | }, 18 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 19 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.\n\n', 20 | { 21 | text: 'It is possible to apply multiple styles, by passing an array. This paragraph uses two styles: quote and small. When multiple styles are provided, they are evaluated in the specified order which is important in case they define the same properties', 22 | style: ['quote', 'small'] 23 | } 24 | ], 25 | styles: { 26 | header: { 27 | fontSize: 18, 28 | bold: true 29 | }, 30 | subheader: { 31 | fontSize: 15, 32 | bold: true 33 | }, 34 | quote: { 35 | italics: true 36 | }, 37 | small: { 38 | fontSize: 8 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dev-playground/public/samples/style-overrides: -------------------------------------------------------------------------------- 1 | content: [ 2 | { 3 | text: 'This paragraph uses header style and extends the alignment property', 4 | style: 'header', 5 | alignment: 'center' 6 | }, 7 | { 8 | text: [ 9 | 'This paragraph uses header style and overrides bold value setting it back to false.\n', 10 | 'Header style in this example sets alignment to justify, so this paragraph should be rendered \n', 11 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.' 12 | ], 13 | style: 'header', 14 | bold: false 15 | } 16 | ], 17 | styles: { 18 | header: { 19 | fontSize: 18, 20 | bold: true, 21 | alignment: 'justify' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dev-playground/public/theme-monokai.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!0,t.cssClass="ace-monokai",t.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}";var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}) 2 | -------------------------------------------------------------------------------- /dev-playground/server.js: -------------------------------------------------------------------------------- 1 | 2 | var http = require('http'); 3 | var express = require('express'); 4 | var path = require('path'); 5 | var bodyParser = require('body-parser'); 6 | 7 | var pdfmake = require('../js/index'); 8 | 9 | var app = express(); 10 | 11 | app.use(express.static(path.join(__dirname, 'public'))); 12 | app.use(bodyParser.json({ limit: '50mb' })); 13 | app.use(bodyParser.urlencoded({ extended: false })); 14 | 15 | function createPdfBinary(docDefinition) { 16 | var fonts = { 17 | Roboto: { 18 | normal: path.join(__dirname, '..', 'examples', '/fonts/Roboto-Regular.ttf'), 19 | bold: path.join(__dirname, '..', 'examples', '/fonts/Roboto-Medium.ttf'), 20 | italics: path.join(__dirname, '..', 'examples', '/fonts/Roboto-Italic.ttf'), 21 | bolditalics: path.join(__dirname, '..', 'examples', '/fonts/Roboto-MediumItalic.ttf') 22 | } 23 | }; 24 | 25 | pdfmake.setFonts(fonts); 26 | 27 | var pdf = pdfmake.createPdf(docDefinition); 28 | return pdf.getDataUrl(); 29 | } 30 | 31 | app.post('/pdf', function (req, res) { 32 | const dd = new Function(req.body.content + '; return dd;')(); 33 | 34 | createPdfBinary(dd).then(function (binary) { 35 | res.contentType('application/pdf'); 36 | res.send(binary); 37 | }, function (error) { 38 | res.send('ERROR:' + error); 39 | }); 40 | 41 | }); 42 | 43 | var server = http.createServer(app); 44 | var port = process.env.PORT || 1234; 45 | server.listen(port); 46 | 47 | console.log('http server listening on port %d', port); 48 | console.log('dev-playground is available at http://localhost:%d', port); 49 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import jsdoc from "eslint-plugin-jsdoc"; 2 | import globals from "globals"; 3 | import js from "@eslint/js"; 4 | 5 | export default [ 6 | { 7 | ignores: ["src/3rd-party/svg-to-pdfkit/*"], 8 | }, 9 | 10 | js.configs.recommended, 11 | 12 | { 13 | plugins: { 14 | jsdoc, 15 | }, 16 | 17 | languageOptions: { 18 | globals: { 19 | ...globals.browser, 20 | ...globals.node, 21 | ...globals.mocha, 22 | }, 23 | 24 | ecmaVersion: 9, 25 | sourceType: "module", 26 | }, 27 | 28 | rules: { 29 | semi: 2, 30 | "no-throw-literal": 2, 31 | "no-prototype-builtins": 0, 32 | "jsdoc/check-examples": 0, 33 | "jsdoc/check-param-names": 1, 34 | "jsdoc/check-tag-names": 1, 35 | "jsdoc/check-types": 1, 36 | "jsdoc/no-undefined-types": 1, 37 | "jsdoc/require-description": 0, 38 | "jsdoc/require-description-complete-sentence": 0, 39 | "jsdoc/require-example": 0, 40 | "jsdoc/require-hyphen-before-param-description": 0, 41 | "jsdoc/require-param": 1, 42 | "jsdoc/require-param-description": 0, 43 | "jsdoc/require-param-name": 1, 44 | "jsdoc/require-param-type": 1, 45 | "jsdoc/require-returns": 1, 46 | "jsdoc/require-returns-check": 1, 47 | "jsdoc/require-returns-description": 0, 48 | "jsdoc/require-returns-type": 1, 49 | "jsdoc/valid-types": 1, 50 | }, 51 | } 52 | ]; 53 | -------------------------------------------------------------------------------- /examples/basics.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | // or you can define the font manually: 8 | /* 9 | pdfmake.addFonts({ 10 | Roboto: { 11 | normal: '../fonts/Roboto/Roboto-Regular.ttf', 12 | bold: '../fonts/Roboto/Roboto-Medium.ttf', 13 | italics: '../fonts/Roboto/Roboto-Italic.ttf', 14 | bolditalics: '../fonts/Roboto/Roboto-MediumItalic.ttf' 15 | } 16 | }); 17 | */ 18 | 19 | var docDefinition = { 20 | content: [ 21 | 'First paragraph', 22 | 'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines' 23 | ] 24 | }; 25 | 26 | var now = new Date(); 27 | 28 | var pdf = pdfmake.createPdf(docDefinition); 29 | pdf.write('pdfs/basics.pdf').then(() => { 30 | console.log(new Date() - now); 31 | }, err => { 32 | console.error(err); 33 | }); 34 | -------------------------------------------------------------------------------- /examples/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /examples/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /examples/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /examples/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/fonts/sampleImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/fonts/sampleImage.jpg -------------------------------------------------------------------------------- /examples/links.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var docDefinition = { 8 | content: [ 9 | { 10 | text: [ 11 | 'Link to ', 12 | { text: 'pdfmake website', link: 'http://pdfmake.org', decoration: 'underline' }, 13 | ' and ', 14 | { text: 'documentation', link: 'https://pdfmake.github.io/docs/', decoration: 'underline' }, 15 | '.' 16 | ] 17 | }, 18 | { text: 'Go to page 2', linkToPage: 2, decoration: 'underline' }, 19 | { text: 'Link to header 2', linkToDestination: 'header2', decoration: 'underline' }, 20 | 'Links are also supported with images:', 21 | { image: 'fonts/sampleImage.jpg', width: 150, link: 'http://pdfmake.org' }, 22 | 'With link to page', 23 | { image: 'fonts/sampleImage.jpg', width: 150, linkToPage: 2 }, 24 | 'And link to header 2', 25 | { image: 'fonts/sampleImage.jpg', width: 150, linkToDestination: 'header2' }, 26 | { text: 'Header on page 2', fontSize: 18, bold: true, pageBreak: 'before' }, 27 | { text: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sollicitudin. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Suspendisse nisl. Mauris elementum mauris vitae tortor. Phasellus et lorem id felis nonummy placerat. Aliquam erat volutpat. In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Etiam bibendum elit eget erat. Nullam rhoncus aliquam metus. Proin mattis lacinia justo. Nullam sit amet magna in magna gravida vehicula. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Integer lacinia. Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor.' }, 28 | '\n\n', 29 | { text: 'Header 2', id: 'header2', fontSize: 18, bold: true }, 30 | { text: 'Go to page 1', linkToPage: 1, decoration: 'underline' }, 31 | ] 32 | }; 33 | 34 | var now = new Date(); 35 | 36 | var pdf = pdfmake.createPdf(docDefinition); 37 | pdf.write('pdfs/links.pdf').then(() => { 38 | console.log(new Date() - now); 39 | }, err => { 40 | console.error(err); 41 | }); 42 | -------------------------------------------------------------------------------- /examples/pdfa.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var docDefinition = { 8 | version: '1.5', // PDF version 9 | subset: 'PDF/A-3a', // Subset types: // PDF/A-1, PDF/A-1a, PDF/A-1b, PDF/A-2, PDF/A-2a, PDF/A-2b, PDF/A-3, PDF/A-3a, PDF/A-3b, PDF/UA 10 | tagged: true, // Mark document as Tagged PDF 11 | displayTitle: true, // Display of document title in window title 12 | info: { 13 | title: 'Awesome PDF document from pdfmake' 14 | }, 15 | content: [ 16 | 'PDF/A document for archive' 17 | ] 18 | }; 19 | 20 | var now = new Date(); 21 | 22 | var pdf = pdfmake.createPdf(docDefinition); 23 | pdf.write('pdfs/pdfa.pdf').then(() => { 24 | console.log(new Date() - now); 25 | }, err => { 26 | console.error(err); 27 | }); 28 | -------------------------------------------------------------------------------- /examples/pdfs/absolute.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/absolute.pdf -------------------------------------------------------------------------------- /examples/pdfs/attachments.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/attachments.pdf -------------------------------------------------------------------------------- /examples/pdfs/background.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/background.pdf -------------------------------------------------------------------------------- /examples/pdfs/basics.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/basics.pdf -------------------------------------------------------------------------------- /examples/pdfs/columns_simple.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/columns_simple.pdf -------------------------------------------------------------------------------- /examples/pdfs/images.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/images.pdf -------------------------------------------------------------------------------- /examples/pdfs/links.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/links.pdf -------------------------------------------------------------------------------- /examples/pdfs/lists.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/lists.pdf -------------------------------------------------------------------------------- /examples/pdfs/margins.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/margins.pdf -------------------------------------------------------------------------------- /examples/pdfs/named_styles.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/named_styles.pdf -------------------------------------------------------------------------------- /examples/pdfs/named_styles_with_overrides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/named_styles_with_overrides.pdf -------------------------------------------------------------------------------- /examples/pdfs/pageReference.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/pageReference.pdf -------------------------------------------------------------------------------- /examples/pdfs/pdfa.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/pdfa.pdf -------------------------------------------------------------------------------- /examples/pdfs/qrCode.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/qrCode.pdf -------------------------------------------------------------------------------- /examples/pdfs/relative.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/relative.pdf -------------------------------------------------------------------------------- /examples/pdfs/sections.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/sections.pdf -------------------------------------------------------------------------------- /examples/pdfs/security.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/security.pdf -------------------------------------------------------------------------------- /examples/pdfs/standardfonts.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/standardfonts.pdf -------------------------------------------------------------------------------- /examples/pdfs/styling_inlines.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/styling_inlines.pdf -------------------------------------------------------------------------------- /examples/pdfs/styling_properties.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/styling_properties.pdf -------------------------------------------------------------------------------- /examples/pdfs/svgs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/svgs.pdf -------------------------------------------------------------------------------- /examples/pdfs/tables.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/tables.pdf -------------------------------------------------------------------------------- /examples/pdfs/textDecorations.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/textDecorations.pdf -------------------------------------------------------------------------------- /examples/pdfs/toc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/toc.pdf -------------------------------------------------------------------------------- /examples/pdfs/vectors.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/vectors.pdf -------------------------------------------------------------------------------- /examples/pdfs/watermark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/examples/pdfs/watermark.pdf -------------------------------------------------------------------------------- /examples/qrCode.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var greeting = 'Can you see me'; 8 | var url = 'http://pdfmake.org'; 9 | var longText = 'The amount of data that can be stored in the QR code symbol depends on the datatype (mode, or input character set), version (1, …, 40, indicating the overall dimensions of the symbol), and error correction level. The maximum storage capacities occur for 40-L symbols (version 40, error correction level L):'; 10 | 11 | 12 | function header(text) { 13 | return { text: text, margins: [0, 0, 0, 8] }; 14 | } 15 | 16 | var docDefinition = { 17 | pageMargins: [10, 10, 10, 10], 18 | content: [ 19 | header(greeting), 20 | { qr: greeting }, 21 | '\n', 22 | 23 | header('Colored QR'), 24 | { qr: greeting, foreground: 'red', background: 'yellow' }, 25 | '\n', 26 | 27 | header(url), 28 | { qr: url }, 29 | '\n', 30 | 31 | header('A very long text (' + longText.length + ' chars)'), 32 | { qr: longText }, 33 | '\n', 34 | 35 | header('same long text with fit = 100 and alignment = right'), 36 | { qr: longText, fit: 150, alignment: 'right' }, 37 | '\n', 38 | 39 | header('same long text with fit = 100 and padding = 1 modules in pixel'), 40 | { qr: longText, fit: 150, padding: 1 }, 41 | ] 42 | }; 43 | 44 | var now = new Date(); 45 | 46 | var pdf = pdfmake.createPdf(docDefinition); 47 | pdf.write('pdfs/qrCode.pdf').then(() => { 48 | console.log(new Date() - now); 49 | }, err => { 50 | console.error(err); 51 | }); 52 | -------------------------------------------------------------------------------- /examples/relative.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var left = 20; 8 | var width = 130; 9 | var top = 20; 10 | var height = 130; 11 | var yAxisWidth = 30; 12 | var xAxisHeight = 30; 13 | var tickSize = 5; 14 | 15 | var chartLines = []; 16 | var chartText = []; 17 | var chart = [{ stack: chartText }, { canvas: chartLines }]; 18 | 19 | buildXAxis(); 20 | buildYAxis(); 21 | 22 | var docDefinition = { 23 | content: [ 24 | { text: 'We sometimes don\'t know the absolute position of text', margin: [10, 0, 0, 50] }, 25 | { 26 | columns: [ 27 | { width: '50%', text: 'horizontal position is not known either' }, 28 | { width: '50%', stack: chart } 29 | ] 30 | }, 31 | { text: 'We can position relative with center and right alignment', margin: [0, 50, 0, 50] }, 32 | { 33 | table: { 34 | widths: [100, 100, 100], 35 | body: [ 36 | ['Column with a lot of text. Column with a lot of text. Column with a lot of text. Column with a lot of text.', 37 | { 38 | text: 'I\'m aligned center', 39 | style: { 40 | alignment: 'center', 41 | }, 42 | relativePosition: { 43 | x: 0, 44 | y: 25, 45 | } 46 | }, 47 | { 48 | text: 'I\'m aligned right', 49 | style: { 50 | alignment: 'right', 51 | }, 52 | relativePosition: { 53 | x: 0, 54 | y: 25, 55 | } 56 | }] 57 | ] 58 | } 59 | } 60 | ] 61 | }; 62 | 63 | var now = new Date(); 64 | 65 | var pdf = pdfmake.createPdf(docDefinition); 66 | pdf.write('pdfs/relative.pdf').then(() => { 67 | console.log(new Date() - now); 68 | }, err => { 69 | console.error(err); 70 | }); 71 | 72 | function buildXAxis() { 73 | var xTicks = [ 74 | { t: '2016', x: 0, y: 0 }, 75 | { t: '2017', x: 25, y: 0 }, 76 | { t: '2018', x: 50, y: 0 }, 77 | { t: '2019', x: 75, y: 0 }, 78 | { t: '2020', x: 100, y: 0 } 79 | ]; 80 | 81 | chartLines.push(horizontalLine(left + yAxisWidth - 1, top + height - xAxisHeight, width - yAxisWidth + 1)); 82 | 83 | xTicks.forEach(function (tick) { 84 | chartLines.push(verticalLine(left + yAxisWidth + tick.x - 0.5, top + height - xAxisHeight, tickSize)); 85 | chartText.push({ text: tick.t, fontSize: 8, relativePosition: { x: left + yAxisWidth + tick.x - 9, y: top + height - xAxisHeight + tickSize + 2 } }); 86 | }); 87 | } 88 | 89 | function buildYAxis() { 90 | var yTicks = [ 91 | { t: '5', y: 0, x: 0 }, 92 | { t: '4', y: 25, x: 0 }, 93 | { t: '3', y: 50, x: 0 }, 94 | { t: '2', y: 75, x: 0 }, 95 | { t: '1', y: 100, x: 0 } 96 | ]; 97 | 98 | chartLines.push(verticalLine(left + yAxisWidth - 0.5, top - 0.5, height - xAxisHeight)); 99 | 100 | yTicks.forEach(function (tick) { 101 | chartLines.push(horizontalLine(left + yAxisWidth - tickSize - 1, top + tick.y, tickSize)); 102 | chartText.push({ text: tick.t, fontSize: 8, relativePosition: { x: left + yAxisWidth - tickSize - 8, y: top + tick.y - 5 } }); 103 | }); 104 | } 105 | 106 | function horizontalLine(x, y, length) { 107 | return { type: 'line', x1: x, y1: y, x2: x + length, y2: y }; 108 | } 109 | 110 | function verticalLine(x, y, height) { 111 | return { type: 'line', x1: x, y1: y, x2: x, y2: y + height }; 112 | } 113 | -------------------------------------------------------------------------------- /examples/sections.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var docDefinition = { 8 | header: function () { return 'default header'; }, 9 | footer: function () { return 'default footer'; }, 10 | background: function() { return { text:'global background', alignment: 'right' }; }, 11 | watermark: 'default watermark', 12 | content: [ 13 | { 14 | section: [ 15 | 'SECTION 1', 16 | 'Text in section.' 17 | ] 18 | }, 19 | { 20 | header: function (currentPage, pageCount) { return 'header: ' + currentPage.toString() + ' of ' + pageCount; }, 21 | footer: function (currentPage, pageCount) { return 'footer: ' + currentPage.toString() + ' of ' + pageCount; }, 22 | background: function() { return { text:'SECTION 2 background', alignment: 'right' }; }, 23 | watermark: 'SECTION 2 watermark', 24 | pageOrientation: 'landscape', 25 | section: [ 26 | 'SECTION 2', 27 | 'Text in section as landscape page.' 28 | ] 29 | }, 30 | { 31 | header: null, 32 | footer: null, 33 | background: null, 34 | watermark: null, 35 | pageSize: 'A7', 36 | pageOrientation: 'portrait', 37 | section: [ 38 | 'SECTION 3', 39 | 'Text in section as A7 page.' 40 | ] 41 | }, 42 | { 43 | watermark: 'inherit', 44 | pageSize: 'A6', 45 | pageOrientation: 'portrait', 46 | pageMargins: 5, 47 | section: [ 48 | 'SECTION 4', 49 | 'Text in section as A6 page with margin.' 50 | ] 51 | }, 52 | { 53 | watermark: 'watermark for inherit', 54 | pageSize: 'A6', 55 | pageOrientation: 'landscape', 56 | pageMargins: 10, 57 | section: [ 58 | 'SECTION 5', 59 | 'Text in section as A6 landscape page with margin.' 60 | ] 61 | }, 62 | { 63 | watermark: 'inherit', 64 | pageSize: 'inherit', 65 | pageOrientation: 'inherit', 66 | pageMargins: 'inherit', 67 | section: [ 68 | 'SECTION 6', 69 | 'Text in section with page definition as previous page. Page size, orientation and margins are inherited.' 70 | ] 71 | }, 72 | { 73 | header: function (currentPage, pageCount) { return 'header in section 8: ' + currentPage.toString() + ' of ' + pageCount; }, 74 | footer: function (currentPage, pageCount) { return 'footer in section 8: ' + currentPage.toString() + ' of ' + pageCount; }, 75 | section: [ 76 | 'SECTION 7', 77 | 'Text in section with page definition as defined in document.' 78 | ] 79 | } 80 | ] 81 | }; 82 | 83 | var now = new Date(); 84 | 85 | var pdf = pdfmake.createPdf(docDefinition); 86 | pdf.write('pdfs/sections.pdf').then(() => { 87 | console.log(new Date() - now); 88 | }, err => { 89 | console.error(err); 90 | }); 91 | -------------------------------------------------------------------------------- /examples/security.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var docDefinition = { 8 | //userPassword: '123', 9 | ownerPassword: '123456', 10 | permissions: { 11 | printing: 'highResolution', //'lowResolution' 12 | modifying: false, 13 | copying: false, 14 | annotating: true, 15 | fillingForms: true, 16 | contentAccessibility: true, 17 | documentAssembly: true 18 | }, 19 | content: [ 20 | 'Document content with security', 21 | 'For details see to source or documentation.' 22 | ] 23 | }; 24 | 25 | var now = new Date(); 26 | 27 | var pdf = pdfmake.createPdf(docDefinition); 28 | pdf.write('pdfs/security.pdf').then(() => { 29 | console.log(new Date() - now); 30 | }, err => { 31 | console.error(err); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/standardfonts.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Courier = require('../standard-fonts/Courier'); 5 | pdfmake.addFonts(Courier); 6 | 7 | var Helvetica = require('../standard-fonts/Helvetica'); 8 | pdfmake.addFonts(Helvetica); 9 | 10 | var Times = require('../standard-fonts/Times'); 11 | pdfmake.addFonts(Times); 12 | 13 | //var Symbol = require('../standard-fonts/Symbol'); 14 | //pdfmake.addFonts(Symbol); 15 | 16 | //var ZapfDingbats = require('../standard-fonts/ZapfDingbats'); 17 | //pdfmake.addFonts(ZapfDingbats); 18 | 19 | 20 | var docDefinition = { 21 | content: [ 22 | { text: 'Standard fonts supports only ANSI code page (only english characters)!', bold: true }, 23 | ' ', 24 | { text: 'Courier font', font: 'Courier' }, 25 | { text: 'Helvetica font', font: 'Helvetica' }, 26 | { text: 'Times font', font: 'Times' }, 27 | //{ text: 'Symbol font', font: 'Symbol' }, 28 | //{ text: 'ZapfDingbats font', font: 'ZapfDingbats' }, 29 | 30 | ], 31 | defaultStyle: { 32 | font: 'Helvetica' 33 | }, 34 | language: "en-AU" 35 | }; 36 | 37 | var now = new Date(); 38 | 39 | var pdf = pdfmake.createPdf(docDefinition); 40 | pdf.write('pdfs/standardfonts.pdf').then(() => { 41 | console.log(new Date() - now); 42 | }, err => { 43 | console.error(err); 44 | }); 45 | -------------------------------------------------------------------------------- /examples/styling_inlines.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var docDefinition = { 8 | content: [ 9 | { 10 | text: 'This is a header (whole paragraph uses the same header style)\n\n', 11 | style: 'header' 12 | }, 13 | { 14 | text: [ 15 | 'It is however possible to provide an array of texts ', 16 | 'to the paragraph (instead of a single string) and have ', 17 | { text: 'a better ', fontSize: 15, bold: true }, 18 | 'control over it. \nEach inline can be ', 19 | { text: 'styled ', fontSize: 20 }, 20 | { text: 'independently ', italics: true, fontSize: 40 }, 21 | 'then.\n\n' 22 | ] 23 | }, 24 | { text: 'Mixing named styles and style-overrides', style: 'header' }, 25 | { 26 | style: 'bigger', 27 | italics: false, 28 | text: [ 29 | 'We can also mix named-styles and style-overrides at both paragraph and inline level. ', 30 | 'For example, this paragraph uses the "bigger" style, which changes fontSize to 15 and sets italics to true. ', 31 | 'Texts are not italics though. It\'s because we\'ve overriden italics back to false at ', 32 | 'the paragraph level. \n\n', 33 | 'We can also change the style of a single inline. Let\'s use a named style called header: ', 34 | { text: 'like here.\n', style: 'header' }, 35 | 'It got bigger and bold.\n\n', 36 | 'OK, now we\'re going to mix named styles and style-overrides at the inline level. ', 37 | 'We\'ll use header style (it makes texts bigger and bold), but we\'ll override ', 38 | 'bold back to false: ', 39 | { text: 'wow! it works!', style: 'header', bold: false }, 40 | '\n\nMake sure to take a look into the sources to understand what\'s going on here.' 41 | ] 42 | } 43 | ], 44 | styles: { 45 | header: { 46 | fontSize: 18, 47 | bold: true 48 | }, 49 | bigger: { 50 | fontSize: 15, 51 | italics: true 52 | } 53 | } 54 | }; 55 | 56 | var now = new Date(); 57 | 58 | var pdf = pdfmake.createPdf(docDefinition); 59 | pdf.write('pdfs/styling_inlines.pdf').then(() => { 60 | console.log(new Date() - now); 61 | }, err => { 62 | console.error(err); 63 | }); 64 | -------------------------------------------------------------------------------- /examples/styling_named_styles.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | var docDefinition = { 8 | content: [ 9 | { 10 | text: 'This is a header, using header style', 11 | style: 'header' 12 | }, 13 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam.\n\n', 14 | { 15 | text: 'Subheader 1 - using subheader style', 16 | style: 'subheader' 17 | }, 18 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 19 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 20 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.\n\n', 21 | { 22 | text: 'Subheader 2 - using subheader style', 23 | style: 'subheader' 24 | }, 25 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 26 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.\n\n', 27 | { 28 | text: 'It is possible to apply multiple styles, by passing an array. This paragraph uses two styles: quote and small. When multiple styles are provided, they are evaluated in the specified order which is important in case they define the same properties', 29 | style: ['quote', 'small'] 30 | } 31 | ], 32 | styles: { 33 | header: { 34 | fontSize: 18, 35 | bold: true 36 | }, 37 | subheader: { 38 | fontSize: 15, 39 | bold: true 40 | }, 41 | quote: { 42 | italics: true 43 | }, 44 | small: { 45 | fontSize: 8 46 | } 47 | } 48 | }; 49 | 50 | var now = new Date(); 51 | 52 | var pdf = pdfmake.createPdf(docDefinition); 53 | pdf.write('pdfs/named_styles.pdf').then(() => { 54 | console.log(new Date() - now); 55 | }, err => { 56 | console.error(err); 57 | }); 58 | -------------------------------------------------------------------------------- /examples/styling_named_styles_with_overrides.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | 8 | var docDefinition = { 9 | content: [ 10 | { 11 | text: 'This paragraph uses header style and extends the alignment property', 12 | style: 'header', 13 | alignment: 'center' 14 | }, 15 | { 16 | text: [ 17 | 'This paragraph uses header style and overrides bold value setting it back to false.\n', 18 | 'Header style in this example sets alignment to justify, so this paragraph should be rendered \n', 19 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Malit profecta versatur nomine ocurreret multavit, officiis viveremus aeternum superstitio suspicor alia nostram, quando nostros congressus susceperant concederetur leguntur iam, vigiliae democritea tantopere causae, atilii plerumque ipsas potitur pertineant multis rem quaeri pro, legendum didicisse credere ex maluisset per videtis. Cur discordans praetereat aliae ruinae dirigentur orestem eodem, praetermittenda divinum. Collegisti, deteriora malint loquuntur officii cotidie finitas referri doleamus ambigua acute. Adhaesiones ratione beate arbitraretur detractis perdiscere, constituant hostis polyaeno. Diu concederetur.' 20 | ], 21 | style: 'header', 22 | bold: false 23 | } 24 | ], 25 | styles: { 26 | header: { 27 | fontSize: 18, 28 | bold: true, 29 | alignment: 'justify' 30 | } 31 | }, 32 | }; 33 | 34 | var now = new Date(); 35 | 36 | var pdf = pdfmake.createPdf(docDefinition); 37 | pdf.write('pdfs/named_styles_with_overrides.pdf').then(() => { 38 | console.log(new Date() - now); 39 | }, err => { 40 | console.error(err); 41 | }); 42 | -------------------------------------------------------------------------------- /examples/styling_properties.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | 8 | var docDefinition = { 9 | content: [ 10 | { 11 | text: 'Paragraphs can also by styled without using named-styles (this one sets fontSize to 25)', 12 | fontSize: 25 13 | }, 14 | 'Another paragraph, using default style, this time a little bit longer to make sure, this line will be divided into at least two lines\n\n', 15 | { 16 | text: 'This paragraph does not use a named-style and sets fontSize to 8 and italics to true', 17 | fontSize: 8, 18 | italics: true 19 | }, 20 | '\n\nFor preserving leading spaces use preserveLeadingSpaces property:', 21 | { text: ' This is a paragraph with preserved leading spaces.', preserveLeadingSpaces: true }, 22 | { text: '{', preserveLeadingSpaces: true }, 23 | { text: ' "sample": {', preserveLeadingSpaces: true }, 24 | { text: ' "json": "nested"', preserveLeadingSpaces: true }, 25 | { text: ' }', preserveLeadingSpaces: true }, 26 | { text: '}', preserveLeadingSpaces: true }, 27 | '\n\nfontFeatures property:', 28 | { text: 'Hello World 1234567890', fontFeatures: ['smcp'] }, 29 | { text: 'Hello World 1234567890', fontFeatures: ['c2sc'] }, 30 | { text: 'Hello World 1234567890', fontFeatures: ['onum'] }, 31 | { text: 'Hello World 1234567890', fontFeatures: ['onum', 'c2sc'] }, 32 | '\n\nText opacity:', 33 | { text: 'Hello World', opacity: 0.8 }, 34 | { text: 'Hello World', opacity: 0.6 }, 35 | { text: 'Hello World', opacity: 0.4 }, 36 | { text: 'Hello World', opacity: 0.2 }, 37 | { text: 'Hello World', opacity: 0.1 }, 38 | '\n\n Subscript, superscript:', 39 | { 40 | text: [ 41 | 'Hello World.', 42 | { 43 | text: '1, 2', 44 | sup: true, 45 | }, 46 | ' Let\'s continue our sentence. Notice the leading space.' 47 | ] 48 | }, 49 | { 50 | text: [ 51 | 'Hello', 52 | { 53 | text: '1, 2', 54 | sub: true, 55 | }, 56 | ' World' 57 | ] 58 | }, 59 | '\n\n', 60 | { 61 | text: 'Text background pattern', background: ['stripe45d', 'gray'] 62 | } 63 | ], 64 | patterns: { 65 | stripe45d: { 66 | boundingBox: [1, 1, 4, 4], 67 | xStep: 3, 68 | yStep: 3, 69 | pattern: '1 w 0 1 m 4 5 l s 2 0 m 5 3 l s' 70 | } 71 | } 72 | }; 73 | 74 | var now = new Date(); 75 | 76 | var pdf = pdfmake.createPdf(docDefinition); 77 | pdf.write('pdfs/styling_properties.pdf').then(() => { 78 | console.log(new Date() - now); 79 | }, err => { 80 | console.error(err); 81 | }); 82 | -------------------------------------------------------------------------------- /examples/textDecorations.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | 8 | var ct = []; 9 | 10 | ct.push({ text: 'Higlighted text', fontSize: 18, background: 'yellow' }); 11 | ct.push(' '); 12 | ct.push({ 13 | columns: [ 14 | { text: 'Underline decoration', decoration: 'underline' }, 15 | { text: 'Line Through decoration', decoration: 'lineThrough' }, 16 | { text: 'Overline decoration', decoration: 'overline' } 17 | ] 18 | }); 19 | ct.push(' '); 20 | ct.push({ 21 | columns: [ 22 | { text: 'Dashed style', decoration: 'underline', decorationStyle: 'dashed' }, 23 | { text: 'Dotted style', decoration: 'underline', decorationStyle: 'dotted' }, 24 | { text: 'Double style', decoration: 'underline', decorationStyle: 'double' }, 25 | { text: 'Wavy style', decoration: 'underline', decorationStyle: 'wavy' } 26 | ] 27 | }); 28 | ct.push(' '); 29 | ct.push({ 30 | columns: [ 31 | { text: 'Using colors', decoration: 'underline', decorationColor: 'blue' }, 32 | { text: 'Using colors', decoration: 'lineThrough', decorationColor: 'red' }, 33 | { text: 'Using colors', decoration: 'underline', decorationStyle: 'wavy', decorationColor: 'green' } 34 | ] 35 | }); 36 | 37 | 38 | 39 | var docDefinition = { 40 | content: ct 41 | }; 42 | 43 | var now = new Date(); 44 | 45 | var pdf = pdfmake.createPdf(docDefinition); 46 | pdf.write('pdfs/textDecorations.pdf').then(() => { 47 | console.log(new Date() - now); 48 | }, err => { 49 | console.error(err); 50 | }); 51 | -------------------------------------------------------------------------------- /examples/toc.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | 8 | var docDefinition = { 9 | content: [ 10 | { 11 | text: 'This is a TOC example. Text elements marked with tocItem: true will be located in the toc. See below.', 12 | pageBreak: 'after' 13 | }, 14 | { 15 | toc: { 16 | title: { text: 'INDEX', style: 'header' }, 17 | //textMargin: [0, 0, 0, 0], 18 | //textStyle: {italics: true}, 19 | numberStyle: { bold: true } 20 | } 21 | }, 22 | { 23 | text: 'This is a header, using header style', 24 | style: 'header', 25 | tocItem: true, 26 | tocStyle: { italics: true }, 27 | tocMargin: [0, 10, 0, 0], 28 | tocNumberStyle: { italics: true, decoration: 'underline' }, 29 | pageBreak: 'before' 30 | }, 31 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam.\n\n', 32 | { 33 | text: 'Subheader 1 - using subheader style', 34 | style: 'subheader', 35 | tocItem: true, 36 | pageBreak: 'before' 37 | }, 38 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 39 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 40 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.\n\n', 41 | { 42 | text: 'Subheader 2 - using subheader style', 43 | style: 'subheader', 44 | tocItem: true, 45 | pageBreak: 'before' 46 | }, 47 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.', 48 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.\n\n', 49 | { 50 | text: 'It is possible to apply multiple styles, by passing an array. This paragraph uses two styles: quote and small. When multiple styles are provided, they are evaluated in the specified order which is important in case they define the same properties', 51 | style: ['quote', 'small'] 52 | }, 53 | { 54 | text: [ 55 | { 56 | text: 'Subheader 3 - using inline text', 57 | style: 'subheader', 58 | tocItem: true 59 | }, 60 | { 61 | text: '; and this text not be displayed in ToC', 62 | italics: true 63 | } 64 | ], 65 | pageBreak: 'before' 66 | }, 67 | 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Confectum ponit legam, perferendis nomine miserum, animi. Moveat nesciunt triari naturam posset, eveniunt specie deorsus efficiat sermone instituendarum fuisse veniat, eademque mutat debeo. Delectet plerique protervi diogenem dixerit logikh levius probabo adipiscuntur afficitur, factis magistra inprobitatem aliquo andriam obiecta, religionis, imitarentur studiis quam, clamat intereant vulgo admonitionem operis iudex stabilitas vacillare scriptum nixam, reperiri inveniri maestitiam istius eaque dissentias idcirco gravis, refert suscipiet recte sapiens oportet ipsam terentianus, perpauca sedatio aliena video.' 68 | ], 69 | styles: { 70 | header: { 71 | fontSize: 18, 72 | bold: true 73 | }, 74 | subheader: { 75 | fontSize: 15, 76 | bold: true 77 | }, 78 | quote: { 79 | italics: true 80 | }, 81 | small: { 82 | fontSize: 8 83 | } 84 | } 85 | }; 86 | 87 | var now = new Date(); 88 | 89 | var pdf = pdfmake.createPdf(docDefinition); 90 | pdf.write('pdfs/toc.pdf').then(() => { 91 | console.log(new Date() - now); 92 | }, err => { 93 | console.error(err); 94 | }); 95 | -------------------------------------------------------------------------------- /examples/vectors.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | 8 | var docDefinition = { 9 | content: [ 10 | { 11 | text: [ 12 | 'This ', 13 | { text: 'is', color: 'green' }, 14 | ' the first ', 15 | { text: 'paragraph', color: 'red' } 16 | ] 17 | }, 18 | { 19 | canvas: [ 20 | { 21 | type: 'rect', 22 | x: 0, 23 | y: 0, 24 | w: 310, 25 | h: 290, 26 | r: 5, 27 | dash: { length: 5 }, 28 | // lineWidth: 10, 29 | lineColor: 'blue', 30 | }, 31 | { 32 | type: 'rect', 33 | x: 1, 34 | y: 1, 35 | w: 308, 36 | h: 288, 37 | r: 4, 38 | lineColor: 'red', 39 | color: '#ffffe0', 40 | }, 41 | { 42 | type: 'polyline', 43 | lineWidth: 3, 44 | closePath: true, 45 | points: [{ x: 10, y: 10 }, { x: 35, y: 40 }, { x: 100, y: 40 }, { x: 125, y: 10 }] 46 | }, 47 | { 48 | type: 'polyline', 49 | lineWidth: 2, 50 | color: 'blue', 51 | lineColor: 'red', 52 | points: [{ x: 10, y: 110 }, { x: 35, y: 140 }, { x: 100, y: 140 }, { x: 125, y: 110 }, { x: 10, y: 110 }] 53 | }, 54 | { 55 | type: 'line', 56 | x1: 40, y1: 60, 57 | x2: 260, y2: 60, 58 | lineWidth: 3 59 | }, 60 | { 61 | type: 'line', 62 | x1: 40, y1: 80, 63 | x2: 260, y2: 80, 64 | lineWidth: 10, 65 | lineCap: 'round' 66 | }, 67 | { 68 | type: 'line', 69 | x1: 40, y1: 100, 70 | x2: 260, y2: 100, 71 | lineWidth: 10, 72 | lineCap: 'square' 73 | }, 74 | { 75 | type: 'ellipse', 76 | x: 150, y: 140, 77 | color: 'red', 78 | fillOpacity: 0.5, 79 | r1: 80, r2: 60 80 | }, 81 | { 82 | type: 'rect', 83 | x: 150, 84 | y: 200, 85 | w: 150, 86 | h: 50, 87 | }, 88 | { 89 | type: 'rect', 90 | x: 10, y: 200, w: 100, h: 10, 91 | linearGradient: ['red', 'blue'] 92 | }, 93 | { 94 | type: 'rect', 95 | x: 10, y: 215, w: 100, h: 10, 96 | linearGradient: ['red', 'green', 'blue'] 97 | }, 98 | { 99 | type: 'rect', 100 | x: 10, y: 230, w: 100, h: 10, 101 | linearGradient: ['red', 'yellow', 'green', 'blue'] 102 | }, 103 | { 104 | type: 'ellipse', 105 | x: 260, y: 140, 106 | r1: 30, r2: 20, 107 | linearGradient: ['red', 'green', 'blue', 'red'], 108 | }, 109 | { 110 | type: 'rect', 111 | x: 10, y: 250, w: 50, h: 30, 112 | color: ['stripe45d', 'blue'], 113 | } 114 | ] 115 | }, 116 | 'This text should be rendered under canvas', 117 | { 118 | color: 'black', 119 | 120 | text: [ 121 | 'This should be black ', 122 | ] 123 | } 124 | ], 125 | defaultStyle: { 126 | color: 'gray', 127 | }, 128 | patterns: { 129 | stripe45d: { 130 | boundingBox: [1, 1, 4, 4], 131 | xStep: 3, 132 | yStep: 3, 133 | pattern: '1 w 0 1 m 4 5 l s 2 0 m 5 3 l s' 134 | } 135 | } 136 | }; 137 | 138 | var now = new Date(); 139 | 140 | var pdf = pdfmake.createPdf(docDefinition); 141 | pdf.write('pdfs/vectors.pdf').then(() => { 142 | console.log(new Date() - now); 143 | }, err => { 144 | console.error(err); 145 | }); 146 | -------------------------------------------------------------------------------- /examples/watermark.js: -------------------------------------------------------------------------------- 1 | var pdfmake = require('../js/index'); // only during development, otherwise use the following line 2 | //var pdfmake = require('pdfmake'); 3 | 4 | var Roboto = require('../fonts/Roboto'); 5 | pdfmake.addFonts(Roboto); 6 | 7 | 8 | var lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id semper massa, nec dapibus mauris. Mauris in mattis nibh. Aenean feugiat volutpat aliquam. Donec sed tellus feugiat, dignissim lectus id, eleifend tortor. Ut at mauris vel dui euismod accumsan. Cras sodales, ante sit amet varius dapibus, dolor neque finibus justo, vel ornare arcu dolor vitae tellus. Aenean faucibus egestas urna in interdum. Mauris convallis dolor a condimentum sagittis. Suspendisse non laoreet nisl. Curabitur sed pharetra ipsum. Curabitur aliquet purus vitae pharetra tincidunt. Cras aliquam tempor justo sit amet euismod. Praesent risus magna, lobortis eget dictum sit amet, tristique vel enim. Duis aliquet, urna maximus sollicitudin lobortis, mi nunc dignissim ligula, et lacinia magna leo non sem.'; 9 | 10 | var docDefinition = { 11 | //watermark: 'test watermark', 12 | watermark: { text: 'test watermark', color: 'blue', opacity: 0.3, bold: true, italics: false }, 13 | content: [ 14 | 'Test page of watermark.\n\n', 15 | lorem 16 | ] 17 | }; 18 | 19 | var now = new Date(); 20 | 21 | var pdf = pdfmake.createPdf(docDefinition); 22 | pdf.write('pdfs/watermark.pdf').then(() => { 23 | console.log(new Date() - now); 24 | }, err => { 25 | console.error(err); 26 | }); 27 | -------------------------------------------------------------------------------- /fonts/Roboto.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Roboto: { 3 | normal: __dirname + '/Roboto/Roboto-Regular.ttf', 4 | bold: __dirname + '/Roboto/Roboto-Medium.ttf', 5 | italics: __dirname + '/Roboto/Roboto-Italic.ttf', 6 | bolditalics: __dirname + '/Roboto/Roboto-MediumItalic.ttf' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/fonts/Roboto/Roboto-Italic.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/fonts/Roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/fonts/Roboto/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pdfmake", 3 | "version": "0.3.0-beta.18", 4 | "description": "Client/server side PDF printing in pure JavaScript", 5 | "main": "js/index.js", 6 | "esnext": "src/index.js", 7 | "browser": "build/pdfmake.js", 8 | "directories": { 9 | "test": "tests" 10 | }, 11 | "dependencies": { 12 | "linebreak": "^1.1.0", 13 | "pdfkit": "^0.17.1", 14 | "xmldoc": "^2.0.1" 15 | }, 16 | "devDependencies": { 17 | "@babel/cli": "^7.27.2", 18 | "@babel/core": "^7.27.1", 19 | "@babel/plugin-transform-modules-commonjs": "^7.27.1", 20 | "@babel/preset-env": "^7.27.2", 21 | "@eslint/js": "^9.26.0", 22 | "assert": "^2.1.0", 23 | "babel-loader": "^10.0.0", 24 | "brfs": "^2.0.2", 25 | "browserify-zlib": "^0.2.0", 26 | "buffer": "^6.0.3", 27 | "core-js": "^3.42.0", 28 | "eslint": "^9.26.0", 29 | "eslint-plugin-jsdoc": "^50.6.11", 30 | "expose-loader": "^5.0.1", 31 | "file-saver": "^2.0.5", 32 | "globals": "^16.1.0", 33 | "mocha": "^11.2.2", 34 | "npm-run-all": "^4.1.5", 35 | "process": "^0.11.10", 36 | "rewire": "^7.0.0", 37 | "shx": "^0.4.0", 38 | "sinon": "^20.0.0", 39 | "source-map-loader": "^5.0.0", 40 | "stream-browserify": "^3.0.0", 41 | "string-replace-webpack-plugin": "^0.1.3", 42 | "svg-to-pdfkit": "^0.1.8", 43 | "terser-webpack-plugin": "^5.3.14", 44 | "transform-loader": "^0.2.4", 45 | "util": "^0.12.5", 46 | "webpack": "^5.99.8", 47 | "webpack-cli": "^6.0.1" 48 | }, 49 | "engines": { 50 | "node": ">=18" 51 | }, 52 | "scripts": { 53 | "test": "run-s build mocha lint", 54 | "build": "run-s build:clean build:3rdparty build:node build:browser build:standard-fonts build:fonts build:vfs", 55 | "build:clean": "shx rm -rf js build", 56 | "build:3rdparty": "shx cp node_modules/svg-to-pdfkit/source.js src/3rd-party/svg-to-pdfkit/source.js && shx cp node_modules/svg-to-pdfkit/LICENSE src/3rd-party/svg-to-pdfkit/LICENSE", 57 | "build:node": "babel src --out-dir js", 58 | "build:browser": "webpack", 59 | "build:vfs": "node build-vfs.js \"./examples/fonts\"", 60 | "build:examples": "node build-examples.js", 61 | "build:standard-fonts": "shx mkdir -p build/standard-fonts && brfs \"./src/browser-extensions/standard-fonts/Courier.js\" > build/standard-fonts/Courier.js && brfs \"./src/browser-extensions/standard-fonts/Helvetica.js\" > build/standard-fonts/Helvetica.js && brfs \"./src/browser-extensions/standard-fonts/Times.js\" > build/standard-fonts/Times.js && brfs \"./src/browser-extensions/standard-fonts/Symbol.js\" > build/standard-fonts/Symbol.js && brfs \"./src/browser-extensions/standard-fonts/ZapfDingbats.js\" > build/standard-fonts/ZapfDingbats.js", 62 | "build:fonts": "shx mkdir -p build/fonts && shx mkdir -p build/fonts/Roboto && shx cp -r fonts/Roboto/*.* build/fonts/Roboto && brfs \"./src/browser-extensions/fonts/Roboto.js\" > build/fonts/Roboto.js", 63 | "lint": "eslint \"./src/**/*.js\" \"./tests/**/*.js\" \"./examples/**/*.js\" \"./standard-fonts/**/*.js\" \"./fonts/**/*.js\"", 64 | "mocha": "mocha --reporter spec \"./tests/**/*.spec.js\"", 65 | "playground": "node dev-playground/server.js" 66 | }, 67 | "repository": { 68 | "type": "git", 69 | "url": "git://github.com/bpampuch/pdfmake.git" 70 | }, 71 | "keywords": [ 72 | "pdf", 73 | "javascript", 74 | "printing", 75 | "layout" 76 | ], 77 | "author": "Bartek Pampuch ", 78 | "license": "MIT", 79 | "bugs": { 80 | "url": "https://github.com/bpampuch/pdfmake/issues" 81 | }, 82 | "homepage": "http://pdfmake.org", 83 | "config": { 84 | "blanket": { 85 | "pattern": "src", 86 | "data-cover-never": [ 87 | "node_modules", 88 | "tests" 89 | ] 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/3rd-party/svg-to-pdfkit.js: -------------------------------------------------------------------------------- 1 | import SVGtoPDF from './svg-to-pdfkit/source.js'; 2 | 3 | export default SVGtoPDF; 4 | -------------------------------------------------------------------------------- /src/3rd-party/svg-to-pdfkit/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 SVG-to-PDFKit contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/Line.js: -------------------------------------------------------------------------------- 1 | class Line { 2 | /** 3 | * @param {number} maxWidth Maximum width this line can have 4 | */ 5 | constructor(maxWidth) { 6 | this.maxWidth = maxWidth; 7 | this.leadingCut = 0; 8 | this.trailingCut = 0; 9 | this.inlineWidths = 0; 10 | this.inlines = []; 11 | } 12 | 13 | /** 14 | * @param {object} inline 15 | */ 16 | addInline(inline) { 17 | if (this.inlines.length === 0) { 18 | this.leadingCut = inline.leadingCut || 0; 19 | } 20 | this.trailingCut = inline.trailingCut || 0; 21 | 22 | inline.x = this.inlineWidths - this.leadingCut; 23 | 24 | this.inlines.push(inline); 25 | this.inlineWidths += inline.width; 26 | 27 | if (inline.lineEnd) { 28 | this.newLineForced = true; 29 | } 30 | } 31 | 32 | /** 33 | * @returns {number} 34 | */ 35 | getHeight() { 36 | let max = 0; 37 | 38 | this.inlines.forEach(item => { 39 | max = Math.max(max, item.height || 0); 40 | }); 41 | 42 | return max; 43 | } 44 | 45 | /** 46 | * @returns {number} 47 | */ 48 | getAscenderHeight() { 49 | let y = 0; 50 | 51 | this.inlines.forEach(inline => { 52 | y = Math.max(y, inline.font.ascender / 1000 * inline.fontSize); 53 | }); 54 | 55 | return y; 56 | } 57 | 58 | /** 59 | * @returns {number} 60 | */ 61 | getWidth() { 62 | return this.inlineWidths - this.leadingCut - this.trailingCut; 63 | } 64 | 65 | /** 66 | * @returns {number} 67 | */ 68 | getAvailableWidth() { 69 | return this.maxWidth - this.getWidth(); 70 | } 71 | 72 | /** 73 | * @param {object} inline 74 | * @param {Array} nextInlines 75 | * @returns {boolean} 76 | */ 77 | hasEnoughSpaceForInline(inline, nextInlines = []) { 78 | if (this.inlines.length === 0) { 79 | return true; 80 | } 81 | if (this.newLineForced) { 82 | return false; 83 | } 84 | 85 | let inlineWidth = inline.width; 86 | let inlineTrailingCut = inline.trailingCut || 0; 87 | if (inline.noNewLine) { 88 | for (let i = 0, l = nextInlines.length; i < l; i++) { 89 | let nextInline = nextInlines[i]; 90 | inlineWidth += nextInline.width; 91 | inlineTrailingCut += nextInline.trailingCut || 0; 92 | if (!nextInline.noNewLine) { 93 | break; 94 | } 95 | } 96 | } 97 | 98 | return (this.inlineWidths + inlineWidth - this.leadingCut - inlineTrailingCut) <= this.maxWidth; 99 | } 100 | 101 | clone() { 102 | let result = new Line(this.maxWidth); 103 | 104 | for (let key in this) { 105 | if (this.hasOwnProperty(key)) { 106 | result[key] = this[key]; 107 | } 108 | } 109 | 110 | return result; 111 | } 112 | } 113 | 114 | export default Line; 115 | -------------------------------------------------------------------------------- /src/OutputDocument.js: -------------------------------------------------------------------------------- 1 | class OutputDocument { 2 | 3 | /** 4 | * @param {Promise} pdfDocumentPromise 5 | */ 6 | constructor(pdfDocumentPromise) { 7 | this.bufferSize = 1073741824; 8 | this.pdfDocumentPromise = pdfDocumentPromise; 9 | this.bufferPromise = null; 10 | } 11 | 12 | /** 13 | * @returns {Promise} 14 | */ 15 | getStream() { 16 | return this.pdfDocumentPromise; 17 | } 18 | 19 | /** 20 | * @returns {Promise} 21 | */ 22 | getBuffer() { 23 | if (this.bufferPromise === null) { 24 | this.bufferPromise = new Promise((resolve, reject) => { 25 | this.getStream().then(stream => { 26 | 27 | let chunks = []; 28 | let result; 29 | stream.on('readable', () => { 30 | let chunk; 31 | while ((chunk = stream.read(this.bufferSize)) !== null) { 32 | chunks.push(chunk); 33 | } 34 | }); 35 | stream.on('end', () => { 36 | result = Buffer.concat(chunks); 37 | resolve(result); 38 | }); 39 | stream.end(); 40 | 41 | }, result => { 42 | reject(result); 43 | }); 44 | }); 45 | } 46 | 47 | return this.bufferPromise; 48 | } 49 | 50 | /** 51 | * @returns {Promise} 52 | */ 53 | getBase64() { 54 | return new Promise((resolve, reject) => { 55 | this.getBuffer().then(buffer => { 56 | resolve(buffer.toString('base64')); 57 | }, result => { 58 | reject(result); 59 | }); 60 | }); 61 | } 62 | 63 | /** 64 | * @returns {Promise} 65 | */ 66 | getDataUrl() { 67 | return new Promise((resolve, reject) => { 68 | this.getBase64().then(data => { 69 | resolve('data:application/pdf;base64,' + data); 70 | }, result => { 71 | reject(result); 72 | }); 73 | }); 74 | } 75 | 76 | } 77 | 78 | export default OutputDocument; 79 | -------------------------------------------------------------------------------- /src/OutputDocumentServer.js: -------------------------------------------------------------------------------- 1 | import OutputDocument from './OutputDocument'; 2 | import fs from 'fs'; 3 | 4 | class OutputDocumentServer extends OutputDocument { 5 | 6 | /** 7 | * @param {string} filename 8 | * @returns {Promise} 9 | */ 10 | write(filename) { 11 | return new Promise((resolve, reject) => { 12 | this.getStream().then(stream => { 13 | stream.pipe(fs.createWriteStream(filename)); 14 | stream.on('end', () => { 15 | resolve(); 16 | }); 17 | stream.end(); 18 | }, result => { 19 | reject(result); 20 | }); 21 | }); 22 | } 23 | 24 | } 25 | 26 | export default OutputDocumentServer; 27 | -------------------------------------------------------------------------------- /src/PDFDocument.js: -------------------------------------------------------------------------------- 1 | import PDFKit from 'pdfkit'; 2 | 3 | const typeName = (bold, italics) => { 4 | let type = 'normal'; 5 | if (bold && italics) { 6 | type = 'bolditalics'; 7 | } else if (bold) { 8 | type = 'bold'; 9 | } else if (italics) { 10 | type = 'italics'; 11 | } 12 | return type; 13 | }; 14 | 15 | class PDFDocument extends PDFKit { 16 | constructor(fonts = {}, images = {}, patterns = {}, attachments = {}, options = {}, virtualfs = null) { 17 | super(options); 18 | 19 | this.fonts = {}; 20 | this.fontCache = {}; 21 | for (let font in fonts) { 22 | if (fonts.hasOwnProperty(font)) { 23 | let fontDef = fonts[font]; 24 | 25 | this.fonts[font] = { 26 | normal: fontDef.normal, 27 | bold: fontDef.bold, 28 | italics: fontDef.italics, 29 | bolditalics: fontDef.bolditalics 30 | }; 31 | } 32 | } 33 | 34 | this.patterns = {}; 35 | for (let pattern in patterns) { 36 | if (patterns.hasOwnProperty(pattern)) { 37 | let patternDef = patterns[pattern]; 38 | this.patterns[pattern] = this.pattern(patternDef.boundingBox, patternDef.xStep, patternDef.yStep, patternDef.pattern, patternDef.colored); 39 | } 40 | } 41 | 42 | 43 | this.images = images; 44 | this.attachments = attachments; 45 | this.virtualfs = virtualfs; 46 | } 47 | 48 | getFontType(bold, italics) { 49 | return typeName(bold, italics); 50 | } 51 | 52 | getFontFile(familyName, bold, italics) { 53 | let type = this.getFontType(bold, italics); 54 | if (!this.fonts[familyName] || !this.fonts[familyName][type]) { 55 | return null; 56 | } 57 | 58 | return this.fonts[familyName][type]; 59 | } 60 | 61 | provideFont(familyName, bold, italics) { 62 | let type = this.getFontType(bold, italics); 63 | if (this.getFontFile(familyName, bold, italics) === null) { 64 | throw new Error(`Font '${familyName}' in style '${type}' is not defined in the font section of the document definition.`); 65 | } 66 | 67 | this.fontCache[familyName] = this.fontCache[familyName] || {}; 68 | 69 | if (!this.fontCache[familyName][type]) { 70 | let def = this.fonts[familyName][type]; 71 | if (!Array.isArray(def)) { 72 | def = [def]; 73 | } 74 | 75 | if (this.virtualfs && this.virtualfs.existsSync(def[0])) { 76 | def[0] = this.virtualfs.readFileSync(def[0]); 77 | } 78 | 79 | this.fontCache[familyName][type] = this.font(...def)._font; 80 | } 81 | 82 | return this.fontCache[familyName][type]; 83 | } 84 | 85 | provideImage(src) { 86 | const realImageSrc = src => { 87 | let image = this.images[src]; 88 | 89 | if (!image) { 90 | return src; 91 | } 92 | 93 | if (this.virtualfs && this.virtualfs.existsSync(image)) { 94 | return this.virtualfs.readFileSync(image); 95 | } 96 | 97 | let index = image.indexOf('base64,'); 98 | if (index < 0) { 99 | return this.images[src]; 100 | } 101 | 102 | return Buffer.from(image.substring(index + 7), 'base64'); 103 | }; 104 | 105 | if (this._imageRegistry[src]) { 106 | return this._imageRegistry[src]; 107 | } 108 | 109 | let image; 110 | 111 | try { 112 | image = this.openImage(realImageSrc(src)); 113 | if (!image) { 114 | throw new Error('No image'); 115 | } 116 | } catch (error) { 117 | throw new Error(`Invalid image: ${error.toString()}\nImages dictionary should contain dataURL entries (or local file paths in node.js)`); 118 | } 119 | 120 | image.embed(this); 121 | this._imageRegistry[src] = image; 122 | 123 | return image; 124 | } 125 | 126 | /** 127 | * @param {Array} color pdfmake format: [, ] 128 | * @returns {Array} pdfkit format: [, ] 129 | */ 130 | providePattern(color) { 131 | if (Array.isArray(color) && color.length === 2) { 132 | return [this.patterns[color[0]], color[1]]; 133 | } 134 | 135 | return null; 136 | } 137 | 138 | provideAttachment(src) { 139 | const checkRequired = obj => { 140 | if (!obj) { 141 | throw new Error('No attachment'); 142 | } 143 | if (!obj.src) { 144 | throw new Error('The "src" key is required for attachments'); 145 | } 146 | 147 | return obj; 148 | }; 149 | 150 | if (typeof src === 'object') { 151 | return checkRequired(src); 152 | } 153 | 154 | let attachment = checkRequired(this.attachments[src]); 155 | 156 | if (this.virtualfs && this.virtualfs.existsSync(attachment.src)) { 157 | return this.virtualfs.readFileSync(attachment.src); 158 | } 159 | 160 | return attachment; 161 | } 162 | 163 | setOpenActionAsPrint() { 164 | let printActionRef = this.ref({ 165 | Type: 'Action', 166 | S: 'Named', 167 | N: 'Print' 168 | }); 169 | this._root.data.OpenAction = printActionRef; 170 | printActionRef.end(); 171 | } 172 | } 173 | 174 | export default PDFDocument; 175 | -------------------------------------------------------------------------------- /src/PageElementWriter.js: -------------------------------------------------------------------------------- 1 | import ElementWriter from './ElementWriter'; 2 | import { normalizePageSize, normalizePageMargin } from './PageSize'; 3 | import DocumentContext from './DocumentContext'; 4 | 5 | /** 6 | * An extended ElementWriter which can handle: 7 | * - page-breaks (it adds new pages when there's not enough space left), 8 | * - repeatable fragments (like table-headers, which are repeated everytime 9 | * a page-break occurs) 10 | * - transactions (used for unbreakable-blocks when we want to make sure 11 | * whole block will be rendered on the same page) 12 | */ 13 | class PageElementWriter extends ElementWriter { 14 | 15 | /** 16 | * @param {DocumentContext} context 17 | */ 18 | constructor(context) { 19 | super(context); 20 | this.transactionLevel = 0; 21 | this.repeatables = []; 22 | } 23 | 24 | addLine(line, dontUpdateContextPosition, index) { 25 | return this._fitOnPage(() => super.addLine(line, dontUpdateContextPosition, index)); 26 | } 27 | 28 | addImage(image, index) { 29 | return this._fitOnPage(() => super.addImage(image, index)); 30 | } 31 | 32 | addCanvas(image, index) { 33 | return this._fitOnPage(() => super.addCanvas(image, index)); 34 | } 35 | 36 | addSVG(image, index) { 37 | return this._fitOnPage(() => super.addSVG(image, index)); 38 | } 39 | 40 | addQr(qr, index) { 41 | return this._fitOnPage(() => super.addQr(qr, index)); 42 | } 43 | 44 | addAttachment(attachment, index) { 45 | return this._fitOnPage(() => super.addAttachment(attachment, index)); 46 | } 47 | 48 | addVector(vector, ignoreContextX, ignoreContextY, index, forcePage) { 49 | return super.addVector(vector, ignoreContextX, ignoreContextY, index, forcePage); 50 | } 51 | 52 | beginClip(width, height) { 53 | return super.beginClip(width, height); 54 | } 55 | 56 | endClip() { 57 | return super.endClip(); 58 | } 59 | 60 | addFragment(fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition) { 61 | return this._fitOnPage(() => super.addFragment(fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition)); 62 | } 63 | 64 | moveToNextPage(pageOrientation) { 65 | let nextPage = this.context().moveToNextPage(pageOrientation); 66 | 67 | // moveToNextPage is called multiple times for table, because is called for each column 68 | // and repeatables are inserted only in the first time. If columns are used, is needed 69 | // call for table in first column and then for table in the second column (is other repeatables). 70 | this.repeatables.forEach(function (rep) { 71 | if (rep.insertedOnPages[this.context().page] === undefined) { 72 | rep.insertedOnPages[this.context().page] = true; 73 | this.addFragment(rep, true); 74 | } else { 75 | this.context().moveDown(rep.height); 76 | } 77 | }, this); 78 | 79 | this.emit('pageChanged', { 80 | prevPage: nextPage.prevPage, 81 | prevY: nextPage.prevY, 82 | y: this.context().y 83 | }); 84 | } 85 | 86 | addPage(pageSize, pageOrientation, pageMargin, customProperties = {}) { 87 | let prevPage = this.page; 88 | let prevY = this.y; 89 | 90 | this.context().addPage(normalizePageSize(pageSize, pageOrientation), normalizePageMargin(pageMargin), customProperties); 91 | 92 | this.emit('pageChanged', { 93 | prevPage: prevPage, 94 | prevY: prevY, 95 | y: this.context().y 96 | }); 97 | } 98 | 99 | beginUnbreakableBlock(width, height) { 100 | if (this.transactionLevel++ === 0) { 101 | this.originalX = this.context().x; 102 | this.pushContext(width, height); 103 | } 104 | } 105 | 106 | commitUnbreakableBlock(forcedX, forcedY) { 107 | if (--this.transactionLevel === 0) { 108 | let unbreakableContext = this.context(); 109 | this.popContext(); 110 | 111 | let nbPages = unbreakableContext.pages.length; 112 | if (nbPages > 0) { 113 | // no support for multi-page unbreakableBlocks 114 | let fragment = unbreakableContext.pages[0]; 115 | fragment.xOffset = forcedX; 116 | fragment.yOffset = forcedY; 117 | 118 | //TODO: vectors can influence height in some situations 119 | if (nbPages > 1) { 120 | // on out-of-context blocs (headers, footers, background) height should be the whole DocumentContext height 121 | if (forcedX !== undefined || forcedY !== undefined) { 122 | fragment.height = unbreakableContext.getCurrentPage().pageSize.height - unbreakableContext.pageMargins.top - unbreakableContext.pageMargins.bottom; 123 | } else { 124 | fragment.height = this.context().getCurrentPage().pageSize.height - this.context().pageMargins.top - this.context().pageMargins.bottom; 125 | for (let i = 0, l = this.repeatables.length; i < l; i++) { 126 | fragment.height -= this.repeatables[i].height; 127 | } 128 | } 129 | } else { 130 | fragment.height = unbreakableContext.y; 131 | } 132 | 133 | if (forcedX !== undefined || forcedY !== undefined) { 134 | super.addFragment(fragment, true, true, true); 135 | } else { 136 | this.addFragment(fragment); 137 | } 138 | } 139 | } 140 | } 141 | 142 | currentBlockToRepeatable() { 143 | let unbreakableContext = this.context(); 144 | let rep = { items: [] }; 145 | 146 | unbreakableContext.pages[0].items.forEach(item => { 147 | rep.items.push(item); 148 | }); 149 | 150 | rep.xOffset = this.originalX; 151 | 152 | //TODO: vectors can influence height in some situations 153 | rep.height = unbreakableContext.y; 154 | 155 | rep.insertedOnPages = []; 156 | 157 | return rep; 158 | } 159 | 160 | pushToRepeatables(rep) { 161 | this.repeatables.push(rep); 162 | } 163 | 164 | popFromRepeatables() { 165 | this.repeatables.pop(); 166 | } 167 | 168 | _fitOnPage(addFct) { 169 | let position = addFct(); 170 | if (!position) { 171 | this.moveToNextPage(); 172 | position = addFct(); 173 | } 174 | return position; 175 | } 176 | 177 | } 178 | 179 | export default PageElementWriter; 180 | -------------------------------------------------------------------------------- /src/PageSize.js: -------------------------------------------------------------------------------- 1 | import sizes from './standardPageSizes'; 2 | import { isString, isNumber } from './helpers/variableType'; 3 | 4 | export function normalizePageSize(pageSize, pageOrientation) { 5 | function isNeedSwapPageSizes(pageOrientation) { 6 | if (isString(pageOrientation)) { 7 | pageOrientation = pageOrientation.toLowerCase(); 8 | return ((pageOrientation === 'portrait') && (size.width > size.height)) || 9 | ((pageOrientation === 'landscape') && (size.width < size.height)); 10 | } 11 | return false; 12 | } 13 | 14 | function pageSizeToWidthAndHeight(pageSize) { 15 | if (isString(pageSize)) { 16 | let size = sizes[pageSize.toUpperCase()]; 17 | if (!size) { 18 | throw new Error(`Page size ${pageSize} not recognized`); 19 | } 20 | return { width: size[0], height: size[1] }; 21 | } 22 | 23 | return pageSize; 24 | } 25 | 26 | // if pageSize.height is set to auto, set the height to infinity so there are no page breaks. 27 | if (pageSize && pageSize.height === 'auto') { 28 | pageSize.height = Infinity; 29 | } 30 | 31 | let size = pageSizeToWidthAndHeight(pageSize || 'A4'); 32 | if (isNeedSwapPageSizes(pageOrientation)) { // swap page sizes 33 | size = { width: size.height, height: size.width }; 34 | } 35 | size.orientation = size.width > size.height ? 'landscape' : 'portrait'; 36 | return size; 37 | } 38 | 39 | export function normalizePageMargin(margin) { 40 | if (isNumber(margin)) { 41 | margin = { left: margin, right: margin, top: margin, bottom: margin }; 42 | } else if (Array.isArray(margin)) { 43 | if (margin.length === 2) { 44 | margin = { left: margin[0], top: margin[1], right: margin[0], bottom: margin[1] }; 45 | } else if (margin.length === 4) { 46 | margin = { left: margin[0], top: margin[1], right: margin[2], bottom: margin[3] }; 47 | } else { 48 | throw new Error('Invalid pageMargins definition'); 49 | } 50 | } 51 | 52 | return margin; 53 | } 54 | -------------------------------------------------------------------------------- /src/SVGMeasure.js: -------------------------------------------------------------------------------- 1 | import xmldoc from 'xmldoc'; 2 | 3 | /** 4 | * Strip unit postfix, parse number, but return undefined instead of NaN for bad input 5 | * 6 | * @param {string} textVal 7 | * @returns {?number} 8 | */ 9 | const stripUnits = textVal => { 10 | let n = parseFloat(textVal); 11 | if (typeof n !== 'number' || isNaN(n)) { 12 | return undefined; 13 | } 14 | return n; 15 | }; 16 | 17 | /** 18 | * Make sure it's valid XML and the root tage is , returns xmldoc DOM 19 | * 20 | * @param {string} svgString 21 | * @returns {object} 22 | */ 23 | const parseSVG = (svgString) => { 24 | let doc; 25 | 26 | try { 27 | doc = new xmldoc.XmlDocument(svgString); 28 | } catch (err) { 29 | throw new Error('SVGMeasure: ' + err); 30 | } 31 | 32 | if (doc.name !== "svg") { 33 | throw new Error('SVGMeasure: expected document'); 34 | } 35 | 36 | return doc; 37 | }; 38 | 39 | class SVGMeasure { 40 | constructor() { 41 | 42 | } 43 | 44 | measureSVG(svgString) { 45 | let doc = parseSVG(svgString); 46 | 47 | let docWidth = stripUnits(doc.attr.width); 48 | let docHeight = stripUnits(doc.attr.height); 49 | 50 | if ((docWidth === undefined || docHeight === undefined) && typeof doc.attr.viewBox === 'string') { 51 | let viewBoxParts = doc.attr.viewBox.split(/[,\s]+/); 52 | if (viewBoxParts.length !== 4) { 53 | throw new Error("Unexpected svg viewbox format, should have 4 entries but found: '" + doc.attr.viewBox + "'"); 54 | } 55 | if (docWidth === undefined) { 56 | docWidth = stripUnits(viewBoxParts[2]); 57 | } 58 | if (docHeight === undefined) { 59 | docHeight = stripUnits(viewBoxParts[3]); 60 | } 61 | } 62 | 63 | return { 64 | width: docWidth, 65 | height: docHeight 66 | }; 67 | } 68 | 69 | writeDimensions(svgString, dimensions) { 70 | let doc = parseSVG(svgString); 71 | 72 | doc.attr.width = "" + dimensions.width; 73 | doc.attr.height = "" + dimensions.height; 74 | 75 | return doc.toString(); 76 | } 77 | } 78 | 79 | export default SVGMeasure; 80 | -------------------------------------------------------------------------------- /src/StyleContextStack.js: -------------------------------------------------------------------------------- 1 | import { isString, isValue } from './helpers/variableType'; 2 | 3 | /** 4 | * Used for style inheritance and style overrides 5 | */ 6 | class StyleContextStack { 7 | 8 | /** 9 | * @param {object} styleDictionary named styles dictionary 10 | * @param {object} defaultStyle optional default style definition 11 | */ 12 | constructor(styleDictionary, defaultStyle = {}) { 13 | this.styleDictionary = styleDictionary; 14 | this.defaultStyle = defaultStyle; 15 | this.styleOverrides = []; 16 | } 17 | 18 | /** 19 | * Creates cloned version of current stack 20 | * 21 | * @returns {StyleContextStack} current stack snapshot 22 | */ 23 | clone() { 24 | let stack = new StyleContextStack(this.styleDictionary, this.defaultStyle); 25 | 26 | this.styleOverrides.forEach(item => { 27 | stack.styleOverrides.push(item); 28 | }); 29 | 30 | return stack; 31 | } 32 | 33 | /** 34 | * Pushes style-name or style-overrides-object onto the stack for future evaluation 35 | * 36 | * @param {string|object} styleNameOrOverride style-name (referring to styleDictionary) or 37 | * a new dictionary defining overriding properties 38 | */ 39 | push(styleNameOrOverride) { 40 | this.styleOverrides.push(styleNameOrOverride); 41 | } 42 | 43 | /** 44 | * Removes last style-name or style-overrides-object from the stack 45 | * 46 | * @param {number} howMany optional number of elements to be popped (if not specified, 47 | * one element will be removed from the stack) 48 | */ 49 | pop(howMany = 1) { 50 | while (howMany-- > 0) { 51 | this.styleOverrides.pop(); 52 | } 53 | } 54 | 55 | /** 56 | * Creates a set of named styles or/and a style-overrides-object based on the item, 57 | * pushes those elements onto the stack for future evaluation and returns the number 58 | * of elements pushed, so they can be easily poped then. 59 | * 60 | * @param {object} item - an object with optional style property and/or style overrides 61 | * @returns {number} the number of items pushed onto the stack 62 | */ 63 | autopush(item) { 64 | if (isString(item)) { 65 | return 0; 66 | } 67 | 68 | if (typeof item.section !== 'undefined') { // section node not support style overrides 69 | return 0; 70 | } 71 | 72 | let styleNames = []; 73 | 74 | if (item.style) { 75 | if (Array.isArray(item.style)) { 76 | styleNames = item.style; 77 | } else { 78 | styleNames = [item.style]; 79 | } 80 | } 81 | 82 | for (let i = 0, l = styleNames.length; i < l; i++) { 83 | this.push(styleNames[i]); 84 | } 85 | 86 | // rather than spend significant time making a styleOverrideObject, just add item 87 | this.push(item); 88 | return styleNames.length + 1; 89 | } 90 | 91 | /** 92 | * Automatically pushes elements onto the stack, using autopush based on item, 93 | * executes callback and then pops elements back. Returns value returned by callback 94 | * 95 | * @param {object} item - an object with optional style property and/or style overrides 96 | * @param {Function} callback to be called between autopush and pop 97 | * @returns {object} value returned by callback 98 | */ 99 | auto(item, callback) { 100 | let pushedItems = this.autopush(item); 101 | let result = callback(); 102 | 103 | if (pushedItems > 0) { 104 | this.pop(pushedItems); 105 | } 106 | 107 | return result; 108 | } 109 | 110 | /** 111 | * Evaluates stack and returns value of a named property 112 | * 113 | * @param {string} property - property name 114 | * @returns {?any} property value or null if not found 115 | */ 116 | getProperty(property) { 117 | if (this.styleOverrides) { 118 | for (let i = this.styleOverrides.length - 1; i >= 0; i--) { 119 | let item = this.styleOverrides[i]; 120 | 121 | if (isString(item)) { // named-style-override 122 | let style = this.styleDictionary[item]; 123 | if (style && isValue(style[property])) { 124 | return style[property]; 125 | } 126 | } else if (isValue(item[property])) { // style-overrides-object 127 | return item[property]; 128 | } 129 | } 130 | } 131 | 132 | return this.defaultStyle && this.defaultStyle[property]; 133 | } 134 | 135 | /** 136 | * @param {object} item 137 | * @param {StyleContextStack} styleContextStack 138 | * @param {string} property 139 | * @param {any} defaultValue 140 | * @returns {any} 141 | */ 142 | static getStyleProperty(item, styleContextStack, property, defaultValue) { 143 | let value; 144 | 145 | if (isValue(item[property])) { // item defines this property 146 | return item[property]; 147 | } 148 | 149 | if (!styleContextStack) { 150 | return defaultValue; 151 | } 152 | 153 | styleContextStack.auto(item, () => { 154 | value = styleContextStack.getProperty(property); 155 | }); 156 | 157 | return isValue(value) ? value : defaultValue; 158 | } 159 | 160 | /** 161 | * @param {object} source 162 | * @param {object} destination 163 | * @returns {object} 164 | */ 165 | static copyStyle(source = {}, destination = {}) { 166 | // TODO: default style to source 167 | 168 | for (let key in source) { 169 | if (key != 'text' && source.hasOwnProperty(key)) { 170 | destination[key] = source[key]; 171 | } 172 | } 173 | 174 | return destination; 175 | } 176 | 177 | } 178 | 179 | export default StyleContextStack; 180 | -------------------------------------------------------------------------------- /src/TextBreaker.js: -------------------------------------------------------------------------------- 1 | import LineBreaker from 'linebreak'; 2 | import { isObject } from './helpers/variableType'; 3 | import StyleContextStack from './StyleContextStack'; 4 | 5 | /** 6 | * @param {string} text 7 | * @param {boolean} noWrap 8 | * @returns {Array} 9 | */ 10 | const splitWords = (text, noWrap) => { 11 | let words = []; 12 | 13 | if (noWrap) { 14 | words.push({ text: text }); 15 | return words; 16 | } 17 | 18 | let breaker = new LineBreaker(text); 19 | let last = 0; 20 | let bk; 21 | 22 | while ((bk = breaker.nextBreak())) { 23 | let word = text.slice(last, bk.position); 24 | 25 | if (bk.required || word.match(/\r?\n$|\r$/)) { // new line 26 | word = word.replace(/\r?\n$|\r$/, ''); 27 | words.push({ text: word, lineEnd: true }); 28 | } else { 29 | words.push({ text: word }); 30 | } 31 | 32 | last = bk.position; 33 | } 34 | 35 | return words; 36 | }; 37 | 38 | /** 39 | * @param {Array} words 40 | * @param {boolean} noWrap 41 | * @returns {?string} 42 | */ 43 | const getFirstWord = (words, noWrap) => { 44 | let word = words[0]; 45 | if (word === undefined) { 46 | return null; 47 | } 48 | 49 | if (noWrap) { // text was not wrapped, we need only first word 50 | let tmpWords = splitWords(word.text, false); 51 | if (tmpWords[0] === undefined) { 52 | return null; 53 | } 54 | word = tmpWords[0]; 55 | } 56 | 57 | return word.text; 58 | }; 59 | 60 | /** 61 | * @param {Array} words 62 | * @param {boolean} noWrap 63 | * @returns {?string} 64 | */ 65 | const getLastWord = (words, noWrap) => { 66 | let word = words[words.length - 1]; 67 | if (word === undefined) { 68 | return null; 69 | } 70 | 71 | if (word.lineEnd) { 72 | return null; 73 | } 74 | 75 | if (noWrap) { // text was not wrapped, we need only last word 76 | let tmpWords = splitWords(word.text, false); 77 | if (tmpWords[tmpWords.length - 1] === undefined) { 78 | return null; 79 | } 80 | word = tmpWords[tmpWords.length - 1]; 81 | } 82 | 83 | return word.text; 84 | }; 85 | 86 | class TextBreaker { 87 | /** 88 | * @param {string|Array} texts 89 | * @param {StyleContextStack} styleContextStack 90 | * @returns {Array} 91 | */ 92 | getBreaks(texts, styleContextStack) { 93 | let results = []; 94 | 95 | if (!Array.isArray(texts)) { 96 | texts = [texts]; 97 | } 98 | 99 | let lastWord = null; 100 | for (let i = 0, l = texts.length; i < l; i++) { 101 | let item = texts[i]; 102 | let style = null; 103 | let words; 104 | 105 | let noWrap = StyleContextStack.getStyleProperty(item || {}, styleContextStack, 'noWrap', false); 106 | if (isObject(item)) { 107 | if (item._textRef && item._textRef._textNodeRef.text) { 108 | item.text = item._textRef._textNodeRef.text; 109 | } 110 | words = splitWords(item.text, noWrap); 111 | style = StyleContextStack.copyStyle(item); 112 | } else { 113 | words = splitWords(item, noWrap); 114 | } 115 | 116 | if (lastWord && words.length) { 117 | let firstWord = getFirstWord(words, noWrap); 118 | 119 | let wrapWords = splitWords(lastWord + firstWord, false); 120 | if (wrapWords.length === 1) { 121 | results[results.length - 1].noNewLine = true; 122 | } 123 | } 124 | 125 | for (let i2 = 0, l2 = words.length; i2 < l2; i2++) { 126 | let result = { 127 | text: words[i2].text 128 | }; 129 | 130 | if (words[i2].lineEnd) { 131 | result.lineEnd = true; 132 | } 133 | 134 | StyleContextStack.copyStyle(style, result); 135 | 136 | results.push(result); 137 | } 138 | 139 | lastWord = null; 140 | if (i + 1 < l) { 141 | lastWord = getLastWord(words, noWrap); 142 | } 143 | } 144 | 145 | return results; 146 | } 147 | } 148 | 149 | export default TextBreaker; 150 | -------------------------------------------------------------------------------- /src/TextDecorator.js: -------------------------------------------------------------------------------- 1 | const groupDecorations = line => { 2 | let groups = []; 3 | let currentGroup = null; 4 | for (let i = 0, l = line.inlines.length; i < l; i++) { 5 | let inline = line.inlines[i]; 6 | let decoration = inline.decoration; 7 | if (!decoration) { 8 | currentGroup = null; 9 | continue; 10 | } 11 | if (!Array.isArray(decoration)) { 12 | decoration = [decoration]; 13 | } 14 | let color = inline.decorationColor || inline.color || 'black'; 15 | let style = inline.decorationStyle || 'solid'; 16 | for (let ii = 0, ll = decoration.length; ii < ll; ii++) { 17 | let decorationItem = decoration[ii]; 18 | if (!currentGroup || decorationItem !== currentGroup.decoration || 19 | style !== currentGroup.decorationStyle || color !== currentGroup.decorationColor) { 20 | 21 | currentGroup = { 22 | line: line, 23 | decoration: decorationItem, 24 | decorationColor: color, 25 | decorationStyle: style, 26 | inlines: [inline] 27 | }; 28 | groups.push(currentGroup); 29 | } else { 30 | currentGroup.inlines.push(inline); 31 | } 32 | } 33 | } 34 | 35 | return groups; 36 | }; 37 | 38 | class TextDecorator { 39 | 40 | constructor(pdfDocument) { 41 | this.pdfDocument = pdfDocument; 42 | } 43 | 44 | drawBackground(line, x, y) { 45 | let height = line.getHeight(); 46 | for (let i = 0, l = line.inlines.length; i < l; i++) { 47 | let inline = line.inlines[i]; 48 | if (!inline.background) { 49 | continue; 50 | } 51 | 52 | let color = inline.background; 53 | let patternColor = this.pdfDocument.providePattern(inline.background); 54 | if (patternColor !== null) { 55 | color = patternColor; 56 | } 57 | 58 | let justifyShift = (inline.justifyShift || 0); 59 | this.pdfDocument.fillColor(color) 60 | .rect(x + inline.x - justifyShift, y, inline.width + justifyShift, height) 61 | .fill(); 62 | } 63 | } 64 | 65 | drawDecorations(line, x, y) { 66 | let groups = groupDecorations(line); 67 | for (let i = 0, l = groups.length; i < l; i++) { 68 | this._drawDecoration(groups[i], x, y); 69 | } 70 | } 71 | 72 | _drawDecoration(group, x, y) { 73 | const maxInline = () => { 74 | let max = 0; 75 | for (let i = 0, l = group.inlines.length; i < l; i++) { 76 | let inline = group.inlines[i]; 77 | max = inline.fontSize > max ? i : max; 78 | } 79 | return group.inlines[max]; 80 | }; 81 | 82 | const width = () => { 83 | let sum = 0; 84 | for (let i = 0, l = group.inlines.length; i < l; i++) { 85 | let justifyShift = (group.inlines[i].justifyShift || 0); 86 | sum += group.inlines[i].width + justifyShift; 87 | } 88 | return sum; 89 | }; 90 | 91 | let firstInline = group.inlines[0]; 92 | let biggerInline = maxInline(); 93 | let totalWidth = width(); 94 | let lineAscent = group.line.getAscenderHeight(); 95 | let ascent = biggerInline.font.ascender / 1000 * biggerInline.fontSize; 96 | let height = biggerInline.height; 97 | let descent = height - ascent; 98 | 99 | let lw = 0.5 + Math.floor(Math.max(biggerInline.fontSize - 8, 0) / 2) * 0.12; 100 | 101 | switch (group.decoration) { 102 | case 'underline': 103 | y += lineAscent + descent * 0.45; 104 | break; 105 | case 'overline': 106 | y += lineAscent - (ascent * 0.85); 107 | break; 108 | case 'lineThrough': 109 | y += lineAscent - (ascent * 0.25); 110 | break; 111 | default: 112 | throw new Error(`Unknown decoration : ${group.decoration}`); 113 | } 114 | this.pdfDocument.save(); 115 | 116 | if (group.decorationStyle === 'double') { 117 | let gap = Math.max(0.5, lw * 2); 118 | this.pdfDocument.fillColor(group.decorationColor) 119 | .rect(x + firstInline.x, y - lw / 2, totalWidth, lw / 2).fill() 120 | .rect(x + firstInline.x, y + gap - lw / 2, totalWidth, lw / 2).fill(); 121 | } else if (group.decorationStyle === 'dashed') { 122 | let nbDashes = Math.ceil(totalWidth / (3.96 + 2.84)); 123 | let rdx = x + firstInline.x; 124 | this.pdfDocument.rect(rdx, y, totalWidth, lw).clip(); 125 | this.pdfDocument.fillColor(group.decorationColor); 126 | for (let i = 0; i < nbDashes; i++) { 127 | this.pdfDocument.rect(rdx, y - lw / 2, 3.96, lw).fill(); 128 | rdx += 3.96 + 2.84; 129 | } 130 | } else if (group.decorationStyle === 'dotted') { 131 | let nbDots = Math.ceil(totalWidth / (lw * 3)); 132 | let rx = x + firstInline.x; 133 | this.pdfDocument.rect(rx, y, totalWidth, lw).clip(); 134 | this.pdfDocument.fillColor(group.decorationColor); 135 | for (let i = 0; i < nbDots; i++) { 136 | this.pdfDocument.rect(rx, y - lw / 2, lw, lw).fill(); 137 | rx += (lw * 3); 138 | } 139 | } else if (group.decorationStyle === 'wavy') { 140 | let sh = 0.7, sv = 1; 141 | let nbWaves = Math.ceil(totalWidth / (sh * 2)) + 1; 142 | let rwx = x + firstInline.x - 1; 143 | this.pdfDocument.rect(x + firstInline.x, y - sv, totalWidth, y + sv).clip(); 144 | this.pdfDocument.lineWidth(0.24); 145 | this.pdfDocument.moveTo(rwx, y); 146 | for (let i = 0; i < nbWaves; i++) { 147 | this.pdfDocument.bezierCurveTo(rwx + sh, y - sv, rwx + sh * 2, y - sv, rwx + sh * 3, y) 148 | .bezierCurveTo(rwx + sh * 4, y + sv, rwx + sh * 5, y + sv, rwx + sh * 6, y); 149 | rwx += sh * 6; 150 | } 151 | this.pdfDocument.stroke(group.decorationColor); 152 | } else { 153 | this.pdfDocument.fillColor(group.decorationColor) 154 | .rect(x + firstInline.x, y - lw / 2, totalWidth, lw) 155 | .fill(); 156 | } 157 | this.pdfDocument.restore(); 158 | } 159 | } 160 | 161 | export default TextDecorator; 162 | -------------------------------------------------------------------------------- /src/URLResolver.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import https from 'https'; 3 | 4 | const MAX_REDIRECTS = 30; 5 | 6 | const fetchUrl = (url, headers = {}, redirectCount = 0) => { 7 | if (redirectCount >= MAX_REDIRECTS) { 8 | return new Promise((_, reject) => { 9 | reject(new Error(`Too many redirects (limit: ${MAX_REDIRECTS})`)); 10 | }); 11 | } 12 | return new Promise((resolve, reject) => { 13 | const parsedUrl = new URL(url); 14 | const h = (parsedUrl.protocol === 'https:') ? https : http; 15 | let options = { 16 | headers: headers 17 | }; 18 | 19 | h.get(url, options, res => { 20 | if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { // redirect url 21 | res.resume(); 22 | 23 | fetchUrl(res.headers.location, {}, redirectCount + 1).then(buffer => { 24 | resolve(buffer); 25 | }, result => { 26 | reject(result); 27 | }); 28 | return; 29 | } 30 | 31 | const ok = res.statusCode >= 200 && res.statusCode < 300; 32 | if (!ok) { 33 | reject(new TypeError(`Failed to fetch (status code: ${res.statusCode}, url: "${url}")`)); 34 | res.resume(); 35 | return; 36 | } 37 | 38 | const chunks = []; 39 | res.on('end', () => resolve(Buffer.concat(chunks))); 40 | res.on('data', d => chunks.push(d)); 41 | }).on('error', reject); 42 | }); 43 | }; 44 | 45 | class URLResolver { 46 | constructor(fs) { 47 | this.fs = fs; 48 | this.resolving = {}; 49 | } 50 | 51 | resolve(url, headers = {}) { 52 | if (!this.resolving[url]) { 53 | this.resolving[url] = new Promise((resolve, reject) => { 54 | if (url.toLowerCase().indexOf('https://') === 0 || url.toLowerCase().indexOf('http://') === 0) { 55 | if (this.fs.existsSync(url)) { 56 | // url was downloaded earlier 57 | resolve(); 58 | } else { 59 | fetchUrl(url, headers).then(buffer => { 60 | this.fs.writeFileSync(url, buffer); 61 | resolve(); 62 | }, result => { 63 | reject(result); 64 | }); 65 | } 66 | } else { 67 | // cannot be resolved 68 | resolve(); 69 | } 70 | }); 71 | } 72 | 73 | return this.resolving[url]; 74 | } 75 | 76 | resolved() { 77 | return new Promise((resolve, reject) => { 78 | Promise.all(Object.values(this.resolving)).then(() => { 79 | resolve(); 80 | }, result => { 81 | reject(result); 82 | }); 83 | }); 84 | } 85 | 86 | } 87 | 88 | export default URLResolver; 89 | -------------------------------------------------------------------------------- /src/base.js: -------------------------------------------------------------------------------- 1 | import Printer from './Printer'; 2 | import virtualfs from './virtual-fs'; 3 | import { pack } from './helpers/tools'; 4 | 5 | class pdfmake { 6 | 7 | constructor() { 8 | this.virtualfs = virtualfs; 9 | this.urlResolver = null; 10 | } 11 | 12 | /** 13 | * @param {object} docDefinition 14 | * @param {?object} options 15 | * @returns {object} 16 | */ 17 | createPdf(docDefinition, options = {}) { 18 | options.progressCallback = this.progressCallback; 19 | options.tableLayouts = this.tableLayouts; 20 | 21 | let printer = new Printer(this.fonts, this.virtualfs, this.urlResolver()); 22 | const pdfDocumentPromise = printer.createPdfKitDocument(docDefinition, options); 23 | 24 | return this._transformToDocument(pdfDocumentPromise); 25 | } 26 | 27 | setProgressCallback(callback) { 28 | this.progressCallback = callback; 29 | } 30 | 31 | addTableLayouts(tableLayouts) { 32 | this.tableLayouts = pack(this.tableLayouts, tableLayouts); 33 | } 34 | 35 | setTableLayouts(tableLayouts) { 36 | this.tableLayouts = tableLayouts; 37 | } 38 | 39 | clearTableLayouts() { 40 | this.tableLayouts = {}; 41 | } 42 | 43 | addFonts(fonts) { 44 | this.fonts = pack(this.fonts, fonts); 45 | } 46 | 47 | setFonts(fonts) { 48 | this.fonts = fonts; 49 | } 50 | 51 | clearFonts() { 52 | this.fonts = {}; 53 | } 54 | 55 | _transformToDocument(doc) { 56 | return doc; 57 | } 58 | 59 | } 60 | 61 | export default pdfmake; 62 | -------------------------------------------------------------------------------- /src/browser-extensions/OutputDocumentBrowser.js: -------------------------------------------------------------------------------- 1 | import OutputDocument from '../OutputDocument'; 2 | import { saveAs } from 'file-saver'; 3 | 4 | /** 5 | * @returns {Window} 6 | */ 7 | const openWindow = () => { 8 | // we have to open the window immediately and store the reference 9 | // otherwise popup blockers will stop us 10 | let win = window.open('', '_blank'); 11 | if (win === null) { 12 | throw new Error('Open PDF in new window blocked by browser'); 13 | } 14 | 15 | return win; 16 | }; 17 | 18 | class OutputDocumentBrowser extends OutputDocument { 19 | 20 | /** 21 | * @returns {Promise} 22 | */ 23 | getBlob() { 24 | return new Promise((resolve, reject) => { 25 | this.getBuffer().then(buffer => { 26 | try { 27 | let blob = new Blob([buffer], { type: 'application/pdf' }); 28 | resolve(blob); 29 | } catch (e) { 30 | reject(e); 31 | } 32 | }, result => { 33 | reject(result); 34 | }); 35 | }); 36 | } 37 | 38 | /** 39 | * @param {string} filename 40 | * @returns {Promise} 41 | */ 42 | download(filename = 'file.pdf') { 43 | return new Promise((resolve, reject) => { 44 | this.getBlob().then(blob => { 45 | try { 46 | saveAs(blob, filename); 47 | resolve(); 48 | } catch (e) { 49 | reject(e); 50 | } 51 | }, result => { 52 | reject(result); 53 | }); 54 | }); 55 | } 56 | 57 | /** 58 | * @param {Window} win 59 | * @returns {Promise} 60 | */ 61 | open(win = null) { 62 | return new Promise((resolve, reject) => { 63 | if (!win) { 64 | win = openWindow(); 65 | } 66 | this.getBlob().then(blob => { 67 | try { 68 | let urlCreator = window.URL || window.webkitURL; 69 | let pdfUrl = urlCreator.createObjectURL(blob); 70 | win.location.href = pdfUrl; 71 | 72 | // 73 | resolve(); 74 | /* temporarily disabled 75 | if (win === window) { 76 | resolve(); 77 | } else { 78 | setTimeout(() => { 79 | if (win.window === null) { // is closed by AdBlock 80 | window.location.href = pdfUrl; // open in actual window 81 | } 82 | resolve(); 83 | }, 500); 84 | } 85 | */ 86 | } catch (e) { 87 | win.close(); 88 | reject(e); 89 | } 90 | }, result => { 91 | reject(result); 92 | }); 93 | }); 94 | } 95 | 96 | /** 97 | * @param {Window} win 98 | * @returns {Promise} 99 | */ 100 | print(win = null) { 101 | return new Promise((resolve, reject) => { 102 | this.getStream().then(stream => { 103 | stream.setOpenActionAsPrint(); 104 | return this.open(win).then(() => { 105 | resolve(); 106 | }, result => { 107 | reject(result); 108 | }); 109 | }, result => { 110 | reject(result); 111 | }); 112 | }); 113 | } 114 | 115 | } 116 | 117 | export default OutputDocumentBrowser; 118 | -------------------------------------------------------------------------------- /src/browser-extensions/URLBrowserResolver.js: -------------------------------------------------------------------------------- 1 | const fetchUrl = (url, headers = {}) => { 2 | return new Promise((resolve, reject) => { 3 | const xhr = new XMLHttpRequest(); 4 | xhr.open('GET', url, true); 5 | for (let headerName in headers) { 6 | xhr.setRequestHeader(headerName, headers[headerName]); 7 | } 8 | xhr.responseType = 'arraybuffer'; 9 | 10 | xhr.onreadystatechange = () => { 11 | if (xhr.readyState !== 4) { 12 | return; 13 | } 14 | 15 | const ok = xhr.status >= 200 && xhr.status < 300; 16 | if (!ok) { 17 | setTimeout(() => { 18 | reject(new TypeError(`Failed to fetch (url: "${url}")`)); 19 | }, 0); 20 | } 21 | }; 22 | 23 | xhr.onload = () => { 24 | const ok = xhr.status >= 200 && xhr.status < 300; 25 | if (ok) { 26 | resolve(xhr.response); 27 | } 28 | }; 29 | 30 | xhr.onerror = () => { 31 | setTimeout(() => { 32 | reject(new TypeError(`Network request failed (url: "${url}")`)); 33 | }, 0); 34 | }; 35 | 36 | xhr.ontimeout = () => { 37 | setTimeout(() => { 38 | reject(new TypeError(`Network request failed (url: "${url}")`)); 39 | }, 0); 40 | }; 41 | 42 | xhr.send(); 43 | }); 44 | }; 45 | 46 | class URLBrowserResolver { 47 | constructor(fs) { 48 | this.fs = fs; 49 | this.resolving = {}; 50 | } 51 | 52 | resolve(url, headers = {}) { 53 | if (!this.resolving[url]) { 54 | this.resolving[url] = new Promise((resolve, reject) => { 55 | if (url.toLowerCase().indexOf('https://') === 0 || url.toLowerCase().indexOf('http://') === 0) { 56 | if (this.fs.existsSync(url)) { 57 | // url was downloaded earlier 58 | resolve(); 59 | } else { 60 | fetchUrl(url, headers).then(buffer => { 61 | this.fs.writeFileSync(url, buffer); 62 | resolve(); 63 | }, result => { 64 | reject(result); 65 | }); 66 | } 67 | } else { 68 | // cannot be resolved 69 | resolve(); 70 | } 71 | }); 72 | } 73 | 74 | return this.resolving[url]; 75 | } 76 | 77 | resolved() { 78 | return new Promise((resolve, reject) => { 79 | Promise.all(Object.values(this.resolving)).then(() => { 80 | resolve(); 81 | }, result => { 82 | reject(result); 83 | }); 84 | }); 85 | } 86 | 87 | } 88 | 89 | export default URLBrowserResolver; 90 | -------------------------------------------------------------------------------- /src/browser-extensions/fonts/Roboto.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var fontContainer = { 4 | vfs: { 5 | 'Roboto-Regular.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-Regular.ttf', 'base64'), encoding: 'base64' }, 6 | 'Roboto-Medium.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-Medium.ttf', 'base64'), encoding: 'base64' }, 7 | 'Roboto-Italic.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-Italic.ttf', 'base64'), encoding: 'base64' }, 8 | 'Roboto-MediumItalic.ttf': { data: fs.readFileSync(__dirname + '/../../../fonts/Roboto/Roboto-MediumItalic.ttf', 'base64'), encoding: 'base64' } 9 | }, 10 | fonts: { 11 | Roboto: { 12 | normal: 'Roboto-Regular.ttf', 13 | bold: 'Roboto-Medium.ttf', 14 | italics: 'Roboto-Italic.ttf', 15 | bolditalics: 'Roboto-MediumItalic.ttf' 16 | } 17 | } 18 | }; 19 | 20 | var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this; 21 | if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') { 22 | _global.pdfMake.addFontContainer(fontContainer); 23 | } 24 | 25 | if (typeof module !== 'undefined') { 26 | module.exports = fontContainer; 27 | } 28 | -------------------------------------------------------------------------------- /src/browser-extensions/index.js: -------------------------------------------------------------------------------- 1 | import pdfmakeBase from '../base'; 2 | import OutputDocumentBrowser from './OutputDocumentBrowser'; 3 | import URLBrowserResolver from './URLBrowserResolver'; 4 | import fs from 'fs'; 5 | import configurator from 'core-js/configurator'; 6 | 7 | // core-js: Polyfills will be used only if natives completely unavailable. 8 | configurator({ 9 | useNative: ['Promise'] 10 | }); 11 | 12 | let defaultClientFonts = { 13 | Roboto: { 14 | normal: 'Roboto-Regular.ttf', 15 | bold: 'Roboto-Medium.ttf', 16 | italics: 'Roboto-Italic.ttf', 17 | bolditalics: 'Roboto-MediumItalic.ttf' 18 | } 19 | }; 20 | 21 | class pdfmake extends pdfmakeBase { 22 | constructor() { 23 | super(); 24 | this.urlResolver = () => new URLBrowserResolver(this.virtualfs); 25 | this.fonts = defaultClientFonts; 26 | } 27 | 28 | addFontContainer(fontContainer) { 29 | this.addVirtualFileSystem(fontContainer.vfs); 30 | this.addFonts(fontContainer.fonts); 31 | } 32 | 33 | addVirtualFileSystem(vfs) { 34 | for (let key in vfs) { 35 | if (vfs.hasOwnProperty(key)) { 36 | let data; 37 | let encoding; 38 | if (typeof vfs[key] === 'object') { 39 | data = vfs[key].data; 40 | encoding = vfs[key].encoding || 'base64'; 41 | } else { 42 | data = vfs[key]; 43 | encoding = 'base64'; 44 | } 45 | fs.writeFileSync(key, data, encoding); 46 | } 47 | } 48 | } 49 | 50 | _transformToDocument(doc) { 51 | return new OutputDocumentBrowser(doc); 52 | } 53 | } 54 | 55 | export default new pdfmake(); 56 | -------------------------------------------------------------------------------- /src/browser-extensions/pdfMake.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./index').default; 2 | -------------------------------------------------------------------------------- /src/browser-extensions/standard-fonts/Courier.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var fontContainer = { 4 | vfs: { 5 | 'data/Courier.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Courier.afm', 'utf8'), encoding: 'utf8' }, 6 | 'data/Courier-Bold.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Courier-Bold.afm', 'utf8'), encoding: 'utf8' }, 7 | 'data/Courier-Oblique.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Courier-Oblique.afm', 'utf8'), encoding: 'utf8' }, 8 | 'data/Courier-BoldOblique.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Courier-BoldOblique.afm', 'utf8'), encoding: 'utf8' } 9 | }, 10 | fonts: { 11 | Courier: { 12 | normal: 'Courier', 13 | bold: 'Courier-Bold', 14 | italics: 'Courier-Oblique', 15 | bolditalics: 'Courier-BoldOblique' 16 | } 17 | } 18 | }; 19 | 20 | var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this; 21 | if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') { 22 | _global.pdfMake.addFontContainer(fontContainer); 23 | } 24 | 25 | if (typeof module !== 'undefined') { 26 | module.exports = fontContainer; 27 | } 28 | -------------------------------------------------------------------------------- /src/browser-extensions/standard-fonts/Helvetica.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var fontContainer = { 4 | vfs: { 5 | 'data/Helvetica.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Helvetica.afm', 'utf8'), encoding: 'utf8' }, 6 | 'data/Helvetica-Bold.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Helvetica-Bold.afm', 'utf8'), encoding: 'utf8' }, 7 | 'data/Helvetica-Oblique.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Helvetica-Oblique.afm', 'utf8'), encoding: 'utf8' }, 8 | 'data/Helvetica-BoldOblique.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Helvetica-BoldOblique.afm', 'utf8'), encoding: 'utf8' } 9 | }, 10 | fonts: { 11 | Helvetica: { 12 | normal: 'Helvetica', 13 | bold: 'Helvetica-Bold', 14 | italics: 'Helvetica-Oblique', 15 | bolditalics: 'Helvetica-BoldOblique' 16 | } 17 | } 18 | }; 19 | 20 | var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this; 21 | if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') { 22 | _global.pdfMake.addFontContainer(fontContainer); 23 | } 24 | 25 | if (typeof module !== 'undefined') { 26 | module.exports = fontContainer; 27 | } 28 | -------------------------------------------------------------------------------- /src/browser-extensions/standard-fonts/Symbol.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var fontContainer = { 4 | vfs: { 5 | 'data/Symbol.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Symbol.afm', 'utf8'), encoding: 'utf8' } 6 | }, 7 | fonts: { 8 | Symbol: { 9 | normal: 'Symbol' 10 | } 11 | } 12 | }; 13 | 14 | var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this; 15 | if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') { 16 | _global.pdfMake.addFontContainer(fontContainer); 17 | } 18 | 19 | if (typeof module !== 'undefined') { 20 | module.exports = fontContainer; 21 | } 22 | -------------------------------------------------------------------------------- /src/browser-extensions/standard-fonts/Times.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var fontContainer = { 4 | vfs: { 5 | 'data/Times-Roman.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Times-Roman.afm', 'utf8'), encoding: 'utf8' }, 6 | 'data/Times-Bold.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Times-Bold.afm', 'utf8'), encoding: 'utf8' }, 7 | 'data/Times-Italic.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Times-Italic.afm', 'utf8'), encoding: 'utf8' }, 8 | 'data/Times-BoldItalic.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/Times-BoldItalic.afm', 'utf8'), encoding: 'utf8' } 9 | }, 10 | fonts: { 11 | Times: { 12 | normal: 'Times-Roman', 13 | bold: 'Times-Bold', 14 | italics: 'Times-Italic', 15 | bolditalics: 'Times-BoldItalic' 16 | } 17 | } 18 | }; 19 | 20 | var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this; 21 | if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') { 22 | _global.pdfMake.addFontContainer(fontContainer); 23 | } 24 | 25 | if (typeof module !== 'undefined') { 26 | module.exports = fontContainer; 27 | } 28 | -------------------------------------------------------------------------------- /src/browser-extensions/standard-fonts/ZapfDingbats.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var fontContainer = { 4 | vfs: { 5 | 'data/ZapfDingbats.afm': { data: fs.readFileSync(__dirname + '/../../../node_modules/pdfkit/js/data/ZapfDingbats.afm', 'utf8'), encoding: 'utf8' } 6 | }, 7 | fonts: { 8 | ZapfDingbats: { 9 | normal: 'ZapfDingbats' 10 | } 11 | } 12 | }; 13 | 14 | var _global = typeof window === 'object' ? window : typeof global === 'object' ? global : typeof self === 'object' ? self : this; 15 | if (typeof _global.pdfMake !== 'undefined' && typeof _global.pdfMake.addFontContainer !== 'undefined') { 16 | _global.pdfMake.addFontContainer(fontContainer); 17 | } 18 | 19 | if (typeof module !== 'undefined') { 20 | module.exports = fontContainer; 21 | } 22 | -------------------------------------------------------------------------------- /src/browser-extensions/virtual-fs-cjs.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../virtual-fs').default; 2 | -------------------------------------------------------------------------------- /src/columnCalculator.js: -------------------------------------------------------------------------------- 1 | import { isString } from './helpers/variableType'; 2 | 3 | function buildColumnWidths(columns, availableWidth, offsetTotal = 0, tableNode) { 4 | let autoColumns = []; 5 | let autoMin = 0; 6 | let autoMax = 0; 7 | let starColumns = []; 8 | let starMaxMin = 0; 9 | let starMaxMax = 0; 10 | let fixedColumns = []; 11 | let initial_availableWidth = availableWidth; 12 | 13 | columns.forEach(column => { 14 | if (isAutoColumn(column)) { 15 | autoColumns.push(column); 16 | autoMin += column._minWidth; 17 | autoMax += column._maxWidth; 18 | } else if (isStarColumn(column)) { 19 | starColumns.push(column); 20 | starMaxMin = Math.max(starMaxMin, column._minWidth); 21 | starMaxMax = Math.max(starMaxMax, column._maxWidth); 22 | } else { 23 | fixedColumns.push(column); 24 | } 25 | }); 26 | 27 | fixedColumns.forEach((col, colIndex) => { 28 | // width specified as % 29 | if (isString(col.width) && /\d+%/.test(col.width)) { 30 | // In tables we have to take into consideration the reserved width for paddings and borders 31 | let reservedWidth = 0; 32 | if (tableNode) { 33 | const paddingLeft = tableNode._layout.paddingLeft(colIndex, tableNode); 34 | const paddingRight = tableNode._layout.paddingRight(colIndex, tableNode); 35 | const borderLeft = tableNode._layout.vLineWidth(colIndex, tableNode); 36 | const borderRight = tableNode._layout.vLineWidth(colIndex + 1, tableNode); 37 | if (colIndex === 0) { 38 | // first column assumes whole borderLeft and half of border right 39 | reservedWidth = paddingLeft + paddingRight + borderLeft + (borderRight / 2); 40 | 41 | } else if (colIndex === fixedColumns.length - 1) { 42 | // last column assumes whole borderRight and half of border left 43 | reservedWidth = paddingLeft + paddingRight + (borderLeft / 2) + borderRight; 44 | 45 | } else { 46 | // Columns in the middle assume half of each border 47 | reservedWidth = paddingLeft + paddingRight + (borderLeft / 2) + (borderRight / 2); 48 | } 49 | } 50 | const totalAvailableWidth = initial_availableWidth + offsetTotal; 51 | col.width = (parseFloat(col.width) * totalAvailableWidth / 100) - reservedWidth; 52 | } 53 | if (col.width < (col._minWidth) && col.elasticWidth) { 54 | col._calcWidth = col._minWidth; 55 | } else { 56 | col._calcWidth = col.width; 57 | } 58 | 59 | availableWidth -= col._calcWidth; 60 | }); 61 | 62 | // http://www.freesoft.org/CIE/RFC/1942/18.htm 63 | // http://www.w3.org/TR/CSS2/tables.html#width-layout 64 | // http://dev.w3.org/csswg/css3-tables-algorithms/Overview.src.htm 65 | let minW = autoMin + starMaxMin * starColumns.length; 66 | let maxW = autoMax + starMaxMax * starColumns.length; 67 | if (minW >= availableWidth) { 68 | // case 1 - there's no way to fit all columns within available width 69 | // that's actually pretty bad situation with PDF as we have no horizontal scroll 70 | // no easy workaround (unless we decide, in the future, to split single words) 71 | // currently we simply use minWidths for all columns 72 | autoColumns.forEach(col => { 73 | col._calcWidth = col._minWidth; 74 | }); 75 | 76 | starColumns.forEach(col => { 77 | col._calcWidth = starMaxMin; // starMaxMin already contains padding 78 | }); 79 | } else { 80 | if (maxW < availableWidth) { 81 | // case 2 - we can fit rest of the table within available space 82 | autoColumns.forEach(col => { 83 | col._calcWidth = col._maxWidth; 84 | availableWidth -= col._calcWidth; 85 | }); 86 | } else { 87 | // maxW is too large, but minW fits within available width 88 | let W = availableWidth - minW; 89 | let D = maxW - minW; 90 | 91 | autoColumns.forEach(col => { 92 | let d = col._maxWidth - col._minWidth; 93 | col._calcWidth = col._minWidth + d * W / D; 94 | availableWidth -= col._calcWidth; 95 | }); 96 | } 97 | 98 | if (starColumns.length > 0) { 99 | let starSize = availableWidth / starColumns.length; 100 | 101 | starColumns.forEach(col => { 102 | col._calcWidth = starSize; 103 | }); 104 | } 105 | } 106 | } 107 | 108 | function isAutoColumn(column) { 109 | return column.width === 'auto'; 110 | } 111 | 112 | function isStarColumn(column) { 113 | return column.width === null || column.width === undefined || column.width === '*' || column.width === 'star'; 114 | } 115 | 116 | //TODO: refactor and reuse in measureTable 117 | function measureMinMax(columns) { 118 | let result = { min: 0, max: 0 }; 119 | let maxStar = { min: 0, max: 0 }; 120 | let starCount = 0; 121 | 122 | for (let i = 0, l = columns.length; i < l; i++) { 123 | let c = columns[i]; 124 | 125 | if (isStarColumn(c)) { 126 | maxStar.min = Math.max(maxStar.min, c._minWidth); 127 | maxStar.max = Math.max(maxStar.max, c._maxWidth); 128 | starCount++; 129 | } else if (isAutoColumn(c)) { 130 | result.min += c._minWidth; 131 | result.max += c._maxWidth; 132 | } else { 133 | result.min += ((c.width !== undefined && c.width) || c._minWidth); 134 | result.max += ((c.width !== undefined && c.width) || c._maxWidth); 135 | } 136 | } 137 | 138 | if (starCount) { 139 | result.min += starCount * maxStar.min; 140 | result.max += starCount * maxStar.max; 141 | } 142 | 143 | return result; 144 | } 145 | 146 | /** 147 | * Calculates column widths 148 | */ 149 | export default { 150 | buildColumnWidths: buildColumnWidths, 151 | measureMinMax: measureMinMax, 152 | isAutoColumn: isAutoColumn, 153 | isStarColumn: isStarColumn 154 | }; 155 | -------------------------------------------------------------------------------- /src/helpers/node.js: -------------------------------------------------------------------------------- 1 | import { isNumber } from './variableType'; 2 | 3 | function fontStringify(key, val) { 4 | if (key === 'font') { 5 | return 'font'; 6 | } 7 | return val; 8 | } 9 | 10 | /** 11 | * Convert node to readable string 12 | * 13 | * @param {object} node 14 | * @returns {string} 15 | */ 16 | export function stringifyNode(node) { 17 | return JSON.stringify(node, fontStringify); 18 | } 19 | 20 | /** 21 | * @param {object} node 22 | * @returns {?string} 23 | */ 24 | export function getNodeId(node) { 25 | if (node.id) { 26 | return node.id; 27 | } 28 | 29 | if (Array.isArray(node.text)) { 30 | for (let n of node.text) { 31 | let nodeId = getNodeId(n); 32 | if (nodeId) { 33 | return nodeId; 34 | } 35 | } 36 | } 37 | 38 | return null; 39 | } 40 | 41 | /** 42 | * @param {object} node 43 | * @param {object} styleStack object is instance of PDFDocument 44 | * @returns {?Array} 45 | */ 46 | export function getNodeMargin(node, styleStack) { 47 | function processSingleMargins(node, currentMargin) { 48 | if (node.marginLeft || node.marginTop || node.marginRight || node.marginBottom) { 49 | return [ 50 | node.marginLeft || currentMargin[0] || 0, 51 | node.marginTop || currentMargin[1] || 0, 52 | node.marginRight || currentMargin[2] || 0, 53 | node.marginBottom || currentMargin[3] || 0 54 | ]; 55 | } 56 | return currentMargin; 57 | } 58 | 59 | function flattenStyleArray(styleArray, styleStack) { 60 | let flattenedStyles = {}; 61 | for (let i = styleArray.length - 1; i >= 0; i--) { 62 | let styleName = styleArray[i]; 63 | let style = styleStack.styleDictionary[styleName]; 64 | for (let key in style) { 65 | if (style.hasOwnProperty(key)) { 66 | flattenedStyles[key] = style[key]; 67 | } 68 | } 69 | } 70 | return flattenedStyles; 71 | } 72 | 73 | function convertMargin(margin) { 74 | if (isNumber(margin)) { 75 | margin = [margin, margin, margin, margin]; 76 | } else if (Array.isArray(margin)) { 77 | if (margin.length === 2) { 78 | margin = [margin[0], margin[1], margin[0], margin[1]]; 79 | } 80 | } 81 | return margin; 82 | } 83 | 84 | let margin = [undefined, undefined, undefined, undefined]; 85 | 86 | if (node.style) { 87 | let styleArray = Array.isArray(node.style) ? node.style : [node.style]; 88 | let flattenedStyleArray = flattenStyleArray(styleArray, styleStack); 89 | 90 | if (flattenedStyleArray) { 91 | margin = processSingleMargins(flattenedStyleArray, margin); 92 | } 93 | 94 | if (flattenedStyleArray.margin) { 95 | margin = convertMargin(flattenedStyleArray.margin); 96 | } 97 | } 98 | 99 | margin = processSingleMargins(node, margin); 100 | 101 | if (node.margin) { 102 | margin = convertMargin(node.margin); 103 | } 104 | 105 | if (margin[0] === undefined && margin[1] === undefined && margin[2] === undefined && margin[3] === undefined) { 106 | return null; 107 | } 108 | 109 | return margin; 110 | } 111 | -------------------------------------------------------------------------------- /src/helpers/tools.js: -------------------------------------------------------------------------------- 1 | export function pack(...args) { 2 | let result = {}; 3 | 4 | for (let i = 0, l = args.length; i < l; i++) { 5 | let obj = args[i]; 6 | 7 | if (obj) { 8 | for (let key in obj) { 9 | if (obj.hasOwnProperty(key)) { 10 | result[key] = obj[key]; 11 | } 12 | } 13 | } 14 | } 15 | 16 | return result; 17 | } 18 | 19 | export function offsetVector(vector, x, y) { 20 | switch (vector.type) { 21 | case 'ellipse': 22 | case 'rect': 23 | vector.x += x; 24 | vector.y += y; 25 | break; 26 | case 'line': 27 | vector.x1 += x; 28 | vector.x2 += x; 29 | vector.y1 += y; 30 | vector.y2 += y; 31 | break; 32 | case 'polyline': 33 | for (let i = 0, l = vector.points.length; i < l; i++) { 34 | vector.points[i].x += x; 35 | vector.points[i].y += y; 36 | } 37 | break; 38 | } 39 | } 40 | 41 | export function convertToDynamicContent(staticContent) { 42 | return () => // copy to new object 43 | JSON.parse(JSON.stringify(staticContent)); 44 | } 45 | -------------------------------------------------------------------------------- /src/helpers/variableType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {any} variable 3 | * @returns {boolean} 4 | */ 5 | export function isString(variable) { 6 | return (typeof variable === 'string') || (variable instanceof String); 7 | } 8 | 9 | /** 10 | * @param {any} variable 11 | * @returns {boolean} 12 | */ 13 | export function isNumber(variable) { 14 | return (typeof variable === 'number') || (variable instanceof Number); 15 | } 16 | 17 | /** 18 | * @param {any} variable 19 | * @returns {boolean} 20 | */ 21 | export function isPositiveInteger(variable) { 22 | if (!isNumber(variable) || !Number.isInteger(variable) || variable <= 0) { 23 | return false; 24 | } 25 | return true; 26 | } 27 | 28 | /** 29 | * @param {any} variable 30 | * @returns {boolean} 31 | */ 32 | export function isObject(variable) { 33 | return (variable !== null) && !Array.isArray(variable) && !isString(variable) && !isNumber(variable) && (typeof variable === 'object'); 34 | } 35 | 36 | /** 37 | * @param {any} variable 38 | * @returns {boolean} 39 | */ 40 | export function isEmptyObject(variable) { 41 | return isObject(variable) && (Object.keys(variable).length === 0); 42 | } 43 | 44 | /** 45 | * @param {any} variable 46 | * @returns {boolean} 47 | */ 48 | export function isValue(variable) { 49 | return (variable !== undefined) && (variable !== null); 50 | } 51 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const pdfmakeBase = require('./base').default; 2 | const OutputDocumentServer = require('./OutputDocumentServer').default; 3 | const URLResolver = require('./URLResolver').default; 4 | 5 | class pdfmake extends pdfmakeBase { 6 | constructor() { 7 | super(); 8 | this.urlResolver = () => new URLResolver(this.virtualfs); 9 | } 10 | 11 | _transformToDocument(doc) { 12 | return new OutputDocumentServer(doc); 13 | } 14 | } 15 | 16 | module.exports = new pdfmake(); 17 | -------------------------------------------------------------------------------- /src/standardPageSizes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '4A0': [4767.87, 6740.79], 3 | '2A0': [3370.39, 4767.87], 4 | A0: [2383.94, 3370.39], 5 | A1: [1683.78, 2383.94], 6 | A2: [1190.55, 1683.78], 7 | A3: [841.89, 1190.55], 8 | A4: [595.28, 841.89], 9 | A5: [419.53, 595.28], 10 | A6: [297.64, 419.53], 11 | A7: [209.76, 297.64], 12 | A8: [147.40, 209.76], 13 | A9: [104.88, 147.40], 14 | A10: [73.70, 104.88], 15 | B0: [2834.65, 4008.19], 16 | B1: [2004.09, 2834.65], 17 | B2: [1417.32, 2004.09], 18 | B3: [1000.63, 1417.32], 19 | B4: [708.66, 1000.63], 20 | B5: [498.90, 708.66], 21 | B6: [354.33, 498.90], 22 | B7: [249.45, 354.33], 23 | B8: [175.75, 249.45], 24 | B9: [124.72, 175.75], 25 | B10: [87.87, 124.72], 26 | C0: [2599.37, 3676.54], 27 | C1: [1836.85, 2599.37], 28 | C2: [1298.27, 1836.85], 29 | C3: [918.43, 1298.27], 30 | C4: [649.13, 918.43], 31 | C5: [459.21, 649.13], 32 | C6: [323.15, 459.21], 33 | C7: [229.61, 323.15], 34 | C8: [161.57, 229.61], 35 | C9: [113.39, 161.57], 36 | C10: [79.37, 113.39], 37 | RA0: [2437.80, 3458.27], 38 | RA1: [1729.13, 2437.80], 39 | RA2: [1218.90, 1729.13], 40 | RA3: [864.57, 1218.90], 41 | RA4: [609.45, 864.57], 42 | SRA0: [2551.18, 3628.35], 43 | SRA1: [1814.17, 2551.18], 44 | SRA2: [1275.59, 1814.17], 45 | SRA3: [907.09, 1275.59], 46 | SRA4: [637.80, 907.09], 47 | EXECUTIVE: [521.86, 756.00], 48 | FOLIO: [612.00, 936.00], 49 | LEGAL: [612.00, 1008.00], 50 | LETTER: [612.00, 792.00], 51 | TABLOID: [792.00, 1224.00] 52 | }; 53 | -------------------------------------------------------------------------------- /src/tableLayouts.js: -------------------------------------------------------------------------------- 1 | /*eslint no-unused-vars: ["error", {"args": "none"}]*/ 2 | 3 | export { 4 | tableLayouts, 5 | defaultTableLayout 6 | }; 7 | 8 | const tableLayouts = { 9 | noBorders: { 10 | hLineWidth(i) { 11 | return 0; 12 | }, 13 | vLineWidth(i) { 14 | return 0; 15 | }, 16 | paddingLeft(i) { 17 | return i && 4 || 0; 18 | }, 19 | paddingRight(i, node) { 20 | return (i < node.table.widths.length - 1) ? 4 : 0; 21 | } 22 | }, 23 | headerLineOnly: { 24 | hLineWidth(i, node) { 25 | if (i === 0 || i === node.table.body.length) { 26 | return 0; 27 | } 28 | return (i === node.table.headerRows) ? 2 : 0; 29 | }, 30 | vLineWidth(i) { 31 | return 0; 32 | }, 33 | paddingLeft(i) { 34 | return i === 0 ? 0 : 8; 35 | }, 36 | paddingRight(i, node) { 37 | return (i === node.table.widths.length - 1) ? 0 : 8; 38 | } 39 | }, 40 | lightHorizontalLines: { 41 | hLineWidth(i, node) { 42 | if (i === 0 || i === node.table.body.length) { 43 | return 0; 44 | } 45 | return (i === node.table.headerRows) ? 2 : 1; 46 | }, 47 | vLineWidth(i) { 48 | return 0; 49 | }, 50 | hLineColor(i) { 51 | return i === 1 ? 'black' : '#aaa'; 52 | }, 53 | paddingLeft(i) { 54 | return i === 0 ? 0 : 8; 55 | }, 56 | paddingRight(i, node) { 57 | return (i === node.table.widths.length - 1) ? 0 : 8; 58 | } 59 | } 60 | }; 61 | 62 | const defaultTableLayout = { 63 | hLineWidth(i, node) { 64 | return 1; 65 | }, 66 | vLineWidth(i, node) { 67 | return 1; 68 | }, 69 | hLineColor(i, node) { 70 | return 'black'; 71 | }, 72 | vLineColor(i, node) { 73 | return 'black'; 74 | }, 75 | hLineStyle(i, node) { 76 | return null; 77 | }, 78 | vLineStyle(i, node) { 79 | return null; 80 | }, 81 | paddingLeft(i, node) { 82 | return 4; 83 | }, 84 | paddingRight(i, node) { 85 | return 4; 86 | }, 87 | paddingTop(i, node) { 88 | return 2; 89 | }, 90 | paddingBottom(i, node) { 91 | return 2; 92 | }, 93 | fillColor(i, node) { 94 | return null; 95 | }, 96 | fillOpacity(i, node) { 97 | return 1; 98 | }, 99 | defaultBorder: true 100 | }; 101 | -------------------------------------------------------------------------------- /src/virtual-fs.js: -------------------------------------------------------------------------------- 1 | const normalizeFilename = filename => { 2 | if (filename.indexOf(__dirname) === 0) { 3 | filename = filename.substring(__dirname.length); 4 | } 5 | 6 | if (filename.indexOf('/') === 0) { 7 | filename = filename.substring(1); 8 | } 9 | 10 | return filename; 11 | }; 12 | 13 | class VirtualFileSystem { 14 | constructor() { 15 | this.storage = {}; 16 | } 17 | 18 | /** 19 | * @param {string} filename 20 | * @returns {boolean} 21 | */ 22 | existsSync(filename) { 23 | const normalizedFilename = normalizeFilename(filename); 24 | return typeof this.storage[normalizedFilename] !== 'undefined'; 25 | } 26 | 27 | /** 28 | * @param {string} filename 29 | * @param {?string|?object} options 30 | * @returns {string|Buffer} 31 | */ 32 | readFileSync(filename, options) { 33 | const normalizedFilename = normalizeFilename(filename); 34 | const encoding = typeof options === 'object' ? options.encoding : options; 35 | 36 | if (!this.existsSync(normalizedFilename)) { 37 | throw new Error(`File '${normalizedFilename}' not found in virtual file system`); 38 | } 39 | 40 | const buffer = this.storage[normalizedFilename]; 41 | if (encoding) { 42 | return buffer.toString(encoding); 43 | } 44 | 45 | return buffer; 46 | } 47 | 48 | /** 49 | * @param {string} filename 50 | * @param {string|Buffer} content 51 | * @param {?string|?object} options 52 | */ 53 | writeFileSync(filename, content, options) { 54 | const normalizedFilename = normalizeFilename(filename); 55 | const encoding = typeof options === 'object' ? options.encoding : options; 56 | 57 | if (!content && !options) { 58 | throw new Error('No content'); 59 | } 60 | 61 | this.storage[normalizedFilename] = encoding || typeof content === 'string' ? new Buffer(content, encoding) : content; 62 | } 63 | 64 | } 65 | 66 | export default new VirtualFileSystem(); 67 | -------------------------------------------------------------------------------- /standard-fonts/Courier.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Courier: { 3 | normal: 'Courier', 4 | bold: 'Courier-Bold', 5 | italics: 'Courier-Oblique', 6 | bolditalics: 'Courier-BoldOblique' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /standard-fonts/Helvetica.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Helvetica: { 3 | normal: 'Helvetica', 4 | bold: 'Helvetica-Bold', 5 | italics: 'Helvetica-Oblique', 6 | bolditalics: 'Helvetica-BoldOblique' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /standard-fonts/Symbol.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Symbol: { 3 | normal: 'Symbol' 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /standard-fonts/Times.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Times: { 3 | normal: 'Times-Roman', 4 | bold: 'Times-Bold', 5 | italics: 'Times-Italic', 6 | bolditalics: 'Times-BoldItalic' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /standard-fonts/ZapfDingbats.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ZapfDingbats: { 3 | normal: 'ZapfDingbats' 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /tests/browser/polyfills.spec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var pdfmake = require('./../../build/pdfmake.js'); 3 | var pdfFonts = require('./../../build/vfs_fonts.js'); 4 | pdfmake.addVirtualFileSystem(pdfFonts); 5 | 6 | describe('core-js polyfill', function () { 7 | it('Object.isExtensible bug', async function () { 8 | var docDefinition = { 9 | content: [ 10 | 'Empty document' 11 | ] 12 | }; 13 | 14 | var pdf = pdfmake.createPdf(docDefinition); 15 | await pdf.getStream().then(() => { 16 | // noop 17 | }, () => { 18 | // noop 19 | }); 20 | 21 | assert.equal(Object.isExtensible(docDefinition), true); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/tests/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /tests/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/tests/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /tests/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/tests/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /tests/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/tests/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /tests/fonts/sampleImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpampuch/pdfmake/4395ebdb4e1ec52a60af8fbc52a758c9939fba97/tests/fonts/sampleImage.jpg -------------------------------------------------------------------------------- /tests/integration/alignment.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var sizes = require('../../js/standardPageSizes').default; 5 | var integrationTestHelper = require('./integrationTestHelper'); 6 | 7 | describe('Integration test: alignment', function () { 8 | 9 | var testHelper = new integrationTestHelper(); 10 | 11 | it('renders text right aligned', function () { 12 | 13 | var dd = { 14 | content: [ 15 | { 16 | text: 'Left aligned before', 17 | alignment: 'left' 18 | }, 19 | { 20 | text: 'Right aligned', 21 | alignment: 'right' 22 | }, 23 | { 24 | text: 'Left aligned after', 25 | alignment: 'left' 26 | } 27 | ] 28 | }; 29 | 30 | var pages = testHelper.renderPages('A6', dd); 31 | 32 | assert.equal(pages.length, 1); 33 | 34 | var itemLeftBefore = pages[0].items[0].item; 35 | assert.equal(itemLeftBefore.x, testHelper.MARGINS.left); 36 | assert.equal(itemLeftBefore.y, testHelper.MARGINS.top); 37 | 38 | var itemRight = pages[0].items[1].item; 39 | assert.equal(itemRight.x, sizes.A6[0] - testHelper.MARGINS.right - testHelper.getWidthOfString('Right aligned')); 40 | assert.equal(itemRight.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT); 41 | 42 | var itemLeftAfter = pages[0].items[2].item; 43 | assert.equal(itemLeftAfter.x, testHelper.MARGINS.left); 44 | assert.equal(itemLeftAfter.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT * 2); 45 | }); 46 | 47 | it('renders text center aligned', function () { 48 | 49 | var dd = { 50 | content: [ 51 | { 52 | text: 'Left aligned before', 53 | alignment: 'left' 54 | }, 55 | { 56 | text: 'Right aligned', 57 | alignment: 'center' 58 | }, 59 | { 60 | text: 'Left aligned after', 61 | alignment: 'left' 62 | } 63 | ] 64 | }; 65 | 66 | var pages = testHelper.renderPages('A6', dd); 67 | 68 | assert.equal(pages.length, 1); 69 | 70 | var itemLeftBefore = pages[0].items[0].item; 71 | assert.equal(itemLeftBefore.x, testHelper.MARGINS.left); 72 | assert.equal(itemLeftBefore.y, testHelper.MARGINS.top); 73 | 74 | var itemCenter = pages[0].items[1].item; 75 | assert.equal(itemCenter.x, sizes.A6[0] / 2 - testHelper.getWidthOfString('Right aligned') / 2); 76 | assert.equal(itemCenter.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT); 77 | 78 | var itemLeftAfter = pages[0].items[2].item; 79 | assert.equal(itemLeftAfter.x, testHelper.MARGINS.left); 80 | assert.equal(itemLeftAfter.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT * 2); 81 | }); 82 | 83 | it('renders text justify aligned', function () { 84 | 85 | var dd = { 86 | content: [ 87 | { 88 | text: 'Left aligned before', 89 | alignment: 'left' 90 | }, 91 | { 92 | text: 'I\'m not sure yet if this is the_desired_behavior. I find it a better.', 93 | alignment: 'justify' 94 | }, 95 | { 96 | text: 'Left aligned after', 97 | alignment: 'left' 98 | } 99 | ] 100 | }; 101 | 102 | var pages = testHelper.renderPages('A6', dd); 103 | 104 | assert.equal(pages.length, 1); 105 | 106 | var itemLeftBefore = pages[0].items[0].item; 107 | assert.equal(itemLeftBefore.x, testHelper.MARGINS.left); 108 | assert.equal(itemLeftBefore.y, testHelper.MARGINS.top); 109 | 110 | var itemJustify1 = pages[0].items[1].item; 111 | assert.equal(itemJustify1.x, testHelper.MARGINS.left); 112 | assert.equal(itemJustify1.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT); 113 | 114 | var availablePageWidth = sizes.A6[0] - testHelper.MARGINS.left - testHelper.MARGINS.right; 115 | var endOfLastItem = itemJustify1.inlines[itemJustify1.inlines.length - 1].x + testHelper.getWidthOfString('is'); 116 | assert.equal(endOfLastItem, availablePageWidth); 117 | 118 | var itemJustify2 = pages[0].items[2].item; 119 | assert.equal(itemJustify2.x, testHelper.MARGINS.left); 120 | assert.equal(itemJustify2.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT * 2); 121 | assert.equal(itemJustify2.inlineWidths, testHelper.getWidthOfString('the_desired_behavior. I find it a better.')); 122 | 123 | var itemLeftAfter = pages[0].items[3].item; 124 | assert.equal(itemLeftAfter.x, testHelper.MARGINS.left); 125 | assert.equal(itemLeftAfter.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT * 3); 126 | }); 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /tests/integration/background.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var integrationTestHelper = require('./integrationTestHelper'); 6 | 7 | describe('Integration test: background', function () { 8 | 9 | var testHelper = new integrationTestHelper(); 10 | 11 | it('renders on every page', function () { 12 | var dd = { 13 | background: function (page) { 14 | return [ 15 | 'Background paragraph on page ' + page 16 | ]; 17 | }, 18 | content: [ 19 | 'First page', 20 | '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', 21 | 'Another Page' 22 | ] 23 | }; 24 | 25 | var pages = testHelper.renderPages('A6', dd); 26 | 27 | assert.equal(pages.length, 2); 28 | 29 | var backgroundPage1 = pages[0].items[0].item; 30 | assert.equal(backgroundPage1.inlines.map(node => node.text).join(''), 'Background paragraph on page 1'); 31 | assert.equal(backgroundPage1.x, 0); 32 | assert.equal(backgroundPage1.y, 0); 33 | 34 | var backgroundPage2 = pages[1].items[0].item; 35 | assert.equal(backgroundPage2.inlines.map(node => node.text).join(''), 'Background paragraph on page 2'); 36 | assert.equal(backgroundPage2.x, 0); 37 | assert.equal(backgroundPage2.y, 0); 38 | }); 39 | 40 | it('table fillColor must be above background', function () { 41 | var dd = { 42 | background: function () { 43 | return [ 44 | 'Background paragraph' 45 | ]; 46 | }, 47 | content: [ 48 | { 49 | table: { 50 | body: [ 51 | [ 52 | { 53 | text: '\n', 54 | fillColor: '#7d02c9' 55 | } 56 | ] 57 | ] 58 | } 59 | } 60 | ] 61 | }; 62 | 63 | var pages = testHelper.renderPages('A6', dd); 64 | 65 | assert.equal(pages.length, 1); 66 | 67 | var fillColorRect = pages[0].items[1].item; 68 | var backgroundPage = pages[0].items[0].item; 69 | 70 | assert.equal(backgroundPage.inlines.map(node => node.text).join(''), 'Background paragraph'); 71 | assert.equal(fillColorRect.type, 'rect'); 72 | assert.equal(fillColorRect.color, '#7d02c9'); 73 | }); 74 | 75 | /** 76 | * This should test any case in which function addPageItem is called with 77 | * defined index. This includes but might not be limited to: 78 | * - Vectors 79 | * - Qr 80 | * - Lists 81 | * - Tables 82 | * - Images 83 | * - Leafs 84 | */ 85 | it('background elements remain at the bottom of item list on every page', function () { 86 | var dd = { 87 | background: function () { 88 | return [ 89 | { 90 | canvas: [ 91 | { 92 | type: 'rect', 93 | x: 0, 94 | y: 0, 95 | w: 50, 96 | h: 50, 97 | color: 'red' 98 | }, 99 | { 100 | type: 'rect', 101 | x: 0, 102 | y: 0, 103 | w: 50, 104 | h: 50, 105 | color: 'green' 106 | }, 107 | { 108 | type: 'rect', 109 | x: 0, 110 | y: 0, 111 | w: 50, 112 | h: 50, 113 | color: 'blue' 114 | } 115 | ] 116 | } 117 | ]; 118 | }, 119 | content: [ 120 | { 121 | table: { 122 | body: [ 123 | [ 124 | { 125 | text: 'a', 126 | fillColor: '#aaa' 127 | }, 128 | { 129 | text: 'b', 130 | fillColor: '#bbb' 131 | }, 132 | { 133 | text: 'c', 134 | fillColor: '#ccc' 135 | } 136 | ] 137 | ] 138 | } 139 | }, 140 | { 141 | qr: 'qr', 142 | foreground: 'red', 143 | background: 'yellow' 144 | }, 145 | { 146 | ul: [ 147 | 'List 1', 148 | 'List 2', 149 | 'List 3' 150 | ] 151 | }, 152 | { 153 | image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' 154 | }, 155 | { 156 | text: 'Leaf', 157 | pageBreak: 'after' 158 | }, 159 | { 160 | table: { 161 | body: [ 162 | [ 163 | { 164 | text: 'a', 165 | fillColor: '#aaa' 166 | }, 167 | { 168 | text: 'b', 169 | fillColor: '#bbb' 170 | }, 171 | { 172 | text: 'c', 173 | fillColor: '#ccc' 174 | } 175 | ] 176 | ] 177 | } 178 | } 179 | ] 180 | }; 181 | var pages = testHelper.renderPages('A6', dd); 182 | assert.equal(pages.length, 2); 183 | var first = pages[0].items[0].item; 184 | var second = pages[0].items[1].item; 185 | var third = pages[0].items[2].item; 186 | var first2 = pages[1].items[0].item; 187 | var second2 = pages[1].items[1].item; 188 | var third2 = pages[1].items[2].item; 189 | assert.equal(first.color, 'red'); 190 | assert.equal(second.color, 'green'); 191 | assert.equal(third.color, 'blue'); 192 | assert.equal(first2.color, 'red'); 193 | assert.equal(second2.color, 'green'); 194 | assert.equal(third2.color, 'blue'); 195 | }); 196 | 197 | }); 198 | -------------------------------------------------------------------------------- /tests/integration/basics.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var sizes = require('../../js/standardPageSizes').default; 5 | 6 | var integrationTestHelper = require('./integrationTestHelper'); 7 | 8 | describe('Integration test: basics', function () { 9 | 10 | var testHelper = new integrationTestHelper(); 11 | 12 | it('renders text on page', function () { 13 | var pages = testHelper.renderPages('A7', { 14 | content: [ 15 | 'First paragraph', 16 | 'Second paragraph on three lines because it is longer' 17 | ] 18 | }); 19 | 20 | assert.equal(pages.length, 1); 21 | assert.equal(pages[0].items.length, 4); 22 | assert.deepEqual(pages[0].items.map(node => node.item).map(item => item.x), [testHelper.MARGINS.left, testHelper.MARGINS.left, testHelper.MARGINS.left, testHelper.MARGINS.left]); 23 | assert.deepEqual(pages[0].items.map(node => node.item).map(item => item.y), [testHelper.MARGINS.top, testHelper.MARGINS.top + testHelper.LINE_HEIGHT, testHelper.MARGINS.top + 2 * testHelper.LINE_HEIGHT, testHelper.MARGINS.top + 3 * testHelper.LINE_HEIGHT]); 24 | assert.deepEqual(testHelper.getInlineTexts(pages, { page: 0, item: 0 }), ['First ', 'paragraph']); 25 | assert.deepEqual(testHelper.getInlineTexts(pages, { page: 0, item: 1 }), ['Second ', 'paragraph ', 'on ']); 26 | assert.deepEqual(testHelper.getInlineTexts(pages, { page: 0, item: 2 }), ['three ', 'lines ', 'because ', 'it ', 'is ']); 27 | assert.deepEqual(testHelper.getInlineTexts(pages, { page: 0, item: 3 }), ['longer']); 28 | }); 29 | 30 | it('renders text with margin', function () { 31 | var customMargin = 10; 32 | var anotherCustomMargin = 13; 33 | var dd = { 34 | content: [ 35 | { text: 'has margin', margin: customMargin }, 36 | { text: 'has only top/bottom margin', margin: [0, customMargin] }, 37 | { 38 | text: 'has single set margin', 39 | margin: [anotherCustomMargin, anotherCustomMargin, anotherCustomMargin, anotherCustomMargin] 40 | }, 41 | { text: 'has only right margin', alignment: 'right', marginRight: 20 } 42 | ] 43 | }; 44 | 45 | var pages = testHelper.renderPages('A5', dd); 46 | 47 | assert.equal(pages.length, 1); 48 | assert.equal(pages[0].items[0].item.x, testHelper.MARGINS.left + customMargin); 49 | assert.equal(pages[0].items[0].item.y, testHelper.MARGINS.top + customMargin); 50 | 51 | assert.equal(pages[0].items[1].item.x, testHelper.MARGINS.left); 52 | assert.equal(pages[0].items[1].item.y, testHelper.MARGINS.top + customMargin * 3 + testHelper.LINE_HEIGHT); 53 | 54 | assert.equal(pages[0].items[2].item.x, testHelper.MARGINS.left + anotherCustomMargin); 55 | assert.equal(pages[0].items[2].item.y.toFixed(3), testHelper.MARGINS.top + customMargin * 4 + anotherCustomMargin + testHelper.LINE_HEIGHT * 2); 56 | 57 | assert.equal(pages[0].items[3].item.x, sizes.A5[0] - testHelper.MARGINS.right - 20 - testHelper.getWidthOfString('has only right margin')); 58 | assert.equal(pages[0].items[3].item.y.toFixed(3), (testHelper.MARGINS.top + customMargin * 4 + anotherCustomMargin * 2 + testHelper.LINE_HEIGHT * 3).toFixed(3)); 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /tests/integration/images.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var integrationTestHelper = require('./integrationTestHelper'); 6 | 7 | describe('Integration Test: images', function () { 8 | 9 | var testHelper = new integrationTestHelper(); 10 | 11 | var INLINE_TEST_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAGAQMAAADNIO3CAAAAA1BMVEUAAN7GEcIJAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB98DBREbA3IZ3d8AAAALSURBVAjXY2BABwAAEgAB74lUpAAAAABJRU5ErkJggg=='; 12 | 13 | describe('basics', function () { 14 | it('renders next element below image', function () { 15 | var imageHeight = 150; 16 | var dd = { 17 | content: [ 18 | { 19 | image: INLINE_TEST_IMAGE, 20 | height: imageHeight 21 | }, 22 | 'some Text' 23 | ] 24 | }; 25 | 26 | var pages = testHelper.renderPages('A6', dd); 27 | 28 | assert.equal(pages.length, 1); 29 | 30 | var image = pages[0].items[0].item; 31 | var someElementAfterImage = pages[0].items[1].item; 32 | 33 | assert.equal(image.x, testHelper.MARGINS.left); 34 | assert.equal(image.y, testHelper.MARGINS.top); 35 | assert.equal(someElementAfterImage.x, testHelper.MARGINS.left); 36 | assert.equal(someElementAfterImage.y, testHelper.MARGINS.top + imageHeight); 37 | }); 38 | 39 | it('renders image below text', function () { 40 | var imageHeight = 150; 41 | var dd = { 42 | content: [ 43 | 'some Text', 44 | { 45 | image: INLINE_TEST_IMAGE, 46 | height: imageHeight 47 | } 48 | ] 49 | }; 50 | 51 | var pages = testHelper.renderPages('A6', dd); 52 | 53 | assert.equal(pages.length, 1); 54 | 55 | var someElementBeforeImage = pages[0].items[0].item; 56 | var image = pages[0].items[1].item; 57 | 58 | 59 | assert.equal(someElementBeforeImage.x, testHelper.MARGINS.left); 60 | assert.equal(someElementBeforeImage.y, testHelper.MARGINS.top); 61 | 62 | assert.equal(image.x, testHelper.MARGINS.left); 63 | assert.equal(image.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT); 64 | }); 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /tests/integration/integrationTestHelper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var PDFDocument = require('../../js/PDFDocument').default; 4 | var sizes = require('../../js/standardPageSizes').default; 5 | var LayoutBuilder = require('../../js/LayoutBuilder').default; 6 | var SVGMeasure = require('../../js/SVGMeasure').default; 7 | 8 | class IntegrationTestHelper { 9 | constructor() { 10 | this.MARGINS = { top: 40, left: 40, right: 40, bottom: 40 }; 11 | this.LINE_HEIGHT = 14.0625; 12 | this.DEFAULT_BULLET_SPACER = '9. '; 13 | } 14 | 15 | renderPages(sizeName, docDefinition) { 16 | var size = sizes[sizeName]; 17 | docDefinition.images = docDefinition.images || {}; 18 | docDefinition.attachments = docDefinition.attachments || {}; 19 | var fontDescriptors = { 20 | Roboto: { 21 | normal: 'tests/fonts/Roboto-Regular.ttf', 22 | bold: 'tests/fonts/Roboto-Medium.ttf', 23 | italics: 'tests/fonts/Roboto-Italic.ttf', 24 | bolditalics: 'tests/fonts/Roboto-Italic.ttf' 25 | } 26 | }; 27 | 28 | var pageSize = { width: size[0], height: size[1], orientation: 'portrait' }; 29 | 30 | this.pdfDocument = new PDFDocument(fontDescriptors, docDefinition.images, docDefinition.attachments, { size: [pageSize.width, pageSize.height], compress: false }); 31 | var builder = new LayoutBuilder(pageSize, { left: this.MARGINS.left, right: this.MARGINS.right, top: this.MARGINS.top, bottom: this.MARGINS.bottom }, new SVGMeasure()); 32 | 33 | return builder.layoutDocument( 34 | docDefinition.content, 35 | this.pdfDocument, docDefinition.styles || {}, 36 | docDefinition.defaultStyle || { fontSize: 12, font: 'Roboto' }, 37 | docDefinition.background, 38 | docDefinition.header, 39 | docDefinition.footer, 40 | docDefinition.images, 41 | docDefinition.watermark, 42 | docDefinition.pageBreakBefore 43 | ); 44 | } 45 | 46 | getInlineTexts(pages, options) { 47 | return pages[options.page].items[options.item].item.inlines.map(inline => inline.text); 48 | } 49 | 50 | getWidthOfString(inlines) { 51 | return this.pdfDocument.fontCache['Roboto'].normal.widthOfString(inlines, 12); 52 | } 53 | } 54 | 55 | module.exports = IntegrationTestHelper; 56 | -------------------------------------------------------------------------------- /tests/integration/svgs.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var integrationTestHelper = require('./integrationTestHelper'); 6 | var SVGMeasure = require('../../js/SVGMeasure').default; 7 | 8 | // NOTE: more tests for SVGMeasure in ../SVGMeasure.js 9 | 10 | describe('Integration Test: svg\'s', function () { 11 | 12 | var testHelper = new integrationTestHelper(); 13 | 14 | var INLINE_TEST_SVG = ''; 15 | 16 | describe('basics', function () { 17 | it('renders next element below svg', function () { 18 | var svgHeight = 150; 19 | var dd = { 20 | content: [ 21 | { 22 | svg: INLINE_TEST_SVG, 23 | height: svgHeight 24 | }, 25 | 'some Text' 26 | ] 27 | }; 28 | 29 | var pages = testHelper.renderPages('A6', dd); 30 | 31 | assert.equal(pages.length, 1); 32 | 33 | var svg = pages[0].items[0].item; 34 | var someElementAfterSvg = pages[0].items[1].item; 35 | 36 | assert.equal(svg.x, testHelper.MARGINS.left); 37 | assert.equal(svg.y, testHelper.MARGINS.top); 38 | assert.equal(someElementAfterSvg.x, testHelper.MARGINS.left); 39 | assert.equal(someElementAfterSvg.y, testHelper.MARGINS.top + svgHeight); 40 | }); 41 | 42 | it('renders svg below text', function () { 43 | var svgHeight = 150; 44 | var dd = { 45 | content: [ 46 | 'some Text', 47 | { 48 | svg: INLINE_TEST_SVG, 49 | height: svgHeight 50 | } 51 | ] 52 | }; 53 | 54 | var pages = testHelper.renderPages('A6', dd); 55 | 56 | assert.equal(pages.length, 1); 57 | 58 | var someElementBeforeSvg = pages[0].items[0].item; 59 | var image = pages[0].items[1].item; 60 | 61 | 62 | assert.equal(someElementBeforeSvg.x, testHelper.MARGINS.left); 63 | assert.equal(someElementBeforeSvg.y, testHelper.MARGINS.top); 64 | 65 | assert.equal(image.x, testHelper.MARGINS.left); 66 | assert.equal(image.y, testHelper.MARGINS.top + testHelper.LINE_HEIGHT); 67 | }); 68 | }); 69 | 70 | describe('dimensions', function () { 71 | 72 | var svgMeasure = new SVGMeasure(); 73 | 74 | it('reads height and width from svg', function () { 75 | var dd = { 76 | content: [ 77 | { 78 | svg: '', 79 | } 80 | ] 81 | }; 82 | 83 | var pages = testHelper.renderPages('A6', dd); 84 | 85 | var svgNode = pages[0].items[0].item; 86 | 87 | assert.equal(svgNode._width, 200); 88 | assert.equal(svgNode._height, 100); 89 | }); 90 | 91 | it('reads height and width from svg (decimals)', function () { 92 | var dd = { 93 | content: [ 94 | { 95 | svg: '', 96 | } 97 | ] 98 | }; 99 | 100 | var pages = testHelper.renderPages('A6', dd); 101 | 102 | var svgNode = pages[0].items[0].item; 103 | 104 | assert.equal(Number(svgNode._width).toFixed(2), 200.15); 105 | assert.equal(Number(svgNode._height).toFixed(2), 100.35); 106 | }); 107 | 108 | it('reads height and width from viewBox', function () { 109 | var dd = { 110 | content: [ 111 | { 112 | svg: '', 113 | } 114 | ] 115 | }; 116 | 117 | var pages = testHelper.renderPages('A6', dd); 118 | 119 | var svgNode = pages[0].items[0].item; 120 | 121 | assert.equal(svgNode._width, 600); 122 | assert.equal(svgNode._height, 300); 123 | }); 124 | 125 | it('reads height and width from viewBox (decimals)', function () { 126 | var dd = { 127 | content: [ 128 | { 129 | svg: '', 130 | } 131 | ] 132 | }; 133 | 134 | var pages = testHelper.renderPages('A6', dd); 135 | 136 | var svgNode = pages[0].items[0].item; 137 | 138 | assert.equal(Number(svgNode._width).toFixed(2), 600.10); 139 | assert.equal(Number(svgNode._height).toFixed(2), 300.20); 140 | }); 141 | 142 | it('writes width and height from definition to svg', function () { 143 | var dd = { 144 | content: [ 145 | { 146 | svg: '', 147 | width: 400, 148 | height: 800 149 | } 150 | ] 151 | }; 152 | 153 | var pages = testHelper.renderPages('A6', dd); 154 | 155 | var svgNode = pages[0].items[0].item; 156 | var svgDimensions = svgMeasure.measureSVG(svgNode.svg); 157 | 158 | assert.equal(svgDimensions.width, 400); 159 | assert.equal(svgDimensions.height, 800); 160 | }); 161 | 162 | it('writes width and height from definition to svg (decimals)', function () { 163 | var dd = { 164 | content: [ 165 | { 166 | svg: '', 167 | width: 400.15, 168 | height: 800.35 169 | } 170 | ] 171 | }; 172 | 173 | var pages = testHelper.renderPages('A6', dd); 174 | 175 | var svgNode = pages[0].items[0].item; 176 | var svgDimensions = svgMeasure.measureSVG(svgNode.svg); 177 | 178 | assert.equal(svgDimensions.width, 400.15); 179 | assert.equal(svgDimensions.height, 800.35); 180 | }); 181 | 182 | it('writes svg in header', function () { 183 | var dd = { 184 | content: [], 185 | header: function () { 186 | return { 187 | svg: '', 188 | }; 189 | } 190 | }; 191 | 192 | var pages = testHelper.renderPages('A6', dd); 193 | assert.equal(pages[0].items[0].type, 'svg'); 194 | }); 195 | 196 | it('writes svg in table', function () { 197 | var dd = { 198 | content: [ 199 | { 200 | table: { 201 | body: [ 202 | [{ svg: '' }] 203 | ] 204 | } 205 | } 206 | ], 207 | }; 208 | 209 | var pages = testHelper.renderPages('A6', dd); 210 | 211 | var types = pages[0].items.map(item => item.type); 212 | assert.ok(types.includes('svg')); 213 | }); 214 | 215 | }); 216 | 217 | }); 218 | -------------------------------------------------------------------------------- /tests/unit/ElementWriter.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var ElementWriter = require('../../js/ElementWriter').default; 6 | 7 | describe('ElementWriter', function () { 8 | var ew, ctx, page, fakePosition; 9 | 10 | beforeEach(function () { 11 | fakePosition = { fake: 'position' }; 12 | page = { items: [] }; 13 | ctx = { 14 | x: 10, 15 | y: 20, 16 | availableWidth: 100, 17 | availableHeight: 100, 18 | getCurrentPage: function () { 19 | return page; 20 | }, 21 | getCurrentPosition: function () { 22 | return fakePosition; 23 | }, 24 | moveDown: function (offset) { 25 | ctx.y += offset; 26 | ctx.availableHeight -= offset; 27 | } 28 | }; 29 | ew = new ElementWriter(ctx); 30 | 31 | }); 32 | 33 | function buildLine(height, alignment, x, y) { 34 | return { 35 | getHeight: function () { 36 | return height; 37 | }, 38 | getWidth: function () { 39 | return 60; 40 | }, 41 | clone: function () { 42 | let result = {}; 43 | 44 | for (let key in this) { 45 | if (this.hasOwnProperty(key)) { 46 | result[key] = this[key]; 47 | } 48 | } 49 | 50 | return result; 51 | }, 52 | inlines: [ 53 | { 54 | alignment: alignment, 55 | x: 0, 56 | }, 57 | { 58 | x: 30, 59 | }, 60 | { 61 | x: 50 62 | } 63 | ], 64 | x: x, 65 | y: y 66 | }; 67 | } 68 | 69 | describe('addLine', function () { 70 | it('should add lines to the current page if there\'s enough space', function () { 71 | var line = buildLine(20); 72 | 73 | var position = ew.addLine(line); 74 | 75 | assert.equal(page.items.length, 1); 76 | assert.equal(position, fakePosition); 77 | }); 78 | 79 | it('should return position on page', function () { 80 | var line = buildLine(20); 81 | 82 | ew.pushContext(50, 50); 83 | ew.pushContext(20, 30); 84 | ew.pushContext(11, 40); 85 | var position = ew.addLine(line); 86 | 87 | assert.equal(position, fakePosition); 88 | }); 89 | 90 | it('should not add line and return false if there\'s not enough space', function () { 91 | var line = buildLine(120); 92 | 93 | assert(!ew.addLine(line)); 94 | assert.equal(page.items.length, 0); 95 | }); 96 | 97 | it('should set line.x and line.y to current context\'s values', function () { 98 | var line = buildLine(30); 99 | 100 | ew.addLine(line); 101 | assert.equal(line.x, 10); 102 | assert.equal(line.y, 20); 103 | }); 104 | 105 | it('should update context.y and context.availableHeight', function () { 106 | ew.addLine(buildLine(30)); 107 | assert.equal(ctx.y, 20 + 30); 108 | assert.equal(ctx.availableHeight, 100 - 30); 109 | }); 110 | 111 | describe('should support line alignment', function () { 112 | it('right', function () { 113 | var line = buildLine(30, 'right'); 114 | ew.addLine(line); 115 | assert.equal(line.x, 10 + 100 - line.getWidth()); 116 | }); 117 | 118 | it('center', function () { 119 | var line = buildLine(30, 'center'); 120 | ew.addLine(line); 121 | assert.equal(line.x, 10 + (100 - line.getWidth()) / 2); 122 | }); 123 | 124 | it('justify', function () { 125 | var line = buildLine(30, 'justify'); 126 | ew.addLine(line); 127 | assert.equal(line.x, 10); 128 | 129 | var additionalSpacing = (100 - 60) / 2; 130 | 131 | assert.equal(line.inlines[1].x, 30 + additionalSpacing); 132 | assert.equal(line.inlines[2].x, 50 + 2 * additionalSpacing); 133 | }); 134 | }); 135 | }); 136 | 137 | describe('addVector', function () { 138 | it('should add vectors to the current page', function () { 139 | ew.addVector({ type: 'rect', x: 10, y: 10 }); 140 | assert.equal(page.items.length, 1); 141 | }); 142 | 143 | it('should offset vectors to the current position', function () { 144 | var rect = { type: 'rect', x: 10, y: 10 }; 145 | var ellipse = { type: 'ellipse', x: 10, y: 10 }; 146 | var line = { type: 'line', x1: 10, x2: 50, y1: 10, y2: 20 }; 147 | var polyline = { type: 'polyline', points: [{ x: 0, y: 0 }, { x: 20, y: 20 }] }; 148 | 149 | ew.addVector(rect); 150 | ew.addVector(ellipse); 151 | ew.addVector(line); 152 | ew.addVector(polyline); 153 | 154 | assert.equal(rect.x, 20); 155 | assert.equal(rect.y, 30); 156 | 157 | assert.equal(ellipse.x, 20); 158 | assert.equal(ellipse.y, 30); 159 | 160 | assert.equal(line.x1, 20); 161 | assert.equal(line.x2, 60); 162 | assert.equal(line.y1, 30); 163 | assert.equal(line.y2, 40); 164 | 165 | assert.equal(polyline.points[0].x, 10); 166 | assert.equal(polyline.points[0].y, 20); 167 | assert.equal(polyline.points[1].x, 30); 168 | assert.equal(polyline.points[1].y, 40); 169 | }); 170 | }); 171 | 172 | describe('addFragment', function () { 173 | var fragment; 174 | 175 | beforeEach(function () { 176 | fragment = { 177 | items: [ 178 | { 179 | type: 'line', 180 | item: buildLine(30, 'left', 10, 10) 181 | }, 182 | { 183 | type: 'line', 184 | item: buildLine(30, 'left', 10, 50) 185 | }, 186 | { 187 | type: 'vector', 188 | item: { type: 'rect', x: 10, y: 20 } 189 | }, 190 | { 191 | type: 'vector', 192 | item: { type: 'rect', x: 40, y: 60 } 193 | } 194 | ] 195 | }; 196 | }); 197 | 198 | it('should add all fragment vectors and lines', function () { 199 | ew.addFragment(fragment); 200 | 201 | assert.equal(page.items.length, 4); 202 | }); 203 | 204 | it('should return false if fragment height is larger than available space', function () { 205 | fragment.height = 120; 206 | 207 | assert(!ew.addFragment(fragment)); 208 | }); 209 | 210 | it('should update current position', function () { 211 | fragment.height = 50; 212 | ew.addFragment(fragment); 213 | 214 | assert.equal(ctx.y, 20 + 50); 215 | }); 216 | 217 | it('should offset lines and vectors', function () { 218 | ew.addFragment(fragment); 219 | 220 | assert.equal(page.items[0].item.x, 20); 221 | assert.equal(page.items[0].item.y, 30); 222 | assert.equal(page.items[1].item.x, 20); 223 | assert.equal(page.items[1].item.y, 70); 224 | 225 | assert.equal(page.items[2].item.x, 20); 226 | assert.equal(page.items[2].item.y, 40); 227 | assert.equal(page.items[3].item.x, 50); 228 | assert.equal(page.items[3].item.y, 80); 229 | }); 230 | 231 | it('should not modify original line/vector positions', function () { 232 | ew.addFragment(fragment); 233 | 234 | assert.equal(fragment.items[0].item.x, 10); 235 | assert.equal(fragment.items[0].item.y, 10); 236 | 237 | assert.equal(fragment.items[3].item.x, 40); 238 | assert.equal(fragment.items[3].item.y, 60); 239 | }); 240 | }); 241 | }); 242 | -------------------------------------------------------------------------------- /tests/unit/Node-interface.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var pdfmake = require('../../js/index'); 4 | 5 | pdfmake.addFonts({ 6 | Roboto: { 7 | normal: 'tests/fonts/Roboto-Regular.ttf', 8 | bold: 'tests/fonts/Roboto-Medium.ttf', 9 | italics: 'tests/fonts/Roboto-Italic.ttf', 10 | bolditalics: 'tests/fonts/Roboto-MediumItalic.ttf' 11 | } 12 | }); 13 | 14 | describe('Node interface', function () { 15 | describe('getBuffer', function () { 16 | it('should return buffer', function () { 17 | 18 | var docDefinition = { 19 | content: [ 20 | 'First paragraph', 21 | 'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines' 22 | ] 23 | }; 24 | 25 | var pdf = pdfmake.createPdf(docDefinition); 26 | pdf.getBuffer().then(() => { 27 | // 28 | }, err => { 29 | throw err; 30 | }); 31 | 32 | }); 33 | 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/unit/PDFDocument.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var PDFDocument = require('../../js/PDFDocument').default; 6 | 7 | describe('PDFDocument', function () { 8 | var pdfDocument; 9 | 10 | beforeEach(function () { 11 | var fontDefinitions = { 12 | Roboto: { 13 | normal: 'tests/fonts/Roboto-Regular.ttf', 14 | bold: 'tests/fonts/Roboto-Medium.ttf', 15 | italics: 'tests/fonts/Roboto-Italic.ttf', 16 | bolditalics: 'tests/fonts/Roboto-MediumItalic.ttf' 17 | } 18 | }; 19 | pdfDocument = new PDFDocument(fontDefinitions); 20 | }); 21 | 22 | describe('provideFont', function () { 23 | 24 | it('throws error when given font not present', function () { 25 | assert.throws(function () { 26 | pdfDocument.provideFont('Arial', true, false); 27 | }, function (error) { 28 | assert.equal(error.message, `Font 'Arial' in style 'bold' is not defined in the font section of the document definition.`); 29 | return true; 30 | }); 31 | }); 32 | 33 | it('should provide normal Roboto font', function () { 34 | var result = pdfDocument.provideFont('Roboto', false, false); 35 | assert.equal(result.font.postscriptName, 'Roboto-Regular'); 36 | }); 37 | 38 | it('should provide bold Roboto font', function () { 39 | var result = pdfDocument.provideFont('Roboto', true, false); 40 | assert.equal(result.font.postscriptName, 'Roboto-Medium'); 41 | }); 42 | 43 | it('should provide italics Roboto font', function () { 44 | var result = pdfDocument.provideFont('Roboto', false, true); 45 | assert.equal(result.font.postscriptName, 'Roboto-Italic'); 46 | }); 47 | 48 | it('should provide bold and italics Roboto font', function () { 49 | var result = pdfDocument.provideFont('Roboto', true, true); 50 | assert.equal(result.font.postscriptName, 'Roboto-MediumItalic'); 51 | }); 52 | 53 | }); 54 | 55 | describe('provideImage', function () { 56 | 57 | // TODO 58 | 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tests/unit/SVGMeasure.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var SVGMeasure = require('../../js/SVGMeasure').default; 5 | 6 | // NOTE: more tests for SVGMeasure in integration/svgs.js 7 | 8 | var inputBasic = 9 | '\n' + 10 | '\n' + 11 | '\n' + 12 | ' \n' + 13 | '\n'; 14 | 15 | var inputWithNewline = 16 | '\n' + 17 | '\n' + 18 | '\n' + 20 | ' \n' + 21 | '\n'; 22 | 23 | var inputWithComment1 = 24 | '\n' + 25 | '\n' + 26 | '\n' + 27 | '\n' + 28 | ' \n' + 29 | '\n'; 30 | 31 | var inputWithComment2 = 32 | '\n' + 33 | '\n' + 34 | '\n' + // [ evil laughter intensifies ] 35 | '\n' + 36 | ' \n' + 37 | '\n'; 38 | 39 | 40 | describe('SVGMeasure', function () { 41 | 42 | var svgMeasure = new SVGMeasure(); 43 | 44 | describe('measureSVG()', function () { 45 | 46 | it('returns correct dimensions for pts', function () { 47 | var dimensions = svgMeasure.measureSVG(inputBasic); 48 | 49 | assert.equal(typeof dimensions, 'object'); 50 | assert.equal(typeof dimensions.width, 'number'); 51 | assert.equal(typeof dimensions.height, 'number'); 52 | 53 | assert.equal(dimensions.width, 105); 54 | assert.equal(dimensions.height, 222); 55 | }); 56 | 57 | it('correctly handles multi-line svg tags', function () { 58 | var dimensions = svgMeasure.measureSVG(inputWithNewline); 59 | 60 | assert.equal(typeof dimensions, 'object'); 61 | assert.equal(typeof dimensions.width, 'number'); 62 | assert.equal(typeof dimensions.height, 'number'); 63 | 64 | assert.equal(dimensions.width, 105); 65 | assert.equal(dimensions.height, 222); 66 | }); 67 | 68 | it('ignores "svg tags" in comments (1)', function () { 69 | var dimensions = svgMeasure.measureSVG(inputWithComment1); 70 | 71 | assert.equal(typeof dimensions, 'object'); 72 | assert.equal(typeof dimensions.width, 'number'); 73 | assert.equal(typeof dimensions.height, 'number'); 74 | 75 | assert.equal(dimensions.width, 105); 76 | assert.equal(dimensions.height, 222); 77 | }); 78 | 79 | it('ignores "svg tags" in comments (2)', function () { 80 | var dimensions = svgMeasure.measureSVG(inputWithComment2); 81 | 82 | assert.equal(typeof dimensions, 'object'); 83 | assert.equal(typeof dimensions.width, 'number'); 84 | assert.equal(typeof dimensions.height, 'number'); 85 | 86 | assert.equal(dimensions.width, 105); 87 | assert.equal(dimensions.height, 222); 88 | }); 89 | }); 90 | 91 | describe('writeDimensions()', function () { 92 | 93 | var replacementDimensions = { 94 | width: 1984, 95 | height: 2001 96 | }; 97 | 98 | it('updates dimensions', function () { 99 | var updatedSVGString = svgMeasure.writeDimensions(inputBasic, replacementDimensions); 100 | var updatedDimensions = svgMeasure.measureSVG(updatedSVGString); 101 | 102 | assert.equal(updatedDimensions.width, 1984); 103 | assert.equal(updatedDimensions.height, 2001); 104 | }); 105 | 106 | it('correctly ignores comments', function () { 107 | var updatedSVGString = svgMeasure.writeDimensions(inputWithComment2, replacementDimensions); 108 | var updatedDimensions = svgMeasure.measureSVG(updatedSVGString); 109 | 110 | assert.equal(updatedDimensions.width, 1984); 111 | assert.equal(updatedDimensions.height, 2001); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /tests/unit/TextDecorator.spec.js: -------------------------------------------------------------------------------- 1 | //var assert = require('assert'); 2 | 3 | describe('TextDecorator', function () { 4 | 5 | // TODO 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /tests/unit/helpers/node.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | const { stringifyNode } = require('../../../js/helpers/node'); 4 | 5 | describe('helpers/node', function () { 6 | 7 | describe('stringifyNode', function () { 8 | it('should be correctly stringify node', function () { 9 | var node = { text: 'Text', font: 'XXX', fontSize: 12 }; 10 | 11 | var result = stringifyNode(node); 12 | assert.equal(result, '{"text":"Text","font":"font","fontSize":12}'); 13 | }); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /tests/unit/helpers/tools.spec.js: -------------------------------------------------------------------------------- 1 | //const assert = require('assert'); 2 | 3 | describe('helpers/tools', function () { 4 | 5 | // TODO 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /tests/unit/helpers/variableType.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const util = require('util'); 3 | 4 | const { isString, isNumber, isObject, isEmptyObject, isValue, isPositiveInteger } = require('../../../js/helpers/variableType'); 5 | 6 | const variableCheckMap = [ 7 | { 8 | value: 'Abc123', 9 | type: [isString, isValue] 10 | }, 11 | { 12 | value: '12', 13 | type: [isString, isValue] 14 | }, 15 | { 16 | value: '12.34', 17 | type: [isString, isValue] 18 | }, 19 | { 20 | value: '0', 21 | type: [isString, isValue] 22 | }, 23 | { 24 | value: '', 25 | type: [isString, isValue] 26 | }, 27 | { 28 | value: new String('Abcdef'), 29 | type: [isString, isValue] 30 | }, 31 | { 32 | value: 56, 33 | type: [isNumber, isValue, isPositiveInteger] 34 | }, 35 | { 36 | value: 56.78, 37 | type: [isNumber, isValue] 38 | }, 39 | { 40 | value: 0, 41 | type: [isNumber, isValue] 42 | }, 43 | { 44 | value: new Number(78), 45 | type: [isNumber, isValue] 46 | }, 47 | { 48 | value: true, 49 | type: [isValue] 50 | }, 51 | { 52 | value: false, 53 | type: [isValue] 54 | }, 55 | { 56 | value: 'true', 57 | type: [isString, isValue] 58 | }, 59 | { 60 | value: 'false', 61 | type: [isString, isValue] 62 | }, 63 | { 64 | value: [], 65 | type: [isValue] 66 | }, 67 | { 68 | value: '[]', 69 | type: [isString, isValue] 70 | }, 71 | { 72 | value: [1, 2, 3], 73 | type: [isValue] 74 | }, 75 | { 76 | value: () => { 77 | }, 78 | type: [isValue] 79 | }, 80 | { 81 | value: { dummyObject: 0 }, 82 | type: [isObject, isValue] 83 | }, 84 | { 85 | value: {}, 86 | type: [isObject, isEmptyObject, isValue] 87 | }, 88 | { 89 | value: '{}', 90 | type: [isString, isValue] 91 | }, 92 | { 93 | value: 'null', 94 | type: [isString, isValue] 95 | }, 96 | { 97 | value: 'undefined', 98 | type: [isString, isValue] 99 | }, 100 | ]; 101 | 102 | var checkVariables = (fnc) => { 103 | variableCheckMap.forEach(value => { 104 | var expectedVal = value.type.indexOf(fnc) !== -1; 105 | var val = fnc(value.value); 106 | assert.equal(val, expectedVal, 'Exception for value ' + util.inspect(value.value, { showHidden: false, depth: null })); 107 | }); 108 | }; 109 | 110 | describe('helpers/variableType', function () { 111 | 112 | it('should be correctly specify string type', function () { 113 | checkVariables(isString); 114 | }); 115 | 116 | it('should be correctly specify number type', function () { 117 | checkVariables(isNumber); 118 | }); 119 | 120 | it('should be correctly specify object', function () { 121 | checkVariables(isObject); 122 | }); 123 | 124 | it('should be correctly specify variable with value', function () { 125 | checkVariables(isValue); 126 | }); 127 | 128 | it('should be correctly specify positive integer type', function () { 129 | checkVariables(isPositiveInteger); 130 | }); 131 | 132 | }); 133 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var TerserPlugin = require('terser-webpack-plugin'); 4 | var StringReplacePlugin = require("string-replace-webpack-plugin"); 5 | var webpack = require('webpack'); 6 | var pkg = require('./package.json'); 7 | 8 | var banner = '/*! ' + pkg.name + ' v' + pkg.version + ', @license ' + pkg.license + ', @link ' + pkg.homepage + ' */'; 9 | 10 | var supportedBrowsers = { 11 | "chrome": "109", 12 | "edge": "109", 13 | "firefox": "102", 14 | "safari": "14" 15 | }; 16 | 17 | module.exports = { 18 | mode: 'production', 19 | entry: { 20 | 'pdfmake': './src/browser-extensions/pdfMake.js', 21 | 'pdfmake.min': './src/browser-extensions/pdfMake.js' 22 | }, 23 | output: { 24 | path: path.join(__dirname, './build'), 25 | filename: '[name].js', 26 | libraryTarget: 'umd', 27 | // Workaround https://github.com/webpack/webpack/issues/6642 until https://github.com/webpack/webpack/issues/6525 lands. 28 | globalObject: `typeof self !== 'undefined' ? self : this` 29 | }, 30 | resolve: { 31 | alias: { 32 | fs: path.join(__dirname, './src/browser-extensions/virtual-fs-cjs.js') 33 | }, 34 | fallback: { 35 | crypto: false, 36 | buffer: require.resolve('buffer/'), 37 | util: require.resolve('util/'), 38 | stream: require.resolve('stream-browserify'), 39 | zlib: require.resolve('browserify-zlib'), 40 | assert: require.resolve('assert/') 41 | } 42 | }, 43 | module: { 44 | rules: [ 45 | { 46 | enforce: 'pre', 47 | test: /\.js$/, 48 | exclude: /node_modules/, 49 | use: { 50 | loader: 'babel-loader', 51 | options: { 52 | presets: [ 53 | [ 54 | "@babel/preset-env", 55 | { 56 | targets: supportedBrowsers, 57 | modules: false, 58 | useBuiltIns: 'usage', 59 | // TODO: after fix in babel remove corejs version and remove core-js dependency in package.json 60 | corejs: "3.0.0", 61 | loose: true 62 | } 63 | ] 64 | ] 65 | } 66 | } 67 | }, 68 | // for fs don't use babel _interopDefault command 69 | { 70 | enforce: 'pre', 71 | test: /pdfkit[/\\]js[/\\]/, 72 | use: { 73 | loader: StringReplacePlugin.replace({ 74 | replacements: [ 75 | { 76 | pattern: "import fs from 'fs';", 77 | replacement: function () { 78 | return "var fs = require('fs');"; 79 | } 80 | } 81 | ] 82 | }) 83 | } 84 | }, 85 | // transpile to inline only required file 86 | { 87 | enforce: 'pre', 88 | test: /pdfkit[/\\]js[/\\]/, 89 | use: { 90 | loader: StringReplacePlugin.replace({ 91 | replacements: [ 92 | { 93 | pattern: "fs.readFileSync(`${__dirname}/data/sRGB_IEC61966_2_1.icc`)", 94 | replacement: function () { 95 | const data = fs.readFileSync('node_modules/pdfkit/js/data/sRGB_IEC61966_2_1.icc'); 96 | return `Buffer("` + data.toString('base64') + `","base64");`; 97 | } 98 | } 99 | ] 100 | }) 101 | } 102 | }, 103 | { 104 | enforce: "pre", 105 | test: /\.(cjs|js)$/, 106 | use: ["source-map-loader"], 107 | }, 108 | { 109 | test: /\.js$/, 110 | include: /(pdfkit|linebreak|fontkit|saslprep|restructure|unicode-trie|unicode-properties|dfa|buffer|png-js|crypto-js)/, 111 | use: { 112 | loader: 'babel-loader', 113 | options: { 114 | presets: [ 115 | [ 116 | "@babel/preset-env", 117 | { 118 | targets: supportedBrowsers, 119 | modules: false, 120 | useBuiltIns: 'usage', 121 | // TODO: after fix in babel remove corejs version and remove core-js dependency in package.json 122 | corejs: "3.0.0", 123 | loose: true 124 | } 125 | ] 126 | ], 127 | plugins: ["@babel/plugin-transform-modules-commonjs"] 128 | } 129 | } 130 | }, 131 | { 132 | test: /pdfMake.js$/, 133 | include: [path.join(__dirname, './src/browser-extensions')], 134 | use: { 135 | loader: 'expose-loader', 136 | options: { 137 | exposes: 'pdfMake', 138 | }, 139 | } 140 | }, 141 | /* temporary bugfix for FileSaver: added hack for mobile device support, see https://github.com/bpampuch/pdfmake/issues/1664 */ 142 | /* waiting to merge and release PR https://github.com/eligrey/FileSaver.js/pull/533 */ 143 | { 144 | test: /FileSaver.min.js$/, 145 | use: { 146 | loader: StringReplacePlugin.replace({ 147 | replacements: [ 148 | { 149 | pattern: '"download"in HTMLAnchorElement.prototype', 150 | replacement: function () { 151 | return '(typeof HTMLAnchorElement !== "undefined" && "download" in HTMLAnchorElement.prototype)'; 152 | } 153 | } 154 | ] 155 | }) 156 | } 157 | }, 158 | ] 159 | }, 160 | optimization: { 161 | minimizer: [ 162 | new TerserPlugin({ 163 | include: /\.min\.js$/, 164 | extractComments: false, 165 | terserOptions: { 166 | format: { 167 | preamble: banner, 168 | comments: false, 169 | }, 170 | compress: { 171 | drop_console: true 172 | }, 173 | keep_classnames: true, 174 | keep_fnames: true 175 | } 176 | }) 177 | ] 178 | }, 179 | plugins: [ 180 | new webpack.ProvidePlugin({ 181 | process: 'process/browser', // require "process" library, fix "process is not defined" error, source: https://stackoverflow.com/a/64553486 182 | Buffer: ['buffer', 'Buffer'] // require "buffer" library, fix "Buffer is not defined" error, source: https://github.com/webpack/changelog-v5/issues/10#issuecomment-615877593 183 | }), 184 | new StringReplacePlugin(), 185 | new webpack.BannerPlugin({ 186 | banner: banner, 187 | raw: true 188 | }) 189 | ], 190 | devtool: 'source-map' 191 | }; 192 | --------------------------------------------------------------------------------