diff --git a/.babelrc.esm.json b/.babelrc.esm.json new file mode 100644 index 000000000..0f88b9fa3 --- /dev/null +++ b/.babelrc.esm.json @@ -0,0 +1,4 @@ +{ + "extends": "./.babelrc.json", + "plugins": ["@babel/plugin-transform-runtime"] +} diff --git a/.babelrc.json b/.babelrc.json new file mode 100644 index 000000000..ba3d837a8 --- /dev/null +++ b/.babelrc.json @@ -0,0 +1,4 @@ +{ + "presets": [["@babel/env"]], + "exclude": ["node_modules/**"] +} diff --git a/.eslintignore b/.eslintignore index 96212a359..7c37bcae0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,4 @@ **/*{.,-}min.js +examples/PDF.js/**/* +examples/css/**/* +examples/html2pdf/examples.css diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9faa37508..000000000 --- a/.eslintrc +++ /dev/null @@ -1,213 +0,0 @@ -ecmaFeatures: - modules: true - jsx: true - -env: - amd: true - browser: true - es6: true - jquery: true - node: true - -# http://eslint.org/docs/rules/ -rules: - # Possible Errors - comma-dangle: [2, never] - no-cond-assign: 2 - no-console: 0 - no-constant-condition: 2 - no-control-regex: 2 - no-debugger: 2 - no-dupe-args: 2 - no-dupe-keys: 2 - no-duplicate-case: 2 - no-empty: 2 - no-empty-character-class: 2 - no-ex-assign: 2 - no-extra-boolean-cast: 2 - no-extra-parens: 0 - no-extra-semi: 2 - no-func-assign: 2 - no-inner-declarations: [2, functions] - no-invalid-regexp: 2 - no-irregular-whitespace: 2 - no-negated-in-lhs: 2 - no-obj-calls: 2 - no-regex-spaces: 2 - no-sparse-arrays: 2 - no-unexpected-multiline: 2 - no-unreachable: 2 - use-isnan: 2 - valid-jsdoc: 0 - valid-typeof: 2 - - # Best Practices - accessor-pairs: 2 - block-scoped-var: 0 - complexity: [2, 6] - consistent-return: 0 - curly: 0 - default-case: 0 - dot-location: 0 - dot-notation: 0 - eqeqeq: 2 - guard-for-in: 2 - no-alert: 2 - no-caller: 2 - no-case-declarations: 2 - no-div-regex: 2 - no-else-return: 0 - no-empty-label: 2 - no-empty-pattern: 2 - no-eq-null: 2 - no-eval: 2 - no-extend-native: 2 - no-extra-bind: 2 - no-fallthrough: 2 - no-floating-decimal: 0 - no-implicit-coercion: 0 - no-implied-eval: 2 - no-invalid-this: 0 - no-iterator: 2 - no-labels: 0 - no-lone-blocks: 2 - no-loop-func: 2 - no-magic-number: 0 - no-multi-spaces: 0 - no-multi-str: 0 - no-native-reassign: 2 - no-new-func: 2 - no-new-wrappers: 2 - no-new: 2 - no-octal-escape: 2 - no-octal: 2 - no-proto: 2 - no-redeclare: 2 - no-return-assign: 2 - no-script-url: 2 - no-self-compare: 2 - no-sequences: 0 - no-throw-literal: 0 - no-unused-expressions: 2 - no-useless-call: 2 - no-useless-concat: 2 - no-void: 2 - no-warning-comments: 0 - no-with: 2 - radix: 2 - vars-on-top: 0 - wrap-iife: 2 - yoda: 0 - - # Strict - strict: 0 - - # Variables - init-declarations: 0 - no-catch-shadow: 2 - no-delete-var: 2 - no-label-var: 2 - no-shadow-restricted-names: 2 - no-shadow: 0 - no-undef-init: 2 - no-undef: 0 - no-undefined: 0 - no-unused-vars: 0 - no-use-before-define: 0 - - # Node.js and CommonJS - callback-return: 2 - global-require: 2 - handle-callback-err: 2 - no-mixed-requires: 0 - no-new-require: 0 - no-path-concat: 2 - no-process-exit: 2 - no-restricted-modules: 0 - no-sync: 0 - - # Stylistic Issues - array-bracket-spacing: 0 - block-spacing: 0 - brace-style: 0 - camelcase: 0 - comma-spacing: 0 - comma-style: 0 - computed-property-spacing: 0 - consistent-this: 0 - eol-last: 0 - func-names: 0 - func-style: 0 - id-length: 0 - id-match: 0 - indent: 0 - jsx-quotes: 0 - key-spacing: 0 - linebreak-style: 0 - lines-around-comment: 0 - max-depth: 0 - max-len: 0 - max-nested-callbacks: 0 - max-params: 0 - max-statements: [2, 30] - new-cap: 0 - new-parens: 0 - newline-after-var: 0 - no-array-constructor: 0 - no-bitwise: 0 - no-continue: 0 - no-inline-comments: 0 - no-lonely-if: 0 - no-mixed-spaces-and-tabs: 0 - no-multiple-empty-lines: 0 - no-negated-condition: 0 - no-nested-ternary: 0 - no-new-object: 0 - no-plusplus: 0 - no-restricted-syntax: 0 - no-spaced-func: 0 - no-ternary: 0 - no-trailing-spaces: 0 - no-underscore-dangle: 0 - no-unneeded-ternary: 0 - object-curly-spacing: 0 - one-var: 0 - operator-assignment: 0 - operator-linebreak: 0 - padded-blocks: 0 - quote-props: 0 - quotes: 0 - require-jsdoc: 0 - semi-spacing: 0 - semi: 0 - sort-vars: 0 - space-after-keywords: 0 - space-before-blocks: 0 - space-before-function-paren: 0 - space-before-keywords: 0 - space-in-parens: 0 - space-infix-ops: 0 - space-return-throw-case: 0 - space-unary-ops: 0 - spaced-comment: 0 - wrap-regex: 0 - - # ECMAScript 6 - arrow-body-style: 0 - arrow-parens: 0 - arrow-spacing: 0 - constructor-super: 0 - generator-star-spacing: 0 - no-arrow-condition: 0 - no-class-assign: 0 - no-const-assign: 0 - no-dupe-class-members: 0 - no-this-before-super: 0 - no-var: 0 - object-shorthand: 0 - prefer-arrow-callback: 0 - prefer-const: 0 - prefer-reflect: 0 - prefer-spread: 0 - prefer-template: 0 - require-yield: 0 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..4f2a8076a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,23 @@ +module.exports = { + parser: "@typescript-eslint/parser", + extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + env: { + browser: true, + commonjs: true, + es6: true, + node: true, + jasmine: true + }, + plugins: ["@typescript-eslint", "jasmine"], + globals: { + Atomics: "readonly", + SharedArrayBuffer: "readonly" + }, + parserOptions: { + ecmaVersion: 2018, + sourceType: "module" + }, + rules: { + "@typescript-eslint/no-var-requires": "off" + } +}; diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..9b9d73e98 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +Thanks for contributing to jsPDF! Please follow our +[Contribution Guidelines](https://github.com/MrRio/jsPDF/blob/master/CONTRIBUTING.md#pull-requests) +when creating a pull request. diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 000000000..1e5d49ade --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,86 @@ +name: Continuous Integration + +on: [push, pull_request, workflow_dispatch] + +jobs: + test-browser: + name: Browser tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 20 + cache: npm + - name: npm install + run: npm ci + - name: Build + run: npm run build + - name: Run tests + run: npm run test-ci + + test-node: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - 20 # current LTS + - 22 # latest + name: Node.js ${{ matrix.node-version }} test + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Install Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + cache: npm + - name: npm install + run: npm ci + - name: Build + run: npm run build + - name: Run tests + run: npm run test-node + + test-typings: + runs-on: ubuntu-latest + strategy: + matrix: + ts-version: + - { ts: '4.0', types-node: '17' } + - { ts: 'latest', types-node: '20' } + name: Typings tests (typescript@${{ matrix.ts-version.ts }}) + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 20 + cache: npm + - name: npm install + run: npm ci + - name: Install typescript@${{ matrix.ts-version.ts }} + run: npm install typescript@${{ matrix.ts-version.ts }} + - name: Install @types/node@${{ matrix.ts-version.types-node }} + run: npm install @types/node@${{ matrix.ts-version.types-node }} + - name: Run tests + run: npm run test-typings + + lint: + runs-on: ubuntu-latest + name: Lint + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 20 + cache: npm + - name: npm install + run: npm ci + - name: Check Prettier + run: npm run lint diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 000000000..8685d35e1 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,22 @@ +# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created +# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages + +name: Node.js Package + +on: + release: + types: [published] + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v6 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..87e1d5fb2 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,21 @@ +name: Mark and close stale issues + +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. It will be closed soon. Please comment/reopen if this issue is still relevant.' + stale-issue-label: 'no-issue-activity' + exempt-issue-labels: 'Bug,Feature Request,persistent' + remove-stale-when-updated: true + days-before-stale: 90 + days-before-close: 7 diff --git a/.gitignore b/.gitignore index e86268469..aa6c5ffec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode .idea .DS_Store node_modules/ diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..8631719eb --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,6 @@ +tasks: + - init: npm install && npm run build + command: npm start +ports: + - port: 8000 + onOpen: open-preview diff --git a/.lgtm.yml b/.lgtm.yml new file mode 100644 index 000000000..b92ae30a6 --- /dev/null +++ b/.lgtm.yml @@ -0,0 +1,4 @@ +path_classifiers: + test: + - types + - spec diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..7c37bcae0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +**/*{.,-}min.js +examples/PDF.js/**/* +examples/css/**/* +examples/html2pdf/examples.css diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/.sauce.yml b/.sauce.yml deleted file mode 100644 index 2bc8b7564..000000000 --- a/.sauce.yml +++ /dev/null @@ -1,4 +0,0 @@ -addons: - sauce_connect: - username: jspdf - access_key: 522494eb-e777-4583-87c5-616a74cbf64d diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a7cbd83a7..000000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: node_js -node_js: - - "4" -install: - - npm -g install yarn - - yarn -cache: - directories: - - ~/.yarn - - ~/.nvm diff --git a/CNAME b/CNAME deleted file mode 100644 index a580b8bbb..000000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -jspdf.com diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..d4937f520 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at james@parall.ax. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..8f3cc92c3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,83 @@ +# Contribution Guidelines + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +## Code of Conduct + +This project and everyone participating in it is governed by the +[Code of Conduct](https://github.com/MrRio/jsPDF/blob/master/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable behavior to +[james@parall.ax](mailto:james@parall.ax). + +## How Can I Contribute? + +jsPDF is an open source project and requires help from the community. Any contributions from the community are very +much welcome! Be it "only" reporting bugs or actually contributing code to the repository. + +### Reporting Bugs + +jsPDF is by no means perfect. If you find something is not working as expected we are glad to receive an +issue report from you. Please make sure to +[search for similar issues](https://github.com/search?q=is%3Aissue+repo%3AMrRio%2FjsPDF&type=issues) first. + +If you did not find any related issues, please report a [new issue](https://github.com/MrRio/jsPDF/issues). +In order to be able to react efficiently we ask you to provide us with the following information: + +- The **version** of jsPDF you are using +- A small code example that reproduces the issue ([mcve](https://stackoverflow.com/help/mcve)) + +Please also abide to these rules: + +- Formulate the issue in English. +- Provide a **clear and descriptive** title +- Stick to the issue template +- Make sure code is properly indented and + [formatted](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code) (Use ``` around + code blocks). Attach large code snippets as file. + +Note that this is an open source project and don't expect us to fix every issue immediately. Please also consider +fixing the issue yourself and preparing a pull request. Sometimes a pointer to a specific line of code already helps +a lot. We will happily discuss and merge contributions from the community! + +### Pull requests + +We are very happy about pull requests from the community and will discuss and merge them in a timely manner. When +preparing a pull request please follow these guidelines: + +- You can find instructions for building and testing jsPDF [below](#building-and-testing-jspdf) +- Make sure to cover new features or bugs with test cases. Test cases should be as small as possible. +- Add new or update types in `types/index.d.ts`. +- Make sure to follow the PDF specification. JsPDF currently implements part of the PDF 1.3 specification. +- Make sure all tests are green before committing (`npm run test-local`) +- You may now (and should!) use modern JavaScript everywhere. The build step will transpile it. Most of the sources + are still in ES5, but all new code should be written in ES6+. +- When using newer EcmaScript or Browser APIs make sure the required polyfills are listed in + `src/polyfills.js`. +- Run `npm run prettier` before committing. +- Don't update the files in `dist` in regular pull requests. These are usually only updated when creating a new release. +- For the commit message, follow these guidelines: + - Use the present tense ("Add feature" not "Added feature") + - Use the imperative mood ("Move cursor to..." not "Moves cursor to...") + - Limit the first line to 72 characters or fewer + - Reference issues and pull requests liberally after the first line +- Make sure the CI tests are green after pushing. If not, please fix them. + +### Building and Testing jsPDF + +You can build the library with `npm install && npm run build`. This will fetch all dependencies and then compile the `dist` +files. To see the examples locally you can start a web server with `npm start` and go to `localhost:8000`. + +To test locally, run + +```sh +npm run test-unit # will run only unit tests +# or +npm run test-local # will also run deployment tests for different module formats using the files in the dist folder +``` + +The tests live in the `test` folder and are a set of `specs` that sometimes compare the result with checked-in +reference PDF files. New reference PDFs can be created by running `npm run test-training` in the background. + +Alternatively, you can build jsPDF using these commands in a readily configured online workspace: + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io#https://github.com/MrRio/jsPDF) diff --git a/HOTFIX_README.md b/HOTFIX_README.md index 9f9f3a7b9..ddb0fff07 100644 --- a/HOTFIX_README.md +++ b/HOTFIX_README.md @@ -1,42 +1,66 @@ -#Hotfixes +# Hotfixes We sometimes bake-in solutions (A.K.A. hotfixes) to solve issues for specific use cases. When we deem a hotfix will not break existing code, - will make it default behaviour and mark the hotfix as _accepted_, - At that point the define can be removed. - - To enable a hotfix, define the following member of your created PDF, -where the pdf.hotfix field is the name of the hotfix. - - var pdf new jsPDF(...); - pdf.hotfix.fill_close = true; - -#Active Hotfixes - -##fill_close -###Applies To -context2d plugin +will make it default behaviour and mark the hotfix as _accepted_, +At that point the define can be removed. -### Affects -Filling paths +To enable a hotfix, pass the `hotfixes` option to the jsPDF constructor: + +```js +new jsPDF({ + hotfixes: ["px_scaling"] +}); +``` + +# Active Hotfixes + +## px_scaling + +### Applies To + +jsPDF Core ### Description -In certain cases, closing a fill would result in a path resolving to an incorrect point. -The was most likely fixed when we refactored matrix logic. Enabling this hotfix will ignore a most-likely unneeded workaround. - -##scale_text +When supplying 'px' as the unit for the PDF, the internal scaling factor was being miscalculated making drawn components +larger than they should be. Enabling this hotfix will correct this scaling calculation and items will be drawn to the +correct scale. + +### To Enable + +To enable this hotfix, supply a 'hotfixes' array to the options object in the jsPDF constructor function, and add the +string 'px_scaling' to this array. + +# Accepted Hotfixes + +## scale_text + +### Applies To -###Applies To context2d plugin ### Affects + Drawing and Filling Text when a scale transformation is active. ### Description + jsPDF currently has no way to draw scaled text. This hotfix scales the current font size by the x-axis scale factor. - -#Accepted Hotfixes -There a currently no accepted hotfixes. \ No newline at end of file + +## fill_close + +### Applies To + +context2d plugin + +### Affects + +Filling paths + +### Description + +In certain cases, closing a fill would result in a path resolving to an incorrect point. +This was most likely fixed when we refactored matrix logic. Enabling this hotfix will ignore a most-likely unneeded workaround. diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 8d08e8b62..43e308d9a 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,13 +1,12 @@ -Thank you for submitting an issue to jsPDF. Please read carefully. +Feature requests, bug reports etc. are very welcome as issues. But questions are directed to stackoverflow with the tag `jspdf`. -**Are you using the latest version of jsPDF?** +If you are facing issues with garbled Unicode characters, please refer to [#2677](https://github.com/MrRio/jsPDF/issues/2677). -**Have you tried using jspdf.debug.js?** +Note that new issues should follow these guidelines. Otherwise, the issue will be closed without comment and tagged with the "Needs Information" label. -**Steps to reproduce** - -Ideally a link too. Try fork this http://jsbin.com/rilace/edit?html,js,output - -**What I saw** - -**What I expected** +1. A bug should be reported as an [mcve](https://stackoverflow.com/help/mcve). +2. Make sure code is properly indented and [formatted](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code) (Use ``` around code blocks). +3. Provide a runnable example. Optimally, a link to an example that runs directly in the browser (JSFiddle, CodePen, etc.). Please don't share framework-specific code such as React components, unless strictly necessary to reproduce the issue. Try to isolate the code as much as possible and use only plain JS/HTML/CSS. +4. Try to make sure and show in your issue that the issue is actually related to jspdf and not your framework of choice or your setup. +5. Read and follow the [contribution guidelines](https://github.com/MrRio/jsPDF/blob/master/CONTRIBUTING.md#reporting-bugs). +6. To make sure you have read this, delete this template and start the issue description with "I have read and understood the contribution guidelines.". diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..dc7d3a9fa --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright +(c) 2010-2025 James Hall, https://github.com/MrRio/jsPDF +(c) 2015-2025 yWorks GmbH, https://www.yworks.com/ + +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: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +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. diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt deleted file mode 100644 index 09b01db3e..000000000 --- a/MIT-LICENSE.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2010-2016 James Hall, https://github.com/MrRio/jsPDF - -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: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -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. diff --git a/README.md b/README.md index d80773a01..8fee22900 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,290 @@ -# jsPDF +# ![jsPDF](https://parall.ax/parallax-2016/img/svg/jspdf-logo.svg) -[![Build Status](https://saucelabs.com/buildstatus/jspdf)](https://saucelabs.com/beta/builds/526e7fda50bd4f97a854bf10f280305d) +[![Continous Integration](https://github.com/MrRio/jsPDF/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/MrRio/jsPDF/actions/workflows/continuous-integration.yml?query=branch%3Amaster) +[![Code Climate](https://codeclimate.com/repos/57f943855cdc43705e00592f/badges/2665cddeba042dc5191f/gpa.svg)](https://codeclimate.com/repos/57f943855cdc43705e00592f/feed) +[![Test Coverage](https://codeclimate.com/repos/57f943855cdc43705e00592f/badges/2665cddeba042dc5191f/coverage.svg)](https://codeclimate.com/repos/57f943855cdc43705e00592f/coverage) +[![GitHub license](https://img.shields.io/github/license/MrRio/jsPDF.svg)](https://github.com/MrRio/jsPDF/blob/master/LICENSE) +[![Total alerts](https://img.shields.io/lgtm/alerts/g/MrRio/jsPDF.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/MrRio/jsPDF/alerts/) +[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/MrRio/jsPDF.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/MrRio/jsPDF/context:javascript) +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/from-referrer/) -[![Code Climate](https://codeclimate.com/repos/57f943855cdc43705e00592f/badges/2665cddeba042dc5191f/gpa.svg)](https://codeclimate.com/repos/57f943855cdc43705e00592f/feed) [![Test Coverage](https://codeclimate.com/repos/57f943855cdc43705e00592f/badges/2665cddeba042dc5191f/coverage.svg)](https://codeclimate.com/repos/57f943855cdc43705e00592f/coverage) - -**A library to generate PDFs in client-side JavaScript.** +**A library to generate PDFs in JavaScript.** You can [catch me on twitter](http://twitter.com/MrRio): [@MrRio](http://twitter.com/MrRio) or head over to [my company's website](http://parall.ax) for consultancy. -## [Live Demo](http://rawgit.com/MrRio/jsPDF/master/) | [Documentation](http://rawgit.com/MrRio/jsPDF/master/docs/) +jsPDF is now co-maintained by [yWorks - the diagramming experts](https://www.yworks.com/). + +## [Live Demo](http://raw.githack.com/MrRio/jsPDF/master/) | [Documentation](http://raw.githack.com/MrRio/jsPDF/master/docs/) + +## Install -## Creating your first document +Recommended: get jsPDF from npm: -The easiest way to get started is to drop the CDN hosted library into your page: +```sh +npm install jspdf --save +# or +yarn add jspdf +``` + +Or always get latest version via [unpkg](https://unpkg.com/browse/jspdf/) ```html - + ``` +The `dist` folder of this package contains different kinds of files: + +- **jspdf.es.\*.js**: Modern ES2015 module format. +- **jspdf.node.\*.js**: For running in Node. Uses file operations for loading/saving files instead of browser APIs. +- **jspdf.umd.\*.js**: UMD module format. For AMD or script-tag loading. +- **polyfills\*.js**: Required polyfills for older browsers like Internet Explorer. The es variant simply imports all + required polyfills from `core-js`, the umd variant is self-contained. + +Usually it is not necessary to specify the exact file in the import statement. Build tools or Node automatically figure +out the right file, so importing "jspdf" is enough. + +## Usage + Then you're ready to start making your document: ```javascript -// Default export is a4 paper, portrait, using milimeters for units -var doc = new jsPDF() +import { jsPDF } from "jspdf"; -doc.text('Hello world!', 10, 10) -doc.save('a4.pdf') +// Default export is a4 paper, portrait, using millimeters for units +const doc = new jsPDF(); + +doc.text("Hello world!", 10, 10); +doc.save("a4.pdf"); ``` If you want to change the paper size, orientation, or units, you can do: ```javascript // Landscape export, 2×4 inches -var doc = new jsPDF({ - orientation: 'landscape', - unit: 'in', +const doc = new jsPDF({ + orientation: "landscape", + unit: "in", format: [4, 2] -}) +}); + +doc.text("Hello world!", 1, 1); +doc.save("two-by-four.pdf"); +``` + +### Running in Node.js + +```javascript +const { jsPDF } = require("jspdf"); // will automatically load the node version + +const doc = new jsPDF(); +doc.text("Hello world!", 10, 10); +doc.save("a4.pdf"); // will save the file in the current working directory +``` + +### Other Module Formats + +
+ + AMD + + +```js +require(["jspdf"], ({ jsPDF }) => { + const doc = new jsPDF(); + doc.text("Hello world!", 10, 10); + doc.save("a4.pdf"); +}); +``` + +
+ +
+ + Globals + + +```js +const { jsPDF } = window.jspdf; + +const doc = new jsPDF(); +doc.text("Hello world!", 10, 10); +doc.save("a4.pdf"); +``` + +
+ +## Security + +We strongly advise you to sanitize user input before passing it to jsPDF! + +For reporting security vulnerabilities, please see [SECURITY.md](https://github.com/parallax/jsPDF/blob/master/SECURITY.md). + +### Reading files from the local file system on node + +When running under Node.js, jsPDF will restrict reading files from the local file system by default. + +Strongly recommended: use Node's permission flags so the runtime enforces access: + +```sh +node --permission --allow-fs-read=... ./scripts/generate.js +``` + +See [Node's documentation](https://nodejs.org/api/permissions.html) for details. Note that you need to include +all imported JavaScript files (including all dependencies) in the `--allow-fs-read` flag. + +Fallback (not recommended): you can allow jsPDF to read specific files by setting `jsPDF.allowFsRead` in your script. + +```js +import { jsPDF } from "jspdf"; + +const doc = new jsPDF(); +doc.allowFsRead = ["./fonts/*", "./images/logo.png"]; // allow everything under ./fonts and a single file +``` + +Warning: We strongly recommend the Node flags over `jsPDF.allowFsRead`, as the flags are enforced by the runtime and offer stronger security. -doc.text('Hello world!', 10, 10) -doc.save('two-by-four.pdf') +### Optional dependencies + +Some functions of jsPDF require optional dependencies. E.g. the `html` method, which depends on `html2canvas` and, +when supplied with a string HTML document, `dompurify`. JsPDF loads them dynamically when required +(using the respective module format, e.g. dynamic imports). Build tools like Webpack will automatically create separate +chunks for each of the optional dependencies. If your application does not use any of the optional dependencies, you +can prevent Webpack from generating the chunks by defining them as external dependencies: + +```js +// webpack.config.js +module.exports = { + // ... + externals: { + // only define the dependencies you are NOT using as externals! + canvg: "canvg", + html2canvas: "html2canvas", + dompurify: "dompurify" + } +}; ``` -Great! Now give us a Star :) +In **Vue CLI** projects, externals can be defined via the [configureWebpack](https://cli.vuejs.org/config/#configurewebpack) +or [chainWebpack](https://cli.vuejs.org/config/#chainwebpack) properties of the `vue.config.js` file +(needs to be created, first, in fresh projects). + +In **Angular** projects, externals can be defined using +[custom webpack builders](https://github.com/just-jeb/angular-builders/tree/master/packages/custom-webpack). + +In **React** (`create-react-app`) projects, externals can be defined by either using +[react-app-rewired](https://github.com/timarney/react-app-rewired) or ejecting. + +### TypeScript/Angular/Webpack/React/etc. Configuration: + +jsPDF can be imported just like any other 3rd party library. This works with all major toolkits and frameworks. jsPDF +also offers a typings file for TypeScript projects. + +```js +import { jsPDF } from "jspdf"; +``` + +You can add jsPDF to your meteor-project as follows: + +``` +meteor add jspdf:core +``` + +### Polyfills + +jsPDF requires modern browser APIs in order to function. To use jsPDF in older browsers like Internet Explorer, +polyfills are required. You can load all required polyfills as follows: + +```js +import "jspdf/dist/polyfills.es.js"; +``` + +Alternatively, you can load the prebundled polyfill file. This is not recommended, since you might end up +loading polyfills multiple times. Might still be nifty for small applications or quick POCs. + +## Use of Unicode Characters / UTF-8: + +The 14 standard fonts in PDF are limited to the ASCII-codepage. If you want to use UTF-8 you have to integrate a +custom font, which provides the needed glyphs. jsPDF supports .ttf-files. So if you want to have for example +Chinese text in your pdf, your font has to have the necessary Chinese glyphs. So, check if your font supports +the wanted glyphs or else it will show garbled characters instead of the right text. + +To add the font to jsPDF use our fontconverter in +[/fontconverter/fontconverter.html](https://rawgit.com/MrRio/jsPDF/master/fontconverter/fontconverter.html). +The fontconverter will create a js-file with the content of the provided ttf-file as base64 encoded string +and additional code for jsPDF. You just have to add this generated js-File to your project. +You are then ready to go to use setFont-method in your code and write your UTF-8 encoded text. + +Alternatively you can just load the content of the \*.ttf file as a binary string using `fetch` or `XMLHttpRequest` and +add the font to the PDF file: + +```js +const doc = new jsPDF(); + +const myFont = ... // load the *.ttf font file as binary string + +// add the font to jsPDF +doc.addFileToVFS("MyFont.ttf", myFont); +doc.addFont("MyFont.ttf", "MyFont", "normal"); +doc.setFont("MyFont"); +``` + +## Advanced Functionality + +Since the merge with the [yWorks fork](https://github.com/yWorks/jsPDF) there are a lot of new features. However, some +of them are API breaking, which is why there is an API-switch between two API modes: + +- In "compat" API mode, jsPDF has the same API as MrRio's original version, which means full compatibility with plugins. + However, some advanced features like transformation matrices and patterns won't work. This is the default mode. +- In "advanced" API mode, jsPDF has the API you're used from the yWorks-fork version. This means the availability of + all advanced features like patterns, FormObjects, and transformation matrices. + +You can switch between the two modes by calling + +```javascript +doc.advancedAPI(doc => { + // your code +}); +// or +doc.compatAPI(doc => { + // your code +}); +``` + +JsPDF will automatically switch back to the original API mode after the callback has run. + +## Support + +Please check if your question is already handled at Stackoverflow . +Feel free to ask a question there with the tag `jspdf`. + +Feature requests, bug reports, etc. are very welcome as issues. Note that bug reports should follow these guidelines: + +- A bug should be reported as an [mcve](https://stackoverflow.com/help/mcve) +- Make sure code is properly indented and [formatted](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code) (Use ``` around code blocks) +- Provide a runnable example. +- Try to make sure and show in your issue that the issue is actually related to jspdf and not your framework of choice. ## Contributing -Build the library with `npm run build`. This will fetch all dependencies and then compile the `dist` files. To see the examples locally you can start a web server with `npm start` and go to `localhost:8000`. + +jsPDF cannot live without help from the community! If you think a feature is missing or you found a bug, please consider +if you can spare one or two hours and prepare a pull request. If you're simply interested in this project and want to +help, have a look at the open issues, especially those labeled with "bug". + +You can find information about building and testing jsPDF in the +[contribution guide](https://github.com/MrRio/jsPDF/blob/master/CONTRIBUTING.md#pull-requests) ## Credits -- Big thanks to Daniel Dotsenko from [Willow Systems Corporation](http://willow-systems.com) for making huge contributions to the codebase. -- Thanks to Ajaxian.com for [featuring us back in 2009](http://ajaxian.com/archives/dynamically-generic-pdfs-with-javascript). + +- Big thanks to Daniel Dotsenko from [Willow Systems Corporation](https://github.com/willowsystems) for making huge contributions to the codebase. +- Thanks to Ajaxian.com for [featuring us back in 2009](http://web.archive.org/web/20111011192314/http://ajaxian.com/archives/dynamically-generic-pdfs-with-javascript). (Internet Archive Wayback Machine reference) +- Our special thanks to GH Lee ([sphilee](https://github.com/sphilee)) for programming the ttf-file-support and providing a large and long sought after feature - Everyone else that's contributed patches or bug reports. You rock. ## License (MIT) -Copyright (c) 2010-2016 James Hall, https://github.com/MrRio/jsPDF + +Copyright +(c) 2010-2025 James Hall, https://github.com/MrRio/jsPDF +(c) 2015-2025 yWorks GmbH, https://www.yworks.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/RELEASE.md b/RELEASE.md index 92d08cd14..7a0c7194c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,10 +1,9 @@ -Release Instructions -==================== +# Release Instructions - Add a new draft release in GitHub. - Describe the Release -- Update the CDN link in the README (@TODO: Automate?) -- `npm version 1.x.y` -- git push origin v1.x.y -- Publish the release on GitHub -- `npm publish` +- Update the version in `bower.json` (@TODO: Automate?) +- `npm version 2.x.y` +- git push origin v2.x.y +- git push origin master +- Publish the release on GitHub (this will automatically `npm publish` it) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..8b4b544be --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +Please also refer to https://github.com/parallax/jsPDF/blob/master/README.md#security + +## Supported Versions + +Only the latest major/minor versions are supported with security updates. + +## Reporting a Vulnerability + +Please use the "Report a vulnerability" button [here](https://github.com/parallax/jsPDF/security). diff --git a/bower.json b/bower.json index 9033b17ab..6f78fd582 100644 --- a/bower.json +++ b/bower.json @@ -1,27 +1,85 @@ { "name": "jspdf", - "homepage": "https://github.com/mrrio/jspdf", + "version": "4.2.1", + "homepage": "https://github.com/parallax/jsPDF", "description": "PDF Document creation from JavaScript", - "main": "dist/jspdf.debug.js", - "moduleType": [ - "amd", - "globals" - ], - "keywords": [ - "pdf" + "main": [ + "dist/jspdf.umd.js", + "dist/jspdf.umd.min.js", + "dist/jspdf.es.js", + "dist/jspdf.es.min.js", + "dist/jspdf.node.js", + "dist/jspdf.node.min.js" ], + "moduleType": ["amd", "globals", "node", "es6"], + "keywords": ["pdf"], + "dependencies": { + "fflate": "101arrowz/fflate#^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + }, + "devDependencies": { + "@babel/core": "^7.8.4", + "@babel/plugin-external-helpers": "7.2.0", + "@babel/preset-env": "^7.8.4", + "@rollup/plugin-replace": "^2.3.3", + "@types/jasmine": "^3.5.11", + "@types/node": "^14.0.18", + "@typescript-eslint/parser": "^3.6.0", + "@typescript-eslint/eslint-plugin": "^3.6.0", + "chalk": "^4.1.0", + "codeclimate-test-reporter": "0.5.1", + "core-js": "^3.6.5", + "diff": "4.0.1", + "docdash": "1.1.0", + "eslint": "^7.4.0", + "eslint-plugin-jasmine": "^4.1.1", + "folder-delete": "1.0.4", + "inquirer": "^6.5.2", + "jasmine": "3.5.0", + "jasmine-core": "3.5.0", + "jasmine-expect": "4.0.3", + "js-yaml": "3.13.1", + "jsdoc": "^3.6.3", + "karma": "5.1.0", + "karma-babel-preprocessor": "8.0.1", + "karma-chrome-launcher": "3.1.0", + "karma-coverage": "2.0.2", + "karma-firefox-launcher": "1.3.0", + "karma-ie-launcher": "1.0.0", + "karma-jasmine": "3.3.1", + "karma-jasmine-matchers": "4.0.2", + "karma-mocha-reporter": "2.2.5", + "karma-typescript": "^5.0.3", + "karma-verbose-reporter": "0.0.6", + "local-web-server": "2.6.1", + "log-utils": "^1.0.0", + "markdown": "0.5.0", + "preprocess": "^3.2.0", + "prettier": "^1.19.1", + "regenerator-runtime": "^0.13.5", + "requirejs": "^2.3.6", + "rollup": "^2.18.2", + "rollup-plugin-license": "^2.1.0", + "rollup-plugin-node-resolve": "4.2.3", + "rollup-plugin-preprocess": "0.0.4", + "@rollup/plugin-terser": "^0.4.4", + "typescript": "^3.9.6" + }, "license": "MIT", "ignore": [ "**/.*", - "libs", + "src", "CNAME", - "jspdf.js", - "examples/jspdf.PLUGINTEMPLATE.js", - "plugins/*", - "todo.txt", - "wscript.py", - "build.sh", + "examples", + "node_modules", + "bower_components", "test", - "tools" + "coverage", + "docs" ] } diff --git a/build.js b/build.js deleted file mode 100644 index 733883d44..000000000 --- a/build.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict' - -var fs = require('fs') -var rollup = require('rollup') -var uglify = require('uglify-js') -var babel = require('rollup-plugin-babel') -var execSync = require('child_process').execSync - -bundle({ - minified: 'dist/jspdf.min.js', - debug: 'dist/jspdf.debug.js' -}) - -// Monkey patching adler32 and filesaver -function monkeyPatch() { - return { - transform: (code, id) => { - var file = id.split('/').pop() - if (file === 'adler32cs.js') { - code = code.replace(/this, function/g, 'jsPDF, function') - code = code.replace(/require\('buffer'\)/g, '{}') - } - return code - } - } -} - -// Rollup removes local variables unless used within a module. -// This plugin makes sure specified local variables are preserved -// and kept local. This plugin wouldn't be necessary if es2015 -// modules would be used. -function rawjs(opts) { - opts = opts || {} - return { - transform: (code, id) => { - var variable = opts[id.split('/').pop()] - if (!variable) return code - - var keepStr = '/*rollup-keeper-start*/window.tmp=' + variable + - ';/*rollup-keeper-end*/' - return code + keepStr - }, - transformBundle: (code) => { - for (var file in opts) { - var r = new RegExp(opts[file] + '\\$\\d+', 'g') - code = code.replace(r, opts[file]) - } - var re = /\/\*rollup-keeper-start\*\/.*\/\*rollup-keeper-end\*\//g - return code.replace(re, '') - } - } -} - -function bundle(paths) { - rollup.rollup({ - entry: './main.js', - plugins: [ - monkeyPatch(), - rawjs({ - 'jspdf.js': 'jsPDF', - 'filesaver.tmp.js': 'saveAs', - 'deflate.js': 'Deflater', - 'zlib.js': 'FlateStream', - 'css_colors.js': 'CssColors', - 'html2pdf.js': 'html2pdf' - }), - babel({ - presets: ['es2015-rollup'], - exclude: ['node_modules/**', 'libs/**'] - }) - ] - }).then((bundle) => { - var code = bundle.generate({ - format: 'umd', - moduleName: 'jspdf' - }).code - code = code.replace( - /Permission\s+is\s+hereby\s+granted[\S\s]+?IN\s+THE\s+SOFTWARE\./, - 'Licensed under the MIT License') - code = code.replace( - /Permission\s+is\s+hereby\s+granted[\S\s]+?IN\s+THE\s+SOFTWARE\./g, - '') - fs.writeFileSync(paths.debug, renew(code)) - - var minified = uglify.minify(code, { - fromString: true, - output: { - comments: /@preserve|@license|copyright/i - } - }) - fs.writeFileSync(paths.minified, renew(minified.code)) - }).catch((err) => { - console.error(err) - }) -} - -function renew(code) { - var date = new Date().toISOString() - var version = require('./package.json').version - var whoami = execSync('whoami').toString().trim() - var commit = execSync('git rev-parse --short=10 HEAD').toString().trim() - - code = code.replace('${versionID}', version + ' Built on ' + date) - code = code.replace('${commitID}', commit) - code = code.replace(/1\.0\.0-trunk/, version + ' ' + date + ':' + whoami) - - return code -} diff --git a/builder.html b/builder.html new file mode 100644 index 000000000..49a378bea --- /dev/null +++ b/builder.html @@ -0,0 +1,256 @@ + + + + jsPDF + + + + + + + + + + + + + + + + + Fork me on GitHub + + +

jsPDF Builder (alpha)

+ +

Build your own jsPDF-package

+ +
+ + +
+ + + diff --git a/cli.js b/cli.js new file mode 100644 index 000000000..ed4926652 --- /dev/null +++ b/cli.js @@ -0,0 +1,116 @@ +const inquirer = require("inquirer"); +const configuration = require("./modules.conf.js"); + +console.log(configuration); + +function uniq(a) { + var prims = { boolean: {}, number: {}, string: {} }, + objs = []; + + return a.filter(function(item) { + var type = typeof item; + if (type in prims) + if (Object.prototype.hasOwnProperty.call(prims[type], item)) { + return false; + } else { + prims[type][item] = true; + return true; + } + else return objs.indexOf(item) >= 0 ? false : objs.push(item); + }); +} + +function generateFileList(list) { + var fileList = []; + var file; + for (var i = 0; i < list.length; i++) { + fileList.push(list[i].name + ".js"); + console.log(list[i]); + console.log(configuration[list[i].name]); + for (var j = 0; j < configuration[list[i]].deps.length; j++) { + file = configuration[list[i]].deps[j]; + configuration[file].type; + fileList.push(configuration[file].type + "/" + ".js"); + } + } + fileList = uniq(fileList); + return fileList; +} + +/** + * Ask use a few questions on command prompt + * @returns {Promise} The promise with the result of the prompt + */ +function promptUser() { + return inquirer + .prompt([ + { + type: "list", + name: "env", + message: "Where does your code run?", + default: ["browser"], + choices: [ + { name: "Browser", value: "browser" }, + { name: "Node", value: "node" } + ] + }, + { + type: "checkbox", + name: "images", + message: "Which ImageTypes should be supported?", + default: ["jpeg_support", "bmp_support", "gif_support", "webp_support"], + choices: [ + { name: "Jpeg", value: "jpeg_support" }, + { name: "Bmp", value: "bmp_support" }, + { name: "Gif", value: "gif_support" }, + { name: "WebP", value: "webp_support" } + ] + }, + { + type: "checkbox", + name: "modules", + message: "Additional Modules", + default: [ + "acroform", + "annotations", + "arabic", + "autoprint", + "context2d", + "fileloading", + "filters", + "html", + "javascript", + "outline", + "setlanguage", + "svg", + "total_pages", + "utf8", + "viewerpreferences", + "xmp_metadata" + ], + choices: [ + { name: "Acroform", value: "acroform" }, + { name: "Annotations", value: "annotations" }, + { name: "Arabic Parser", value: "arabic" }, + { name: "Autoprint", value: "autoprint" }, + { name: "Context2d", value: "context2d" }, + { name: "File Loading", value: "fileloading" }, + { name: "Filters", value: "filters" }, + { name: "HTML", value: "html" }, + { name: "Javascript", value: "javascript" }, + { name: "Outline", value: "outline" }, + { name: "Language-Tagging", value: "setlanguage" }, + { name: "SVG", value: "svg" }, + { name: "TotalPages", value: "total_pages" }, + { name: "Unicode", value: "utf8" }, + { name: "ViewerPreferences", value: "viewerpreferences" }, + { name: "XMP Metadata", value: "xmp_metadata" } + ] + } + ]) + .then(result => { + console.log(generateFileList([...result.images, ...result.modules])); + }); +} + +promptUser(); diff --git a/deletedocs.js b/deletedocs.js new file mode 100644 index 000000000..d2a184496 --- /dev/null +++ b/deletedocs.js @@ -0,0 +1,5 @@ +const deleteFolder = require("folder-delete"); + +try { + deleteFolder("docs", { debugLog: false }); +} catch (e) {} diff --git a/dist/jspdf.debug.js b/dist/jspdf.debug.js deleted file mode 100644 index 1e9fddbff..000000000 --- a/dist/jspdf.debug.js +++ /dev/null @@ -1,17364 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.jspdf = factory()); -}(this, (function () { 'use strict'; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; -} : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; -}; - - - - - -var asyncGenerator = function () { - function AwaitValue(value) { - this.value = value; - } - - function AsyncGenerator(gen) { - var front, back; - - function send(key, arg) { - return new Promise(function (resolve, reject) { - var request = { - key: key, - arg: arg, - resolve: resolve, - reject: reject, - next: null - }; - - if (back) { - back = back.next = request; - } else { - front = back = request; - resume(key, arg); - } - }); - } - - function resume(key, arg) { - try { - var result = gen[key](arg); - var value = result.value; - - if (value instanceof AwaitValue) { - Promise.resolve(value.value).then(function (arg) { - resume("next", arg); - }, function (arg) { - resume("throw", arg); - }); - } else { - settle(result.done ? "return" : "normal", result.value); - } - } catch (err) { - settle("throw", err); - } - } - - function settle(type, value) { - switch (type) { - case "return": - front.resolve({ - value: value, - done: true - }); - break; - - case "throw": - front.reject(value); - break; - - default: - front.resolve({ - value: value, - done: false - }); - break; - } - - front = front.next; - - if (front) { - resume(front.key, front.arg); - } else { - back = null; - } - } - - this._invoke = send; - - if (typeof gen.return !== "function") { - this.return = undefined; - } - } - - if (typeof Symbol === "function" && Symbol.asyncIterator) { - AsyncGenerator.prototype[Symbol.asyncIterator] = function () { - return this; - }; - } - - AsyncGenerator.prototype.next = function (arg) { - return this._invoke("next", arg); - }; - - AsyncGenerator.prototype.throw = function (arg) { - return this._invoke("throw", arg); - }; - - AsyncGenerator.prototype.return = function (arg) { - return this._invoke("return", arg); - }; - - return { - wrap: function (fn) { - return function () { - return new AsyncGenerator(fn.apply(this, arguments)); - }; - }, - await: function (value) { - return new AwaitValue(value); - } - }; -}(); - - - - - - - - - - - - - - - -var get$1 = function get$1(object, property, receiver) { - if (object === null) object = Function.prototype; - var desc = Object.getOwnPropertyDescriptor(object, property); - - if (desc === undefined) { - var parent = Object.getPrototypeOf(object); - - if (parent === null) { - return undefined; - } else { - return get$1(parent, property, receiver); - } - } else if ("value" in desc) { - return desc.value; - } else { - var getter = desc.get; - - if (getter === undefined) { - return undefined; - } - - return getter.call(receiver); - } -}; - - - - - - - - - - - - - - - - - -var set$1 = function set$1(object, property, value, receiver) { - var desc = Object.getOwnPropertyDescriptor(object, property); - - if (desc === undefined) { - var parent = Object.getPrototypeOf(object); - - if (parent !== null) { - set$1(parent, property, value, receiver); - } - } else if ("value" in desc && desc.writable) { - desc.value = value; - } else { - var setter = desc.set; - - if (setter !== undefined) { - setter.call(receiver, value); - } - } - - return value; -}; - -/** @preserve - * jsPDF - PDF Document creation from JavaScript - * Version 1.3.3 Built on 2017-02-23T15:31:28.692Z - * CommitID c2fa0d3c14 - * - * Copyright (c) 2010-2016 James Hall , https://github.com/MrRio/jsPDF - * 2010 Aaron Spike, https://github.com/acspike - * 2012 Willow Systems Corporation, willow-systems.com - * 2012 Pablo Hess, https://github.com/pablohess - * 2012 Florian Jenett, https://github.com/fjenett - * 2013 Warren Weckesser, https://github.com/warrenweckesser - * 2013 Youssef Beddad, https://github.com/lifof - * 2013 Lee Driscoll, https://github.com/lsdriscoll - * 2013 Stefan Slonevskiy, https://github.com/stefslon - * 2013 Jeremy Morel, https://github.com/jmorel - * 2013 Christoph Hartmann, https://github.com/chris-rock - * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria - * 2014 James Makes, https://github.com/dollaruw - * 2014 Diego Casorran, https://github.com/diegocr - * 2014 Steven Spungin, https://github.com/Flamenco - * 2014 Kenneth Glassey, https://github.com/Gavvers - * - * Licensed under the MIT License - * - * Contributor(s): - * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, - * kim3er, mfo, alnorth, Flamenco - */ - -/** - * Creates new jsPDF document object instance. - * - * @class - * @param orientation One of "portrait" or "landscape" (or shortcuts "p" (Default), "l") - * @param unit Measurement unit to be used when coordinates are specified. - * One of "pt" (points), "mm" (Default), "cm", "in" - * @param format One of 'pageFormats' as shown below, default: a4 - * @returns {jsPDF} - * @name jsPDF - */ -var jsPDF = function (global) { - 'use strict'; - - var pdfVersion = '1.3', - pageFormats = { // Size in pt of various paper formats - 'a0': [2383.94, 3370.39], - 'a1': [1683.78, 2383.94], - 'a2': [1190.55, 1683.78], - 'a3': [841.89, 1190.55], - 'a4': [595.28, 841.89], - 'a5': [419.53, 595.28], - 'a6': [297.64, 419.53], - 'a7': [209.76, 297.64], - 'a8': [147.40, 209.76], - 'a9': [104.88, 147.40], - 'a10': [73.70, 104.88], - 'b0': [2834.65, 4008.19], - 'b1': [2004.09, 2834.65], - 'b2': [1417.32, 2004.09], - 'b3': [1000.63, 1417.32], - 'b4': [708.66, 1000.63], - 'b5': [498.90, 708.66], - 'b6': [354.33, 498.90], - 'b7': [249.45, 354.33], - 'b8': [175.75, 249.45], - 'b9': [124.72, 175.75], - 'b10': [87.87, 124.72], - 'c0': [2599.37, 3676.54], - 'c1': [1836.85, 2599.37], - 'c2': [1298.27, 1836.85], - 'c3': [918.43, 1298.27], - 'c4': [649.13, 918.43], - 'c5': [459.21, 649.13], - 'c6': [323.15, 459.21], - 'c7': [229.61, 323.15], - 'c8': [161.57, 229.61], - 'c9': [113.39, 161.57], - 'c10': [79.37, 113.39], - 'dl': [311.81, 623.62], - 'letter': [612, 792], - 'government-letter': [576, 756], - 'legal': [612, 1008], - 'junior-legal': [576, 360], - 'ledger': [1224, 792], - 'tabloid': [792, 1224], - 'credit-card': [153, 243] - }; - - /** - * jsPDF's Internal PubSub Implementation. - * See mrrio.github.io/jsPDF/doc/symbols/PubSub.html - * Backward compatible rewritten on 2014 by - * Diego Casorran, https://github.com/diegocr - * - * @class - * @name PubSub - * @ignore This should not be in the public docs. - */ - function PubSub(context) { - var topics = {}; - - this.subscribe = function (topic, callback, once) { - if (typeof callback !== 'function') { - return false; - } - - if (!topics.hasOwnProperty(topic)) { - topics[topic] = {}; - } - - var id = Math.random().toString(35); - topics[topic][id] = [callback, !!once]; - - return id; - }; - - this.unsubscribe = function (token) { - for (var topic in topics) { - if (topics[topic][token]) { - delete topics[topic][token]; - return true; - } - } - return false; - }; - - this.publish = function (topic) { - if (topics.hasOwnProperty(topic)) { - var args = Array.prototype.slice.call(arguments, 1), - idr = []; - - for (var id in topics[topic]) { - var sub = topics[topic][id]; - try { - sub[0].apply(context, args); - } catch (ex) { - if (global.console) { - console.error('jsPDF PubSub Error', ex.message, ex); - } - } - if (sub[1]) idr.push(id); - } - if (idr.length) idr.forEach(this.unsubscribe); - } - }; - } - - /** - * @constructor - * @private - */ - function jsPDF(orientation, unit, format, compressPdf) { - var options = {}; - - if ((typeof orientation === 'undefined' ? 'undefined' : _typeof(orientation)) === 'object') { - options = orientation; - - orientation = options.orientation; - unit = options.unit || unit; - format = options.format || format; - compressPdf = options.compress || options.compressPdf || compressPdf; - } - - // Default options - unit = unit || 'mm'; - format = format || 'a4'; - orientation = ('' + (orientation || 'P')).toLowerCase(); - - var format_as_string = ('' + format).toLowerCase(), - compress = !!compressPdf && typeof Uint8Array === 'function', - textColor = options.textColor || '0 g', - drawColor = options.drawColor || '0 G', - activeFontSize = options.fontSize || 16, - lineHeightProportion = options.lineHeight || 1.15, - lineWidth = options.lineWidth || 0.200025, - // 2mm - objectNumber = 2, - // 'n' Current object number - outToPages = !1, - // switches where out() prints. outToPages true = push to pages obj. outToPages false = doc builder content - offsets = [], - // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. - fonts = {}, - // collection of font objects, where key is fontKey - a dynamically created label for a given font. - fontmap = {}, - // mapping structure fontName > fontStyle > font key - performance layer. See addFont() - activeFontKey, - // will be string representing the KEY of the font as combination of fontName + fontStyle - k, - // Scale factor - tmp, - page = 0, - currentPage, - pages = [], - pagesContext = [], - // same index as pages and pagedim - pagedim = [], - content = [], - additionalObjects = [], - lineCapID = 0, - lineJoinID = 0, - content_length = 0, - pageWidth, - pageHeight, - pageMode, - zoomMode, - layoutMode, - documentProperties = { - 'title': '', - 'subject': '', - 'author': '', - 'keywords': '', - 'creator': '' - }, - API = {}, - events = new PubSub(API), - - - ///////////////////// - // Private functions - ///////////////////// - f2 = function f2(number) { - return number.toFixed(2); // Ie, %.2f - }, - f3 = function f3(number) { - return number.toFixed(3); // Ie, %.3f - }, - padd2 = function padd2(number) { - return ('0' + parseInt(number)).slice(-2); - }, - out = function out(string) { - if (outToPages) { - /* set by beginPage */ - pages[currentPage].push(string); - } else { - // +1 for '\n' that will be used to join 'content' - content_length += string.length + 1; - content.push(string); - } - }, - newObject = function newObject() { - // Begin a new object - objectNumber++; - offsets[objectNumber] = content_length; - out(objectNumber + ' 0 obj'); - return objectNumber; - }, - - // Does not output the object until after the pages have been output. - // Returns an object containing the objectId and content. - // All pages have been added so the object ID can be estimated to start right after. - // This does not modify the current objectNumber; It must be updated after the newObjects are output. - newAdditionalObject = function newAdditionalObject() { - var objId = pages.length * 2 + 1; - objId += additionalObjects.length; - var obj = { - objId: objId, - content: '' - }; - additionalObjects.push(obj); - return obj; - }, - - // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data - newObjectDeferred = function newObjectDeferred() { - objectNumber++; - offsets[objectNumber] = function () { - return content_length; - }; - return objectNumber; - }, - newObjectDeferredBegin = function newObjectDeferredBegin(oid) { - offsets[oid] = content_length; - }, - putStream = function putStream(str) { - out('stream'); - out(str); - out('endstream'); - }, - putPages = function putPages() { - var n, - p, - arr, - i, - deflater, - adler32, - adler32cs, - wPt, - hPt, - pageObjectNumbers = []; - - adler32cs = global.adler32cs || jsPDF.adler32cs; - if (compress && typeof adler32cs === 'undefined') { - compress = false; - } - - // outToPages = false as set in endDocument(). out() writes to content. - - for (n = 1; n <= page; n++) { - pageObjectNumbers.push(newObject()); - wPt = (pageWidth = pagedim[n].width) * k; - hPt = (pageHeight = pagedim[n].height) * k; - out('<>'); - out('endobj'); - - // Page content - p = pages[n].join('\n'); - newObject(); - if (compress) { - arr = []; - i = p.length; - while (i--) { - arr[i] = p.charCodeAt(i); - } - adler32 = adler32cs.from(p); - deflater = new Deflater(6); - deflater.append(new Uint8Array(arr)); - p = deflater.flush(); - arr = new Uint8Array(p.length + 6); - arr.set(new Uint8Array([120, 156])), arr.set(p, 2); - arr.set(new Uint8Array([adler32 & 0xFF, adler32 >> 8 & 0xFF, adler32 >> 16 & 0xFF, adler32 >> 24 & 0xFF]), p.length + 2); - p = String.fromCharCode.apply(null, arr); - out('<>'); - } else { - out('<>'); - } - putStream(p); - out('endobj'); - } - offsets[1] = content_length; - out('1 0 obj'); - out('<>'); - out('endobj'); - events.publish('postPutPages'); - }, - putFont = function putFont(font) { - font.objectNumber = newObject(); - out('<>'); - out('endobj'); - }, - putFonts = function putFonts() { - for (var fontKey in fonts) { - if (fonts.hasOwnProperty(fontKey)) { - putFont(fonts[fontKey]); - } - } - }, - putXobjectDict = function putXobjectDict() { - // Loop through images, or other data objects - events.publish('putXobjectDict'); - }, - putResourceDictionary = function putResourceDictionary() { - out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); - out('/Font <<'); - - // Do this for each font, the '1' bit is the index of the font - for (var fontKey in fonts) { - if (fonts.hasOwnProperty(fontKey)) { - out('/' + fontKey + ' ' + fonts[fontKey].objectNumber + ' 0 R'); - } - } - out('>>'); - out('/XObject <<'); - putXobjectDict(); - out('>>'); - }, - putResources = function putResources() { - putFonts(); - events.publish('putResources'); - // Resource dictionary - offsets[2] = content_length; - out('2 0 obj'); - out('<<'); - putResourceDictionary(); - out('>>'); - out('endobj'); - events.publish('postPutResources'); - }, - putAdditionalObjects = function putAdditionalObjects() { - events.publish('putAdditionalObjects'); - for (var i = 0; i < additionalObjects.length; i++) { - var obj = additionalObjects[i]; - offsets[obj.objId] = content_length; - out(obj.objId + ' 0 obj'); - out(obj.content); - out('endobj'); - } - objectNumber += additionalObjects.length; - events.publish('postPutAdditionalObjects'); - }, - addToFontDictionary = function addToFontDictionary(fontKey, fontName, fontStyle) { - // this is mapping structure for quick font key lookup. - // returns the KEY of the font (ex: "F1") for a given - // pair of font name and type (ex: "Arial". "Italic") - if (!fontmap.hasOwnProperty(fontName)) { - fontmap[fontName] = {}; - } - fontmap[fontName][fontStyle] = fontKey; - }, - - /** - * FontObject describes a particular font as member of an instnace of jsPDF - * - * It's a collection of properties like 'id' (to be used in PDF stream), - * 'fontName' (font's family name), 'fontStyle' (font's style variant label) - * - * @class - * @public - * @property id {String} PDF-document-instance-specific label assinged to the font. - * @property PostScriptName {String} PDF specification full name for the font - * @property encoding {Object} Encoding_name-to-Font_metrics_object mapping. - * @name FontObject - * @ignore This should not be in the public docs. - */ - addFont = function addFont(PostScriptName, fontName, fontStyle, encoding) { - var fontKey = 'F' + (Object.keys(fonts).length + 1).toString(10), - - // This is FontObject - font = fonts[fontKey] = { - 'id': fontKey, - 'PostScriptName': PostScriptName, - 'fontName': fontName, - 'fontStyle': fontStyle, - 'encoding': encoding, - 'metadata': {} - }; - addToFontDictionary(fontKey, fontName, fontStyle); - events.publish('addFont', font); - - return fontKey; - }, - addFonts = function addFonts() { - - var HELVETICA = "helvetica", - TIMES = "times", - COURIER = "courier", - NORMAL = "normal", - BOLD = "bold", - ITALIC = "italic", - BOLD_ITALIC = "bolditalic", - encoding = 'StandardEncoding', - ZAPF = "zapfdingbats", - standardFonts = [['Helvetica', HELVETICA, NORMAL], ['Helvetica-Bold', HELVETICA, BOLD], ['Helvetica-Oblique', HELVETICA, ITALIC], ['Helvetica-BoldOblique', HELVETICA, BOLD_ITALIC], ['Courier', COURIER, NORMAL], ['Courier-Bold', COURIER, BOLD], ['Courier-Oblique', COURIER, ITALIC], ['Courier-BoldOblique', COURIER, BOLD_ITALIC], ['Times-Roman', TIMES, NORMAL], ['Times-Bold', TIMES, BOLD], ['Times-Italic', TIMES, ITALIC], ['Times-BoldItalic', TIMES, BOLD_ITALIC], ['ZapfDingbats', ZAPF]]; - - for (var i = 0, l = standardFonts.length; i < l; i++) { - var fontKey = addFont(standardFonts[i][0], standardFonts[i][1], standardFonts[i][2], encoding); - - // adding aliases for standard fonts, this time matching the capitalization - var parts = standardFonts[i][0].split('-'); - addToFontDictionary(fontKey, parts[0], parts[1] || ''); - } - events.publish('addFonts', { - fonts: fonts, - dictionary: fontmap - }); - }, - SAFE = function __safeCall(fn) { - fn.foo = function __safeCallWrapper() { - try { - return fn.apply(this, arguments); - } catch (e) { - var stack = e.stack || ''; - if (~stack.indexOf(' at ')) stack = stack.split(" at ")[1]; - var m = "Error in function " + stack.split("\n")[0].split('<')[0] + ": " + e.message; - if (global.console) { - global.console.error(m, e); - if (global.alert) alert(m); - } else { - throw new Error(m); - } - } - }; - fn.foo.bar = fn; - return fn.foo; - }, - to8bitStream = function to8bitStream(text, flags) { - /** - * PDF 1.3 spec: - * "For text strings encoded in Unicode, the first two bytes must be 254 followed by - * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts - * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely - * to be a meaningful beginning of a word or phrase.) The remainder of the - * string consists of Unicode character codes, according to the UTF-16 encoding - * specified in the Unicode standard, version 2.0. Commonly used Unicode values - * are represented as 2 bytes per character, with the high-order byte appearing first - * in the string." - * - * In other words, if there are chars in a string with char code above 255, we - * recode the string to UCS2 BE - string doubles in length and BOM is prepended. - * - * HOWEVER! - * Actual *content* (body) text (as opposed to strings used in document properties etc) - * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID) - * - * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have - * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could - * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode - * code page. There, however, all characters in the stream are treated as GIDs, - * including BOM, which is the reason we need to skip BOM in content text (i.e. that - * that is tied to a font). - * - * To signal this "special" PDFEscape / to8bitStream handling mode, - * API.text() function sets (unless you overwrite it with manual values - * given to API.text(.., flags) ) - * flags.autoencode = true - * flags.noBOM = true - * - * =================================================================================== - * `flags` properties relied upon: - * .sourceEncoding = string with encoding label. - * "Unicode" by default. = encoding of the incoming text. - * pass some non-existing encoding name - * (ex: 'Do not touch my strings! I know what I am doing.') - * to make encoding code skip the encoding step. - * .outputEncoding = Either valid PDF encoding name - * (must be supported by jsPDF font metrics, otherwise no encoding) - * or a JS object, where key = sourceCharCode, value = outputCharCode - * missing keys will be treated as: sourceCharCode === outputCharCode - * .noBOM - * See comment higher above for explanation for why this is important - * .autoencode - * See comment higher above for explanation for why this is important - */ - - var i, l, sourceEncoding, encodingBlock, outputEncoding, newtext, isUnicode, ch, bch; - - flags = flags || {}; - sourceEncoding = flags.sourceEncoding || 'Unicode'; - outputEncoding = flags.outputEncoding; - - // This 'encoding' section relies on font metrics format - // attached to font objects by, among others, - // "Willow Systems' standard_font_metrics plugin" - // see jspdf.plugin.standard_font_metrics.js for format - // of the font.metadata.encoding Object. - // It should be something like - // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}} - // .widths = {0:width, code:width, ..., 'fof':divisor} - // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...} - if ((flags.autoencode || outputEncoding) && fonts[activeFontKey].metadata && fonts[activeFontKey].metadata[sourceEncoding] && fonts[activeFontKey].metadata[sourceEncoding].encoding) { - encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding; - - // each font has default encoding. Some have it clearly defined. - if (!outputEncoding && fonts[activeFontKey].encoding) { - outputEncoding = fonts[activeFontKey].encoding; - } - - // Hmmm, the above did not work? Let's try again, in different place. - if (!outputEncoding && encodingBlock.codePages) { - outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default - } - - if (typeof outputEncoding === 'string') { - outputEncoding = encodingBlock[outputEncoding]; - } - // we want output encoding to be a JS Object, where - // key = sourceEncoding's character code and - // value = outputEncoding's character code. - if (outputEncoding) { - isUnicode = false; - newtext = []; - for (i = 0, l = text.length; i < l; i++) { - ch = outputEncoding[text.charCodeAt(i)]; - if (ch) { - newtext.push(String.fromCharCode(ch)); - } else { - newtext.push(text[i]); - } - - // since we are looping over chars anyway, might as well - // check for residual unicodeness - if (newtext[i].charCodeAt(0) >> 8) { - /* more than 255 */ - isUnicode = true; - } - } - text = newtext.join(''); - } - } - - i = text.length; - // isUnicode may be set to false above. Hence the triple-equal to undefined - while (isUnicode === undefined && i !== 0) { - if (text.charCodeAt(i - 1) >> 8) { - /* more than 255 */ - isUnicode = true; - } - i--; - } - if (!isUnicode) { - return text; - } - - newtext = flags.noBOM ? [] : [254, 255]; - for (i = 0, l = text.length; i < l; i++) { - ch = text.charCodeAt(i); - bch = ch >> 8; // divide by 256 - if (bch >> 8) { - /* something left after dividing by 256 second time */ - throw new Error("Character at position " + i + " of string '" + text + "' exceeds 16bits. Cannot be encoded into UCS-2 BE"); - } - newtext.push(bch); - newtext.push(ch - (bch << 8)); - } - return String.fromCharCode.apply(undefined, newtext); - }, - pdfEscape = function pdfEscape(text, flags) { - /** - * Replace '/', '(', and ')' with pdf-safe versions - * - * Doing to8bitStream does NOT make this PDF display unicode text. For that - * we also need to reference a unicode font and embed it - royal pain in the rear. - * - * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars, - * which JavaScript Strings are happy to provide. So, while we still cannot display - * 2-byte characters property, at least CONDITIONALLY converting (entire string containing) - * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF - * is still parseable. - * This will allow immediate support for unicode in document properties strings. - */ - return to8bitStream(text, flags).replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); - }, - putInfo = function putInfo() { - out('/Producer (jsPDF ' + jsPDF.version + ')'); - for (var key in documentProperties) { - if (documentProperties.hasOwnProperty(key) && documentProperties[key]) { - out('/' + key.substr(0, 1).toUpperCase() + key.substr(1) + ' (' + pdfEscape(documentProperties[key]) + ')'); - } - } - var created = new Date(), - tzoffset = created.getTimezoneOffset(), - tzsign = tzoffset < 0 ? '+' : '-', - tzhour = Math.floor(Math.abs(tzoffset / 60)), - tzmin = Math.abs(tzoffset % 60), - tzstr = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(''); - out(['/CreationDate (D:', created.getFullYear(), padd2(created.getMonth() + 1), padd2(created.getDate()), padd2(created.getHours()), padd2(created.getMinutes()), padd2(created.getSeconds()), tzstr, ')'].join('')); - }, - putCatalog = function putCatalog() { - out('/Type /Catalog'); - out('/Pages 1 0 R'); - // PDF13ref Section 7.2.1 - if (!zoomMode) zoomMode = 'fullwidth'; - switch (zoomMode) { - case 'fullwidth': - out('/OpenAction [3 0 R /FitH null]'); - break; - case 'fullheight': - out('/OpenAction [3 0 R /FitV null]'); - break; - case 'fullpage': - out('/OpenAction [3 0 R /Fit]'); - break; - case 'original': - out('/OpenAction [3 0 R /XYZ null null 1]'); - break; - default: - var pcn = '' + zoomMode; - if (pcn.substr(pcn.length - 1) === '%') zoomMode = parseInt(zoomMode) / 100; - if (typeof zoomMode === 'number') { - out('/OpenAction [3 0 R /XYZ null null ' + f2(zoomMode) + ']'); - } - } - if (!layoutMode) layoutMode = 'continuous'; - switch (layoutMode) { - case 'continuous': - out('/PageLayout /OneColumn'); - break; - case 'single': - out('/PageLayout /SinglePage'); - break; - case 'two': - case 'twoleft': - out('/PageLayout /TwoColumnLeft'); - break; - case 'tworight': - out('/PageLayout /TwoColumnRight'); - break; - } - if (pageMode) { - /** - * A name object specifying how the document should be displayed when opened: - * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT - * UseOutlines : Document outline visible - * UseThumbs : Thumbnail images visible - * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible - */ - out('/PageMode /' + pageMode); - } - events.publish('putCatalog'); - }, - putTrailer = function putTrailer() { - out('/Size ' + (objectNumber + 1)); - out('/Root ' + objectNumber + ' 0 R'); - out('/Info ' + (objectNumber - 1) + ' 0 R'); - }, - beginPage = function beginPage(width, height) { - // Dimensions are stored as user units and converted to points on output - var orientation = typeof height === 'string' && height.toLowerCase(); - if (typeof width === 'string') { - var format = width.toLowerCase(); - if (pageFormats.hasOwnProperty(format)) { - width = pageFormats[format][0] / k; - height = pageFormats[format][1] / k; - } - } - if (Array.isArray(width)) { - height = width[1]; - width = width[0]; - } - if (orientation) { - switch (orientation.substr(0, 1)) { - case 'l': - if (height > width) orientation = 's'; - break; - case 'p': - if (width > height) orientation = 's'; - break; - } - if (orientation === 's') { - tmp = width; - width = height; - height = tmp; - } - } - outToPages = true; - pages[++page] = []; - pagedim[page] = { - width: Number(width) || pageWidth, - height: Number(height) || pageHeight - }; - pagesContext[page] = {}; - _setPage(page); - }, - _addPage = function _addPage() { - beginPage.apply(this, arguments); - // Set line width - out(f2(lineWidth * k) + ' w'); - // Set draw color - out(drawColor); - // resurrecting non-default line caps, joins - if (lineCapID !== 0) { - out(lineCapID + ' J'); - } - if (lineJoinID !== 0) { - out(lineJoinID + ' j'); - } - events.publish('addPage', { - pageNumber: page - }); - }, - _deletePage = function _deletePage(n) { - if (n > 0 && n <= page) { - pages.splice(n, 1); - pagedim.splice(n, 1); - page--; - if (currentPage > page) { - currentPage = page; - } - this.setPage(currentPage); - } - }, - _setPage = function _setPage(n) { - if (n > 0 && n <= page) { - currentPage = n; - pageWidth = pagedim[n].width; - pageHeight = pagedim[n].height; - } - }, - - /** - * Returns a document-specific font key - a label assigned to a - * font name + font type combination at the time the font was added - * to the font inventory. - * - * Font key is used as label for the desired font for a block of text - * to be added to the PDF document stream. - * @private - * @function - * @param fontName {String} can be undefined on "falthy" to indicate "use current" - * @param fontStyle {String} can be undefined on "falthy" to indicate "use current" - * @returns {String} Font key. - */ - _getFont = function _getFont(fontName, fontStyle) { - var key; - - fontName = fontName !== undefined ? fontName : fonts[activeFontKey].fontName; - fontStyle = fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; - - if (fontName !== undefined) { - fontName = fontName.toLowerCase(); - } - switch (fontName) { - case 'sans-serif': - case 'verdana': - case 'arial': - case 'helvetica': - fontName = 'helvetica'; - break; - case 'fixed': - case 'monospace': - case 'terminal': - case 'courier': - fontName = 'courier'; - break; - case 'serif': - case 'cursive': - case 'fantasy': - default: - fontName = 'times'; - break; - } - - try { - // get a string like 'F3' - the KEY corresponding tot he font + type combination. - key = fontmap[fontName][fontStyle]; - } catch (e) {} - - if (!key) { - //throw new Error("Unable to look up font label for font '" + fontName + "', '" - //+ fontStyle + "'. Refer to getFontList() for available fonts."); - key = fontmap['times'][fontStyle]; - if (key == null) { - key = fontmap['times']['normal']; - } - } - return key; - }, - buildDocument = function buildDocument() { - outToPages = false; // switches out() to content - - objectNumber = 2; - content_length = 0; - content = []; - offsets = []; - additionalObjects = []; - // Added for AcroForm - events.publish('buildDocument'); - - // putHeader() - out('%PDF-' + pdfVersion); - - putPages(); - - // Must happen after putPages - // Modifies current object Id - putAdditionalObjects(); - - putResources(); - - // Info - newObject(); - out('<<'); - putInfo(); - out('>>'); - out('endobj'); - - // Catalog - newObject(); - out('<<'); - putCatalog(); - out('>>'); - out('endobj'); - - // Cross-ref - var o = content_length, - i, - p = "0000000000"; - out('xref'); - out('0 ' + (objectNumber + 1)); - out(p + ' 65535 f '); - for (i = 1; i <= objectNumber; i++) { - var offset = offsets[i]; - if (typeof offset === 'function') { - out((p + offsets[i]()).slice(-10) + ' 00000 n '); - } else { - out((p + offsets[i]).slice(-10) + ' 00000 n '); - } - } - // Trailer - out('trailer'); - out('<<'); - putTrailer(); - out('>>'); - out('startxref'); - out('' + o); - out('%%EOF'); - - outToPages = true; - - return content.join('\n'); - }, - getStyle = function getStyle(style) { - // see path-painting operators in PDF spec - var op = 'S'; // stroke - if (style === 'F') { - op = 'f'; // fill - } else if (style === 'FD' || style === 'DF') { - op = 'B'; // both - } else if (style === 'f' || style === 'f*' || style === 'B' || style === 'B*') { - /* - Allow direct use of these PDF path-painting operators: - - f fill using nonzero winding number rule - - f* fill using even-odd rule - - B fill then stroke with fill using non-zero winding number rule - - B* fill then stroke with fill using even-odd rule - */ - op = style; - } - return op; - }, - getArrayBuffer = function getArrayBuffer() { - var data = buildDocument(), - len = data.length, - ab = new ArrayBuffer(len), - u8 = new Uint8Array(ab); - - while (len--) { - u8[len] = data.charCodeAt(len); - }return ab; - }, - getBlob = function getBlob() { - return new Blob([getArrayBuffer()], { - type: "application/pdf" - }); - }, - - /** - * Generates the PDF document. - * - * If `type` argument is undefined, output is raw body of resulting PDF returned as a string. - * - * @param {String} type A string identifying one of the possible output types. - * @param {Object} options An object providing some additional signalling to PDF generator. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name output - */ - _output = SAFE(function (type, options) { - var datauri = ('' + type).substr(0, 6) === 'dataur' ? 'data:application/pdf;base64,' + btoa(buildDocument()) : 0; - - switch (type) { - case undefined: - return buildDocument(); - case 'save': - if (navigator.getUserMedia) { - if (global.URL === undefined || global.URL.createObjectURL === undefined) { - return API.output('dataurlnewwindow'); - } - } - saveAs(getBlob(), options); - if (typeof saveAs.unload === 'function') { - if (global.setTimeout) { - setTimeout(saveAs.unload, 911); - } - } - break; - case 'arraybuffer': - return getArrayBuffer(); - case 'blob': - return getBlob(); - case 'bloburi': - case 'bloburl': - // User is responsible of calling revokeObjectURL - return global.URL && global.URL.createObjectURL(getBlob()) || void 0; - case 'datauristring': - case 'dataurlstring': - return datauri; - case 'dataurlnewwindow': - var nW = global.open(datauri); - if (nW || typeof safari === "undefined") return nW; - /* pass through */ - case 'datauri': - case 'dataurl': - return global.document.location.href = datauri; - default: - throw new Error('Output type "' + type + '" is not supported.'); - } - // @TODO: Add different output options - }); - - switch (unit) { - case 'pt': - k = 1; - break; - case 'mm': - k = 72 / 25.4000508; - break; - case 'cm': - k = 72 / 2.54000508; - break; - case 'in': - k = 72; - break; - case 'px': - k = 96 / 72; - break; - case 'pc': - k = 12; - break; - case 'em': - k = 12; - break; - case 'ex': - k = 6; - break; - default: - throw 'Invalid unit: ' + unit; - } - - //--------------------------------------- - // Public API - - /** - * Object exposing internal API to plugins - * @public - */ - API.internal = { - 'pdfEscape': pdfEscape, - 'getStyle': getStyle, - /** - * Returns {FontObject} describing a particular font. - * @public - * @function - * @param fontName {String} (Optional) Font's family name - * @param fontStyle {String} (Optional) Font's style variation name (Example:"Italic") - * @returns {FontObject} - */ - 'getFont': function getFont() { - return fonts[_getFont.apply(API, arguments)]; - }, - 'getFontSize': function getFontSize() { - return activeFontSize; - }, - 'getLineHeight': function getLineHeight() { - return activeFontSize * lineHeightProportion; - }, - 'write': function write(string1 /*, string2, string3, etc */) { - out(arguments.length === 1 ? string1 : Array.prototype.join.call(arguments, ' ')); - }, - 'getCoordinateString': function getCoordinateString(value) { - return f2(value * k); - }, - 'getVerticalCoordinateString': function getVerticalCoordinateString(value) { - return f2((pageHeight - value) * k); - }, - 'collections': {}, - 'newObject': newObject, - 'newAdditionalObject': newAdditionalObject, - 'newObjectDeferred': newObjectDeferred, - 'newObjectDeferredBegin': newObjectDeferredBegin, - 'putStream': putStream, - 'events': events, - // ratio that you use in multiplication of a given "size" number to arrive to 'point' - // units of measurement. - // scaleFactor is set at initialization of the document and calculated against the stated - // default measurement units for the document. - // If default is "mm", k is the number that will turn number in 'mm' into 'points' number. - // through multiplication. - 'scaleFactor': k, - 'pageSize': { - get width() { - return pageWidth; - }, - get height() { - return pageHeight; - } - }, - 'output': function output(type, options) { - return _output(type, options); - }, - 'getNumberOfPages': function getNumberOfPages() { - return pages.length - 1; - }, - 'pages': pages, - 'out': out, - 'f2': f2, - 'getPageInfo': function getPageInfo(pageNumberOneBased) { - var objId = (pageNumberOneBased - 1) * 2 + 3; - return { - objId: objId, - pageNumber: pageNumberOneBased, - pageContext: pagesContext[pageNumberOneBased] - }; - }, - 'getCurrentPageInfo': function getCurrentPageInfo() { - var objId = (currentPage - 1) * 2 + 3; - return { - objId: objId, - pageNumber: currentPage, - pageContext: pagesContext[currentPage] - }; - }, - 'getPDFVersion': function getPDFVersion() { - return pdfVersion; - } - }; - - /** - * Adds (and transfers the focus to) new page to the PDF document. - * @function - * @returns {jsPDF} - * - * @methodOf jsPDF# - * @name addPage - */ - API.addPage = function () { - _addPage.apply(this, arguments); - return this; - }; - /** - * Adds (and transfers the focus to) new page to the PDF document. - * @function - * @returns {jsPDF} - * - * @methodOf jsPDF# - * @name setPage - * @param {Number} page Switch the active page to the page number specified - * @example - * doc = jsPDF() - * doc.addPage() - * doc.addPage() - * doc.text('I am on page 3', 10, 10) - * doc.setPage(1) - * doc.text('I am on page 1', 10, 10) - */ - API.setPage = function () { - _setPage.apply(this, arguments); - return this; - }; - API.insertPage = function (beforePage) { - this.addPage(); - this.movePage(currentPage, beforePage); - return this; - }; - API.movePage = function (targetPage, beforePage) { - if (targetPage > beforePage) { - var tmpPages = pages[targetPage]; - var tmpPagedim = pagedim[targetPage]; - var tmpPagesContext = pagesContext[targetPage]; - for (var i = targetPage; i > beforePage; i--) { - pages[i] = pages[i - 1]; - pagedim[i] = pagedim[i - 1]; - pagesContext[i] = pagesContext[i - 1]; - } - pages[beforePage] = tmpPages; - pagedim[beforePage] = tmpPagedim; - pagesContext[beforePage] = tmpPagesContext; - this.setPage(beforePage); - } else if (targetPage < beforePage) { - var tmpPages = pages[targetPage]; - var tmpPagedim = pagedim[targetPage]; - var tmpPagesContext = pagesContext[targetPage]; - for (var i = targetPage; i < beforePage; i++) { - pages[i] = pages[i + 1]; - pagedim[i] = pagedim[i + 1]; - pagesContext[i] = pagesContext[i + 1]; - } - pages[beforePage] = tmpPages; - pagedim[beforePage] = tmpPagedim; - pagesContext[beforePage] = tmpPagesContext; - this.setPage(beforePage); - } - return this; - }; - - API.deletePage = function () { - _deletePage.apply(this, arguments); - return this; - }; - - /** - * Set the display mode options of the page like zoom and layout. - * - * @param {integer|String} zoom You can pass an integer or percentage as - * a string. 2 will scale the document up 2x, '200%' will scale up by the - * same amount. You can also set it to 'fullwidth', 'fullheight', - * 'fullpage', or 'original'. - * - * Only certain PDF readers support this, such as Adobe Acrobat - * - * @param {String} layout Layout mode can be: 'continuous' - this is the - * default continuous scroll. 'single' - the single page mode only shows one - * page at a time. 'twoleft' - two column left mode, first page starts on - * the left, and 'tworight' - pages are laid out in two columns, with the - * first page on the right. This would be used for books. - * @param {String} pmode 'UseOutlines' - it shows the - * outline of the document on the left. 'UseThumbs' - shows thumbnails along - * the left. 'FullScreen' - prompts the user to enter fullscreen mode. - * - * @function - * @returns {jsPDF} - * @name setDisplayMode - */ - API.setDisplayMode = function (zoom, layout, pmode) { - zoomMode = zoom; - layoutMode = layout; - pageMode = pmode; - - var validPageModes = [undefined, null, 'UseNone', 'UseOutlines', 'UseThumbs', 'FullScreen']; - if (validPageModes.indexOf(pmode) == -1) { - throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + pmode + '" is not recognized.'); - } - return this; - }, - - /** - * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings. - * - * @function - * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down per font, spacing settings declared before this call. - * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Object} flags Collection of settings signalling how the text must be encoded. Defaults are sane. If you think you want to pass some flags, you likely can read the source. - * @returns {jsPDF} - * @methodOf jsPDF# - * @name text - */ - API.text = function (text, x, y, flags, angle, align) { - /** - * Inserts something like this into PDF - * BT - * /F1 16 Tf % Font name + size - * 16 TL % How many units down for next line in multiline text - * 0 g % color - * 28.35 813.54 Td % position - * (line one) Tj - * T* (line two) Tj - * T* (line three) Tj - * ET - */ - function ESC(s) { - s = s.split("\t").join(Array(options.TabLen || 9).join(" ")); - return pdfEscape(s, flags); - } - - // Pre-August-2012 the order of arguments was function(x, y, text, flags) - // in effort to make all calls have similar signature like - // function(data, coordinates... , miscellaneous) - // this method had its args flipped. - // code below allows backward compatibility with old arg order. - if (typeof text === 'number') { - tmp = y; - y = x; - x = text; - text = tmp; - } - - // If there are any newlines in text, we assume - // the user wanted to print multiple lines, so break the - // text up into an array. If the text is already an array, - // we assume the user knows what they are doing. - // Convert text into an array anyway to simplify - // later code. - if (typeof text === 'string') { - if (text.match(/[\n\r]/)) { - text = text.split(/\r\n|\r|\n/g); - } else { - text = [text]; - } - } - if (typeof angle === 'string') { - align = angle; - angle = null; - } - if (typeof flags === 'string') { - align = flags; - flags = null; - } - if (typeof flags === 'number') { - angle = flags; - flags = null; - } - var xtra = '', - mode = 'Td', - todo; - if (angle) { - angle *= Math.PI / 180; - var c = Math.cos(angle), - s = Math.sin(angle); - xtra = [f2(c), f2(s), f2(s * -1), f2(c), ''].join(" "); - mode = 'Tm'; - } - flags = flags || {}; - if (!('noBOM' in flags)) flags.noBOM = true; - if (!('autoencode' in flags)) flags.autoencode = true; - - var strokeOption = ''; - var pageContext = this.internal.getCurrentPageInfo().pageContext; - if (true === flags.stroke) { - if (pageContext.lastTextWasStroke !== true) { - strokeOption = '1 Tr\n'; - pageContext.lastTextWasStroke = true; - } - } else { - if (pageContext.lastTextWasStroke) { - strokeOption = '0 Tr\n'; - } - pageContext.lastTextWasStroke = false; - } - - if (typeof this._runningPageHeight === 'undefined') { - this._runningPageHeight = 0; - } - - if (typeof text === 'string') { - text = ESC(text); - } else if (Object.prototype.toString.call(text) === '[object Array]') { - // we don't want to destroy original text array, so cloning it - var sa = text.concat(), - da = [], - len = sa.length; - // we do array.join('text that must not be PDFescaped") - // thus, pdfEscape each component separately - while (len--) { - da.push(ESC(sa.shift())); - } - var linesLeft = Math.ceil((pageHeight - y - this._runningPageHeight) * k / (activeFontSize * lineHeightProportion)); - if (0 <= linesLeft && linesLeft < da.length + 1) { - //todo = da.splice(linesLeft-1); - } - - if (align) { - var left, - prevX, - maxLineLength, - leading = activeFontSize * lineHeightProportion, - lineWidths = text.map(function (v) { - return this.getStringUnitWidth(v) * activeFontSize / k; - }, this); - maxLineLength = Math.max.apply(Math, lineWidths); - // The first line uses the "main" Td setting, - // and the subsequent lines are offset by the - // previous line's x coordinate. - if (align === "center") { - // The passed in x coordinate defines - // the center point. - left = x - maxLineLength / 2; - x -= lineWidths[0] / 2; - } else if (align === "right") { - // The passed in x coordinate defines the - // rightmost point of the text. - left = x - maxLineLength; - x -= lineWidths[0]; - } else { - throw new Error('Unrecognized alignment option, use "center" or "right".'); - } - prevX = x; - text = da[0]; - for (var i = 1, len = da.length; i < len; i++) { - var delta = maxLineLength - lineWidths[i]; - if (align === "center") delta /= 2; - // T* = x-offset leading Td ( text ) - text += ") Tj\n" + (left - prevX + delta) + " -" + leading + " Td (" + da[i]; - prevX = left + delta; - } - } else { - text = da.join(") Tj\nT* ("); - } - } else { - throw new Error('Type of text must be string or Array. "' + text + '" is not recognized.'); - } - // Using "'" ("go next line and render text" mark) would save space but would complicate our rendering code, templates - - // BT .. ET does NOT have default settings for Tf. You must state that explicitely every time for BT .. ET - // if you want text transformation matrix (+ multiline) to work reliably (which reads sizes of things from font declarations) - // Thus, there is NO useful, *reliable* concept of "default" font for a page. - // The fact that "default" (reuse font used before) font worked before in basic cases is an accident - // - readers dealing smartly with brokenness of jsPDF's markup. - - var curY; - - if (todo) { - //this.addPage(); - //this._runningPageHeight += y - (activeFontSize * 1.7 / k); - //curY = f2(pageHeight - activeFontSize * 1.7 /k); - } else { - curY = f2((pageHeight - y) * k); - } - //curY = f2((pageHeight - (y - this._runningPageHeight)) * k); - - // if (curY < 0){ - // console.log('auto page break'); - // this.addPage(); - // this._runningPageHeight = y - (activeFontSize * 1.7 / k); - // curY = f2(pageHeight - activeFontSize * 1.7 /k); - // } - - out('BT\n/' + activeFontKey + ' ' + activeFontSize + ' Tf\n' + // font face, style, size - activeFontSize * lineHeightProportion + ' TL\n' + // line spacing - strokeOption + // stroke option - textColor + '\n' + xtra + f2(x * k) + ' ' + curY + ' ' + mode + '\n(' + text + ') Tj\nET'); - - if (todo) { - //this.text( todo, x, activeFontSize * 1.7 / k); - //this.text( todo, x, this._runningPageHeight + (activeFontSize * 1.7 / k)); - this.text(todo, x, y); // + (activeFontSize * 1.7 / k)); - } - - return this; - }; - - /** - * Letter spacing method to print text with gaps - * - * @function - * @param {String|Array} text String to be added to the page. - * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} spacing Spacing (in units declared at inception) - * @returns {jsPDF} - * @methodOf jsPDF# - * @name lstext - * @deprecated We'll be removing this function. It doesn't take character width into account. - */ - API.lstext = function (text, x, y, spacing) { - console.warn('jsPDF.lstext is deprecated'); - for (var i = 0, len = text.length; i < len; i++, x += spacing) { - this.text(text[i], x, y); - }return this; - }; - - API.line = function (x1, y1, x2, y2) { - return this.lines([[x2 - x1, y2 - y1]], x1, y1); - }; - - API.clip = function () { - // By patrick-roberts, github.com/MrRio/jsPDF/issues/328 - // Call .clip() after calling .rect() with a style argument of null - out('W'); // clip - out('S'); // stroke path; necessary for clip to work - }; - - /** - * This fixes the previous function clip(). Perhaps the 'stroke path' hack was due to the missing 'n' instruction? - * We introduce the fixed version so as to not break API. - * @param fillRule - */ - API.clip_fixed = function (fillRule) { - // Call .clip() after calling drawing ops with a style argument of null - // W is the PDF clipping op - if ('evenodd' === fillRule) { - out('W*'); - } else { - out('W'); - } - // End the path object without filling or stroking it. - // This operator is a path-painting no-op, used primarily for the side effect of changing the current clipping path - // (see Section 4.4.3, “Clipping Path Operators”) - out('n'); - }; - - /** - * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates. - * All data points in `lines` are relative to last line origin. - * `x`, `y` become x1,y1 for first line / curve in the set. - * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point. - * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1. - * - * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, 10) // line, line, bezier curve, line - * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves). - * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction. - * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. - * @param {Boolean} closed If true, the path is closed with a straight line from the end of the last curve to the starting point. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name lines - */ - API.lines = function (lines, x, y, scale, style, closed) { - var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4; - - // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style) - // in effort to make all calls have similar signature like - // function(content, coordinateX, coordinateY , miscellaneous) - // this method had its args flipped. - // code below allows backward compatibility with old arg order. - if (typeof lines === 'number') { - tmp = y; - y = x; - x = lines; - lines = tmp; - } - - scale = scale || [1, 1]; - - // starting point - out(f3(x * k) + ' ' + f3((pageHeight - y) * k) + ' m '); - - scalex = scale[0]; - scaley = scale[1]; - l = lines.length; - //, x2, y2 // bezier only. In page default measurement "units", *after* scaling - //, x3, y3 // bezier only. In page default measurement "units", *after* scaling - // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling - x4 = x; // last / ending point = starting point for first item. - y4 = y; // last / ending point = starting point for first item. - - for (i = 0; i < l; i++) { - leg = lines[i]; - if (leg.length === 2) { - // simple line - x4 = leg[0] * scalex + x4; // here last x4 was prior ending point - y4 = leg[1] * scaley + y4; // here last y4 was prior ending point - out(f3(x4 * k) + ' ' + f3((pageHeight - y4) * k) + ' l'); - } else { - // bezier curve - x2 = leg[0] * scalex + x4; // here last x4 is prior ending point - y2 = leg[1] * scaley + y4; // here last y4 is prior ending point - x3 = leg[2] * scalex + x4; // here last x4 is prior ending point - y3 = leg[3] * scaley + y4; // here last y4 is prior ending point - x4 = leg[4] * scalex + x4; // here last x4 was prior ending point - y4 = leg[5] * scaley + y4; // here last y4 was prior ending point - out(f3(x2 * k) + ' ' + f3((pageHeight - y2) * k) + ' ' + f3(x3 * k) + ' ' + f3((pageHeight - y3) * k) + ' ' + f3(x4 * k) + ' ' + f3((pageHeight - y4) * k) + ' c'); - } - } - - if (closed) { - out(' h'); - } - - // stroking / filling / both the path - if (style !== null) { - out(getStyle(style)); - } - return this; - }; - - /** - * Adds a rectangle to PDF - * - * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} w Width (in units declared at inception of PDF document) - * @param {Number} h Height (in units declared at inception of PDF document) - * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name rect - */ - API.rect = function (x, y, w, h, style) { - var op = getStyle(style); - out([f2(x * k), f2((pageHeight - y) * k), f2(w * k), f2(-h * k), 're'].join(' ')); - - if (style !== null) { - out(getStyle(style)); - } - - return this; - }; - - /** - * Adds a triangle to PDF - * - * @param {Number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name triangle - */ - API.triangle = function (x1, y1, x2, y2, x3, y3, style) { - this.lines([[x2 - x1, y2 - y1], // vector to point 2 - [x3 - x2, y3 - y2], // vector to point 3 - [x1 - x3, y1 - y3] // closing vector back to point 1 - ], x1, y1, // start of path - [1, 1], style, true); - return this; - }; - - /** - * Adds a rectangle with rounded corners to PDF - * - * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} w Width (in units declared at inception of PDF document) - * @param {Number} h Height (in units declared at inception of PDF document) - * @param {Number} rx Radius along x axis (in units declared at inception of PDF document) - * @param {Number} rx Radius along y axis (in units declared at inception of PDF document) - * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name roundedRect - */ - API.roundedRect = function (x, y, w, h, rx, ry, style) { - var MyArc = 4 / 3 * (Math.SQRT2 - 1); - this.lines([[w - 2 * rx, 0], [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry], [0, h - 2 * ry], [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry], [-w + 2 * rx, 0], [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry], [0, -h + 2 * ry], [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry]], x + rx, y, // start of path - [1, 1], style); - return this; - }; - - /** - * Adds an ellipse to PDF - * - * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} rx Radius along x axis (in units declared at inception of PDF document) - * @param {Number} rx Radius along y axis (in units declared at inception of PDF document) - * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name ellipse - */ - API.ellipse = function (x, y, rx, ry, style) { - var lx = 4 / 3 * (Math.SQRT2 - 1) * rx, - ly = 4 / 3 * (Math.SQRT2 - 1) * ry; - - out([f2((x + rx) * k), f2((pageHeight - y) * k), 'm', f2((x + rx) * k), f2((pageHeight - (y - ly)) * k), f2((x + lx) * k), f2((pageHeight - (y - ry)) * k), f2(x * k), f2((pageHeight - (y - ry)) * k), 'c'].join(' ')); - out([f2((x - lx) * k), f2((pageHeight - (y - ry)) * k), f2((x - rx) * k), f2((pageHeight - (y - ly)) * k), f2((x - rx) * k), f2((pageHeight - y) * k), 'c'].join(' ')); - out([f2((x - rx) * k), f2((pageHeight - (y + ly)) * k), f2((x - lx) * k), f2((pageHeight - (y + ry)) * k), f2(x * k), f2((pageHeight - (y + ry)) * k), 'c'].join(' ')); - out([f2((x + lx) * k), f2((pageHeight - (y + ry)) * k), f2((x + rx) * k), f2((pageHeight - (y + ly)) * k), f2((x + rx) * k), f2((pageHeight - y) * k), 'c'].join(' ')); - - if (style !== null) { - out(getStyle(style)); - } - - return this; - }; - - /** - * Adds an circle to PDF - * - * @param {Number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {Number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {Number} r Radius (in units declared at inception of PDF document) - * @param {String} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name circle - */ - API.circle = function (x, y, r, style) { - return this.ellipse(x, y, r, r, style); - }; - - /** - * Adds a properties to the PDF document - * - * @param {Object} A property_name-to-property_value object structure. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setProperties - */ - API.setProperties = function (properties) { - // copying only those properties we can render. - for (var property in documentProperties) { - if (documentProperties.hasOwnProperty(property) && properties[property]) { - documentProperties[property] = properties[property]; - } - } - return this; - }; - - /** - * Sets font size for upcoming text elements. - * - * @param {Number} size Font size in points. - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setFontSize - */ - API.setFontSize = function (size) { - activeFontSize = size; - return this; - }; - - /** - * Sets text font face, variant for upcoming text elements. - * See output of jsPDF.getFontList() for possible font names, styles. - * - * @param {String} fontName Font name or family. Example: "times" - * @param {String} fontStyle Font style or variant. Example: "italic" - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setFont - */ - API.setFont = function (fontName, fontStyle) { - activeFontKey = _getFont(fontName, fontStyle); - // if font is not found, the above line blows up and we never go further - return this; - }; - - /** - * Switches font style or variant for upcoming text elements, - * while keeping the font face or family same. - * See output of jsPDF.getFontList() for possible font names, styles. - * - * @param {String} style Font style or variant. Example: "italic" - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setFontStyle - */ - API.setFontStyle = API.setFontType = function (style) { - activeFontKey = _getFont(undefined, style); - // if font is not found, the above line blows up and we never go further - return this; - }; - - /** - * Returns an object - a tree of fontName to fontStyle relationships available to - * active PDF document. - * - * @public - * @function - * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... } - * @methodOf jsPDF# - * @name getFontList - */ - API.getFontList = function () { - // TODO: iterate over fonts array or return copy of fontmap instead in case more are ever added. - var list = {}, - fontName, - fontStyle, - tmp; - - for (fontName in fontmap) { - if (fontmap.hasOwnProperty(fontName)) { - list[fontName] = tmp = []; - for (fontStyle in fontmap[fontName]) { - if (fontmap[fontName].hasOwnProperty(fontStyle)) { - tmp.push(fontStyle); - } - } - } - } - - return list; - }; - - /** - * Add a custom font. - * - * @param {String} Postscript name of the Font. Example: "Menlo-Regular" - * @param {String} Name of font-family from @font-face definition. Example: "Menlo Regular" - * @param {String} Font style. Example: "normal" - * @function - * @returns the {fontKey} (same as the internal method) - * @methodOf jsPDF# - * @name addFont - */ - API.addFont = function (postScriptName, fontName, fontStyle) { - addFont(postScriptName, fontName, fontStyle, 'StandardEncoding'); - }; - - /** - * Sets line width for upcoming lines. - * - * @param {Number} width Line width (in units declared at inception of PDF document) - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setLineWidth - */ - API.setLineWidth = function (width) { - out((width * k).toFixed(2) + ' w'); - return this; - }; - - /** - * Sets the stroke color for upcoming elements. - * - * Depending on the number of arguments given, Gray, RGB, or CMYK - * color space is implied. - * - * When only ch1 is given, "Gray" color space is implied and it - * must be a value in the range from 0.00 (solid black) to to 1.00 (white) - * if values are communicated as String types, or in range from 0 (black) - * to 255 (white) if communicated as Number type. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each - * value must be in the range from 0.00 (minimum intensity) to to 1.00 - * (max intensity) if values are communicated as String types, or - * from 0 (min intensity) to to 255 (max intensity) if values are communicated - * as Number types. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each - * value must be a in the range from 0.00 (0% concentration) to to - * 1.00 (100% concentration) - * - * Because JavaScript treats fixed point numbers badly (rounds to - * floating point nearest to binary representation) it is highly advised to - * communicate the fractional numbers as String types, not JavaScript Number type. - * - * @param {Number|String} ch1 Color channel value - * @param {Number|String} ch2 Color channel value - * @param {Number|String} ch3 Color channel value - * @param {Number|String} ch4 Color channel value - * - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setDrawColor - */ - API.setDrawColor = function (ch1, ch2, ch3, ch4) { - var color; - if (ch2 === undefined || ch4 === undefined && ch1 === ch2 === ch3) { - // Gray color space. - if (typeof ch1 === 'string') { - color = ch1 + ' G'; - } else { - color = f2(ch1 / 255) + ' G'; - } - } else if (ch4 === undefined) { - // RGB - if (typeof ch1 === 'string') { - color = [ch1, ch2, ch3, 'RG'].join(' '); - } else { - color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'RG'].join(' '); - } - } else { - // CMYK - if (typeof ch1 === 'string') { - color = [ch1, ch2, ch3, ch4, 'K'].join(' '); - } else { - color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'K'].join(' '); - } - } - - out(color); - return this; - }; - - /** - * Sets the fill color for upcoming elements. - * - * Depending on the number of arguments given, Gray, RGB, or CMYK - * color space is implied. - * - * When only ch1 is given, "Gray" color space is implied and it - * must be a value in the range from 0.00 (solid black) to to 1.00 (white) - * if values are communicated as String types, or in range from 0 (black) - * to 255 (white) if communicated as Number type. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each - * value must be in the range from 0.00 (minimum intensity) to to 1.00 - * (max intensity) if values are communicated as String types, or - * from 0 (min intensity) to to 255 (max intensity) if values are communicated - * as Number types. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each - * value must be a in the range from 0.00 (0% concentration) to to - * 1.00 (100% concentration) - * - * Because JavaScript treats fixed point numbers badly (rounds to - * floating point nearest to binary representation) it is highly advised to - * communicate the fractional numbers as String types, not JavaScript Number type. - * - * @param {Number|String} ch1 Color channel value - * @param {Number|String} ch2 Color channel value - * @param {Number|String} ch3 Color channel value - * @param {Number|String} ch4 Color channel value - * - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setFillColor - */ - API.setFillColor = function (ch1, ch2, ch3, ch4) { - var color; - - if (ch2 === undefined || ch4 === undefined && ch1 === ch2 === ch3) { - // Gray color space. - if (typeof ch1 === 'string') { - color = ch1 + ' g'; - } else { - color = f2(ch1 / 255) + ' g'; - } - } else if (ch4 === undefined || (typeof ch4 === 'undefined' ? 'undefined' : _typeof(ch4)) === 'object') { - // RGB - if (typeof ch1 === 'string') { - color = [ch1, ch2, ch3, 'rg'].join(' '); - } else { - color = [f2(ch1 / 255), f2(ch2 / 255), f2(ch3 / 255), 'rg'].join(' '); - } - if (ch4 && ch4.a === 0) { - //TODO Implement transparency. - //WORKAROUND use white for now - color = ['255', '255', '255', 'rg'].join(' '); - } - } else { - // CMYK - if (typeof ch1 === 'string') { - color = [ch1, ch2, ch3, ch4, 'k'].join(' '); - } else { - color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), 'k'].join(' '); - } - } - - out(color); - return this; - }; - - /** - * Sets the text color for upcoming elements. - * If only one, first argument is given, - * treats the value as gray-scale color value. - * - * @param {Number} r Red channel color value in range 0-255 or {String} r color value in hexadecimal, example: '#FFFFFF' - * @param {Number} g Green channel color value in range 0-255 - * @param {Number} b Blue channel color value in range 0-255 - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setTextColor - */ - API.setTextColor = function (r, g, b) { - if (typeof r === 'string' && /^#[0-9A-Fa-f]{6}$/.test(r)) { - var hex = parseInt(r.substr(1), 16); - r = hex >> 16 & 255; - g = hex >> 8 & 255; - b = hex & 255; - } - - if (r === 0 && g === 0 && b === 0 || typeof g === 'undefined') { - textColor = f3(r / 255) + ' g'; - } else { - textColor = [f3(r / 255), f3(g / 255), f3(b / 255), 'rg'].join(' '); - } - return this; - }; - - /** - * Is an Object providing a mapping from human-readable to - * integer flag values designating the varieties of line cap - * and join styles. - * - * @returns {Object} - * @fieldOf jsPDF# - * @name CapJoinStyles - */ - API.CapJoinStyles = { - 0: 0, - 'butt': 0, - 'but': 0, - 'miter': 0, - 1: 1, - 'round': 1, - 'rounded': 1, - 'circle': 1, - 2: 2, - 'projecting': 2, - 'project': 2, - 'square': 2, - 'bevel': 2 - }; - - /** - * Sets the line cap styles - * See {jsPDF.CapJoinStyles} for variants - * - * @param {String|Number} style A string or number identifying the type of line cap - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setLineCap - */ - API.setLineCap = function (style) { - var id = this.CapJoinStyles[style]; - if (id === undefined) { - throw new Error("Line cap style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"); - } - lineCapID = id; - out(id + ' J'); - - return this; - }; - - /** - * Sets the line join styles - * See {jsPDF.CapJoinStyles} for variants - * - * @param {String|Number} style A string or number identifying the type of line join - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name setLineJoin - */ - API.setLineJoin = function (style) { - var id = this.CapJoinStyles[style]; - if (id === undefined) { - throw new Error("Line join style of '" + style + "' is not recognized. See or extend .CapJoinStyles property for valid styles"); - } - lineJoinID = id; - out(id + ' j'); - - return this; - }; - - // Output is both an internal (for plugins) and external function - API.output = _output; - - /** - * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf') - * @param {String} filename The filename including extension. - * - * @function - * @returns {jsPDF} - * @methodOf jsPDF# - * @name save - */ - API.save = function (filename) { - API.output('save', filename); - }; - - // applying plugins (more methods) ON TOP of built-in API. - // this is intentional as we allow plugins to override - // built-ins - for (var plugin in jsPDF.API) { - if (jsPDF.API.hasOwnProperty(plugin)) { - if (plugin === 'events' && jsPDF.API.events.length) { - (function (events, newEvents) { - - // jsPDF.API.events is a JS Array of Arrays - // where each Array is a pair of event name, handler - // Events were added by plugins to the jsPDF instantiator. - // These are always added to the new instance and some ran - // during instantiation. - var eventname, handler_and_args, i; - - for (i = newEvents.length - 1; i !== -1; i--) { - // subscribe takes 3 args: 'topic', function, runonce_flag - // if undefined, runonce is false. - // users can attach callback directly, - // or they can attach an array with [callback, runonce_flag] - // that's what the "apply" magic is for below. - eventname = newEvents[i][0]; - handler_and_args = newEvents[i][1]; - events.subscribe.apply(events, [eventname].concat(typeof handler_and_args === 'function' ? [handler_and_args] : handler_and_args)); - } - })(events, jsPDF.API.events); - } else { - API[plugin] = jsPDF.API[plugin]; - } - } - } - - ////////////////////////////////////////////////////// - // continuing initialization of jsPDF Document object - ////////////////////////////////////////////////////// - // Add the first page automatically - addFonts(); - activeFontKey = 'F1'; - _addPage(format, orientation); - - events.publish('initialized'); - return API; - } - - /** - * jsPDF.API is a STATIC property of jsPDF class. - * jsPDF.API is an object you can add methods and properties to. - * The methods / properties you add will show up in new jsPDF objects. - * - * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics, - * callbacks to this object. These will be reassigned to all new instances of jsPDF. - * Examples: - * jsPDF.API.events['initialized'] = function(){ 'this' is API object } - * jsPDF.API.events['addFont'] = function(added_font_object){ 'this' is API object } - * - * @static - * @public - * @memberOf jsPDF - * @name API - * - * @example - * jsPDF.API.mymethod = function(){ - * // 'this' will be ref to internal API object. see jsPDF source - * // , so you can refer to built-in methods like so: - * // this.line(....) - * // this.text(....) - * } - * var pdfdoc = new jsPDF() - * pdfdoc.mymethod() // <- !!!!!! - */ - jsPDF.API = { - events: [] - }; - jsPDF.version = "1.x-master"; - - if (typeof define === 'function' && define.amd) { - define('jsPDF', function () { - return jsPDF; - }); - } else if (typeof module !== 'undefined' && module.exports) { - module.exports = jsPDF; - } else { - global.jsPDF = jsPDF; - } - return jsPDF; -}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || undefined); - - -/** - * jsPDF AcroForm Plugin - * Copyright (c) 2016 Alexander Weidt, https://github.com/BiggA94 - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -(window.AcroForm = function (jsPDFAPI) { - 'use strict'; - - var AcroForm = window.AcroForm; - - AcroForm.scale = function (x) { - return x * (acroformPlugin.internal.scaleFactor / 1); // 1 = (96 / 72) - }; - AcroForm.antiScale = function (x) { - return 1 / acroformPlugin.internal.scaleFactor * x; - }; - - var acroformPlugin = { - fields: [], - xForms: [], - /** - * acroFormDictionaryRoot contains information about the AcroForm Dictionary - * 0: The Event-Token, the AcroFormDictionaryCallback has - * 1: The Object ID of the Root - */ - acroFormDictionaryRoot: null, - /** - * After the PDF gets evaluated, the reference to the root has to be reset, - * this indicates, whether the root has already been printed out - */ - printedOut: false, - internal: null - }; - - jsPDF.API.acroformPlugin = acroformPlugin; - - var annotReferenceCallback = function annotReferenceCallback() { - for (var i in this.acroformPlugin.acroFormDictionaryRoot.Fields) { - var formObject = this.acroformPlugin.acroFormDictionaryRoot.Fields[i]; - // add Annot Reference! - if (formObject.hasAnnotation) { - // If theres an Annotation Widget in the Form Object, put the Reference in the /Annot array - createAnnotationReference.call(this, formObject); - } - } - }; - - var createAcroForm = function createAcroForm() { - if (this.acroformPlugin.acroFormDictionaryRoot) { - //return; - throw new Error("Exception while creating AcroformDictionary"); - } - - // The Object Number of the AcroForm Dictionary - this.acroformPlugin.acroFormDictionaryRoot = new AcroForm.AcroFormDictionary(); - - this.acroformPlugin.internal = this.internal; - - // add Callback for creating the AcroForm Dictionary - this.acroformPlugin.acroFormDictionaryRoot._eventID = this.internal.events.subscribe('postPutResources', AcroFormDictionaryCallback); - - this.internal.events.subscribe('buildDocument', annotReferenceCallback); //buildDocument - - // Register event, that is triggered when the DocumentCatalog is written, in order to add /AcroForm - this.internal.events.subscribe('putCatalog', putCatalogCallback); - - // Register event, that creates all Fields - this.internal.events.subscribe('postPutPages', createFieldCallback); - }; - - /** - * Create the Reference to the widgetAnnotation, so that it gets referenced in the Annot[] int the+ - * (Requires the Annotation Plugin) - */ - var createAnnotationReference = function createAnnotationReference(object) { - var options = { - type: 'reference', - object: object - }; - jsPDF.API.annotationPlugin.annotations[this.internal.getPageInfo(object.page).pageNumber].push(options); - }; - - var putForm = function putForm(formObject) { - if (this.acroformPlugin.printedOut) { - this.acroformPlugin.printedOut = false; - this.acroformPlugin.acroFormDictionaryRoot = null; - } - if (!this.acroformPlugin.acroFormDictionaryRoot) { - createAcroForm.call(this); - } - this.acroformPlugin.acroFormDictionaryRoot.Fields.push(formObject); - }; - - // Callbacks - - var putCatalogCallback = function putCatalogCallback() { - //Put reference to AcroForm to DocumentCatalog - if (typeof this.acroformPlugin.acroFormDictionaryRoot != 'undefined') { - // for safety, shouldn't normally be the case - this.internal.write('/AcroForm ' + this.acroformPlugin.acroFormDictionaryRoot.objId + ' ' + 0 + ' R'); - } else { - console.log('Root missing...'); - } - }; - - /** - * Adds /Acroform X 0 R to Document Catalog, - * and creates the AcroForm Dictionary - */ - var AcroFormDictionaryCallback = function AcroFormDictionaryCallback() { - // Remove event - this.internal.events.unsubscribe(this.acroformPlugin.acroFormDictionaryRoot._eventID); - - delete this.acroformPlugin.acroFormDictionaryRoot._eventID; - - this.acroformPlugin.printedOut = true; - }; - - /** - * Creates the single Fields and writes them into the Document - * - * If fieldArray is set, use the fields that are inside it instead of the fields from the AcroRoot - * (for the FormXObjects...) - */ - var createFieldCallback = function createFieldCallback(fieldArray) { - var standardFields = !fieldArray; - - if (!fieldArray) { - // in case there is no fieldArray specified, we want to print out the Fields of the AcroForm - // Print out Root - this.internal.newObjectDeferredBegin(this.acroformPlugin.acroFormDictionaryRoot.objId); - this.internal.out(this.acroformPlugin.acroFormDictionaryRoot.getString()); - } - - var fieldArray = fieldArray || this.acroformPlugin.acroFormDictionaryRoot.Kids; - - for (var i in fieldArray) { - var key = i; - var form = fieldArray[i]; - - var oldRect = form.Rect; - - if (form.Rect) { - form.Rect = AcroForm.internal.calculateCoordinates.call(this, form.Rect); - } - - // Start Writing the Object - this.internal.newObjectDeferredBegin(form.objId); - - var content = ""; - content += form.objId + " 0 obj\n"; - - content += "<<\n" + form.getContent(); - - form.Rect = oldRect; - - if (form.hasAppearanceStream && !form.appearanceStreamContent) { - // Calculate Appearance - var appearance = AcroForm.internal.calculateAppearanceStream.call(this, form); - content += "/AP << /N " + appearance + " >>\n"; - - this.acroformPlugin.xForms.push(appearance); - } - - // Assume AppearanceStreamContent is a Array with N,R,D (at least one of them!) - if (form.appearanceStreamContent) { - content += "/AP << "; - // Iterate over N,R and D - for (var k in form.appearanceStreamContent) { - var value = form.appearanceStreamContent[k]; - content += "/" + k + " "; - content += "<< "; - if (Object.keys(value).length >= 1 || Array.isArray(value)) { - // appearanceStream is an Array or Object! - for (var i in value) { - var obj = value[i]; - if (typeof obj === 'function') { - // if Function is referenced, call it in order to get the FormXObject - obj = obj.call(this, form); - } - content += "/" + i + " " + obj + " "; - - // In case the XForm is already used, e.g. OffState of CheckBoxes, don't add it - if (!(this.acroformPlugin.xForms.indexOf(obj) >= 0)) this.acroformPlugin.xForms.push(obj); - } - } else { - var obj = value; - if (typeof obj === 'function') { - // if Function is referenced, call it in order to get the FormXObject - obj = obj.call(this, form); - } - content += "/" + i + " " + obj + " \n"; - if (!(this.acroformPlugin.xForms.indexOf(obj) >= 0)) this.acroformPlugin.xForms.push(obj); - } - content += " >>\n"; - } - - // appearance stream is a normal Object.. - content += ">>\n"; - } - - content += ">>\nendobj\n"; - - this.internal.out(content); - } - if (standardFields) { - createXFormObjectCallback.call(this, this.acroformPlugin.xForms); - } - }; - - var createXFormObjectCallback = function createXFormObjectCallback(fieldArray) { - for (var i in fieldArray) { - var key = i; - var form = fieldArray[i]; - // Start Writing the Object - this.internal.newObjectDeferredBegin(form && form.objId); - - var content = ""; - content += form ? form.getString() : ''; - this.internal.out(content); - - delete fieldArray[key]; - } - }; - - // Public: - - jsPDFAPI.addField = function (fieldObject) { - //var opt = parseOptions(fieldObject); - if (fieldObject instanceof AcroForm.TextField) { - addTextField.call(this, fieldObject); - } else if (fieldObject instanceof AcroForm.ChoiceField) { - addChoiceField.call(this, fieldObject); - } else if (fieldObject instanceof AcroForm.Button) { - addButton.call(this, fieldObject); - } else if (fieldObject instanceof AcroForm.ChildClass) { - putForm.call(this, fieldObject); - } else if (fieldObject) { - // try to put.. - putForm.call(this, fieldObject); - } - fieldObject.page = this.acroformPlugin.internal.getCurrentPageInfo().pageNumber; - return this; - }; - - // ############### sort in: - - /** - * Button - * FT = Btn - */ - var addButton = function addButton(options) { - var options = options || new AcroForm.Field(); - - options.FT = '/Btn'; - - /** - * Calculating the Ff entry: - * - * The Ff entry contains flags, that have to be set bitwise - * In the Following the number in the Comment is the BitPosition - */ - var flags = options.Ff || 0; - - // 17, Pushbutton - if (options.pushbutton) { - // Options.pushbutton should be 1 or 0 - flags = AcroForm.internal.setBitPosition(flags, 17); - delete options.pushbutton; - } - - //16, Radio - if (options.radio) { - //flags = options.Ff | options.radio << 15; - flags = AcroForm.internal.setBitPosition(flags, 16); - delete options.radio; - } - - // 15, NoToggleToOff (Radio buttons only - if (options.noToggleToOff) { - //flags = options.Ff | options.noToggleToOff << 14; - flags = AcroForm.internal.setBitPosition(flags, 15); - //delete options.noToggleToOff; - } - - // In case, there is no Flag set, it is a check-box - options.Ff = flags; - - putForm.call(this, options); - }; - - var addTextField = function addTextField(options) { - var options = options || new AcroForm.Field(); - - options.FT = '/Tx'; - - /** - * Calculating the Ff entry: - * - * The Ff entry contains flags, that have to be set bitwise - * In the Following the number in the Comment is the BitPosition - */ - - var flags = options.Ff || 0; - - // 13, multiline - if (options.multiline) { - // Set Flag - flags = flags | 1 << 12; - // Remove multiline from FieldObject - //delete options.multiline; - } - - // 14, Password - if (options.password) { - flags = flags | 1 << 13; - //delete options.password; - } - - // 21, FileSelect, PDF 1.4... - if (options.fileSelect) { - flags = flags | 1 << 20; - //delete options.fileSelect; - } - - // 23, DoNotSpellCheck, PDF 1.4... - if (options.doNotSpellCheck) { - flags = flags | 1 << 22; - //delete options.doNotSpellCheck; - } - - // 24, DoNotScroll, PDF 1.4... - if (options.doNotScroll) { - flags = flags | 1 << 23; - //delete options.doNotScroll; - } - - options.Ff = options.Ff || flags; - - // Add field - putForm.call(this, options); - }; - - var addChoiceField = function addChoiceField(opt) { - var options = opt || new AcroForm.Field(); - - options.FT = '/Ch'; - - /** - * Calculating the Ff entry: - * - * The Ff entry contains flags, that have to be set bitwise - * In the Following the number in the Comment is the BitPosition - */ - - var flags = options.Ff || 0; - - // 18, Combo (If not set, the choiceField is a listBox!!) - if (options.combo) { - // Set Flag - flags = AcroForm.internal.setBitPosition(flags, 18); - // Remove combo from FieldObject - delete options.combo; - } - - // 19, Edit - if (options.edit) { - flags = AcroForm.internal.setBitPosition(flags, 19); - delete options.edit; - } - - // 20, Sort - if (options.sort) { - flags = AcroForm.internal.setBitPosition(flags, 20); - delete options.sort; - } - - // 22, MultiSelect (PDF 1.4) - if (options.multiSelect && this.internal.getPDFVersion() >= 1.4) { - flags = AcroForm.internal.setBitPosition(flags, 22); - delete options.multiSelect; - } - - // 23, DoNotSpellCheck (PDF 1.4) - if (options.doNotSpellCheck && this.internal.getPDFVersion() >= 1.4) { - flags = AcroForm.internal.setBitPosition(flags, 23); - delete options.doNotSpellCheck; - } - - options.Ff = flags; - - //options.hasAnnotation = true; - - // Add field - putForm.call(this, options); - }; -})(jsPDF.API); - -var AcroForm = window.AcroForm; - -AcroForm.internal = {}; - -AcroForm.createFormXObject = function (formObject) { - var xobj = new AcroForm.FormXObject(); - var height = AcroForm.Appearance.internal.getHeight(formObject) || 0; - var width = AcroForm.Appearance.internal.getWidth(formObject) || 0; - xobj.BBox = [0, 0, width, height]; - return xobj; -}; - -// Contains Methods for creating standard appearances -AcroForm.Appearance = { - CheckBox: { - createAppearanceStream: function createAppearanceStream() { - var appearance = { - N: { - On: AcroForm.Appearance.CheckBox.YesNormal - }, - D: { - On: AcroForm.Appearance.CheckBox.YesPushDown, - Off: AcroForm.Appearance.CheckBox.OffPushDown - } - }; - - return appearance; - }, - /** - * If any other icons are needed, the number between the brackets can be changed - * @returns {string} - */ - createMK: function createMK() { - // 3-> Hook - return "<< /CA (3)>>"; - }, - /** - * Returns the standard On Appearance for a CheckBox - * @returns {AcroForm.FormXObject} - */ - YesPushDown: function YesPushDown(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - // F13 is ZapfDingbats (Symbolic) - formObject.Q = 1; // set text-alignment as centered - var calcRes = AcroForm.internal.calculateX(formObject, "3", "ZapfDingbats", 50); - stream += "0.749023 g\n\ - 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\ - f\n\ - BMC\n\ - q\n\ - 0 0 1 rg\n\ - /F13 " + calcRes.fontSize + " Tf 0 g\n\ - BT\n"; - stream += calcRes.text; - stream += "ET\n\ - Q\n\ - EMC\n"; - xobj.stream = stream; - return xobj; - }, - - YesNormal: function YesNormal(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - formObject.Q = 1; // set text-alignment as centered - var calcRes = AcroForm.internal.calculateX(formObject, "3", "ZapfDingbats", AcroForm.Appearance.internal.getHeight(formObject) * 0.9); - stream += "1 g\n\ -0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\ -f\n\ -q\n\ -0 0 1 rg\n\ -0 0 " + (AcroForm.Appearance.internal.getWidth(formObject) - 1) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 1) + " re\n\ -W\n\ -n\n\ -0 g\n\ -BT\n\ -/F13 " + calcRes.fontSize + " Tf 0 g\n"; - stream += calcRes.text; - stream += "ET\n\ - Q\n"; - xobj.stream = stream; - return xobj; - }, - - /** - * Returns the standard Off Appearance for a CheckBox - * @returns {AcroForm.FormXObject} - */ - OffPushDown: function OffPushDown(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - stream += "0.749023 g\n\ - 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\ - f\n"; - xobj.stream = stream; - return xobj; - } - }, - - RadioButton: { - Circle: { - createAppearanceStream: function createAppearanceStream(name) { - var appearanceStreamContent = { - D: { - 'Off': AcroForm.Appearance.RadioButton.Circle.OffPushDown - }, - N: {} - }; - appearanceStreamContent.N[name] = AcroForm.Appearance.RadioButton.Circle.YesNormal; - appearanceStreamContent.D[name] = AcroForm.Appearance.RadioButton.Circle.YesPushDown; - return appearanceStreamContent; - }, - createMK: function createMK() { - return "<< /CA (l)>>"; - }, - - YesNormal: function YesNormal(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - // Make the Radius of the Circle relative to min(height, width) of formObject - var DotRadius = AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject) ? AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4; - // The Borderpadding... - DotRadius *= 0.9; - var c = AcroForm.Appearance.internal.Bezier_C; - /* - The Following is a Circle created with Bezier-Curves. - */ - stream += "q\n\ -1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\ -" + DotRadius + " 0 m\n\ -" + DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c\n\ --" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c\n\ --" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c\n\ -" + DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c\n\ -f\n\ -Q\n"; - xobj.stream = stream; - return xobj; - }, - YesPushDown: function YesPushDown(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - var DotRadius = AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject) ? AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4; - // The Borderpadding... - DotRadius *= 0.9; - // Save results for later use; no need to waste processor ticks on doing math - var k = DotRadius * 2; - // var c = AcroForm.Appearance.internal.Bezier_C; - var kc = k * AcroForm.Appearance.internal.Bezier_C; - var dc = DotRadius * AcroForm.Appearance.internal.Bezier_C; - // stream += "0.749023 g\n\ - // q\n\ - // 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\ - // " + DotRadius * 2 + " 0 m\n\ - // " + DotRadius * 2 + " " + DotRadius * 2 * c + " " + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 " + DotRadius * 2 + " c\n\ - // -" + DotRadius * 2 * c + " " + DotRadius * 2 + " -" + DotRadius * 2 + " " + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 c\n\ - // -" + DotRadius * 2 + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 -" + DotRadius * 2 + " c\n\ - // " + DotRadius * 2 * c + " -" + DotRadius * 2 + " " + DotRadius * 2 + " -" + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 c\n\ - // f\n\ - // Q\n\ - // 0 g\n\ - // q\n\ - // 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\ - // " + DotRadius + " 0 m\n\ - // " + DotRadius + " " + DotRadius * c + " " + DotRadius * c + " " + DotRadius + " 0 " + DotRadius + " c\n\ - // -" + DotRadius * c + " " + DotRadius + " -" + DotRadius + " " + DotRadius * c + " -" + DotRadius + " 0 c\n\ - // -" + DotRadius + " -" + DotRadius * c + " -" + DotRadius * c + " -" + DotRadius + " 0 -" + DotRadius + " c\n\ - // " + DotRadius * c + " -" + DotRadius + " " + DotRadius + " -" + DotRadius * c + " " + DotRadius + " 0 c\n\ - // f\n\ - // Q\n"; - - // FASTER VERSION with less processor ticks spent on math operations - stream += "0.749023 g\n\ - q\n\ - 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\ -" + k + " 0 m\n\ -" + k + " " + kc + " " + kc + " " + k + " 0 " + k + " c\n\ --" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c\n\ --" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c\n\ -" + kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c\n\ - f\n\ - Q\n\ - 0 g\n\ - q\n\ - 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\ -" + DotRadius + " 0 m\n\ -" + DotRadius + " " + dc + " " + dc + " " + DotRadius + " 0 " + DotRadius + " c\n\ --" + dc + " " + DotRadius + " -" + DotRadius + " " + dc + " -" + DotRadius + " 0 c\n\ --" + DotRadius + " -" + dc + " -" + dc + " -" + DotRadius + " 0 -" + DotRadius + " c\n\ -" + dc + " -" + DotRadius + " " + DotRadius + " -" + dc + " " + DotRadius + " 0 c\n\ - f\n\ - Q\n"; - xobj.stream = stream; - return xobj; - }, - OffPushDown: function OffPushDown(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - var DotRadius = AcroForm.Appearance.internal.getWidth(formObject) <= AcroForm.Appearance.internal.getHeight(formObject) ? AcroForm.Appearance.internal.getWidth(formObject) / 4 : AcroForm.Appearance.internal.getHeight(formObject) / 4; - // The Borderpadding... - DotRadius *= 0.9; - // Save results for later use; no need to waste processor ticks on doing math - var k = DotRadius * 2; - // var c = AcroForm.Appearance.internal.Bezier_C; - var kc = k * AcroForm.Appearance.internal.Bezier_C; - // stream += "0.749023 g\n\ - // q\n\ - // 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\ - // " + DotRadius * 2 + " 0 m\n\ - // " + DotRadius * 2 + " " + DotRadius * 2 * c + " " + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 " + DotRadius * 2 + " c\n\ - // -" + DotRadius * 2 * c + " " + DotRadius * 2 + " -" + DotRadius * 2 + " " + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 c\n\ - // -" + DotRadius * 2 + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 * c + " -" + DotRadius * 2 + " 0 -" + DotRadius * 2 + " c\n\ - // " + DotRadius * 2 * c + " -" + DotRadius * 2 + " " + DotRadius * 2 + " -" + DotRadius * 2 * c + " " + DotRadius * 2 + " 0 c\n\ - // f\n\ - // Q\n"; - - // FASTER VERSION with less processor ticks spent on math operations - stream += "0.749023 g\n\ - q\n\ - 1 0 0 1 " + AcroForm.Appearance.internal.getWidth(formObject) / 2 + " " + AcroForm.Appearance.internal.getHeight(formObject) / 2 + " cm\n\ -" + k + " 0 m\n\ -" + k + " " + kc + " " + kc + " " + k + " 0 " + k + " c\n\ --" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c\n\ --" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c\n\ -" + kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c\n\ - f\n\ - Q\n"; - xobj.stream = stream; - return xobj; - } - }, - - Cross: { - /** - * Creates the Actual AppearanceDictionary-References - * @param name - * @returns - */ - createAppearanceStream: function createAppearanceStream(name) { - var appearanceStreamContent = { - D: { - 'Off': AcroForm.Appearance.RadioButton.Cross.OffPushDown - }, - N: {} - }; - appearanceStreamContent.N[name] = AcroForm.Appearance.RadioButton.Cross.YesNormal; - appearanceStreamContent.D[name] = AcroForm.Appearance.RadioButton.Cross.YesPushDown; - return appearanceStreamContent; - }, - createMK: function createMK() { - return "<< /CA (8)>>"; - }, - - YesNormal: function YesNormal(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - var cross = AcroForm.Appearance.internal.calculateCross(formObject); - stream += "q\n\ - 1 1 " + (AcroForm.Appearance.internal.getWidth(formObject) - 2) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 2) + " re\n\ - W\n\ - n\n\ - " + cross.x1.x + " " + cross.x1.y + " m\n\ - " + cross.x2.x + " " + cross.x2.y + " l\n\ - " + cross.x4.x + " " + cross.x4.y + " m\n\ - " + cross.x3.x + " " + cross.x3.y + " l\n\ - s\n\ - Q\n"; - xobj.stream = stream; - return xobj; - }, - YesPushDown: function YesPushDown(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var cross = AcroForm.Appearance.internal.calculateCross(formObject); - var stream = ""; - stream += "0.749023 g\n\ - 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\ - f\n\ - q\n\ - 1 1 " + (AcroForm.Appearance.internal.getWidth(formObject) - 2) + " " + (AcroForm.Appearance.internal.getHeight(formObject) - 2) + " re\n\ - W\n\ - n\n\ - " + cross.x1.x + " " + cross.x1.y + " m\n\ - " + cross.x2.x + " " + cross.x2.y + " l\n\ - " + cross.x4.x + " " + cross.x4.y + " m\n\ - " + cross.x3.x + " " + cross.x3.y + " l\n\ - s\n\ - Q\n"; - xobj.stream = stream; - return xobj; - }, - OffPushDown: function OffPushDown(formObject) { - var xobj = AcroForm.createFormXObject(formObject); - var stream = ""; - stream += "0.749023 g\n\ - 0 0 " + AcroForm.Appearance.internal.getWidth(formObject) + " " + AcroForm.Appearance.internal.getHeight(formObject) + " re\n\ - f\n"; - xobj.stream = stream; - return xobj; - } - } - }, - - /** - * Returns the standard Appearance - * @returns {AcroForm.FormXObject} - */ - createDefaultAppearanceStream: function createDefaultAppearanceStream(formObject) { - var stream = ""; - // Set Helvetica to Standard Font (size: auto) - // Color: Black - stream += "/Helv 0 Tf 0 g"; - return stream; - } -}; - -AcroForm.Appearance.internal = { - Bezier_C: 0.551915024494, - - calculateCross: function calculateCross(formObject) { - var min = function min(x, y) { - return x > y ? y : x; - }; - - var width = AcroForm.Appearance.internal.getWidth(formObject); - var height = AcroForm.Appearance.internal.getHeight(formObject); - var a = min(width, height); - var crossSize = a; - var borderPadding = 2; // The Padding in px - - - var cross = { - x1: { // upperLeft - x: (width - a) / 2, - y: (height - a) / 2 + a }, - x2: { // lowerRight - x: (width - a) / 2 + a, - y: (height - a) / 2 //borderPadding - }, - x3: { // lowerLeft - x: (width - a) / 2, - y: (height - a) / 2 //borderPadding - }, - x4: { // upperRight - x: (width - a) / 2 + a, - y: (height - a) / 2 + a } - }; - - return cross; - } -}; -AcroForm.Appearance.internal.getWidth = function (formObject) { - return formObject.Rect[2]; //(formObject.Rect[2] - formObject.Rect[0]) || 0; -}; -AcroForm.Appearance.internal.getHeight = function (formObject) { - return formObject.Rect[3]; //(formObject.Rect[1] - formObject.Rect[3]) || 0; -}; - -// ########################## - -//### For inheritance: -AcroForm.internal.inherit = function (child, parent) { - var ObjectCreate = Object.create || function (o) { - var F = function F() {}; - F.prototype = o; - return new F(); - }; - child.prototype = Object.create(parent.prototype); - child.prototype.constructor = child; -}; - -// ### Handy Functions: - -AcroForm.internal.arrayToPdfArray = function (array) { - if (Array.isArray(array)) { - var content = ' ['; - for (var i in array) { - var element = array[i].toString(); - content += element; - content += i < array.length - 1 ? ' ' : ''; - } - content += ']'; - - return content; - } -}; - -AcroForm.internal.toPdfString = function (string) { - string = string || ""; - - // put Bracket at the Beginning of the String - if (string.indexOf('(') !== 0) { - string = '(' + string; - } - - if (string.substring(string.length - 1) != ')') { - string += '('; - } - return string; -}; - -// ########################## -// Classes -// ########################## - - -AcroForm.PDFObject = function () { - // The Object ID in the PDF Object Model - // todo - var _objId; - Object.defineProperty(this, 'objId', { - get: function get() { - if (!_objId) { - if (this.internal) { - _objId = this.internal.newObjectDeferred(); - } else if (jsPDF.API.acroformPlugin.internal) { - // todo - find better option, that doesn't rely on a Global Static var - _objId = jsPDF.API.acroformPlugin.internal.newObjectDeferred(); - } - } - if (!_objId) { - console.log("Couldn't create Object ID"); - } - return _objId; - }, - configurable: false - }); -}; - -AcroForm.PDFObject.prototype.toString = function () { - return this.objId + " 0 R"; -}; - -AcroForm.PDFObject.prototype.getString = function () { - var res = this.objId + " 0 obj\n<<"; - var content = this.getContent(); - - res += content + ">>\n"; - if (this.stream) { - res += "stream\n"; - res += this.stream; - res += "endstream\n"; - } - res += "endobj\n"; - return res; -}; - -AcroForm.PDFObject.prototype.getContent = function () { - /** - * Prints out all enumerable Variables from the Object - * @param fieldObject - * @returns {string} - */ - var createContentFromFieldObject = function createContentFromFieldObject(fieldObject) { - var content = ''; - - var keys = Object.keys(fieldObject).filter(function (key) { - return key != 'content' && key != 'appearanceStreamContent' && key.substring(0, 1) != "_"; - }); - - for (var i in keys) { - var key = keys[i]; - var value = fieldObject[key]; - - /*if (key == 'Rect' && value) { - value = AcroForm.internal.calculateCoordinates.call(jsPDF.API.acroformPlugin.internal, value); - }*/ - - if (value) { - if (Array.isArray(value)) { - content += '/' + key + ' ' + AcroForm.internal.arrayToPdfArray(value) + "\n"; - } else if (value instanceof AcroForm.PDFObject) { - // In case it is a reference to another PDFObject, take the referennce number - content += '/' + key + ' ' + value.objId + " 0 R" + "\n"; - } else { - content += '/' + key + ' ' + value + '\n'; - } - } - } - return content; - }; - - var object = ""; - - object += createContentFromFieldObject(this); - return object; -}; - -AcroForm.FormXObject = function () { - AcroForm.PDFObject.call(this); - this.Type = "/XObject"; - this.Subtype = "/Form"; - this.FormType = 1; - this.BBox; - this.Matrix; - this.Resources = "2 0 R"; - this.PieceInfo; - var _stream; - Object.defineProperty(this, 'Length', { - enumerable: true, - get: function get() { - return _stream !== undefined ? _stream.length : 0; - } - }); - Object.defineProperty(this, 'stream', { - enumerable: false, - set: function set(val) { - _stream = val; - }, - get: function get() { - if (_stream) { - return _stream; - } else { - return null; - } - } - }); -}; - -AcroForm.internal.inherit(AcroForm.FormXObject, AcroForm.PDFObject); - -AcroForm.AcroFormDictionary = function () { - AcroForm.PDFObject.call(this); - var _Kids = []; - Object.defineProperty(this, 'Kids', { - enumerable: false, - configurable: true, - get: function get() { - if (_Kids.length > 0) { - return _Kids; - } else { - return; - } - } - }); - Object.defineProperty(this, 'Fields', { - enumerable: true, - configurable: true, - get: function get() { - return _Kids; - } - }); - // Default Appearance - this.DA; -}; - -AcroForm.internal.inherit(AcroForm.AcroFormDictionary, AcroForm.PDFObject); - -// ##### The Objects, the User can Create: - - -// The Field Object contains the Variables, that every Field needs -// Rectangle for Appearance: lower_left_X, lower_left_Y, width, height -AcroForm.Field = function () { - 'use strict'; - - AcroForm.PDFObject.call(this); - - var _Rect; - Object.defineProperty(this, 'Rect', { - enumerable: true, - configurable: false, - get: function get() { - if (!_Rect) { - return; - } - var tmp = _Rect; - //var calculatedRes = AcroForm.internal.calculateCoordinates(_Rect); // do later! - return tmp; - }, - set: function set(val) { - _Rect = val; - } - }); - - var _FT = ""; - Object.defineProperty(this, 'FT', { - enumerable: true, - set: function set(val) { - _FT = val; - }, - get: function get() { - return _FT; - } - }); - /** - * The Partial name of the Field Object. - * It has to be unique. - */ - var _T; - - Object.defineProperty(this, 'T', { - enumerable: true, - configurable: false, - set: function set(val) { - _T = val; - }, - get: function get() { - if (!_T || _T.length < 1) { - if (this instanceof AcroForm.ChildClass) { - // In case of a Child from a Radio´Group, you don't need a FieldName!!! - return; - } - return "(FieldObject" + AcroForm.Field.FieldNum++ + ")"; - } - if (_T.substring(0, 1) == "(" && _T.substring(_T.length - 1)) { - return _T; - } - return "(" + _T + ")"; - } - }); - - var _DA; - // Defines the default appearance (Needed for variable Text) - Object.defineProperty(this, 'DA', { - enumerable: true, - get: function get() { - if (!_DA) { - return; - } - return '(' + _DA + ')'; - }, - set: function set(val) { - _DA = val; - } - }); - - var _DV; - // Defines the default value - Object.defineProperty(this, 'DV', { - enumerable: true, - configurable: true, - get: function get() { - if (!_DV) { - return; - } - return _DV; - }, - set: function set(val) { - _DV = val; - } - }); - - //this.Type = "/Annot"; - //this.Subtype = "/Widget"; - Object.defineProperty(this, 'Type', { - enumerable: true, - get: function get() { - return this.hasAnnotation ? "/Annot" : null; - } - }); - - Object.defineProperty(this, 'Subtype', { - enumerable: true, - get: function get() { - return this.hasAnnotation ? "/Widget" : null; - } - }); - - /** - * - * @type {Array} - */ - this.BG; - - Object.defineProperty(this, 'hasAnnotation', { - enumerable: false, - get: function get() { - if (this.Rect || this.BC || this.BG) { - return true; - } - return false; - } - }); - - Object.defineProperty(this, 'hasAppearanceStream', { - enumerable: false, - configurable: true, - writable: true - }); - - Object.defineProperty(this, 'page', { - enumerable: false, - configurable: true, - writable: true - }); -}; -AcroForm.Field.FieldNum = 0; - -AcroForm.internal.inherit(AcroForm.Field, AcroForm.PDFObject); - -AcroForm.ChoiceField = function () { - AcroForm.Field.call(this); - // Field Type = Choice Field - this.FT = "/Ch"; - // options - this.Opt = []; - this.V = '()'; - // Top Index - this.TI = 0; - /** - * Defines, whether the - * @type {boolean} - */ - this.combo = false; - /** - * Defines, whether the Choice Field is an Edit Field. - * An Edit Field is automatically an Combo Field. - */ - Object.defineProperty(this, 'edit', { - enumerable: true, - set: function set(val) { - if (val == true) { - this._edit = true; - // ComboBox has to be true - this.combo = true; - } else { - this._edit = false; - } - }, - get: function get() { - if (!this._edit) { - return false; - } - return this._edit; - }, - configurable: false - }); - this.hasAppearanceStream = true; - Object.defineProperty(this, 'V', { - get: function get() { - AcroForm.internal.toPdfString(); - } - }); -}; -AcroForm.internal.inherit(AcroForm.ChoiceField, AcroForm.Field); -window["ChoiceField"] = AcroForm.ChoiceField; - -AcroForm.ListBox = function () { - AcroForm.ChoiceField.call(this); - //var combo = true; -}; -AcroForm.internal.inherit(AcroForm.ListBox, AcroForm.ChoiceField); -window["ListBox"] = AcroForm.ListBox; - -AcroForm.ComboBox = function () { - AcroForm.ListBox.call(this); - this.combo = true; -}; -AcroForm.internal.inherit(AcroForm.ComboBox, AcroForm.ListBox); -window["ComboBox"] = AcroForm.ComboBox; - -AcroForm.EditBox = function () { - AcroForm.ComboBox.call(this); - this.edit = true; -}; -AcroForm.internal.inherit(AcroForm.EditBox, AcroForm.ComboBox); -window["EditBox"] = AcroForm.EditBox; - -AcroForm.Button = function () { - AcroForm.Field.call(this); - this.FT = "/Btn"; - //this.hasAnnotation = true; -}; -AcroForm.internal.inherit(AcroForm.Button, AcroForm.Field); -window["Button"] = AcroForm.Button; - -AcroForm.PushButton = function () { - AcroForm.Button.call(this); - this.pushbutton = true; -}; -AcroForm.internal.inherit(AcroForm.PushButton, AcroForm.Button); -window["PushButton"] = AcroForm.PushButton; - -AcroForm.RadioButton = function () { - AcroForm.Button.call(this); - this.radio = true; - var _Kids = []; - Object.defineProperty(this, 'Kids', { - enumerable: true, - get: function get() { - if (_Kids.length > 0) { - return _Kids; - } - } - }); - - Object.defineProperty(this, '__Kids', { - get: function get() { - return _Kids; - } - }); - - var _noToggleToOff; - - Object.defineProperty(this, 'noToggleToOff', { - enumerable: false, - get: function get() { - return _noToggleToOff; - }, - set: function set(val) { - _noToggleToOff = val; - } - }); - - //this.hasAnnotation = false; -}; -AcroForm.internal.inherit(AcroForm.RadioButton, AcroForm.Button); -window["RadioButton"] = AcroForm.RadioButton; - -/* - * The Child classs of a RadioButton (the radioGroup) - * -> The single Buttons - */ -AcroForm.ChildClass = function (parent, name) { - AcroForm.Field.call(this); - this.Parent = parent; - - // todo: set AppearanceType as variable that can be set from the outside... - this._AppearanceType = AcroForm.Appearance.RadioButton.Circle; // The Default appearanceType is the Circle - this.appearanceStreamContent = this._AppearanceType.createAppearanceStream(name); - - // Set Print in the Annot Flag - this.F = AcroForm.internal.setBitPosition(this.F, 3, 1); - - // Set AppearanceCharacteristicsDictionary with default appearance if field is not interacting with user - this.MK = this._AppearanceType.createMK(); // (8) -> Cross, (1)-> Circle, ()-> nothing - - // Default Appearance is Off - this.AS = "/Off"; // + name; - - this._Name = name; -}; -AcroForm.internal.inherit(AcroForm.ChildClass, AcroForm.Field); - -AcroForm.RadioButton.prototype.setAppearance = function (appearance) { - if (!('createAppearanceStream' in appearance && 'createMK' in appearance)) { - console.log("Couldn't assign Appearance to RadioButton. Appearance was Invalid!"); - return; - } - for (var i in this.__Kids) { - var child = this.__Kids[i]; - - child.appearanceStreamContent = appearance.createAppearanceStream(child._Name); - child.MK = appearance.createMK(); - } -}; - -AcroForm.RadioButton.prototype.createOption = function (name) { - var parent = this; - var kidCount = this.__Kids.length; - - // Create new Child for RadioGroup - var child = new AcroForm.ChildClass(parent, name); - // Add to Parent - this.__Kids.push(child); - - jsPDF.API.addField(child); - - return child; -}; - -AcroForm.CheckBox = function () { - Button.call(this); - this.appearanceStreamContent = AcroForm.Appearance.CheckBox.createAppearanceStream(); - this.MK = AcroForm.Appearance.CheckBox.createMK(); - this.AS = "/On"; - this.V = "/On"; -}; -AcroForm.internal.inherit(AcroForm.CheckBox, AcroForm.Button); -window["CheckBox"] = AcroForm.CheckBox; - -AcroForm.TextField = function () { - AcroForm.Field.call(this); - this.DA = AcroForm.Appearance.createDefaultAppearanceStream(); - this.F = 4; - var _V; - Object.defineProperty(this, 'V', { - get: function get() { - if (_V) { - return "(" + _V + ")"; - } else { - return _V; - } - }, - enumerable: true, - set: function set(val) { - _V = val; - } - }); - - var _DV; - Object.defineProperty(this, 'DV', { - get: function get() { - if (_DV) { - return "(" + _DV + ")"; - } else { - return _DV; - } - }, - enumerable: true, - set: function set(val) { - _DV = val; - } - }); - - var _multiline = false; - Object.defineProperty(this, 'multiline', { - enumerable: false, - get: function get() { - return _multiline; - }, - set: function set(val) { - _multiline = val; - } - }); - - //this.multiline = false; - //this.password = false; - /** - * For PDF 1.4 - * @type {boolean} - */ - //this.fileSelect = false; - /** - * For PDF 1.4 - * @type {boolean} - */ - //this.doNotSpellCheck = false; - /** - * For PDF 1.4 - * @type {boolean} - */ - //this.doNotScroll = false; - - var _MaxLen = false; - Object.defineProperty(this, 'MaxLen', { - enumerable: true, - get: function get() { - return _MaxLen; - }, - set: function set(val) { - _MaxLen = val; - } - }); - - Object.defineProperty(this, 'hasAppearanceStream', { - enumerable: false, - get: function get() { - return this.V || this.DV; - } - }); -}; -AcroForm.internal.inherit(AcroForm.TextField, AcroForm.Field); -window["TextField"] = AcroForm.TextField; - -AcroForm.PasswordField = function () { - TextField.call(this); - Object.defineProperty(this, 'password', { - value: true, - enumerable: false, - configurable: false, - writable: false - }); -}; -AcroForm.internal.inherit(AcroForm.PasswordField, AcroForm.TextField); -window["PasswordField"] = AcroForm.PasswordField; - -// ############ internal functions - -/* - * small workaround for calculating the TextMetric approximately - * @param text - * @param fontsize - * @returns {TextMetrics} (Has Height and Width) - */ -AcroForm.internal.calculateFontSpace = function (text, fontsize, fonttype) { - var fonttype = fonttype || "helvetica"; - //re-use canvas object for speed improvements - var canvas = AcroForm.internal.calculateFontSpace.canvas || (AcroForm.internal.calculateFontSpace.canvas = document.createElement('canvas')); - - var context = canvas.getContext('2d'); - context.save(); - var newFont = fontsize + " " + fonttype; - context.font = newFont; - var res = context.measureText(text); - context.fontcolor = 'black'; - // Calculate height: - var context = canvas.getContext('2d'); - res.height = context.measureText("3").width * 1.5; // 3 because in ZapfDingbats its a Hook and a 3 in normal fonts - context.restore(); - - var width = res.width; - - return res; -}; - -AcroForm.internal.calculateX = function (formObject, text, font, maxFontSize) { - var maxFontSize = maxFontSize || 12; - var font = font || "helvetica"; - var returnValue = { - text: "", - fontSize: "" - }; - // Remove Brackets - text = text.substr(0, 1) == '(' ? text.substr(1) : text; - text = text.substr(text.length - 1) == ')' ? text.substr(0, text.length - 1) : text; - // split into array of words - var textSplit = text.split(' '); - - /** - * the color could be ((alpha)||(r,g,b)||(c,m,y,k)) - * @type {string} - */ - var color = "0 g\n"; - var fontSize = maxFontSize; // The Starting fontSize (The Maximum) - var lineSpacing = 2; - var borderPadding = 2; - - var height = AcroForm.Appearance.internal.getHeight(formObject) || 0; - height = height < 0 ? -height : height; - var width = AcroForm.Appearance.internal.getWidth(formObject) || 0; - width = width < 0 ? -width : width; - - var isSmallerThanWidth = function isSmallerThanWidth(i, lastLine, fontSize) { - if (i + 1 < textSplit.length) { - var tmp = lastLine + " " + textSplit[i + 1]; - var TextWidth = AcroForm.internal.calculateFontSpace(tmp, fontSize + "px", font).width; - var FieldWidth = width - 2 * borderPadding; - return TextWidth <= FieldWidth; - } else { - return false; - } - }; - - fontSize++; - FontSize: while (true) { - var text = ""; - fontSize--; - var textHeight = AcroForm.internal.calculateFontSpace("3", fontSize + "px", font).height; - var startY = formObject.multiline ? height - fontSize : (height - textHeight) / 2; - startY += lineSpacing; - var startX = -borderPadding; - - var lastX = startX, - lastY = startY; - var firstWordInLine = 0, - lastWordInLine = 0; - var lastLength = 0; - - var y = 0; - if (fontSize == 0) { - // In case, the Text doesn't fit at all - fontSize = 12; - text = "(...) Tj\n"; - text += "% Width of Text: " + AcroForm.internal.calculateFontSpace(text, "1px").width + ", FieldWidth:" + width + "\n"; - break; - } - - lastLength = AcroForm.internal.calculateFontSpace(textSplit[0] + " ", fontSize + "px", font).width; - - var lastLine = ""; - var lineCount = 0; - Line: for (var i in textSplit) { - lastLine += textSplit[i] + " "; - // Remove last blank - lastLine = lastLine.substr(lastLine.length - 1) == " " ? lastLine.substr(0, lastLine.length - 1) : lastLine; - var key = parseInt(i); - lastLength = AcroForm.internal.calculateFontSpace(lastLine + " ", fontSize + "px", font).width; - var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize); - var isLastWord = i >= textSplit.length - 1; - if (nextLineIsSmaller && !isLastWord) { - lastLine += " "; - continue; // Line - } else if (!nextLineIsSmaller && !isLastWord) { - if (!formObject.multiline) { - continue FontSize; - } else { - if ((textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) { - // If the Text is higher than the FieldObject - continue FontSize; - } - lastWordInLine = key; - // go on - } - } else if (isLastWord) { - lastWordInLine = key; - } else { - if (formObject.multiline && (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > height) { - // If the Text is higher than the FieldObject - continue FontSize; - } - } - - var line = ''; - - for (var x = firstWordInLine; x <= lastWordInLine; x++) { - line += textSplit[x] + ' '; - } - - // Remove last blank - line = line.substr(line.length - 1) == " " ? line.substr(0, line.length - 1) : line; - //lastLength -= blankSpace.width; - lastLength = AcroForm.internal.calculateFontSpace(line, fontSize + "px", font).width; - - // Calculate startX - switch (formObject.Q) { - case 2: - // Right justified - startX = width - lastLength - borderPadding; - break; - case 1: - // Q = 1 := Text-Alignment: Center - startX = (width - lastLength) / 2; - break; - case 0: - default: - startX = borderPadding; - break; - } - text += startX + ' ' + lastY + ' Td\n'; - text += '(' + line + ') Tj\n'; - // reset X in PDF - text += -startX + ' 0 Td\n'; - - // After a Line, adjust y position - lastY = -(fontSize + lineSpacing); - lastX = startX; - - // Reset for next iteration step - lastLength = 0; - firstWordInLine = lastWordInLine + 1; - lineCount++; - - lastLine = ""; - continue Line; - } - break; - } - - returnValue.text = text; - returnValue.fontSize = fontSize; - - return returnValue; -}; - -AcroForm.internal.calculateAppearanceStream = function (formObject) { - if (formObject.appearanceStreamContent) { - // If appearanceStream is already set, use it - return formObject.appearanceStreamContent; - } - - if (!formObject.V && !formObject.DV) { - return; - } - - // else calculate it - - var stream = ''; - - var text = formObject.V || formObject.DV; - - var calcRes = AcroForm.internal.calculateX(formObject, text); - - stream += '/Tx BMC\n' + 'q\n' + - //color + '\n' + - '/F1 ' + calcRes.fontSize + ' Tf\n' + - // Text Matrix - '1 0 0 1 0 0 Tm\n'; - // Begin Text - stream += 'BT\n'; - stream += calcRes.text; - // End Text - stream += 'ET\n'; - stream += 'Q\n' + 'EMC\n'; - - var appearanceStreamContent = new AcroForm.createFormXObject(formObject); - - appearanceStreamContent.stream = stream; - - var appearance = { - N: { - 'Normal': appearanceStreamContent - } - }; - - return appearanceStreamContent; -}; - -/* - * Converts the Parameters from x,y,w,h to lowerLeftX, lowerLeftY, upperRightX, upperRightY - * @param x - * @param y - * @param w - * @param h - * @returns {*[]} - */ -AcroForm.internal.calculateCoordinates = function (x, y, w, h) { - var coordinates = {}; - - if (this.internal) { - var mmtopx = function mmtopx(x) { - return x * this.internal.scaleFactor; - }; - - if (Array.isArray(x)) { - x[0] = AcroForm.scale(x[0]); - x[1] = AcroForm.scale(x[1]); - x[2] = AcroForm.scale(x[2]); - x[3] = AcroForm.scale(x[3]); - - coordinates.lowerLeft_X = x[0] || 0; - coordinates.lowerLeft_Y = mmtopx.call(this, this.internal.pageSize.height) - x[3] - x[1] || 0; - coordinates.upperRight_X = x[0] + x[2] || 0; - coordinates.upperRight_Y = mmtopx.call(this, this.internal.pageSize.height) - x[1] || 0; - } else { - x = AcroForm.scale(x); - y = AcroForm.scale(y); - w = AcroForm.scale(w); - h = AcroForm.scale(h); - coordinates.lowerLeft_X = x || 0; - coordinates.lowerLeft_Y = this.internal.pageSize.height - y || 0; - coordinates.upperRight_X = x + w || 0; - coordinates.upperRight_Y = this.internal.pageSize.height - y + h || 0; - } - } else { - // old method, that is fallback, if we can't get the pageheight, the coordinate-system starts from lower left - if (Array.isArray(x)) { - coordinates.lowerLeft_X = x[0] || 0; - coordinates.lowerLeft_Y = x[1] || 0; - coordinates.upperRight_X = x[0] + x[2] || 0; - coordinates.upperRight_Y = x[1] + x[3] || 0; - } else { - coordinates.lowerLeft_X = x || 0; - coordinates.lowerLeft_Y = y || 0; - coordinates.upperRight_X = x + w || 0; - coordinates.upperRight_Y = y + h || 0; - } - } - - return [coordinates.lowerLeft_X, coordinates.lowerLeft_Y, coordinates.upperRight_X, coordinates.upperRight_Y]; -}; - -AcroForm.internal.calculateColor = function (r, g, b) { - var color = new Array(3); - color.r = r | 0; - color.g = g | 0; - color.b = b | 0; - return color; -}; - -AcroForm.internal.getBitPosition = function (variable, position) { - variable = variable || 0; - var bitMask = 1; - bitMask = bitMask << position - 1; - return variable | bitMask; -}; - -AcroForm.internal.setBitPosition = function (variable, position, value) { - variable = variable || 0; - value = value || 1; - - var bitMask = 1; - bitMask = bitMask << position - 1; - - if (value == 1) { - // Set the Bit to 1 - var variable = variable | bitMask; - } else { - // Set the Bit to 0 - var variable = variable & ~bitMask; - } - - return variable; -}; - -/** - * jsPDF addHTML PlugIn - * Copyright (c) 2014 Diego Casorran - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -(function (jsPDFAPI) { - 'use strict'; - - /** - * Renders an HTML element to canvas object which added to the PDF - * - * This feature requires [html2canvas](https://github.com/niklasvh/html2canvas) - * or [rasterizeHTML](https://github.com/cburgmer/rasterizeHTML.js) - * - * @returns {jsPDF} - * @name addHTML - * @param element {Mixed} HTML Element, or anything supported by html2canvas. - * @param x {Number} starting X coordinate in jsPDF instance's declared units. - * @param y {Number} starting Y coordinate in jsPDF instance's declared units. - * @param options {Object} Additional options, check the code below. - * @param callback {Function} to call when the rendering has finished. - * NOTE: Every parameter is optional except 'element' and 'callback', in such - * case the image is positioned at 0x0 covering the whole PDF document - * size. Ie, to easily take screenshots of webpages saving them to PDF. - * @deprecated This is being replace with a vector-supporting API. See - * [this link](https://cdn.rawgit.com/MrRio/jsPDF/master/examples/html2pdf/showcase_supported_html.html) - */ - - jsPDFAPI.addHTML = function (element, x, y, options, callback) { - 'use strict'; - - if (typeof html2canvas === 'undefined' && typeof rasterizeHTML === 'undefined') throw new Error('You need either ' + 'https://github.com/niklasvh/html2canvas' + ' or https://github.com/cburgmer/rasterizeHTML.js'); - - if (typeof x !== 'number') { - options = x; - callback = y; - } - - if (typeof options === 'function') { - callback = options; - options = null; - } - - var I = this.internal, - K = I.scaleFactor, - W = I.pageSize.width, - H = I.pageSize.height; - - options = options || {}; - options.onrendered = function (obj) { - x = parseInt(x) || 0; - y = parseInt(y) || 0; - var dim = options.dim || {}; - var h = dim.h || 0; - var w = dim.w || Math.min(W, obj.width / K) - x; - - var format = 'JPEG'; - if (options.format) format = options.format; - - if (obj.height > H && options.pagesplit) { - var crop = function () { - var cy = 0; - while (1) { - var canvas = document.createElement('canvas'); - canvas.width = Math.min(W * K, obj.width); - canvas.height = Math.min(H * K, obj.height - cy); - var ctx = canvas.getContext('2d'); - ctx.drawImage(obj, 0, cy, obj.width, canvas.height, 0, 0, canvas.width, canvas.height); - var args = [canvas, x, cy ? 0 : y, canvas.width / K, canvas.height / K, format, null, 'SLOW']; - this.addImage.apply(this, args); - cy += canvas.height; - if (cy >= obj.height) break; - this.addPage(); - } - callback(w, cy, null, args); - }.bind(this); - if (obj.nodeName === 'CANVAS') { - var img = new Image(); - img.onload = crop; - img.src = obj.toDataURL("image/png"); - obj = img; - } else { - crop(); - } - } else { - var alias = Math.random().toString(35); - var args = [obj, x, y, w, h, format, alias, 'SLOW']; - - this.addImage.apply(this, args); - - callback(w, h, alias, args); - } - }.bind(this); - - if (typeof html2canvas !== 'undefined' && !options.rstz) { - return html2canvas(element, options); - } - - if (typeof rasterizeHTML !== 'undefined') { - var meth = 'drawDocument'; - if (typeof element === 'string') { - meth = /^http/.test(element) ? 'drawURL' : 'drawHTML'; - } - options.width = options.width || W * K; - return rasterizeHTML[meth](element, void 0, options).then(function (r) { - options.onrendered(r.image); - }, function (e) { - callback(null, e); - }); - } - - return null; - }; -})(jsPDF.API); - -/** @preserve - * jsPDF addImage plugin - * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ - * 2013 Chris Dowling, https://github.com/gingerchris - * 2013 Trinh Ho, https://github.com/ineedfat - * 2013 Edwin Alejandro Perez, https://github.com/eaparango - * 2013 Norah Smith, https://github.com/burnburnrocket - * 2014 Diego Casorran, https://github.com/diegocr - * 2014 James Robb, https://github.com/jamesbrobb - * - * - */ - -(function (jsPDFAPI) { - 'use strict'; - - var namespace = 'addImage_', - supported_image_types = ['jpeg', 'jpg', 'png']; - - // Image functionality ported from pdf.js - var putImage = function putImage(img) { - - var objectNumber = this.internal.newObject(), - out = this.internal.write, - putStream = this.internal.putStream; - - img['n'] = objectNumber; - - out('<>'); - } - if ('trns' in img && img['trns'].constructor == Array) { - var trns = '', - i = 0, - len = img['trns'].length; - for (; i < len; i++) { - trns += img['trns'][i] + ' ' + img['trns'][i] + ' '; - }out('/Mask [' + trns + ']'); - } - if ('smask' in img) { - out('/SMask ' + (objectNumber + 1) + ' 0 R'); - } - out('/Length ' + img['data'].length + '>>'); - - putStream(img['data']); - - out('endobj'); - - // Soft mask - if ('smask' in img) { - var dp = '/Predictor ' + img['p'] + ' /Colors 1 /BitsPerComponent ' + img['bpc'] + ' /Columns ' + img['w']; - var smask = { 'w': img['w'], 'h': img['h'], 'cs': 'DeviceGray', 'bpc': img['bpc'], 'dp': dp, 'data': img['smask'] }; - if ('f' in img) smask.f = img['f']; - putImage.call(this, smask); - } - - //Palette - if (img['cs'] === this.color_spaces.INDEXED) { - - this.internal.newObject(); - //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>'); - //putStream(zlib.compress(img['pal'])); - out('<< /Length ' + img['pal'].length + '>>'); - putStream(this.arrayBufferToBinaryString(new Uint8Array(img['pal']))); - out('endobj'); - } - }, - putResourcesCallback = function putResourcesCallback() { - var images = this.internal.collections[namespace + 'images']; - for (var i in images) { - putImage.call(this, images[i]); - } - }, - putXObjectsDictCallback = function putXObjectsDictCallback() { - var images = this.internal.collections[namespace + 'images'], - out = this.internal.write, - image; - for (var i in images) { - image = images[i]; - out('/I' + image['i'], image['n'], '0', 'R'); - } - }, - checkCompressValue = function checkCompressValue(value) { - if (value && typeof value === 'string') value = value.toUpperCase(); - return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE; - }, - getImages = function getImages() { - var images = this.internal.collections[namespace + 'images']; - //first run, so initialise stuff - if (!images) { - this.internal.collections[namespace + 'images'] = images = {}; - this.internal.events.subscribe('putResources', putResourcesCallback); - this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback); - } - - return images; - }, - getImageIndex = function getImageIndex(images) { - var imageIndex = 0; - - if (images) { - // this is NOT the first time this method is ran on this instance of jsPDF object. - imageIndex = Object.keys ? Object.keys(images).length : function (o) { - var i = 0; - for (var e in o) { - if (o.hasOwnProperty(e)) { - i++; - } - } - return i; - }(images); - } - - return imageIndex; - }, - notDefined = function notDefined(value) { - return typeof value === 'undefined' || value === null; - }, - generateAliasFromData = function generateAliasFromData(data) { - return typeof data === 'string' && jsPDFAPI.sHashCode(data); - }, - doesNotSupportImageType = function doesNotSupportImageType(type) { - return supported_image_types.indexOf(type) === -1; - }, - processMethodNotEnabled = function processMethodNotEnabled(type) { - return typeof jsPDFAPI['process' + type.toUpperCase()] !== 'function'; - }, - isDOMElement = function isDOMElement(object) { - return (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && object.nodeType === 1; - }, - createDataURIFromElement = function createDataURIFromElement(element, format, angle) { - - //if element is an image which uses data url definition, just return the dataurl - if (element.nodeName === 'IMG' && element.hasAttribute('src')) { - var src = '' + element.getAttribute('src'); - if (!angle && src.indexOf('data:image/') === 0) return src; - - // only if the user doesn't care about a format - if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = 'png'; - } - - if (element.nodeName === 'CANVAS') { - var canvas = element; - } else { - var canvas = document.createElement('canvas'); - canvas.width = element.clientWidth || element.width; - canvas.height = element.clientHeight || element.height; - - var ctx = canvas.getContext('2d'); - if (!ctx) { - throw 'addImage requires canvas to be supported by browser.'; - } - if (angle) { - var x, - y, - b, - c, - s, - w, - h, - to_radians = Math.PI / 180, - angleInRadians; - - if ((typeof angle === 'undefined' ? 'undefined' : _typeof(angle)) === 'object') { - x = angle.x; - y = angle.y; - b = angle.bg; - angle = angle.angle; - } - angleInRadians = angle * to_radians; - c = Math.abs(Math.cos(angleInRadians)); - s = Math.abs(Math.sin(angleInRadians)); - w = canvas.width; - h = canvas.height; - canvas.width = h * s + w * c; - canvas.height = h * c + w * s; - - if (isNaN(x)) x = canvas.width / 2; - if (isNaN(y)) y = canvas.height / 2; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = b || 'white'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.save(); - ctx.translate(x, y); - ctx.rotate(angleInRadians); - ctx.drawImage(element, -(w / 2), -(h / 2)); - ctx.rotate(-angleInRadians); - ctx.translate(-x, -y); - ctx.restore(); - } else { - ctx.drawImage(element, 0, 0, canvas.width, canvas.height); - } - } - return canvas.toDataURL(('' + format).toLowerCase() == 'png' ? 'image/png' : 'image/jpeg'); - }, - checkImagesForAlias = function checkImagesForAlias(alias, images) { - var cached_info; - if (images) { - for (var e in images) { - if (alias === images[e].alias) { - cached_info = images[e]; - break; - } - } - } - return cached_info; - }, - determineWidthAndHeight = function determineWidthAndHeight(w, h, info) { - if (!w && !h) { - w = -96; - h = -96; - } - if (w < 0) { - w = -1 * info['w'] * 72 / w / this.internal.scaleFactor; - } - if (h < 0) { - h = -1 * info['h'] * 72 / h / this.internal.scaleFactor; - } - if (w === 0) { - w = h * info['w'] / info['h']; - } - if (h === 0) { - h = w * info['h'] / info['w']; - } - - return [w, h]; - }, - writeImageToPDF = function writeImageToPDF(x, y, w, h, info, index, images) { - var dims = determineWidthAndHeight.call(this, w, h, info), - coord = this.internal.getCoordinateString, - vcoord = this.internal.getVerticalCoordinateString; - - w = dims[0]; - h = dims[1]; - - images[index] = info; - - this.internal.write('q', coord(w), '0 0', coord(h) // TODO: check if this should be shifted by vcoord - , coord(x), vcoord(y + h), 'cm /I' + info['i'], 'Do Q'); - }; - - /** - * COLOR SPACES - */ - jsPDFAPI.color_spaces = { - DEVICE_RGB: 'DeviceRGB', - DEVICE_GRAY: 'DeviceGray', - DEVICE_CMYK: 'DeviceCMYK', - CAL_GREY: 'CalGray', - CAL_RGB: 'CalRGB', - LAB: 'Lab', - ICC_BASED: 'ICCBased', - INDEXED: 'Indexed', - PATTERN: 'Pattern', - SEPARATION: 'Separation', - DEVICE_N: 'DeviceN' - }; - - /** - * DECODE METHODS - */ - jsPDFAPI.decode = { - DCT_DECODE: 'DCTDecode', - FLATE_DECODE: 'FlateDecode', - LZW_DECODE: 'LZWDecode', - JPX_DECODE: 'JPXDecode', - JBIG2_DECODE: 'JBIG2Decode', - ASCII85_DECODE: 'ASCII85Decode', - ASCII_HEX_DECODE: 'ASCIIHexDecode', - RUN_LENGTH_DECODE: 'RunLengthDecode', - CCITT_FAX_DECODE: 'CCITTFaxDecode' - }; - - /** - * IMAGE COMPRESSION TYPES - */ - jsPDFAPI.image_compression = { - NONE: 'NONE', - FAST: 'FAST', - MEDIUM: 'MEDIUM', - SLOW: 'SLOW' - }; - - jsPDFAPI.sHashCode = function (str) { - return Array.prototype.reduce && str.split("").reduce(function (a, b) { - a = (a << 5) - a + b.charCodeAt(0);return a & a; - }, 0); - }; - - jsPDFAPI.isString = function (object) { - return typeof object === 'string'; - }; - - /** - * Strips out and returns info from a valid base64 data URI - * @param {String[dataURI]} a valid data URI of format 'data:[][;base64],' - * @returns an Array containing the following - * [0] the complete data URI - * [1] - * [2] format - the second part of the mime-type i.e 'png' in 'image/png' - * [4] - */ - jsPDFAPI.extractInfoFromBase64DataURI = function (dataURI) { - return (/^data:([\w]+?\/([\w]+?));base64,(.+?)$/g.exec(dataURI) - ); - }; - - /** - * Check to see if ArrayBuffer is supported - */ - jsPDFAPI.supportsArrayBuffer = function () { - return typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined'; - }; - - /** - * Tests supplied object to determine if ArrayBuffer - * @param {Object[object]} - */ - jsPDFAPI.isArrayBuffer = function (object) { - if (!this.supportsArrayBuffer()) return false; - return object instanceof ArrayBuffer; - }; - - /** - * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface - * @param {Object[object]} - */ - jsPDFAPI.isArrayBufferView = function (object) { - if (!this.supportsArrayBuffer()) return false; - if (typeof Uint32Array === 'undefined') return false; - return object instanceof Int8Array || object instanceof Uint8Array || typeof Uint8ClampedArray !== 'undefined' && object instanceof Uint8ClampedArray || object instanceof Int16Array || object instanceof Uint16Array || object instanceof Int32Array || object instanceof Uint32Array || object instanceof Float32Array || object instanceof Float64Array; - }; - - /** - * Exactly what it says on the tin - */ - jsPDFAPI.binaryStringToUint8Array = function (binary_string) { - /* - * not sure how efficient this will be will bigger files. Is there a native method? - */ - var len = binary_string.length; - var bytes = new Uint8Array(len); - for (var i = 0; i < len; i++) { - bytes[i] = binary_string.charCodeAt(i); - } - return bytes; - }; - - /** - * @see this discussion - * http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers - * - * As stated, i imagine the method below is highly inefficent for large files. - * - * Also of note from Mozilla, - * - * "However, this is slow and error-prone, due to the need for multiple conversions (especially if the binary data is not actually byte-format data, but, for example, 32-bit integers or floats)." - * - * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView - * - * Although i'm strugglig to see how StringView solves this issue? Doesn't appear to be a direct method for conversion? - * - * Async method using Blob and FileReader could be best, but i'm not sure how to fit it into the flow? - */ - jsPDFAPI.arrayBufferToBinaryString = function (buffer) { - /*if('TextDecoder' in window){ - var decoder = new TextDecoder('ascii'); - return decoder.decode(buffer); - }*/ - - if (this.isArrayBuffer(buffer)) buffer = new Uint8Array(buffer); - - var binary_string = ''; - var len = buffer.byteLength; - for (var i = 0; i < len; i++) { - binary_string += String.fromCharCode(buffer[i]); - } - return binary_string; - /* - * Another solution is the method below - convert array buffer straight to base64 and then use atob - */ - //return atob(this.arrayBufferToBase64(buffer)); - }; - - /** - * Converts an ArrayBuffer directly to base64 - * - * Taken from here - * - * http://jsperf.com/encoding-xhr-image-data/31 - * - * Need to test if this is a better solution for larger files - * - */ - jsPDFAPI.arrayBufferToBase64 = function (arrayBuffer) { - var base64 = ''; - var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - - var bytes = new Uint8Array(arrayBuffer); - var byteLength = bytes.byteLength; - var byteRemainder = byteLength % 3; - var mainLength = byteLength - byteRemainder; - - var a, b, c, d; - var chunk; - - // Main loop deals with bytes in chunks of 3 - for (var i = 0; i < mainLength; i = i + 3) { - // Combine the three bytes into a single integer - chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2]; - - // Use bitmasks to extract 6-bit segments from the triplet - a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18 - b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12 - c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6 - d = chunk & 63; // 63 = 2^6 - 1 - - // Convert the raw binary segments to the appropriate ASCII encoding - base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; - } - - // Deal with the remaining bytes and padding - if (byteRemainder == 1) { - chunk = bytes[mainLength]; - - a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2 - - // Set the 4 least significant bits to zero - b = (chunk & 3) << 4; // 3 = 2^2 - 1 - - base64 += encodings[a] + encodings[b] + '=='; - } else if (byteRemainder == 2) { - chunk = bytes[mainLength] << 8 | bytes[mainLength + 1]; - - a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10 - b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4 - - // Set the 2 least significant bits to zero - c = (chunk & 15) << 2; // 15 = 2^4 - 1 - - base64 += encodings[a] + encodings[b] + encodings[c] + '='; - } - - return base64; - }; - - jsPDFAPI.createImageInfo = function (data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask, p) { - var info = { - alias: alias, - w: wd, - h: ht, - cs: cs, - bpc: bpc, - i: imageIndex, - data: data - // n: objectNumber will be added by putImage code - }; - - if (f) info.f = f; - if (dp) info.dp = dp; - if (trns) info.trns = trns; - if (pal) info.pal = pal; - if (smask) info.smask = smask; - if (p) info.p = p; // predictor parameter for PNG compression - - return info; - }; - - jsPDFAPI.addImage = function (imageData, format, x, y, w, h, alias, compression, rotation) { - 'use strict'; - - if (typeof format !== 'string') { - var tmp = h; - h = w; - w = y; - y = x; - x = format; - format = tmp; - } - - if ((typeof imageData === 'undefined' ? 'undefined' : _typeof(imageData)) === 'object' && !isDOMElement(imageData) && "imageData" in imageData) { - var options = imageData; - - imageData = options.imageData; - format = options.format || format; - x = options.x || x || 0; - y = options.y || y || 0; - w = options.w || w; - h = options.h || h; - alias = options.alias || alias; - compression = options.compression || compression; - rotation = options.rotation || options.angle || rotation; - } - - if (isNaN(x) || isNaN(y)) { - console.error('jsPDF.addImage: Invalid coordinates', arguments); - throw new Error('Invalid coordinates passed to jsPDF.addImage'); - } - - var images = getImages.call(this), - info; - - if (!(info = checkImagesForAlias(imageData, images))) { - var dataAsBinaryString; - - if (isDOMElement(imageData)) imageData = createDataURIFromElement(imageData, format, rotation); - - if (notDefined(alias)) alias = generateAliasFromData(imageData); - - if (!(info = checkImagesForAlias(alias, images))) { - - if (this.isString(imageData)) { - - var base64Info = this.extractInfoFromBase64DataURI(imageData); - - if (base64Info) { - - format = base64Info[2]; - imageData = atob(base64Info[3]); //convert to binary string - } else { - - if (imageData.charCodeAt(0) === 0x89 && imageData.charCodeAt(1) === 0x50 && imageData.charCodeAt(2) === 0x4e && imageData.charCodeAt(3) === 0x47) format = 'png'; - } - } - format = (format || 'JPEG').toLowerCase(); - - if (doesNotSupportImageType(format)) throw new Error('addImage currently only supports formats ' + supported_image_types + ', not \'' + format + '\''); - - if (processMethodNotEnabled(format)) throw new Error('please ensure that the plugin for \'' + format + '\' support is added'); - - /** - * need to test if it's more efficient to convert all binary strings - * to TypedArray - or should we just leave and process as string? - */ - if (this.supportsArrayBuffer()) { - // no need to convert if imageData is already uint8array - if (!(imageData instanceof Uint8Array)) { - dataAsBinaryString = imageData; - imageData = this.binaryStringToUint8Array(imageData); - } - } - - info = this['process' + format.toUpperCase()](imageData, getImageIndex(images), alias, checkCompressValue(compression), dataAsBinaryString); - - if (!info) throw new Error('An unkwown error occurred whilst processing the image'); - } - } - - writeImageToPDF.call(this, x, y, w, h, info, info.i, images); - - return this; - }; - - /** - * JPEG SUPPORT - **/ - - //takes a string imgData containing the raw bytes of - //a jpeg image and returns [width, height] - //Algorithm from: http://www.64lines.com/jpeg-width-height - var getJpegSize = function getJpegSize(imgData) { - 'use strict'; - - var width, height, numcomponents; - // Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00 - if (!imgData.charCodeAt(0) === 0xff || !imgData.charCodeAt(1) === 0xd8 || !imgData.charCodeAt(2) === 0xff || !imgData.charCodeAt(3) === 0xe0 || !imgData.charCodeAt(6) === 'J'.charCodeAt(0) || !imgData.charCodeAt(7) === 'F'.charCodeAt(0) || !imgData.charCodeAt(8) === 'I'.charCodeAt(0) || !imgData.charCodeAt(9) === 'F'.charCodeAt(0) || !imgData.charCodeAt(10) === 0x00) { - throw new Error('getJpegSize requires a binary string jpeg file'); - } - var blockLength = imgData.charCodeAt(4) * 256 + imgData.charCodeAt(5); - var i = 4, - len = imgData.length; - while (i < len) { - i += blockLength; - if (imgData.charCodeAt(i) !== 0xff) { - throw new Error('getJpegSize could not find the size of the image'); - } - if (imgData.charCodeAt(i + 1) === 0xc0 || //(SOF) Huffman - Baseline DCT - imgData.charCodeAt(i + 1) === 0xc1 || //(SOF) Huffman - Extended sequential DCT - imgData.charCodeAt(i + 1) === 0xc2 || // Progressive DCT (SOF2) - imgData.charCodeAt(i + 1) === 0xc3 || // Spatial (sequential) lossless (SOF3) - imgData.charCodeAt(i + 1) === 0xc4 || // Differential sequential DCT (SOF5) - imgData.charCodeAt(i + 1) === 0xc5 || // Differential progressive DCT (SOF6) - imgData.charCodeAt(i + 1) === 0xc6 || // Differential spatial (SOF7) - imgData.charCodeAt(i + 1) === 0xc7) { - height = imgData.charCodeAt(i + 5) * 256 + imgData.charCodeAt(i + 6); - width = imgData.charCodeAt(i + 7) * 256 + imgData.charCodeAt(i + 8); - numcomponents = imgData.charCodeAt(i + 9); - return [width, height, numcomponents]; - } else { - i += 2; - blockLength = imgData.charCodeAt(i) * 256 + imgData.charCodeAt(i + 1); - } - } - }, - getJpegSizeFromBytes = function getJpegSizeFromBytes(data) { - - var hdr = data[0] << 8 | data[1]; - - if (hdr !== 0xFFD8) throw new Error('Supplied data is not a JPEG'); - - var len = data.length, - block = (data[4] << 8) + data[5], - pos = 4, - bytes, - width, - height, - numcomponents; - - while (pos < len) { - pos += block; - bytes = readBytes(data, pos); - block = (bytes[2] << 8) + bytes[3]; - if ((bytes[1] === 0xC0 || bytes[1] === 0xC2) && bytes[0] === 0xFF && block > 7) { - bytes = readBytes(data, pos + 5); - width = (bytes[2] << 8) + bytes[3]; - height = (bytes[0] << 8) + bytes[1]; - numcomponents = bytes[4]; - return { width: width, height: height, numcomponents: numcomponents }; - } - - pos += 2; - } - - throw new Error('getJpegSizeFromBytes could not find the size of the image'); - }, - readBytes = function readBytes(data, offset) { - return data.subarray(offset, offset + 5); - }; - - jsPDFAPI.processJPEG = function (data, index, alias, compression, dataAsBinaryString) { - 'use strict'; - - var colorSpace = this.color_spaces.DEVICE_RGB, - filter = this.decode.DCT_DECODE, - bpc = 8, - dims; - - if (this.isString(data)) { - dims = getJpegSize(data); - return this.createImageInfo(data, dims[0], dims[1], dims[3] == 1 ? this.color_spaces.DEVICE_GRAY : colorSpace, bpc, filter, index, alias); - } - - if (this.isArrayBuffer(data)) data = new Uint8Array(data); - - if (this.isArrayBufferView(data)) { - - dims = getJpegSizeFromBytes(data); - - // if we already have a stored binary string rep use that - data = dataAsBinaryString || this.arrayBufferToBinaryString(data); - - return this.createImageInfo(data, dims.width, dims.height, dims.numcomponents == 1 ? this.color_spaces.DEVICE_GRAY : colorSpace, bpc, filter, index, alias); - } - - return null; - }; - - jsPDFAPI.processJPG = function () /*data, index, alias, compression, dataAsBinaryString*/{ - return this.processJPEG.apply(this, arguments); - }; -})(jsPDF.API); - -/** - * jsPDF Annotations PlugIn - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * There are many types of annotations in a PDF document. Annotations are placed - * on a page at a particular location. They are not 'attached' to an object. - *
- * This plugin current supports
- *
  • Goto Page (set pageNumber and top in options) - *
  • Goto Name (set name and top in options) - *
  • Goto URL (set url in options) - *

    - * The destination magnification factor can also be specified when goto is a page number or a named destination. (see documentation below) - * (set magFactor in options). XYZ is the default. - *

    - *

    - * Links, Text, Popup, and FreeText are supported. - *

    - *

    - * Options In PDF spec Not Implemented Yet - *

  • link border - *
  • named target - *
  • page coordinates - *
  • destination page scaling and layout - *
  • actions other than URL and GotoPage - *
  • background / hover actions - *

    - */ - -/* - Destination Magnification Factors - See PDF 1.3 Page 386 for meanings and options - - [supported] - XYZ (options; left top zoom) - Fit (no options) - FitH (options: top) - FitV (options: left) - - [not supported] - FitR - FitB - FitBH - FitBV - */ - -(function (jsPDFAPI) { - 'use strict'; - - var annotationPlugin = { - - /** - * An array of arrays, indexed by pageNumber. - */ - annotations: [], - - f2: function f2(number) { - return number.toFixed(2); - }, - - notEmpty: function notEmpty(obj) { - if (typeof obj != 'undefined') { - if (obj != '') { - return true; - } - } - } - }; - - jsPDF.API.annotationPlugin = annotationPlugin; - - jsPDF.API.events.push(['addPage', function (info) { - this.annotationPlugin.annotations[info.pageNumber] = []; - }]); - - jsPDFAPI.events.push(['putPage', function (info) { - //TODO store annotations in pageContext so reorder/remove will not affect them. - var pageAnnos = this.annotationPlugin.annotations[info.pageNumber]; - - var found = false; - for (var a = 0; a < pageAnnos.length && !found; a++) { - var anno = pageAnnos[a]; - switch (anno.type) { - case 'link': - if (annotationPlugin.notEmpty(anno.options.url) || annotationPlugin.notEmpty(anno.options.pageNumber)) { - found = true; - break; - } - case 'reference': - case 'text': - case 'freetext': - found = true; - break; - } - } - if (found == false) { - return; - } - - this.internal.write("/Annots ["); - var f2 = this.annotationPlugin.f2; - var k = this.internal.scaleFactor; - var pageHeight = this.internal.pageSize.height; - var pageInfo = this.internal.getPageInfo(info.pageNumber); - for (var a = 0; a < pageAnnos.length; a++) { - var anno = pageAnnos[a]; - - switch (anno.type) { - case 'reference': - // References to Widget Anotations (for AcroForm Fields) - this.internal.write(' ' + anno.object.objId + ' 0 R '); - break; - case 'text': - // Create a an object for both the text and the popup - var objText = this.internal.newAdditionalObject(); - var objPopup = this.internal.newAdditionalObject(); - - var title = anno.title || 'Note'; - var rect = "/Rect [" + f2(anno.bounds.x * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] "; - line = '<>'; - objText.content = line; - - var parent = objText.objId + ' 0 R'; - var popoff = 30; - var rect = "/Rect [" + f2((anno.bounds.x + popoff) * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + " " + f2((anno.bounds.x + anno.bounds.w + popoff) * k) + " " + f2((pageHeight - anno.bounds.y) * k) + "] "; - //var rect2 = "/Rect [" + f2(anno.bounds.x * k) + " " + f2((pageHeight - anno.bounds.y) * k) + " " + f2(anno.bounds.x + anno.bounds.w * k) + " " + f2(pageHeight - (anno.bounds.y + anno.bounds.h) * k) + "] "; - line = '<>'; - } else if (anno.options.pageNumber) { - // first page is 0 - var info = this.internal.getPageInfo(anno.options.pageNumber); - line = '< pageNumber or url [required] - *

    If pageNumber is specified, top and zoom may also be specified

    - */ - jsPDFAPI.link = function (x, y, w, h, options) { - 'use strict'; - - this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push({ - x: x, - y: y, - w: w, - h: h, - options: options, - type: 'link' - }); - }; - - /** - * valid options - *
  • pageNumber or url [required] - *

    If pageNumber is specified, top and zoom may also be specified

    - */ - jsPDFAPI.link = function (x, y, w, h, options) { - 'use strict'; - - this.annotationPlugin.annotations[this.internal.getCurrentPageInfo().pageNumber].push({ - x: x, - y: y, - w: w, - h: h, - options: options, - type: 'link' - }); - }; - - /** - * Currently only supports single line text. - * Returns the width of the text/link - */ - jsPDFAPI.textWithLink = function (text, x, y, options) { - 'use strict'; - - var width = this.getTextWidth(text); - var height = this.internal.getLineHeight(); - this.text(text, x, y); - //TODO We really need the text baseline height to do this correctly. - // Or ability to draw text on top, bottom, center, or baseline. - y += height * .2; - this.link(x, y - height, width, height, options); - return width; - }; - - //TODO move into external library - jsPDFAPI.getTextWidth = function (text) { - 'use strict'; - - var fontSize = this.internal.getFontSize(); - var txtWidth = this.getStringUnitWidth(text) * fontSize / this.internal.scaleFactor; - return txtWidth; - }; - - //TODO move into external library - jsPDFAPI.getLineHeight = function () { - return this.internal.getLineHeight(); - }; - - return this; -})(jsPDF.API); - -/** - * jsPDF Autoprint Plugin - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** -* Makes the PDF automatically print. This works in Chrome, Firefox, Acrobat -* Reader. -* -* @returns {jsPDF} -* @name autoPrint -* @example -* var doc = new jsPDF() -* doc.text(10, 10, 'This is a test') -* doc.autoPrint() -* doc.save('autoprint.pdf') -*/ - -(function (jsPDFAPI) { - 'use strict'; - - jsPDFAPI.autoPrint = function () { - 'use strict'; - - var refAutoPrintTag; - - this.internal.events.subscribe('postPutResources', function () { - refAutoPrintTag = this.internal.newObject(); - this.internal.write("<< /S/Named /Type/Action /N/Print >>", "endobj"); - }); - - this.internal.events.subscribe("putCatalog", function () { - this.internal.write("/OpenAction " + refAutoPrintTag + " 0" + " R"); - }); - return this; - }; -})(jsPDF.API); - -/** - * jsPDF Canvas PlugIn - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * This plugin mimics the HTML5 Canvas - * - * The goal is to provide a way for current canvas users to print directly to a PDF. - */ - -(function (jsPDFAPI) { - 'use strict'; - - jsPDFAPI.events.push(['initialized', function () { - this.canvas.pdf = this; - }]); - - jsPDFAPI.canvas = { - getContext: function getContext(name) { - this.pdf.context2d._canvas = this; - return this.pdf.context2d; - }, - style: {} - }; - - Object.defineProperty(jsPDFAPI.canvas, 'width', { - get: function get() { - return this._width; - }, - set: function set(value) { - this._width = value; - this.getContext('2d').pageWrapX = value + 1; - } - }); - - Object.defineProperty(jsPDFAPI.canvas, 'height', { - get: function get() { - return this._height; - }, - set: function set(value) { - this._height = value; - this.getContext('2d').pageWrapY = value + 1; - } - }); - - return this; -})(jsPDF.API); - -/** ==================================================================== - * jsPDF Cell plugin - * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com - * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br - * 2013 Lee Driscoll, https://github.com/lsdriscoll - * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria - * 2014 James Hall, james@parall.ax - * 2014 Diego Casorran, https://github.com/diegocr - * - * - * ==================================================================== - */ - -(function (jsPDFAPI) { - 'use strict'; - /*jslint browser:true */ - /*global document: false, jsPDF */ - - var fontName, - fontSize, - fontStyle, - padding = 3, - margin = 13, - headerFunction, - lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }, - pages = 1, - setLastCellPosition = function setLastCellPosition(x, y, w, h, ln) { - lastCellPos = { 'x': x, 'y': y, 'w': w, 'h': h, 'ln': ln }; - }, - getLastCellPosition = function getLastCellPosition() { - return lastCellPos; - }, - NO_MARGINS = { left: 0, top: 0, bottom: 0 }; - - jsPDFAPI.setHeaderFunction = function (func) { - headerFunction = func; - }; - - jsPDFAPI.getTextDimensions = function (txt) { - fontName = this.internal.getFont().fontName; - fontSize = this.table_font_size || this.internal.getFontSize(); - fontStyle = this.internal.getFont().fontStyle; - // 1 pixel = 0.264583 mm and 1 mm = 72/25.4 point - var px2pt = 0.264583 * 72 / 25.4, - dimensions, - text; - - text = document.createElement('font'); - text.id = "jsPDFCell"; - - try { - text.style.fontStyle = fontStyle; - } catch (e) { - text.style.fontWeight = fontStyle; - } - - text.style.fontName = fontName; - text.style.fontSize = fontSize + 'pt'; - try { - text.textContent = txt; - } catch (e) { - text.innerText = txt; - } - - document.body.appendChild(text); - - dimensions = { w: (text.offsetWidth + 1) * px2pt, h: (text.offsetHeight + 1) * px2pt }; - - document.body.removeChild(text); - - return dimensions; - }; - - jsPDFAPI.cellAddPage = function () { - var margins = this.margins || NO_MARGINS; - - this.addPage(); - - setLastCellPosition(margins.left, margins.top, undefined, undefined); - //setLastCellPosition(undefined, undefined, undefined, undefined, undefined); - pages += 1; - }; - - jsPDFAPI.cellInitialize = function () { - lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }; - pages = 1; - }; - - jsPDFAPI.cell = function (x, y, w, h, txt, ln, align) { - var curCell = getLastCellPosition(); - var pgAdded = false; - - // If this is not the first cell, we must change its position - if (curCell.ln !== undefined) { - if (curCell.ln === ln) { - //Same line - x = curCell.x + curCell.w; - y = curCell.y; - } else { - //New line - var margins = this.margins || NO_MARGINS; - if (curCell.y + curCell.h + h + margin >= this.internal.pageSize.height - margins.bottom) { - this.cellAddPage(); - pgAdded = true; - if (this.printHeaders && this.tableHeaderRow) { - this.printHeaderRow(ln, true); - } - } - //We ignore the passed y: the lines may have different heights - y = getLastCellPosition().y + getLastCellPosition().h; - if (pgAdded) y = margin + 10; - } - } - - if (txt[0] !== undefined) { - if (this.printingHeaderRow) { - this.rect(x, y, w, h, 'FD'); - } else { - this.rect(x, y, w, h); - } - if (align === 'right') { - if (!(txt instanceof Array)) { - txt = [txt]; - } - for (var i = 0; i < txt.length; i++) { - var currentLine = txt[i]; - var textSize = this.getStringUnitWidth(currentLine) * this.internal.getFontSize(); - this.text(currentLine, x + w - textSize - padding, y + this.internal.getLineHeight() * (i + 1)); - } - } else { - this.text(txt, x + padding, y + this.internal.getLineHeight()); - } - } - setLastCellPosition(x, y, w, h, ln); - return this; - }; - - /** - * Return the maximum value from an array - * @param array - * @param comparisonFn - * @returns {*} - */ - jsPDFAPI.arrayMax = function (array, comparisonFn) { - var max = array[0], - i, - ln, - item; - - for (i = 0, ln = array.length; i < ln; i += 1) { - item = array[i]; - - if (comparisonFn) { - if (comparisonFn(max, item) === -1) { - max = item; - } - } else { - if (item > max) { - max = item; - } - } - } - - return max; - }; - - /** - * Create a table from a set of data. - * @param {Integer} [x] : left-position for top-left corner of table - * @param {Integer} [y] top-position for top-left corner of table - * @param {Object[]} [data] As array of objects containing key-value pairs corresponding to a row of data. - * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost - * @param {Object} [config.printHeaders] True to print column headers at the top of every page - * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value - * @param {Object} [config.margins] margin values for left, top, bottom, and width - * @param {Object} [config.fontSize] Integer fontSize to use (optional) - */ - - jsPDFAPI.table = function (x, y, data, headers, config) { - if (!data) { - throw 'No data for PDF table'; - } - - var headerNames = [], - headerPrompts = [], - header, - i, - ln, - cln, - columnMatrix = {}, - columnWidths = {}, - columnData, - column, - columnMinWidths = [], - j, - tableHeaderConfigs = [], - model, - jln, - func, - - - //set up defaults. If a value is provided in config, defaults will be overwritten: - autoSize = false, - printHeaders = true, - fontSize = 12, - margins = NO_MARGINS; - - margins.width = this.internal.pageSize.width; - - if (config) { - //override config defaults if the user has specified non-default behavior: - if (config.autoSize === true) { - autoSize = true; - } - if (config.printHeaders === false) { - printHeaders = false; - } - if (config.fontSize) { - fontSize = config.fontSize; - } - if (config.css && typeof config.css['font-size'] !== "undefined") { - fontSize = config.css['font-size'] * 16; - } - if (config.margins) { - margins = config.margins; - } - } - - /** - * @property {Number} lnMod - * Keep track of the current line number modifier used when creating cells - */ - this.lnMod = 0; - lastCellPos = { x: undefined, y: undefined, w: undefined, h: undefined, ln: undefined }, pages = 1; - - this.printHeaders = printHeaders; - this.margins = margins; - this.setFontSize(fontSize); - this.table_font_size = fontSize; - - // Set header values - if (headers === undefined || headers === null) { - // No headers defined so we derive from data - headerNames = Object.keys(data[0]); - } else if (headers[0] && typeof headers[0] !== 'string') { - var px2pt = 0.264583 * 72 / 25.4; - - // Split header configs into names and prompts - for (i = 0, ln = headers.length; i < ln; i += 1) { - header = headers[i]; - headerNames.push(header.name); - headerPrompts.push(header.prompt); - columnWidths[header.name] = header.width * px2pt; - } - } else { - headerNames = headers; - } - - if (autoSize) { - // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]} - func = function func(rec) { - return rec[header]; - }; - - for (i = 0, ln = headerNames.length; i < ln; i += 1) { - header = headerNames[i]; - - columnMatrix[header] = data.map(func); - - // get header width - columnMinWidths.push(this.getTextDimensions(headerPrompts[i] || header).w); - column = columnMatrix[header]; - - // get cell widths - for (j = 0, cln = column.length; j < cln; j += 1) { - columnData = column[j]; - columnMinWidths.push(this.getTextDimensions(columnData).w); - } - - // get final column width - columnWidths[header] = jsPDFAPI.arrayMax(columnMinWidths); - - //have to reset - columnMinWidths = []; - } - } - - // -- Construct the table - - if (printHeaders) { - var lineHeight = this.calculateLineHeight(headerNames, columnWidths, headerPrompts.length ? headerPrompts : headerNames); - - // Construct the header row - for (i = 0, ln = headerNames.length; i < ln; i += 1) { - header = headerNames[i]; - tableHeaderConfigs.push([x, y, columnWidths[header], lineHeight, String(headerPrompts.length ? headerPrompts[i] : header)]); - } - - // Store the table header config - this.setTableHeaderRow(tableHeaderConfigs); - - // Print the header for the start of the table - this.printHeaderRow(1, false); - } - - // Construct the data rows - for (i = 0, ln = data.length; i < ln; i += 1) { - var lineHeight; - model = data[i]; - lineHeight = this.calculateLineHeight(headerNames, columnWidths, model); - - for (j = 0, jln = headerNames.length; j < jln; j += 1) { - header = headerNames[j]; - this.cell(x, y, columnWidths[header], lineHeight, model[header], i + 2, header.align); - } - } - this.lastCellPos = lastCellPos; - this.table_x = x; - this.table_y = y; - return this; - }; - /** - * Calculate the height for containing the highest column - * @param {String[]} headerNames is the header, used as keys to the data - * @param {Integer[]} columnWidths is size of each column - * @param {Object[]} model is the line of data we want to calculate the height of - */ - jsPDFAPI.calculateLineHeight = function (headerNames, columnWidths, model) { - var header, - lineHeight = 0; - for (var j = 0; j < headerNames.length; j++) { - header = headerNames[j]; - model[header] = this.splitTextToSize(String(model[header]), columnWidths[header] - padding); - var h = this.internal.getLineHeight() * model[header].length + padding; - if (h > lineHeight) lineHeight = h; - } - return lineHeight; - }; - - /** - * Store the config for outputting a table header - * @param {Object[]} config - * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell - * except the ln parameter is excluded - */ - jsPDFAPI.setTableHeaderRow = function (config) { - this.tableHeaderRow = config; - }; - - /** - * Output the store header row - * @param lineNumber The line number to output the header at - */ - jsPDFAPI.printHeaderRow = function (lineNumber, new_page) { - if (!this.tableHeaderRow) { - throw 'Property tableHeaderRow does not exist.'; - } - - var tableHeaderCell, tmpArray, i, ln; - - this.printingHeaderRow = true; - if (headerFunction !== undefined) { - var position = headerFunction(this, pages); - setLastCellPosition(position[0], position[1], position[2], position[3], -1); - } - this.setFontStyle('bold'); - var tempHeaderConf = []; - for (i = 0, ln = this.tableHeaderRow.length; i < ln; i += 1) { - this.setFillColor(200, 200, 200); - - tableHeaderCell = this.tableHeaderRow[i]; - if (new_page) { - this.margins.top = margin; - tableHeaderCell[1] = this.margins && this.margins.top || 0; - tempHeaderConf.push(tableHeaderCell); - } - tmpArray = [].concat(tableHeaderCell); - this.cell.apply(this, tmpArray.concat(lineNumber)); - } - if (tempHeaderConf.length > 0) { - this.setTableHeaderRow(tempHeaderConf); - } - this.setFontStyle('normal'); - this.printingHeaderRow = false; - }; -})(jsPDF.API); - -/** - * jsPDF Context2D PlugIn Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. http://opensource.org/licenses/mit-license - */ - -/** - * This plugin mimics the HTML5 Canvas's context2d. - * - * The goal is to provide a way for current canvas implementations to print directly to a PDF. - */ - -/** - * TODO implement stroke opacity (refactor from fill() method ) - * TODO transform angle and radii parameters - */ - -/** - * require('jspdf.js'); require('lib/css_colors.js'); - */ - -(function (jsPDFAPI) { - 'use strict'; - - jsPDFAPI.events.push(['initialized', function () { - this.context2d.pdf = this; - this.context2d.internal.pdf = this; - this.context2d.ctx = new context(); - this.context2d.ctxStack = []; - this.context2d.path = []; - }]); - - jsPDFAPI.context2d = { - pageWrapXEnabled: false, - pageWrapYEnabled: false, - pageWrapX: 9999999, - pageWrapY: 9999999, - ctx: new context(), - f2: function f2(number) { - return number.toFixed(2); - }, - - fillRect: function fillRect(x, y, w, h) { - if (this._isFillTransparent()) { - return; - } - x = this._wrapX(x); - y = this._wrapY(y); - - var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); - this.pdf.rect(xRect.x, xRect.y, xRect.w, xRect.h, "f"); - }, - - strokeRect: function strokeRect(x, y, w, h) { - if (this._isStrokeTransparent()) { - return; - } - x = this._wrapX(x); - y = this._wrapY(y); - - var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); - this.pdf.rect(xRect.x, xRect.y, xRect.w, xRect.h, "s"); - }, - - /** - * We cannot clear PDF commands that were already written to PDF, so we use white instead.
    - * As a special case, read a special flag (ignoreClearRect) and do nothing if it is set. - * This results in all calls to clearRect() to do nothing, and keep the canvas transparent. - * This flag is stored in the save/restore context and is managed the same way as other drawing states. - * @param x - * @param y - * @param w - * @param h - */ - clearRect: function clearRect(x, y, w, h) { - if (this.ctx.ignoreClearRect) { - return; - } - - x = this._wrapX(x); - y = this._wrapY(y); - - var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); - this.save(); - this.setFillStyle('#ffffff'); - //TODO This is hack to fill with white. - this.pdf.rect(xRect.x, xRect.y, xRect.w, xRect.h, "f"); - this.restore(); - }, - - save: function save() { - this.ctx._fontSize = this.pdf.internal.getFontSize(); - var ctx = new context(); - ctx.copy(this.ctx); - this.ctxStack.push(this.ctx); - this.ctx = ctx; - }, - - restore: function restore() { - this.ctx = this.ctxStack.pop(); - this.setFillStyle(this.ctx.fillStyle); - this.setStrokeStyle(this.ctx.strokeStyle); - this.setFont(this.ctx.font); - this.pdf.setFontSize(this.ctx._fontSize); - this.setLineCap(this.ctx.lineCap); - this.setLineWidth(this.ctx.lineWidth); - this.setLineJoin(this.ctx.lineJoin); - }, - - rect: function rect(x, y, w, h) { - this.moveTo(x, y); - this.lineTo(x + w, y); - this.lineTo(x + w, y + h); - this.lineTo(x, y + h); - this.lineTo(x, y); //TODO not needed - this.closePath(); - }, - - beginPath: function beginPath() { - this.path = []; - }, - - closePath: function closePath() { - this.path.push({ - type: 'close' - }); - }, - - _getRgba: function _getRgba(style) { - // get the decimal values of r, g, and b; - var rgba = {}; - - if (this.internal.rxTransparent.test(style)) { - rgba.r = 0; - rgba.g = 0; - rgba.b = 0; - rgba.a = 0; - } else { - var m = this.internal.rxRgb.exec(style); - if (m != null) { - rgba.r = parseInt(m[1]); - rgba.g = parseInt(m[2]); - rgba.b = parseInt(m[3]); - rgba.a = 1; - } else { - m = this.internal.rxRgba.exec(style); - if (m != null) { - rgba.r = parseInt(m[1]); - rgba.g = parseInt(m[2]); - rgba.b = parseInt(m[3]); - rgba.a = parseFloat(m[4]); - } else { - rgba.a = 1; - if (style.charAt(0) != '#') { - style = CssColors.colorNameToHex(style); - if (!style) { - style = '#000000'; - } - } else {} - - if (style.length === 4) { - rgba.r = style.substring(1, 2); - rgba.r += r; - rgba.g = style.substring(2, 3); - rgba.g += g; - rgba.b = style.substring(3, 4); - rgba.b += b; - } else { - rgba.r = style.substring(1, 3); - rgba.g = style.substring(3, 5); - rgba.b = style.substring(5, 7); - } - rgba.r = parseInt(rgba.r, 16); - rgba.g = parseInt(rgba.g, 16); - rgba.b = parseInt(rgba.b, 16); - } - } - } - rgba.style = style; - return rgba; - }, - - setFillStyle: function setFillStyle(style) { - // get the decimal values of r, g, and b; - var r, g, b, a; - - if (this.internal.rxTransparent.test(style)) { - r = 0; - g = 0; - b = 0; - a = 0; - } else { - var m = this.internal.rxRgb.exec(style); - if (m != null) { - r = parseInt(m[1]); - g = parseInt(m[2]); - b = parseInt(m[3]); - a = 1; - } else { - m = this.internal.rxRgba.exec(style); - if (m != null) { - r = parseInt(m[1]); - g = parseInt(m[2]); - b = parseInt(m[3]); - a = parseFloat(m[4]); - } else { - a = 1; - if (style.charAt(0) != '#') { - style = CssColors.colorNameToHex(style); - if (!style) { - style = '#000000'; - } - } else {} - - if (style.length === 4) { - r = style.substring(1, 2); - r += r; - g = style.substring(2, 3); - g += g; - b = style.substring(3, 4); - b += b; - } else { - r = style.substring(1, 3); - g = style.substring(3, 5); - b = style.substring(5, 7); - } - r = parseInt(r, 16); - g = parseInt(g, 16); - b = parseInt(b, 16); - } - } - } - - this.ctx.fillStyle = style; - this.ctx._isFillTransparent = a == 0; - this.ctx._fillOpacity = a; - - this.pdf.setFillColor(r, g, b, { - a: a - }); - this.pdf.setTextColor(r, g, b, { - a: a - }); - }, - - setStrokeStyle: function setStrokeStyle(style) { - var rgba = this._getRgba(style); - - this.ctx.strokeStyle = rgba.style; - this.ctx._isStrokeTransparent = rgba.a == 0; - this.ctx._strokeOpacity = rgba.a; - - //TODO jsPDF to handle rgba - if (rgba.a === 0) { - this.pdf.setDrawColor(255, 255, 255); - } else if (rgba.a === 1) { - this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); - } else { - //this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b, {a: rgba.a}); - this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); - } - }, - - fillText: function fillText(text, x, y, maxWidth) { - if (this._isFillTransparent()) { - return; - } - x = this._wrapX(x); - y = this._wrapY(y); - - var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); - x = xpt[0]; - y = xpt[1]; - var rads = this._matrix_rotation(this.ctx._transform); - var degs = rads * 57.2958; - - //TODO only push the clip if it has not been applied to the current PDF context - if (this.ctx._clip_path.length > 0) { - var lines; - if (window.outIntercept) { - lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; - } else { - lines = this.internal.getCurrentPage(); - } - lines.push("q"); - var origPath = this.path; - this.path = this.ctx._clip_path; - this.ctx._clip_path = []; - this._fill(null, true); - this.ctx._clip_path = this.path; - this.path = origPath; - } - - var scale; - if (this.pdf.hotfix && this.pdf.hotfix.scale_text) { - scale = this._getTransform()[0]; - } else { - scale = 1; - } - // In some cases the transform was very small (5.715760606202283e-17). Most likely a canvg rounding error. - if (scale < .01) { - this.pdf.text(text, x, this._getBaseline(y), null, degs); - } else { - var oldSize = this.pdf.internal.getFontSize(); - this.pdf.setFontSize(oldSize * scale); - this.pdf.text(text, x, this._getBaseline(y), null, degs); - this.pdf.setFontSize(oldSize); - } - - if (this.ctx._clip_path.length > 0) { - lines.push('Q'); - } - }, - - strokeText: function strokeText(text, x, y, maxWidth) { - if (this._isStrokeTransparent()) { - return; - } - x = this._wrapX(x); - y = this._wrapY(y); - - var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); - x = xpt[0]; - y = xpt[1]; - var rads = this._matrix_rotation(this.ctx._transform); - var degs = rads * 57.2958; - - //TODO only push the clip if it has not been applied to the current PDF context - if (this.ctx._clip_path.length > 0) { - var lines; - if (window.outIntercept) { - lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; - } else { - lines = this.internal.getCurrentPage(); - } - lines.push("q"); - var origPath = this.path; - this.path = this.ctx._clip_path; - this.ctx._clip_path = []; - this._fill(null, true); - this.ctx._clip_path = this.path; - this.path = origPath; - } - - var scale; - if (this.pdf.hotfix && this.pdf.hotfix.scale_text) { - scale = this._getTransform()[0]; - } else { - scale = 1; - } - if (scale === 1) { - this.pdf.text(text, x, this._getBaseline(y), { - stroke: true - }, degs); - } else { - var oldSize = this.pdf.internal.getFontSize(); - this.pdf.setFontSize(oldSize * scale); - this.pdf.text(text, x, this._getBaseline(y), { - stroke: true - }, degs); - this.pdf.setFontSize(oldSize); - } - - if (this.ctx._clip_path.length > 0) { - lines.push('Q'); - } - }, - - setFont: function setFont(font) { - this.ctx.font = font; - - //var rx = /\s*(\w+)\s+(\w+)\s+(\w+)\s+([\d\.]+)(px|pt|em)\s+["']?(\w+)['"]?/; - var rx = /\s*(\w+)\s+(\w+)\s+(\w+)\s+([\d\.]+)(px|pt|em)\s+(.*)?/; - m = rx.exec(font); - if (m != null) { - var fontStyle = m[1]; - var fontVariant = m[2]; - var fontWeight = m[3]; - var fontSize = m[4]; - var fontSizeUnit = m[5]; - var fontFamily = m[6]; - - if ('px' === fontSizeUnit) { - fontSize = Math.floor(parseFloat(fontSize)); - // fontSize = fontSize * 1.25; - } else if ('em' === fontSizeUnit) { - fontSize = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize()); - } else { - fontSize = Math.floor(parseFloat(fontSize)); - } - - this.pdf.setFontSize(fontSize); - - if (fontWeight === 'bold' || fontWeight === '700') { - this.pdf.setFontStyle('bold'); - } else { - if (fontStyle === 'italic') { - this.pdf.setFontStyle('italic'); - } else { - this.pdf.setFontStyle('normal'); - } - } - - var name = fontFamily; - var parts = name.toLowerCase().split(/\s*,\s*/); - var jsPdfFontName; - - if (parts.indexOf('arial') != -1) { - jsPdfFontName = 'Arial'; - } else if (parts.indexOf('verdana') != -1) { - jsPdfFontName = 'Verdana'; - } else if (parts.indexOf('helvetica') != -1) { - jsPdfFontName = 'Helvetica'; - } else if (parts.indexOf('sans-serif') != -1) { - jsPdfFontName = 'sans-serif'; - } else if (parts.indexOf('fixed') != -1) { - jsPdfFontName = 'Fixed'; - } else if (parts.indexOf('monospace') != -1) { - jsPdfFontName = 'Monospace'; - } else if (parts.indexOf('terminal') != -1) { - jsPdfFontName = 'Terminal'; - } else if (parts.indexOf('courier') != -1) { - jsPdfFontName = 'Courier'; - } else if (parts.indexOf('times') != -1) { - jsPdfFontName = 'Times'; - } else if (parts.indexOf('cursive') != -1) { - jsPdfFontName = 'Cursive'; - } else if (parts.indexOf('fantasy') != -1) { - jsPdfFontName = 'Fantasy'; - } else if (parts.indexOf('serif') != -1) { - jsPdfFontName = 'Serif'; - } else { - jsPdfFontName = 'Serif'; - } - - //TODO check more cases - var style; - if ('bold' === fontWeight) { - style = 'bold'; - } else { - style = 'normal'; - } - - this.pdf.setFont(jsPdfFontName, style); - } else { - var rx = /(\d+)(pt|px|em)\s+(\w+)\s*(\w+)?/; - var m = rx.exec(font); - if (m != null) { - var size = m[1]; - var unit = m[2]; - var name = m[3]; - var style = m[4]; - if (!style) { - style = 'normal'; - } - if ('em' === fontSizeUnit) { - size = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize()); - } else { - size = Math.floor(parseFloat(size)); - } - this.pdf.setFontSize(size); - this.pdf.setFont(name, style); - } - } - }, - - setTextBaseline: function setTextBaseline(baseline) { - this.ctx.textBaseline = baseline; - }, - - getTextBaseline: function getTextBaseline() { - return this.ctx.textBaseline; - }, - - //TODO implement textAlign - setTextAlign: function setTextAlign(align) { - this.ctx.textAlign = align; - }, - - getTextAlign: function getTextAlign() { - return this.ctx.textAlign; - }, - - setLineWidth: function setLineWidth(width) { - this.ctx.lineWidth = width; - this.pdf.setLineWidth(width); - }, - - setLineCap: function setLineCap(style) { - this.ctx.lineCap = style; - this.pdf.setLineCap(style); - }, - - setLineJoin: function setLineJoin(style) { - this.ctx.lineJoin = style; - this.pdf.setLineJoin(style); - }, - - moveTo: function moveTo(x, y) { - x = this._wrapX(x); - y = this._wrapY(y); - - var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); - x = xpt[0]; - y = xpt[1]; - - var obj = { - type: 'mt', - x: x, - y: y - }; - this.path.push(obj); - }, - - _wrapX: function _wrapX(x) { - if (this.pageWrapXEnabled) { - return x % this.pageWrapX; - } else { - return x; - } - }, - - _wrapY: function _wrapY(y) { - if (this.pageWrapYEnabled) { - this._gotoPage(this._page(y)); - return (y - this.lastBreak) % this.pageWrapY; - } else { - return y; - } - }, - - transform: function transform(a, b, c, d, e, f) { - //TODO apply to current transformation instead of replacing - this.ctx._transform = [a, b, c, d, e, f]; - }, - - setTransform: function setTransform(a, b, c, d, e, f) { - this.ctx._transform = [a, b, c, d, e, f]; - }, - - _getTransform: function _getTransform() { - return this.ctx._transform; - }, - - lastBreak: 0, - // Y Position of page breaks. - pageBreaks: [], - // returns: One-based Page Number - // Should only be used if pageWrapYEnabled is true - _page: function _page(y) { - if (this.pageWrapYEnabled) { - this.lastBreak = 0; - var manualBreaks = 0; - var autoBreaks = 0; - for (var i = 0; i < this.pageBreaks.length; i++) { - if (y >= this.pageBreaks[i]) { - manualBreaks++; - if (this.lastBreak === 0) { - autoBreaks++; - } - var spaceBetweenLastBreak = this.pageBreaks[i] - this.lastBreak; - this.lastBreak = this.pageBreaks[i]; - var pagesSinceLastBreak = Math.floor(spaceBetweenLastBreak / this.pageWrapY); - autoBreaks += pagesSinceLastBreak; - } - } - if (this.lastBreak === 0) { - var pagesSinceLastBreak = Math.floor(y / this.pageWrapY) + 1; - autoBreaks += pagesSinceLastBreak; - } - return autoBreaks + manualBreaks; - } else { - return this.pdf.internal.getCurrentPageInfo().pageNumber; - } - }, - - _gotoPage: function _gotoPage(pageOneBased) { - // This is a stub to be overriden if needed - }, - - lineTo: function lineTo(x, y) { - x = this._wrapX(x); - y = this._wrapY(y); - - var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); - x = xpt[0]; - y = xpt[1]; - - var obj = { - type: 'lt', - x: x, - y: y - }; - this.path.push(obj); - }, - - bezierCurveTo: function bezierCurveTo(x1, y1, x2, y2, x, y) { - x1 = this._wrapX(x1); - y1 = this._wrapY(y1); - x2 = this._wrapX(x2); - y2 = this._wrapY(y2); - x = this._wrapX(x); - y = this._wrapY(y); - - var xpt; - xpt = this._matrix_map_point(this.ctx._transform, [x, y]); - x = xpt[0]; - y = xpt[1]; - xpt = this._matrix_map_point(this.ctx._transform, [x1, y1]); - x1 = xpt[0]; - y1 = xpt[1]; - xpt = this._matrix_map_point(this.ctx._transform, [x2, y2]); - x2 = xpt[0]; - y2 = xpt[1]; - - var obj = { - type: 'bct', - x1: x1, - y1: y1, - x2: x2, - y2: y2, - x: x, - y: y - }; - this.path.push(obj); - }, - - quadraticCurveTo: function quadraticCurveTo(x1, y1, x, y) { - x1 = this._wrapX(x1); - y1 = this._wrapY(y1); - x = this._wrapX(x); - y = this._wrapY(y); - - var xpt; - xpt = this._matrix_map_point(this.ctx._transform, [x, y]); - x = xpt[0]; - y = xpt[1]; - xpt = this._matrix_map_point(this.ctx._transform, [x1, y1]); - x1 = xpt[0]; - y1 = xpt[1]; - - var obj = { - type: 'qct', - x1: x1, - y1: y1, - x: x, - y: y - }; - this.path.push(obj); - }, - - arc: function arc(x, y, radius, startAngle, endAngle, anticlockwise) { - x = this._wrapX(x); - y = this._wrapY(y); - - if (!this._matrix_is_identity(this.ctx._transform)) { - var xpt = this._matrix_map_point(this.ctx._transform, [x, y]); - x = xpt[0]; - y = xpt[1]; - - var x_radPt0 = this._matrix_map_point(this.ctx._transform, [0, 0]); - var x_radPt = this._matrix_map_point(this.ctx._transform, [0, radius]); - radius = Math.sqrt(Math.pow(x_radPt[0] - x_radPt0[0], 2) + Math.pow(x_radPt[1] - x_radPt0[1], 2)); - - //TODO angles need to be transformed - } - - var obj = { - type: 'arc', - x: x, - y: y, - radius: radius, - startAngle: startAngle, - endAngle: endAngle, - anticlockwise: anticlockwise - }; - this.path.push(obj); - }, - - drawImage: function drawImage(img, x, y, w, h, x2, y2, w2, h2) { - if (x2 !== undefined) { - x = x2; - y = y2; - w = w2; - h = h2; - } - x = this._wrapX(x); - y = this._wrapY(y); - - var xRect = this._matrix_map_rect(this.ctx._transform, { x: x, y: y, w: w, h: h }); - var xRect2 = this._matrix_map_rect(this.ctx._transform, { x: x2, y: y2, w: w2, h: h2 }); - - // TODO implement source clipping and image scaling - var format; - var rx = /data:image\/(\w+).*/i; - var m = rx.exec(img); - if (m != null) { - format = m[1]; - } else { - // format = "jpeg"; - format = "png"; - } - - this.pdf.addImage(img, format, xRect.x, xRect.y, xRect.w, xRect.h); - }, - - /** - * Multiply the first matrix by the second - * @param m1 - * @param m2 - * @returns {*[]} - * @private - */ - _matrix_multiply: function _matrix_multiply(m2, m1) { - var sx = m1[0]; - var shy = m1[1]; - var shx = m1[2]; - var sy = m1[3]; - var tx = m1[4]; - var ty = m1[5]; - - var t0 = sx * m2[0] + shy * m2[2]; - var t2 = shx * m2[0] + sy * m2[2]; - var t4 = tx * m2[0] + ty * m2[2] + m2[4]; - shy = sx * m2[1] + shy * m2[3]; - sy = shx * m2[1] + sy * m2[3]; - ty = tx * m2[1] + ty * m2[3] + m2[5]; - sx = t0; - shx = t2; - tx = t4; - - return [sx, shy, shx, sy, tx, ty]; - }, - - _matrix_rotation: function _matrix_rotation(m) { - return Math.atan2(m[2], m[0]); - }, - - _matrix_decompose: function _matrix_decompose(matrix) { - - var a = matrix[0]; - var b = matrix[1]; - var c = matrix[2]; - var d = matrix[3]; - - var scaleX = Math.sqrt(a * a + b * b); - a /= scaleX; - b /= scaleX; - - var shear = a * c + b * d; - c -= a * shear; - d -= b * shear; - - var scaleY = Math.sqrt(c * c + d * d); - c /= scaleY; - d /= scaleY; - shear /= scaleY; - - if (a * d < b * c) { - a = -a; - b = -b; - shear = -shear; - scaleX = -scaleX; - } - - return { - scale: [scaleX, 0, 0, scaleY, 0, 0], - translate: [1, 0, 0, 1, matrix[4], matrix[5]], - rotate: [a, b, -b, a, 0, 0], - skew: [1, 0, shear, 1, 0, 0] - }; - }, - - _matrix_map_point: function _matrix_map_point(m1, pt) { - var sx = m1[0]; - var shy = m1[1]; - var shx = m1[2]; - var sy = m1[3]; - var tx = m1[4]; - var ty = m1[5]; - - var px = pt[0]; - var py = pt[1]; - - var x = px * sx + py * shx + tx; - var y = px * shy + py * sy + ty; - return [x, y]; - }, - - _matrix_map_point_obj: function _matrix_map_point_obj(m1, pt) { - var xpt = this._matrix_map_point(m1, [pt.x, pt.y]); - return { x: xpt[0], y: xpt[1] }; - }, - - _matrix_map_rect: function _matrix_map_rect(m1, rect) { - var p1 = this._matrix_map_point(m1, [rect.x, rect.y]); - var p2 = this._matrix_map_point(m1, [rect.x + rect.w, rect.y + rect.h]); - return { x: p1[0], y: p1[1], w: p2[0] - p1[0], h: p2[1] - p1[1] }; - }, - - _matrix_is_identity: function _matrix_is_identity(m1) { - if (m1[0] != 1) { - return false; - } - if (m1[1] != 0) { - return false; - } - if (m1[2] != 0) { - return false; - } - if (m1[3] != 1) { - return false; - } - if (m1[4] != 0) { - return false; - } - if (m1[5] != 0) { - return false; - } - return true; - }, - - rotate: function rotate(angle) { - var matrix = [Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0.0, 0.0]; - this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix); - }, - - scale: function scale(sx, sy) { - var matrix = [sx, 0.0, 0.0, sy, 0.0, 0.0]; - this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix); - }, - - translate: function translate(x, y) { - var matrix = [1.0, 0.0, 0.0, 1.0, x, y]; - this.ctx._transform = this._matrix_multiply(this.ctx._transform, matrix); - }, - - stroke: function stroke() { - if (this.ctx._clip_path.length > 0) { - - var lines; - if (window.outIntercept) { - lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; - } else { - lines = this.internal.getCurrentPage(); - } - lines.push("q"); - - var origPath = this.path; - this.path = this.ctx._clip_path; - this.ctx._clip_path = []; - this._stroke(true); - - this.ctx._clip_path = this.path; - this.path = origPath; - this._stroke(false); - - lines.push("Q"); - } else { - this._stroke(false); - } - }, - - _stroke: function _stroke(isClip) { - if (!isClip && this._isStrokeTransparent()) { - return; - } - - //TODO opacity - - var moves = []; - var closed = false; - - var xPath = this.path; - - for (var i = 0; i < xPath.length; i++) { - var pt = xPath[i]; - switch (pt.type) { - case 'mt': - moves.push({ start: pt, deltas: [], abs: [] }); - break; - case 'lt': - var delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; - moves[moves.length - 1].deltas.push(delta); - moves[moves.length - 1].abs.push(pt); - break; - case 'bct': - var delta = [pt.x1 - xPath[i - 1].x, pt.y1 - xPath[i - 1].y, pt.x2 - xPath[i - 1].x, pt.y2 - xPath[i - 1].y, pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; - moves[moves.length - 1].deltas.push(delta); - break; - case 'qct': - // convert to bezier - var x1 = xPath[i - 1].x + 2.0 / 3.0 * (pt.x1 - xPath[i - 1].x); - var y1 = xPath[i - 1].y + 2.0 / 3.0 * (pt.y1 - xPath[i - 1].y); - var x2 = pt.x + 2.0 / 3.0 * (pt.x1 - pt.x); - var y2 = pt.y + 2.0 / 3.0 * (pt.y1 - pt.y); - var x3 = pt.x; - var y3 = pt.y; - var delta = [x1 - xPath[i - 1].x, y1 - xPath[i - 1].y, x2 - xPath[i - 1].x, y2 - xPath[i - 1].y, x3 - xPath[i - 1].x, y3 - xPath[i - 1].y]; - moves[moves.length - 1].deltas.push(delta); - break; - case 'arc': - //TODO this was hack to avoid out-of-bounds issue - // No move-to before drawing the arc - if (moves.length == 0) { - moves.push({ start: { x: 0, y: 0 }, deltas: [], abs: [] }); - } - moves[moves.length - 1].arc = true; - moves[moves.length - 1].abs.push(pt); - break; - case 'close': - closed = true; - break; - } - } - - for (var i = 0; i < moves.length; i++) { - var style; - if (i == moves.length - 1) { - style = 's'; - } else { - style = null; - } - if (moves[i].arc) { - var arcs = moves[i].abs; - for (var ii = 0; ii < arcs.length; ii++) { - var arc = arcs[ii]; - var start = arc.startAngle * 360 / (2 * Math.PI); - var end = arc.endAngle * 360 / (2 * Math.PI); - var x = arc.x; - var y = arc.y; - this.internal.arc2(this, x, y, arc.radius, start, end, arc.anticlockwise, style, isClip); - } - } else { - var x = moves[i].start.x; - var y = moves[i].start.y; - if (!isClip) { - this.pdf.lines(moves[i].deltas, x, y, null, style); - } else { - this.pdf.lines(moves[i].deltas, x, y, null, null); - this.pdf.clip_fixed(); - } - } - } - }, - - _isFillTransparent: function _isFillTransparent() { - return this.ctx._isFillTransparent || this.globalAlpha == 0; - }, - - _isStrokeTransparent: function _isStrokeTransparent() { - return this.ctx._isStrokeTransparent || this.globalAlpha == 0; - }, - - fill: function fill(fillRule) { - //evenodd or nonzero (default) - if (this.ctx._clip_path.length > 0) { - - var lines; - if (window.outIntercept) { - lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; - } else { - lines = this.internal.getCurrentPage(); - } - lines.push("q"); - - var origPath = this.path; - this.path = this.ctx._clip_path; - this.ctx._clip_path = []; - this._fill(fillRule, true); - - this.ctx._clip_path = this.path; - this.path = origPath; - this._fill(fillRule, false); - - lines.push('Q'); - } else { - this._fill(fillRule, false); - } - }, - - _fill: function _fill(fillRule, isClip) { - if (this._isFillTransparent()) { - return; - } - var v2Support = typeof this.pdf.internal.newObject2 === 'function'; - - var lines; - if (window.outIntercept) { - lines = window.outIntercept.type === 'group' ? window.outIntercept.stream : window.outIntercept; - } else { - lines = this.internal.getCurrentPage(); - } - - // if (this.ctx._clip_path.length > 0) { - // lines.push('q'); - // var oldPath = this.path; - // this.path = this.ctx._clip_path; - // this.ctx._clip_path = []; - // this._fill(fillRule, true); - // this.ctx._clip_path = this.path; - // this.path = oldPath; - // } - - var moves = []; - var outInterceptOld = window.outIntercept; - - if (v2Support) { - // Blend and Mask - switch (this.ctx.globalCompositeOperation) { - case 'normal': - case 'source-over': - break; - case 'destination-in': - case 'destination-out': - //TODO this need to be added to the current group or page - // define a mask stream - var obj = this.pdf.internal.newStreamObject(); - - // define a mask state - var obj2 = this.pdf.internal.newObject2(); - obj2.push('<>'); // /S /Luminosity will need to define color space - obj2.push('>>'); - - // add mask to page resources - var gsName = 'MASK' + obj2.objId; - this.pdf.internal.addGraphicsState(gsName, obj2.objId); - - var instruction = '/' + gsName + ' gs'; - // add mask to page, group, or stream - lines.splice(0, 0, 'q'); - lines.splice(1, 0, instruction); - lines.push('Q'); - - window.outIntercept = obj; - break; - default: - var dictionaryEntry = '/' + this.pdf.internal.blendModeMap[this.ctx.globalCompositeOperation.toUpperCase()]; - if (dictionaryEntry) { - this.pdf.internal.out(dictionaryEntry + ' gs'); - } - break; - } - } - - var alpha = this.ctx.globalAlpha; - if (this.ctx._fillOpacity < 1) { - // TODO combine this with global opacity - alpha = this.ctx._fillOpacity; - } - - //TODO check for an opacity graphics state that was already created - //TODO do not set opacity if current value is already active - if (v2Support) { - var objOpac = this.pdf.internal.newObject2(); - objOpac.push('<>'); - var gsName = 'GS_O_' + objOpac.objId; - this.pdf.internal.addGraphicsState(gsName, objOpac.objId); - this.pdf.internal.out('/' + gsName + ' gs'); - } - - var xPath = this.path; - - for (var i = 0; i < xPath.length; i++) { - var pt = xPath[i]; - switch (pt.type) { - case 'mt': - moves.push({ start: pt, deltas: [], abs: [] }); - break; - case 'lt': - var delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; - moves[moves.length - 1].deltas.push(delta); - moves[moves.length - 1].abs.push(pt); - break; - case 'bct': - var delta = [pt.x1 - xPath[i - 1].x, pt.y1 - xPath[i - 1].y, pt.x2 - xPath[i - 1].x, pt.y2 - xPath[i - 1].y, pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; - moves[moves.length - 1].deltas.push(delta); - break; - case 'qct': - // convert to bezier - var x1 = xPath[i - 1].x + 2.0 / 3.0 * (pt.x1 - xPath[i - 1].x); - var y1 = xPath[i - 1].y + 2.0 / 3.0 * (pt.y1 - xPath[i - 1].y); - var x2 = pt.x + 2.0 / 3.0 * (pt.x1 - pt.x); - var y2 = pt.y + 2.0 / 3.0 * (pt.y1 - pt.y); - var x3 = pt.x; - var y3 = pt.y; - var delta = [x1 - xPath[i - 1].x, y1 - xPath[i - 1].y, x2 - xPath[i - 1].x, y2 - xPath[i - 1].y, x3 - xPath[i - 1].x, y3 - xPath[i - 1].y]; - moves[moves.length - 1].deltas.push(delta); - break; - case 'arc': - //TODO this was hack to avoid out-of-bounds issue when drawing circle - // No move-to before drawing the arc - if (moves.length === 0) { - moves.push({ deltas: [], abs: [] }); - } - moves[moves.length - 1].arc = true; - moves[moves.length - 1].abs.push(pt); - break; - case 'close': - moves.push({ close: true }); - break; - } - } - - for (var i = 0; i < moves.length; i++) { - var style; - if (i == moves.length - 1) { - style = 'f'; - if (fillRule === 'evenodd') { - style += '*'; - } - } else { - style = null; - } - - if (moves[i].close) { - this.pdf.internal.out('h'); - this.pdf.internal.out('f'); - } else if (moves[i].arc) { - if (moves[i].start) { - this.internal.move2(this, moves[i].start.x, moves[i].start.y); - } - var arcs = moves[i].abs; - for (var ii = 0; ii < arcs.length; ii++) { - var arc = arcs[ii]; - //TODO lines deltas were getting in here - if (typeof arc.startAngle !== 'undefined') { - var start = arc.startAngle * 360 / (2 * Math.PI); - var end = arc.endAngle * 360 / (2 * Math.PI); - var x = arc.x; - var y = arc.y; - if (ii === 0) { - this.internal.move2(this, x, y); - } - this.internal.arc2(this, x, y, arc.radius, start, end, arc.anticlockwise, null, isClip); - if (ii === arcs.length - 1) { - // The original arc move did not occur because of the algorithm - if (moves[i].start) { - var x = moves[i].start.x; - var y = moves[i].start.y; - this.internal.line2(c2d, x, y); - } - } - } else { - this.internal.line2(c2d, arc.x, arc.y); - } - } - } else { - var x = moves[i].start.x; - var y = moves[i].start.y; - if (!isClip) { - this.pdf.lines(moves[i].deltas, x, y, null, style); - } else { - this.pdf.lines(moves[i].deltas, x, y, null, null); - this.pdf.clip_fixed(); - } - } - } - - window.outIntercept = outInterceptOld; - - // if (this.ctx._clip_path.length > 0) { - // lines.push('Q'); - // } - }, - - pushMask: function pushMask() { - var v2Support = typeof this.pdf.internal.newObject2 === 'function'; - - if (!v2Support) { - console.log('jsPDF v2 not enabled'); - return; - } - - // define a mask stream - var obj = this.pdf.internal.newStreamObject(); - - // define a mask state - var obj2 = this.pdf.internal.newObject2(); - obj2.push('<>'); // /S /Luminosity will need to define color space - obj2.push('>>'); - - // add mask to page resources - var gsName = 'MASK' + obj2.objId; - this.pdf.internal.addGraphicsState(gsName, obj2.objId); - - var instruction = '/' + gsName + ' gs'; - this.pdf.internal.out(instruction); - }, - - clip: function clip() { - //TODO do we reset the path, or just copy it? - if (this.ctx._clip_path.length > 0) { - for (var i = 0; i < this.path.length; i++) { - this.ctx._clip_path.push(this.path[i]); - } - } else { - this.ctx._clip_path = this.path; - } - this.path = []; - }, - - measureText: function measureText(text) { - var pdf = this.pdf; - return { - getWidth: function getWidth() { - var fontSize = pdf.internal.getFontSize(); - var txtWidth = pdf.getStringUnitWidth(text) * fontSize / pdf.internal.scaleFactor; - // Convert points to pixels - txtWidth *= 1.3333; - return txtWidth; - }, - - get width() { - return this.getWidth(text); - } - }; - }, - _getBaseline: function _getBaseline(y) { - var height = parseInt(this.pdf.internal.getFontSize()); - // TODO Get descent from font descriptor - var descent = height * .25; - switch (this.ctx.textBaseline) { - case 'bottom': - return y - descent; - case 'top': - return y + height; - case 'hanging': - return y + height - descent; - case 'middle': - return y + height / 2 - descent; - case 'ideographic': - // TODO not implemented - return y; - case 'alphabetic': - default: - return y; - } - } - }; - - var c2d = jsPDFAPI.context2d; - - // accessor methods - Object.defineProperty(c2d, 'fillStyle', { - set: function set(value) { - this.setFillStyle(value); - }, - get: function get() { - return this.ctx.fillStyle; - } - }); - Object.defineProperty(c2d, 'strokeStyle', { - set: function set(value) { - this.setStrokeStyle(value); - }, - get: function get() { - return this.ctx.strokeStyle; - } - }); - Object.defineProperty(c2d, 'lineWidth', { - set: function set(value) { - this.setLineWidth(value); - }, - get: function get() { - return this.ctx.lineWidth; - } - }); - Object.defineProperty(c2d, 'lineCap', { - set: function set(val) { - this.setLineCap(val); - }, - get: function get() { - return this.ctx.lineCap; - } - }); - Object.defineProperty(c2d, 'lineJoin', { - set: function set(val) { - this.setLineJoin(val); - }, - get: function get() { - return this.ctx.lineJoin; - } - }); - Object.defineProperty(c2d, 'miterLimit', { - set: function set(val) { - this.ctx.miterLimit = val; - }, - get: function get() { - return this.ctx.miterLimit; - } - }); - Object.defineProperty(c2d, 'textBaseline', { - set: function set(value) { - this.setTextBaseline(value); - }, - get: function get() { - return this.getTextBaseline(); - } - }); - Object.defineProperty(c2d, 'textAlign', { - set: function set(value) { - this.setTextAlign(value); - }, - get: function get() { - return this.getTextAlign(); - } - }); - Object.defineProperty(c2d, 'font', { - set: function set(value) { - this.setFont(value); - }, - get: function get() { - return this.ctx.font; - } - }); - Object.defineProperty(c2d, 'globalCompositeOperation', { - set: function set(value) { - this.ctx.globalCompositeOperation = value; - }, - get: function get() { - return this.ctx.globalCompositeOperation; - } - }); - Object.defineProperty(c2d, 'globalAlpha', { - set: function set(value) { - this.ctx.globalAlpha = value; - }, - get: function get() { - return this.ctx.globalAlpha; - } - }); - // Not HTML API - Object.defineProperty(c2d, 'ignoreClearRect', { - set: function set(value) { - this.ctx.ignoreClearRect = value; - }, - get: function get() { - return this.ctx.ignoreClearRect; - } - }); - // End Not HTML API - - c2d.internal = {}; - - c2d.internal.rxRgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/; - c2d.internal.rxRgba = /rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d\.]+)\s*\)/; - c2d.internal.rxTransparent = /transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/; - - // http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html - c2d.internal.arc = function (c2d, xc, yc, r, a1, a2, anticlockwise, style) { - var includeMove = true; - - var k = this.pdf.internal.scaleFactor; - var pageHeight = this.pdf.internal.pageSize.height; - var f2 = this.pdf.internal.f2; - - var a1r = a1 * (Math.PI / 180); - var a2r = a2 * (Math.PI / 180); - var curves = this.createArc(r, a1r, a2r, anticlockwise); - var pathData = null; - - for (var i = 0; i < curves.length; i++) { - var curve = curves[i]; - if (includeMove && i === 0) { - this.pdf.internal.out([f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), 'm', f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'].join(' ')); - } else { - this.pdf.internal.out([f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c'].join(' ')); - } - - //c2d._lastPoint = {x: curve.x1 + xc, y: curve.y1 + yc}; - c2d._lastPoint = { x: xc, y: yc }; - // f2((curve.x1 + xc) * k), f2((pageHeight - (curve.y1 + yc)) * k), 'm', f2((curve.x2 + xc) * k), f2((pageHeight - (curve.y2 + yc)) * k), f2((curve.x3 + xc) * k), f2((pageHeight - (curve.y3 + yc)) * k), f2((curve.x4 + xc) * k), f2((pageHeight - (curve.y4 + yc)) * k), 'c' - } - - if (style !== null) { - this.pdf.internal.out(this.pdf.internal.getStyle(style)); - } - }; - - /** - * - * @param x Edge point X - * @param y Edge point Y - * @param r Radius - * @param a1 start angle - * @param a2 end angle - * @param anticlockwise - * @param style - * @param isClip - */ - c2d.internal.arc2 = function (c2d, x, y, r, a1, a2, anticlockwise, style, isClip) { - // we need to convert from cartesian to polar here methinks. - var centerX = x; // + r; - var centerY = y; - - if (!isClip) { - this.arc(c2d, centerX, centerY, r, a1, a2, anticlockwise, style); - } else { - this.arc(c2d, centerX, centerY, r, a1, a2, anticlockwise, null); - this.pdf.clip_fixed(); - } - }; - - c2d.internal.move2 = function (c2d, x, y) { - var k = this.pdf.internal.scaleFactor; - var pageHeight = this.pdf.internal.pageSize.height; - var f2 = this.pdf.internal.f2; - - this.pdf.internal.out([f2(x * k), f2((pageHeight - y) * k), 'm'].join(' ')); - c2d._lastPoint = { x: x, y: y }; - }; - - c2d.internal.line2 = function (c2d, dx, dy) { - var k = this.pdf.internal.scaleFactor; - var pageHeight = this.pdf.internal.pageSize.height; - var f2 = this.pdf.internal.f2; - - //var pt = {x: c2d._lastPoint.x + dx, y: c2d._lastPoint.y + dy}; - var pt = { x: dx, y: dy }; - - this.pdf.internal.out([f2(pt.x * k), f2((pageHeight - pt.y) * k), 'l'].join(' ')); - //this.pdf.internal.out('f'); - c2d._lastPoint = pt; - }; - - /** - * Return a array of objects that represent bezier curves which approximate the circular arc centered at the origin, from startAngle to endAngle (radians) with the specified radius. - * - * Each bezier curve is an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. - */ - - c2d.internal.createArc = function (radius, startAngle, endAngle, anticlockwise) { - var EPSILON = 0.00001; // Roughly 1/1000th of a degree, see below - var twoPI = Math.PI * 2; - var piOverTwo = Math.PI / 2.0; - - // normalize startAngle, endAngle to [0, 2PI] - var startAngleN = startAngle; - if (startAngleN < twoPI || startAngleN > twoPI) { - startAngleN = startAngleN % twoPI; - } - if (startAngleN < 0) { - startAngleN = twoPI + startAngleN; - } - - while (startAngle > endAngle) { - startAngle = startAngle - twoPI; - } - var totalAngle = Math.abs(endAngle - startAngle); - if (totalAngle < twoPI) { - if (anticlockwise) { - totalAngle = twoPI - totalAngle; - } - } - - // Compute the sequence of arc curves, up to PI/2 at a time. - var curves = []; - var sgn = anticlockwise ? -1 : +1; - - var a1 = startAngleN; - for (; totalAngle > EPSILON;) { - var remain = sgn * Math.min(totalAngle, piOverTwo); - var a2 = a1 + remain; - curves.push(this.createSmallArc(radius, a1, a2)); - totalAngle -= Math.abs(a2 - a1); - a1 = a2; - } - - return curves; - }; - - c2d.internal.getCurrentPage = function () { - return this.pdf.internal.pages[this.pdf.internal.getCurrentPageInfo().pageNumber]; - }; - - /** - * Cubic bezier approximation of a circular arc centered at the origin, from (radians) a1 to a2, where a2-a1 < pi/2. The arc's radius is r. - * - * Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. - * - * This algorithm is based on the approach described in: A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa," Information Technology and Control, 35(4), 2006 pp. 371-378. - */ - - c2d.internal.createSmallArc = function (r, a1, a2) { - // Compute all four points for an arc that subtends the same total angle - // but is centered on the X-axis - - var a = (a2 - a1) / 2.0; - - var x4 = r * Math.cos(a); - var y4 = r * Math.sin(a); - var x1 = x4; - var y1 = -y4; - - var q1 = x1 * x1 + y1 * y1; - var q2 = q1 + x1 * x4 + y1 * y4; - var k2 = 4 / 3 * (Math.sqrt(2 * q1 * q2) - q2) / (x1 * y4 - y1 * x4); - - var x2 = x1 - k2 * y1; - var y2 = y1 + k2 * x1; - var x3 = x2; - var y3 = -y2; - - // Find the arc points' actual locations by computing x1,y1 and x4,y4 - // and rotating the control points by a + a1 - - var ar = a + a1; - var cos_ar = Math.cos(ar); - var sin_ar = Math.sin(ar); - - return { - x1: r * Math.cos(a1), - y1: r * Math.sin(a1), - x2: x2 * cos_ar - y2 * sin_ar, - y2: x2 * sin_ar + y2 * cos_ar, - x3: x3 * cos_ar - y3 * sin_ar, - y3: x3 * sin_ar + y3 * cos_ar, - x4: r * Math.cos(a2), - y4: r * Math.sin(a2) - }; - }; - - function context() { - this._isStrokeTransparent = false; - this._strokeOpacity = 1; - this.strokeStyle = '#000000'; - this.fillStyle = '#000000'; - this._isFillTransparent = false; - this._fillOpacity = 1; - this.font = "12pt times"; - this.textBaseline = 'alphabetic'; // top,bottom,middle,ideographic,alphabetic,hanging - this.textAlign = 'start'; - this.lineWidth = 1; - this.lineJoin = 'miter'; // round, bevel, miter - this.lineCap = 'butt'; // butt, round, square - this._transform = [1, 0, 0, 1, 0, 0]; // sx, shy, shx, sy, tx, ty - this.globalCompositeOperation = 'normal'; - this.globalAlpha = 1.0; - this._clip_path = []; - // TODO miter limit //default 10 - - // Not HTML API - this.ignoreClearRect = false; - - this.copy = function (ctx) { - this._isStrokeTransparent = ctx._isStrokeTransparent; - this._strokeOpacity = ctx._strokeOpacity; - this.strokeStyle = ctx.strokeStyle; - this._isFillTransparent = ctx._isFillTransparent; - this._fillOpacity = ctx._fillOpacity; - this.fillStyle = ctx.fillStyle; - this.font = ctx.font; - this.lineWidth = ctx.lineWidth; - this.lineJoin = ctx.lineJoin; - this.lineCap = ctx.lineCap; - this.textBaseline = ctx.textBaseline; - this.textAlign = ctx.textAlign; - this._fontSize = ctx._fontSize; - this._transform = ctx._transform.slice(0); - this.globalCompositeOperation = ctx.globalCompositeOperation; - this.globalAlpha = ctx.globalAlpha; - this._clip_path = ctx._clip_path.slice(0); //TODO deep copy? - - // Not HTML API - this.ignoreClearRect = ctx.ignoreClearRect; - }; - } - - return this; -})(jsPDF.API); - -/** @preserve - * jsPDF fromHTML plugin. BETA stage. API subject to change. Needs browser - * Copyright (c) 2012 Willow Systems Corporation, willow-systems.com - * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria - * 2014 Diego Casorran, https://github.com/diegocr - * 2014 Daniel Husar, https://github.com/danielhusar - * 2014 Wolfgang Gassler, https://github.com/woolfg - * 2014 Steven Spungin, https://github.com/flamenco - * - * - * ==================================================================== - */ - -(function (jsPDFAPI) { - var clone, _DrillForContent, FontNameDB, FontStyleMap, TextAlignMap, FontWeightMap, FloatMap, ClearMap, GetCSS, PurgeWhiteSpace, Renderer, ResolveFont, ResolveUnitedNumber, UnitedNumberMap, elementHandledElsewhere, images, loadImgs, checkForFooter, process, tableToJson; - clone = function () { - return function (obj) { - Clone.prototype = obj; - return new Clone(); - }; - function Clone() {} - }(); - PurgeWhiteSpace = function PurgeWhiteSpace(array) { - var fragment, i, l, lTrimmed, r, rTrimmed, trailingSpace; - i = 0; - l = array.length; - fragment = void 0; - lTrimmed = false; - rTrimmed = false; - while (!lTrimmed && i !== l) { - fragment = array[i] = array[i].trimLeft(); - if (fragment) { - lTrimmed = true; - } - i++; - } - i = l - 1; - while (l && !rTrimmed && i !== -1) { - fragment = array[i] = array[i].trimRight(); - if (fragment) { - rTrimmed = true; - } - i--; - } - r = /\s+$/g; - trailingSpace = true; - i = 0; - while (i !== l) { - // Leave the line breaks intact - if (array[i] != "\u2028") { - fragment = array[i].replace(/\s+/g, " "); - if (trailingSpace) { - fragment = fragment.trimLeft(); - } - if (fragment) { - trailingSpace = r.test(fragment); - } - array[i] = fragment; - } - i++; - } - return array; - }; - Renderer = function Renderer(pdf, x, y, settings) { - this.pdf = pdf; - this.x = x; - this.y = y; - this.settings = settings; - //list of functions which are called after each element-rendering process - this.watchFunctions = []; - this.init(); - return this; - }; - ResolveFont = function ResolveFont(css_font_family_string) { - var name, part, parts; - name = void 0; - parts = css_font_family_string.split(","); - part = parts.shift(); - while (!name && part) { - name = FontNameDB[part.trim().toLowerCase()]; - part = parts.shift(); - } - return name; - }; - ResolveUnitedNumber = function ResolveUnitedNumber(css_line_height_string) { - - //IE8 issues - css_line_height_string = css_line_height_string === "auto" ? "0px" : css_line_height_string; - if (css_line_height_string.indexOf("em") > -1 && !isNaN(Number(css_line_height_string.replace("em", "")))) { - css_line_height_string = Number(css_line_height_string.replace("em", "")) * 18.719 + "px"; - } - if (css_line_height_string.indexOf("pt") > -1 && !isNaN(Number(css_line_height_string.replace("pt", "")))) { - css_line_height_string = Number(css_line_height_string.replace("pt", "")) * 1.333 + "px"; - } - - var normal, undef, value; - undef = void 0; - normal = 16.00; - value = UnitedNumberMap[css_line_height_string]; - if (value) { - return value; - } - value = { - "xx-small": 9, - "x-small": 11, - small: 13, - medium: 16, - large: 19, - "x-large": 23, - "xx-large": 28, - auto: 0 - }[{ css_line_height_string: css_line_height_string }]; - - if (value !== undef) { - return UnitedNumberMap[css_line_height_string] = value / normal; - } - if (value = parseFloat(css_line_height_string)) { - return UnitedNumberMap[css_line_height_string] = value / normal; - } - value = css_line_height_string.match(/([\d\.]+)(px)/); - if (value.length === 3) { - return UnitedNumberMap[css_line_height_string] = parseFloat(value[1]) / normal; - } - return UnitedNumberMap[css_line_height_string] = 1; - }; - GetCSS = function GetCSS(element) { - var css, tmp, computedCSSElement; - computedCSSElement = function (el) { - var compCSS; - compCSS = function (el) { - if (document.defaultView && document.defaultView.getComputedStyle) { - return document.defaultView.getComputedStyle(el, null); - } else if (el.currentStyle) { - return el.currentStyle; - } else { - return el.style; - } - }(el); - return function (prop) { - prop = prop.replace(/-\D/g, function (match) { - return match.charAt(1).toUpperCase(); - }); - return compCSS[prop]; - }; - }(element); - css = {}; - tmp = void 0; - css["font-family"] = ResolveFont(computedCSSElement("font-family")) || "times"; - css["font-style"] = FontStyleMap[computedCSSElement("font-style")] || "normal"; - css["text-align"] = TextAlignMap[computedCSSElement("text-align")] || "left"; - tmp = FontWeightMap[computedCSSElement("font-weight")] || "normal"; - if (tmp === "bold") { - if (css["font-style"] === "normal") { - css["font-style"] = tmp; - } else { - css["font-style"] = tmp + css["font-style"]; - } - } - css["font-size"] = ResolveUnitedNumber(computedCSSElement("font-size")) || 1; - css["line-height"] = ResolveUnitedNumber(computedCSSElement("line-height")) || 1; - css["display"] = computedCSSElement("display") === "inline" ? "inline" : "block"; - - tmp = css["display"] === "block"; - css["margin-top"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-top")) || 0; - css["margin-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-bottom")) || 0; - css["padding-top"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-top")) || 0; - css["padding-bottom"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-bottom")) || 0; - css["margin-left"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-left")) || 0; - css["margin-right"] = tmp && ResolveUnitedNumber(computedCSSElement("margin-right")) || 0; - css["padding-left"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-left")) || 0; - css["padding-right"] = tmp && ResolveUnitedNumber(computedCSSElement("padding-right")) || 0; - - css["page-break-before"] = computedCSSElement("page-break-before") || "auto"; - - //float and clearing of floats - css["float"] = FloatMap[computedCSSElement("cssFloat")] || "none"; - css["clear"] = ClearMap[computedCSSElement("clear")] || "none"; - - css["color"] = computedCSSElement("color"); - - return css; - }; - elementHandledElsewhere = function elementHandledElsewhere(element, renderer, elementHandlers) { - var handlers, i, isHandledElsewhere, l, t; - isHandledElsewhere = false; - i = void 0; - l = void 0; - t = void 0; - handlers = elementHandlers["#" + element.id]; - if (handlers) { - if (typeof handlers === "function") { - isHandledElsewhere = handlers(element, renderer); - } else { - i = 0; - l = handlers.length; - while (!isHandledElsewhere && i !== l) { - isHandledElsewhere = handlers[i](element, renderer); - i++; - } - } - } - handlers = elementHandlers[element.nodeName]; - if (!isHandledElsewhere && handlers) { - if (typeof handlers === "function") { - isHandledElsewhere = handlers(element, renderer); - } else { - i = 0; - l = handlers.length; - while (!isHandledElsewhere && i !== l) { - isHandledElsewhere = handlers[i](element, renderer); - i++; - } - } - } - return isHandledElsewhere; - }; - tableToJson = function tableToJson(table, renderer) { - var data, headers, i, j, rowData, tableRow, table_obj, table_with, cell, l; - data = []; - headers = []; - i = 0; - l = table.rows[0].cells.length; - table_with = table.clientWidth; - while (i < l) { - cell = table.rows[0].cells[i]; - headers[i] = { - name: cell.textContent.toLowerCase().replace(/\s+/g, ''), - prompt: cell.textContent.replace(/\r?\n/g, ''), - width: cell.clientWidth / table_with * renderer.pdf.internal.pageSize.width - }; - i++; - } - i = 1; - while (i < table.rows.length) { - tableRow = table.rows[i]; - rowData = {}; - j = 0; - while (j < tableRow.cells.length) { - rowData[headers[j].name] = tableRow.cells[j].textContent.replace(/\r?\n/g, ''); - j++; - } - data.push(rowData); - i++; - } - return table_obj = { - rows: data, - headers: headers - }; - }; - var SkipNode = { - SCRIPT: 1, - STYLE: 1, - NOSCRIPT: 1, - OBJECT: 1, - EMBED: 1, - SELECT: 1 - }; - var listCount = 1; - _DrillForContent = function DrillForContent(element, renderer, elementHandlers) { - var cn, cns, fragmentCSS, i, isBlock, l, px2pt, table2json, cb; - cns = element.childNodes; - cn = void 0; - fragmentCSS = GetCSS(element); - isBlock = fragmentCSS.display === "block"; - if (isBlock) { - renderer.setBlockBoundary(); - renderer.setBlockStyle(fragmentCSS); - } - px2pt = 0.264583 * 72 / 25.4; - i = 0; - l = cns.length; - while (i < l) { - cn = cns[i]; - if ((typeof cn === "undefined" ? "undefined" : _typeof(cn)) === "object") { - - //execute all watcher functions to e.g. reset floating - renderer.executeWatchFunctions(cn); - - /*** HEADER rendering **/ - if (cn.nodeType === 1 && cn.nodeName === 'HEADER') { - var header = cn; - //store old top margin - var oldMarginTop = renderer.pdf.margins_doc.top; - //subscribe for new page event and render header first on every page - renderer.pdf.internal.events.subscribe('addPage', function (pageInfo) { - //set current y position to old margin - renderer.y = oldMarginTop; - //render all child nodes of the header element - _DrillForContent(header, renderer, elementHandlers); - //set margin to old margin + rendered header + 10 space to prevent overlapping - //important for other plugins (e.g. table) to start rendering at correct position after header - renderer.pdf.margins_doc.top = renderer.y + 10; - renderer.y += 10; - }, false); - } - - if (cn.nodeType === 8 && cn.nodeName === "#comment") { - if (~cn.textContent.indexOf("ADD_PAGE")) { - renderer.pdf.addPage(); - renderer.y = renderer.pdf.margins_doc.top; - } - } else if (cn.nodeType === 1 && !SkipNode[cn.nodeName]) { - /*** IMAGE RENDERING ***/ - var cached_image; - if (cn.nodeName === "IMG") { - var url = cn.getAttribute("src"); - cached_image = images[renderer.pdf.sHashCode(url) || url]; - } - if (cached_image) { - if (renderer.pdf.internal.pageSize.height - renderer.pdf.margins_doc.bottom < renderer.y + cn.height && renderer.y > renderer.pdf.margins_doc.top) { - renderer.pdf.addPage(); - renderer.y = renderer.pdf.margins_doc.top; - //check if we have to set back some values due to e.g. header rendering for new page - renderer.executeWatchFunctions(cn); - } - - var imagesCSS = GetCSS(cn); - var imageX = renderer.x; - var fontToUnitRatio = 12 / renderer.pdf.internal.scaleFactor; - - //define additional paddings, margins which have to be taken into account for margin calculations - var additionalSpaceLeft = (imagesCSS["margin-left"] + imagesCSS["padding-left"]) * fontToUnitRatio; - var additionalSpaceRight = (imagesCSS["margin-right"] + imagesCSS["padding-right"]) * fontToUnitRatio; - var additionalSpaceTop = (imagesCSS["margin-top"] + imagesCSS["padding-top"]) * fontToUnitRatio; - var additionalSpaceBottom = (imagesCSS["margin-bottom"] + imagesCSS["padding-bottom"]) * fontToUnitRatio; - - //if float is set to right, move the image to the right border - //add space if margin is set - if (imagesCSS['float'] !== undefined && imagesCSS['float'] === 'right') { - imageX += renderer.settings.width - cn.width - additionalSpaceRight; - } else { - imageX += additionalSpaceLeft; - } - - renderer.pdf.addImage(cached_image, imageX, renderer.y + additionalSpaceTop, cn.width, cn.height); - cached_image = undefined; - //if the float prop is specified we have to float the text around the image - if (imagesCSS['float'] === 'right' || imagesCSS['float'] === 'left') { - //add functiont to set back coordinates after image rendering - renderer.watchFunctions.push(function (diffX, thresholdY, diffWidth, el) { - //undo drawing box adaptions which were set by floating - if (renderer.y >= thresholdY) { - renderer.x += diffX; - renderer.settings.width += diffWidth; - return true; - } else if (el && el.nodeType === 1 && !SkipNode[el.nodeName] && renderer.x + el.width > renderer.pdf.margins_doc.left + renderer.pdf.margins_doc.width) { - renderer.x += diffX; - renderer.y = thresholdY; - renderer.settings.width += diffWidth; - return true; - } else { - return false; - } - }.bind(this, imagesCSS['float'] === 'left' ? -cn.width - additionalSpaceLeft - additionalSpaceRight : 0, renderer.y + cn.height + additionalSpaceTop + additionalSpaceBottom, cn.width)); - //reset floating by clear:both divs - //just set cursorY after the floating element - renderer.watchFunctions.push(function (yPositionAfterFloating, pages, el) { - if (renderer.y < yPositionAfterFloating && pages === renderer.pdf.internal.getNumberOfPages()) { - if (el.nodeType === 1 && GetCSS(el).clear === 'both') { - renderer.y = yPositionAfterFloating; - return true; - } else { - return false; - } - } else { - return true; - } - }.bind(this, renderer.y + cn.height, renderer.pdf.internal.getNumberOfPages())); - - //if floating is set we decrease the available width by the image width - renderer.settings.width -= cn.width + additionalSpaceLeft + additionalSpaceRight; - //if left just add the image width to the X coordinate - if (imagesCSS['float'] === 'left') { - renderer.x += cn.width + additionalSpaceLeft + additionalSpaceRight; - } - } else { - //if no floating is set, move the rendering cursor after the image height - renderer.y += cn.height + additionalSpaceTop + additionalSpaceBottom; - } - - /*** TABLE RENDERING ***/ - } else if (cn.nodeName === "TABLE") { - table2json = tableToJson(cn, renderer); - renderer.y += 10; - renderer.pdf.table(renderer.x, renderer.y, table2json.rows, table2json.headers, { - autoSize: false, - printHeaders: elementHandlers.printHeaders, - margins: renderer.pdf.margins_doc, - css: GetCSS(cn) - }); - renderer.y = renderer.pdf.lastCellPos.y + renderer.pdf.lastCellPos.h + 20; - } else if (cn.nodeName === "OL" || cn.nodeName === "UL") { - listCount = 1; - if (!elementHandledElsewhere(cn, renderer, elementHandlers)) { - _DrillForContent(cn, renderer, elementHandlers); - } - renderer.y += 10; - } else if (cn.nodeName === "LI") { - var temp = renderer.x; - renderer.x += 20 / renderer.pdf.internal.scaleFactor; - renderer.y += 3; - if (!elementHandledElsewhere(cn, renderer, elementHandlers)) { - _DrillForContent(cn, renderer, elementHandlers); - } - renderer.x = temp; - } else if (cn.nodeName === "BR") { - renderer.y += fragmentCSS["font-size"] * renderer.pdf.internal.scaleFactor; - renderer.addText("\u2028", clone(fragmentCSS)); - } else { - if (!elementHandledElsewhere(cn, renderer, elementHandlers)) { - _DrillForContent(cn, renderer, elementHandlers); - } - } - } else if (cn.nodeType === 3) { - var value = cn.nodeValue; - if (cn.nodeValue && cn.parentNode.nodeName === "LI") { - if (cn.parentNode.parentNode.nodeName === "OL") { - value = listCount++ + '. ' + value; - } else { - var fontSize = fragmentCSS["font-size"]; - var offsetX = (3 - fontSize * 0.75) * renderer.pdf.internal.scaleFactor; - var offsetY = fontSize * 0.75 * renderer.pdf.internal.scaleFactor; - var radius = fontSize * 1.74 / renderer.pdf.internal.scaleFactor; - cb = function cb(x, y) { - this.pdf.circle(x + offsetX, y + offsetY, radius, 'FD'); - }; - } - } - // Only add the text if the text node is in the body element - // Add compatibility with IE11 - if (!!(cn.ownerDocument.body.compareDocumentPosition(cn) & 16)) { - renderer.addText(value, fragmentCSS); - } - } else if (typeof cn === "string") { - renderer.addText(cn, fragmentCSS); - } - } - i++; - } - elementHandlers.outY = renderer.y; - - if (isBlock) { - return renderer.setBlockBoundary(cb); - } - }; - images = {}; - loadImgs = function loadImgs(element, renderer, elementHandlers, cb) { - var imgs = element.getElementsByTagName('img'), - l = imgs.length, - found_images, - x = 0; - function done() { - renderer.pdf.internal.events.publish('imagesLoaded'); - cb(found_images); - } - function loadImage(url, width, height) { - if (!url) return; - var img = new Image(); - found_images = ++x; - img.crossOrigin = ''; - img.onerror = img.onload = function () { - if (img.complete) { - //to support data urls in images, set width and height - //as those values are not recognized automatically - if (img.src.indexOf('data:image/') === 0) { - img.width = width || img.width || 0; - img.height = height || img.height || 0; - } - //if valid image add to known images array - if (img.width + img.height) { - var hash = renderer.pdf.sHashCode(url) || url; - images[hash] = images[hash] || img; - } - } - if (! --x) { - done(); - } - }; - img.src = url; - } - while (l--) { - loadImage(imgs[l].getAttribute("src"), imgs[l].width, imgs[l].height); - }return x || done(); - }; - checkForFooter = function checkForFooter(elem, renderer, elementHandlers) { - //check if we can found a