├── .npmrc ├── docs ├── .nuxtrc ├── .npmrc ├── tsconfig.json ├── public │ └── nuxt-pdf.txt ├── content │ ├── 3.server-side │ │ ├── _dir.yml │ │ └── 1.index.md │ ├── _dir.yml │ ├── 1.getting-started │ │ ├── _dir.yml │ │ ├── 2.installation.md │ │ ├── 4.getting-help.md │ │ ├── 3.quick-start.md │ │ └── 1.index.md │ ├── 1.index.md │ └── 2.application-side │ │ ├── _dir.yml │ │ └── 4.composables.md ├── nuxt.config.ts ├── app.config.ts ├── renovate.json ├── .gitignore ├── package.json └── README.md ├── .eslintignore ├── playground ├── app.vue ├── tsconfig.json ├── server │ ├── tsconfig.json │ └── api │ │ └── pdf │ │ ├── basic.ts │ │ ├── password.ts │ │ ├── landscape.ts │ │ └── withLayout.ts ├── nuxt.config.ts ├── package.json ├── pages │ ├── index.vue │ └── client.vue └── components │ └── TestingStage.vue ├── tsconfig.json ├── .eslintrc ├── .github ├── preview.jpg ├── sync.yml └── workflows │ ├── sync.yaml │ └── ci.yaml ├── src ├── runtime │ ├── server │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── drawHorizontalLine.ts │ │ │ └── layout.ts │ │ ├── index.ts │ │ └── pdf.ts │ ├── composables │ │ ├── exportToPDF.ts │ │ └── htmlToPDF.ts │ └── types.ts └── module.ts ├── .nuxtrc ├── .editorconfig ├── .gitignore ├── LICENSE ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /docs/.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=true 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | docs 4 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/public/nuxt-pdf.txt: -------------------------------------------------------------------------------- 1 | This is a test asset to move to the docs folder. 2 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["@nuxt/eslint-config"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidebase/nuxt-pdf/HEAD/.github/preview.jpg -------------------------------------------------------------------------------- /docs/content/3.server-side/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Server-Side 2 | icon: heroicons-outline:server 3 | -------------------------------------------------------------------------------- /playground/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/content/_dir.yml: -------------------------------------------------------------------------------- 1 | title: nuxt-pdf 2 | icon: heroicons-outline:document 3 | layout: module 4 | -------------------------------------------------------------------------------- /docs/content/1.getting-started/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Getting Started 2 | icon: heroicons-outline:sparkles 3 | -------------------------------------------------------------------------------- /docs/content/1.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | navigation: false 3 | redirect: /nuxt-pdf/getting-started 4 | --- 5 | -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | extends: '@nuxt-themes/docus' 3 | }) 4 | -------------------------------------------------------------------------------- /docs/content/2.application-side/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Application-Side 2 | icon: heroicons-outline:computer-desktop 3 | -------------------------------------------------------------------------------- /docs/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | docus: { 3 | title: 'NuxtPDF docs' 4 | } 5 | }) 6 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | modules: ['../src/module'], 3 | devtools: { enabled: true }, 4 | }) 5 | -------------------------------------------------------------------------------- /src/runtime/server/components/index.ts: -------------------------------------------------------------------------------- 1 | export { drawHorizontalLine } from './drawHorizontalLine' 2 | export { applyLayout } from './layout' 3 | -------------------------------------------------------------------------------- /docs/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ], 5 | "lockFileMaintenance": { 6 | "enabled": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | .output 13 | -------------------------------------------------------------------------------- /docs/content/3.server-side/1.index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This page is a work in progress. Please have a look at the playground until we get around to these! 4 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | # enable TypeScript bundler module resolution - https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler 2 | experimental.typescriptBundlerResolution=true 3 | -------------------------------------------------------------------------------- /src/runtime/server/index.ts: -------------------------------------------------------------------------------- 1 | export { createPDF, streamReturnPDF } from './pdf' 2 | export * from './components' 3 | 4 | export type { PDFDocumentType, PDFOptions } from '../types' 5 | -------------------------------------------------------------------------------- /.github/sync.yml: -------------------------------------------------------------------------------- 1 | sidebase/nuxt-pdf: 2 | - docs/content 3 | - docs/public 4 | sidebase/docs: 5 | - source: docs/content/ 6 | dest: content/6.nuxt-pdf/ 7 | - source: docs/public/ 8 | dest: public/ 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /playground/server/api/pdf/basic.ts: -------------------------------------------------------------------------------- 1 | import { createPDF, streamReturnPDF } from '#pdf' 2 | 3 | export default eventHandler(async (event) => { 4 | const pdf = createPDF() 5 | pdf.text('Welcome to NuxtPDF!') 6 | 7 | pdf.end() 8 | 9 | return streamReturnPDF(event, pdf) 10 | }) 11 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@sidebase/nuxt-pdf-playground", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate" 9 | }, 10 | "dependencies": { 11 | "nuxt": "latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /playground/server/api/pdf/password.ts: -------------------------------------------------------------------------------- 1 | import { createPDF, streamReturnPDF } from '#pdf' 2 | 3 | export default eventHandler(async (event) => { 4 | const pdf = createPDF({ 5 | userPassword: 'hunter2' 6 | }) 7 | pdf.text('I am protected!') 8 | 9 | pdf.end() 10 | 11 | return streamReturnPDF(event, pdf) 12 | }) 13 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docus-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate", 9 | "preview": "nuxi preview" 10 | }, 11 | "devDependencies": { 12 | "nuxt": "^3.0.0", 13 | "@nuxt-themes/docus": "^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/runtime/composables/exportToPDF.ts: -------------------------------------------------------------------------------- 1 | import type { HTMLOptions, jsPDFOptions } from 'jspdf' 2 | import htmlToPDF from './htmlToPDF' 3 | 4 | export default async function(fileName: string, element: HTMLElement, documentOptions?: jsPDFOptions, htmlOptions?: HTMLOptions) { 5 | const pdf = await htmlToPDF(element, documentOptions, htmlOptions) 6 | return pdf.save(fileName) 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/sync.yaml: -------------------------------------------------------------------------------- 1 | name: Sync docs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | jobs: 8 | sync: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout Repository 12 | uses: actions/checkout@master 13 | - name: Run GitHub File Sync 14 | uses: BetaHuhn/repo-file-sync-action@v1 15 | with: 16 | GH_PAT: ${{ secrets.GH_TOKEN }} 17 | -------------------------------------------------------------------------------- /docs/content/1.getting-started/2.installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "How to install nuxt-pdf." 3 | --- 4 | 5 | # Installation 6 | 7 | To install the module begin by installing our package 8 | ::code-group 9 | ```bash [npm] 10 | npm install -D @sidebase/nuxt-pdf 11 | ``` 12 | ```bash [yarn] 13 | yarn add --dev @sidebase/nuxt-pdf 14 | ``` 15 | ```bash [pnpm] 16 | pnpm i -D @sidebase/nuxt-pdf 17 | ``` 18 | :: 19 | 20 | `nuxt-pdf` only requires a Nuxt 3 app to run. 21 | -------------------------------------------------------------------------------- /playground/server/api/pdf/landscape.ts: -------------------------------------------------------------------------------- 1 | import { createPDF, streamReturnPDF, type PDFOptions } from '#pdf' 2 | 3 | export default eventHandler(async (event) => { 4 | const options: PDFOptions = { 5 | margins: { 6 | top: 100, 7 | bottom: 100, 8 | left: 50, 9 | right: 50, 10 | }, 11 | layout: 'landscape' 12 | } 13 | const pdf = createPDF(options) 14 | pdf.text('Welcome to NuxtPDF!') 15 | 16 | pdf.end() 17 | 18 | return streamReturnPDF(event, pdf) 19 | }) 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | ci: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - run: corepack enable 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: 20 20 | cache: "pnpm" 21 | 22 | - run: pnpm install && pnpm dev:prepare 23 | - run: pnpm lint 24 | - run: pnpm build 25 | - run: pnpm dev:build 26 | -------------------------------------------------------------------------------- /src/runtime/types.ts: -------------------------------------------------------------------------------- 1 | import PDFDocument from 'pdfkit' 2 | import type { LayoutOptions } from './server/pdf' 3 | 4 | export interface PDFOptions extends PDFKit.PDFDocumentOptions { 5 | margins: { 6 | left: number 7 | right: number 8 | top: number 9 | bottom: number 10 | } 11 | } 12 | type PDFDocument = typeof PDFDocument & { 13 | options: PDFOptions 14 | } 15 | 16 | export interface ModuleOptions { 17 | defaultDocOptions: PDFOptions 18 | } 19 | 20 | export type PDFDocumentType = PDFDocument & { 21 | data?: TData 22 | layout?: LayoutOptions 23 | } 24 | -------------------------------------------------------------------------------- /src/runtime/server/components/drawHorizontalLine.ts: -------------------------------------------------------------------------------- 1 | import type { PDFDocumentType } from "../../types" 2 | 3 | /** 4 | * Draw a Horizontal line across the document 5 | * @param doc The PDF document object 6 | * @param moveDown The amount of lines to move down before and after drawing the line. Default: 1 7 | */ 8 | export function drawHorizontalLine(doc: PDFDocumentType, moveDown = 1): void { 9 | doc 10 | .moveDown(moveDown) 11 | .moveTo(doc.options.margins.left, doc.y) 12 | .lineTo(doc.page.width - doc.options.margins.right, doc.y) 13 | .stroke() 14 | .moveDown(moveDown + 0.5) 15 | } 16 | -------------------------------------------------------------------------------- /src/runtime/composables/htmlToPDF.ts: -------------------------------------------------------------------------------- 1 | import jsPDF, { type HTMLOptions, type jsPDFOptions } from 'jspdf' 2 | 3 | export default async function(element: HTMLElement, documentOptions?: jsPDFOptions, htmlOptions?: HTMLOptions) { 4 | if (!(element instanceof HTMLElement)) { 5 | throw new TypeError('usePDFExport: element is not a HTMLElement.') 6 | } 7 | const orientation = (element.offsetWidth > element.offsetHeight) ? 'l' : 'p' 8 | 9 | // eslint-disable-next-line new-cap 10 | const pdf = new jsPDF({ 11 | orientation: documentOptions?.orientation ?? orientation, 12 | unit: documentOptions?.unit ?? 'px', 13 | format: documentOptions?.format ?? 'A4', 14 | encryption: documentOptions?.encryption 15 | }) 16 | 17 | await pdf.html(element, htmlOptions) 18 | return pdf 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .data 23 | .vercel_build_output 24 | .build-* 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | -------------------------------------------------------------------------------- /playground/server/api/pdf/withLayout.ts: -------------------------------------------------------------------------------- 1 | import { createPDF, streamReturnPDF, drawHorizontalLine, applyLayout } from '#pdf' 2 | 3 | export default eventHandler(async (event) => { 4 | const pdf = createPDF({info: { Title: 'Welcome to NuxtPDF!' }}, undefined, { 5 | header: { 6 | height: 30, 7 | render: async (doc) => { 8 | // This can be Async or not! 9 | await new Promise(resolve => setTimeout(resolve, 100)) 10 | doc.moveDown(1.5) 11 | doc.text('Welcome to NuxtPDF!') 12 | drawHorizontalLine(doc, 0.5) 13 | } 14 | }, 15 | footer: { 16 | height: 30, 17 | render: (doc) => { 18 | doc.text('Created with <3 by sidebase') 19 | } 20 | } 21 | }) 22 | 23 | pdf.text('The PDF Module by sidebase!') 24 | pdf.addPage() 25 | pdf.text('Its pretty nice.') 26 | 27 | await applyLayout(pdf) 28 | pdf.end() 29 | 30 | return streamReturnPDF(event, pdf) 31 | }) 32 | -------------------------------------------------------------------------------- /docs/content/1.getting-started/4.getting-help.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "How to get help when using `nuxt-pdf` in your Vue / Nuxt 3 application." 3 | --- 4 | 5 | # Getting Help 6 | 7 | At some point, you may find that there's an issue you need some help with. 8 | 9 | But don't worry! We're a friendly community of developers and we'd love to help. Concretely this means to: 10 | - Checkout the docs (page that you are currently viewing), 11 | - Search open issues and discussions: https://github.com/sidebase/nuxt-pdf/issues 12 | - Hop on Discord to ask us directly: https://discord.gg/VzABbVsqAc, 13 | - Open an issue to file a bug, ask for an enhancement or get an answer to a question: https://github.com/sidebase/nuxt-pdf/issues/new/choose 14 | 15 | We aim to follow the getting-help standards of the nuxt-project as described here and ask you to do the same when opening an issue or pinging us for help: https://nuxt.com/docs/community/getting-help#getting-help. 16 | -------------------------------------------------------------------------------- /src/runtime/server/components/layout.ts: -------------------------------------------------------------------------------- 1 | import type { PDFDocumentType } from "../../types" 2 | 3 | async function printFooters(doc: PDFDocumentType) { 4 | if (!doc.layout?.footer) return 5 | 6 | const { start, count } = doc.bufferedPageRange() 7 | for (let c = 0; c < count; c++) { 8 | doc.switchToPage(start + c) 9 | doc.y = doc.page.height - (doc.layout.footer.height ?? 50) 10 | 11 | await Promise.resolve(doc.layout.footer.render(doc)) 12 | } 13 | } 14 | async function printHeaders(doc: PDFDocumentType) { 15 | if (!doc.layout?.header) return 16 | 17 | const { start, count } = doc.bufferedPageRange() 18 | for (let c = 0; c < count; c++) { 19 | doc.switchToPage(start + c) 20 | doc.y = 0 21 | 22 | await Promise.resolve(doc.layout.header.render(doc)) 23 | } 24 | } 25 | 26 | /** 27 | * Applies the Header and Footer designs to every page. Requires layout option to be set on PDF initialization 28 | * @param doc The PDF document object 29 | */ 30 | export async function applyLayout(doc: PDFDocumentType): Promise { 31 | await printHeaders(doc) 32 | await printFooters(doc) 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SIDESTREAM GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docus Starter 2 | 3 | Starter template for [Docus](https://docus.dev). 4 | 5 | ## Clone 6 | 7 | Clone the repository (using `nuxi`): 8 | 9 | ```bash 10 | npx nuxi init docs -t nuxt-themes/docus-starter 11 | ``` 12 | 13 | ## Setup 14 | 15 | Install dependencies: 16 | 17 | ```bash 18 | yarn install 19 | ``` 20 | 21 | ## Development 22 | 23 | ```bash 24 | yarn dev 25 | ``` 26 | 27 | ## Edge Side Rendering 28 | 29 | Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compatible environments. 30 | 31 | Look at all the available presets [here](https://v3.nuxtjs.org/guide/deploy/presets). 32 | 33 | ```bash 34 | yarn build 35 | ``` 36 | 37 | ## Static Generation 38 | 39 | Use the `generate` command to build your application. 40 | 41 | The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. 42 | 43 | ```bash 44 | yarn generate 45 | ``` 46 | 47 | ## Preview build 48 | 49 | You might want to preview the result of your build locally, to do so, run the following command: 50 | 51 | ```bash 52 | yarn preview 53 | ``` 54 | 55 | --- 56 | 57 | For a detailed explanation of how things work, check out [Docus](https://docus.dev). 58 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 57 | -------------------------------------------------------------------------------- /playground/pages/client.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 49 | 50 | 59 | -------------------------------------------------------------------------------- /docs/content/1.getting-started/3.quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "How to use nuxt-pdf." 3 | --- 4 | 5 | # Quick Start 6 | 7 | After [installing the package](/nuxt-pdf/getting-started/installation), add the package to your `nuxt.config.ts`: 8 | 9 | ```ts 10 | export default defineNuxtConfig({ 11 | modules: ['@sidebase/nuxt-pdf'], 12 | }) 13 | ``` 14 | 15 | ## Examples 16 | 17 | That's it! You can now begin creating PDFs in your Nuxt 3 application! 18 | 19 | ### Application side 20 | 21 | You can export Vue components to HTML through our composables. You can learn more about using `nuxt-pdf` in the application side [here](/nuxt-pdf/application-side). 22 | 23 | ```vue 24 | // file: ~/components/PDF.vue 25 | 28 | 29 | 39 | ``` 40 | 41 | ### Server side 42 | 43 | Or use our server side code based approch to define your design! You can learn more about using `nuxt-pdf` on the server side [here](/nuxt-pdf/server-side). 44 | 45 | ```ts 46 | // file: ~/server/api/pdf/my-pdf.vue 47 | import { createPDF, streamReturnPDF } from '#pdf' 48 | 49 | export default eventHandler(async (event) => { 50 | const pdf = createPDF() 51 | pdf.text('Welcome to NuxtPDF!') 52 | pdf.end() 53 | 54 | return streamReturnPDF(event, pdf) 55 | }) 56 | ``` 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sidebase/nuxt-pdf", 3 | "version": "1.0.0-alpha.0", 4 | "description": "A Nuxt 3 module to create Server Side rendered PDFs", 5 | "repository": "@sidebase/nuxt-pdf", 6 | "license": "MIT", 7 | "type": "module", 8 | "exports": { 9 | ".": { 10 | "types": "./dist/types.d.ts", 11 | "import": "./dist/module.mjs", 12 | "require": "./dist/module.cjs" 13 | } 14 | }, 15 | "main": "./dist/module.cjs", 16 | "types": "./dist/types.d.ts", 17 | "files": [ 18 | "dist" 19 | ], 20 | "scripts": { 21 | "prepare": "nuxt-module-build prepare && nuxi prepare playground", 22 | "dev": "nuxi dev playground", 23 | "dev:build": "nuxi build playground", 24 | "dev:prepare": "npm run build --stub && npm run prepare", 25 | "build": "nuxt-module-build build", 26 | "prepack": "nuxt-module-build", 27 | "release": "npm run lint && npm run prepack && changelogen --release --push && npm publish && git push --follow-tags", 28 | "lint": "eslint ." 29 | }, 30 | "dependencies": { 31 | "@nuxt/devtools-kit": "^1.0.8", 32 | "@nuxt/kit": "^3.10.1", 33 | "defu": "^6.1.4", 34 | "h3": "^1.10.1", 35 | "jspdf": "^2.5.1", 36 | "pdfkit": "^0.14.0" 37 | }, 38 | "devDependencies": { 39 | "@nuxt/devtools": "latest", 40 | "@nuxt/devtools-ui-kit": "^1.0.8", 41 | "@nuxt/eslint-config": "^0.2.0", 42 | "@nuxt/module-builder": "^0.5.5", 43 | "@nuxt/schema": "^3.10.1", 44 | "@types/node": "^20.11.16", 45 | "@types/pdfkit": "^0.13.4", 46 | "eslint": "^8.56.0", 47 | "nuxt": "^3.10.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtModule, createResolver, addImportsDir } from '@nuxt/kit' 2 | import { defu } from 'defu' 3 | import type { ModuleOptions } from './runtime/types' 4 | 5 | const PACKAGE_NAME = '@sidebase/nuxt-pdf' 6 | 7 | // Module options TypeScript interface definition 8 | const defaultOptions: ModuleOptions = { 9 | defaultDocOptions: { 10 | size: 'A4', 11 | bufferPages: true, 12 | margins: { 13 | top: 25, 14 | left: 25, 15 | right: 25, 16 | bottom: 25, 17 | }, 18 | } 19 | } 20 | 21 | export default defineNuxtModule({ 22 | meta: { 23 | name: PACKAGE_NAME, 24 | configKey: 'pdf' 25 | }, 26 | // Default configuration options of the Nuxt module 27 | defaults: defaultOptions, 28 | setup (options, nuxt) { 29 | const resolver = createResolver(import.meta.url) 30 | 31 | // @ts-expect-error 32 | nuxt.options.runtimeConfig.public.pdf = options 33 | 34 | // Step 1: Inject Sever side PDFDocument creation 35 | nuxt.hook('nitro:config', (nitroConfig) => { 36 | nitroConfig.alias = nitroConfig.alias || {} 37 | 38 | // Inline module runtime in Nitro bundle 39 | nitroConfig.externals = defu( 40 | typeof nitroConfig.externals === 'object' ? nitroConfig.externals : {}, 41 | { 42 | inline: [resolver.resolve('./runtime')] 43 | } 44 | ) 45 | nitroConfig.alias['#pdf'] = resolver.resolve('./runtime/server') 46 | }) 47 | 48 | // Step 2: Inject Client side composables to create pdfs from vue components 49 | const composables = resolver.resolve('./runtime/composables') 50 | addImportsDir(composables) 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![nuxt-pdf](.github/preview.jpg) 2 | 3 | # 📄 nuxt-pdf 4 | 5 | [![npm version][npm-version-src]][npm-version-href] 6 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 7 | [![GitHub stars](https://badgen.net/github/stars/sidebase/nuxt-pdf)](https://GitHub.com/sidebase/nuxt-pdf/) 8 | [![License][license-src]][license-href] 9 | [![Follow us on Twitter](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/sidebase_io) 10 | [![Join our Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/NDDgQkcv3s) 11 | 12 | > `nuxt-pdf` is a easy to use, pdf exporting module to convert Nuxt 3 components into downloadable PDFs. 13 | 14 | ## Features 15 | 16 | - Easily export your Nuxt 3 components into PDFs 17 | - Generate PDFs on your server 18 | - Guides for PDF specific styles 19 | - Track exporting progress 20 | 21 | ## Quick Setup 22 | 23 | 1. Add `@sidebase/nuxt-pdf` dependency to your project 24 | 25 | ```bash 26 | # Using pnpm 27 | pnpm add -D @sidebase/nuxt-pdf 28 | 29 | # Using yarn 30 | yarn add --dev @sidebase/nuxt-pdf 31 | 32 | # Using npm 33 | npm install --save-dev @sidebase/nuxt-pdf 34 | ``` 35 | 36 | 2. Add `@sidebase/nuxt-pdf` to the `modules` section of `nuxt.config.ts` 37 | 38 | ```js 39 | export default defineNuxtConfig({ 40 | modules: [ 41 | '@sidebase/nuxt-pdf' 42 | ] 43 | }) 44 | ``` 45 | 46 | That's it! You can now use NuxtPDF in your Nuxt 3 app ✨ 47 | 48 | Afterwards you can use the [Quick Start documentation](https://sidebase.io/nuxt-pdf/getting-started/quick-start) to explore the features and properly configure your NuxtPDF instance! 49 | 50 | ## Development 51 | 52 | ```bash 53 | # Install dependencies 54 | pnpm install 55 | 56 | # Generate type stubs 57 | pnpm run dev:prepare 58 | 59 | # Develop with the playground 60 | pnpm run dev 61 | 62 | # Build the playground 63 | pnpm run dev:build 64 | 65 | # Run ESLint 66 | pnpm run lint 67 | 68 | # Publish a new release (bump version before) 69 | pnpm publish --access public 70 | ``` 71 | 72 | 73 | [npm-version-src]: https://img.shields.io/npm/v/@sidebase/nuxt-pdf/latest.svg 74 | [npm-version-href]: https://npmjs.com/package/@sidebase/nuxt-pdf 75 | 76 | [npm-downloads-src]: https://img.shields.io/npm/dt/@sidebase/nuxt-pdf.svg 77 | [npm-downloads-href]: https://npmjs.com/package/@sidebase/nuxt-pdf 78 | 79 | [license-src]: https://img.shields.io/npm/l/@sidebase/nuxt-pdf.svg 80 | [license-href]: https://npmjs.com/package/@sidebase/nuxt-pdf 81 | -------------------------------------------------------------------------------- /src/runtime/server/pdf.ts: -------------------------------------------------------------------------------- 1 | import PDFDocument from 'pdfkit' 2 | import { sendStream, setHeader } from 'h3' 3 | import { defu } from 'defu' 4 | import { useRuntimeConfig } from "#imports"; 5 | import type { H3Event } from 'h3' 6 | import type { WriteStream } from 'node:fs' 7 | import type { PDFOptions, PDFDocumentType } from '../types' 8 | 9 | export interface LayoutOptions { 10 | header?: { 11 | render: (doc: PDFDocumentType) => Promise | void 12 | height: number 13 | } 14 | footer?: { 15 | render: (doc: PDFDocumentType) => Promise | void 16 | height: number 17 | } 18 | } 19 | 20 | /** 21 | * Create a blank pdfkit-PDF to be filled with life later on. 22 | * 23 | * @param options Configure PDF. Optional, otherwise the defaults set in the Nuxt Config will be used. 24 | * @param data Data to attach to PDF object, for later, global consumption during PDF creation 25 | * @param streamToFile Stream to write PDF to while creating the pdf. In the end this stream can be stored to a file, or streamed to an email server, or ... 26 | */ 27 | export function createPDF(options?: PDFKit.PDFDocumentOptions, data?: TData, layout?: LayoutOptions, streamToFile?: WriteStream): PDFDocumentType { 28 | // Determine PDF Options 29 | const optionsWithDefaults = defu(options, useRuntimeConfig().public.pdf.defaultDocOptions) as PDFOptions 30 | const formattedOptions: PDFOptions = { 31 | ...optionsWithDefaults, 32 | margins: { 33 | ...optionsWithDefaults.margins, 34 | ...(layout?.header ? { top: optionsWithDefaults.margins.top + layout.header.height } : { }), 35 | ...(layout?.footer ? { bottom: optionsWithDefaults.margins.bottom - layout.footer.height } : { }) 36 | } 37 | } 38 | 39 | // Init PDF 40 | const doc = new PDFDocument(formattedOptions) as PDFDocumentType 41 | 42 | // Inject futhur PDF data 43 | doc.data = data 44 | doc.layout = layout 45 | if (streamToFile) { doc.pipe(streamToFile) } 46 | 47 | return doc 48 | } 49 | 50 | /** 51 | * Render a PDFDocument using an H3Event. 52 | * Return this function at the end of your event handler. 53 | * 54 | * @param event The H3Event passed from the Event handler 55 | * @param pdf The created PDF Object from createPDF. This can be a promise or a static object 56 | */ 57 | export const streamReturnPDF = async (event: H3Event, pdf: PDFKit.PDFDocument | Promise) => { 58 | try { 59 | const doc = await Promise.resolve(pdf) 60 | if (doc.info.Title) { 61 | setHeader(event, 'Content-disposition', `filename=${doc.info.Title}.pdf`) 62 | } 63 | 64 | setHeader(event, 'Content-Type', 'application/pdf') 65 | return sendStream(event, doc as any) 66 | } catch(error) { 67 | console.error('nuxt-pdf: Error during PDF generation:', error) 68 | throw new Error('PDF generated failed.') 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docs/content/2.application-side/4.composables.md: -------------------------------------------------------------------------------- 1 | # Composables 2 | 3 | `nuxt-pdf` exposes multiple composables, that you can access to easily create pdfs through vue code. 4 | 5 | ## Options 6 | 7 | Both of our composables, accept 2 seperate configuration options. 8 | - The `documentOptions`, allow you to customize the general layout of your document. You can find the options [here](http://raw.githack.com/MrRio/jsPDF/master/docs/jsPDF.html). 9 | - The `htmlOptions`, allow you to customize the conversion of your HTML to a canvas. You can find the options [here](http://raw.githack.com/MrRio/jsPDF/master/docs/module-html.html#~html). 10 | 11 | 12 | ## `exportToPDF` 13 | 14 | `exportToPDF` allows you to usea native vue ref, to target either a native HTML element, or a Vue component, which will then be converted into a PDF. 15 | 16 | The composable accepts 4 parameters: 17 | - The fileName 18 | - The component you wish to convert 19 | - The documentOptions 20 | - The HTMLOptions. 21 | 22 | To learn more about the options, please refer [here](#options) 23 | 24 | ```vue 25 | 35 | 36 | 41 | ``` 42 | 43 | ### Tips & Tricks 44 | 45 | - Ensure that the section you want to export has a fixed width, to ensure the layout does not shift when exporting 46 | - Setting a fixed width and height, to match your document size, will lead to better results 47 | - Use the `scale` option in the document section, to slightly increase or decrease the size of the rendered html 48 | - Optimize your HTML to reduce the PDF file size 49 | 50 | ## `htmlToPDF` 51 | 52 | `htmlToPdf` allows you to pass an HTML string and format it into a PDF. This can allow you to futhur customize the behaviour of how the module interacts with your UI. 53 | 54 | ```ts 55 | import { htmlToPdf } from '#imports' 56 | 57 | const openInWindow = async (HTMLElement: HTMLElement) => { 58 | const pdf = await htmlToPdf(HTMLElement, 59 | undefined, 60 | { 61 | html2canvas: { 62 | scale: 0.7, 63 | useCORS: true 64 | } 65 | }) 66 | const totalPages = pdf.getNumberOfPages() 67 | const pdfHeight = pdf.getPageHeight() 68 | await pdf.html('I am a custom pdf!!!', { 69 | x: 20, 70 | y: (pdfHeight - 50) * totalPages // place in the bottom 71 | }) 72 | const blob = pdf.output('blob') 73 | window.open(URL.createObjectURL(blob), '_blank') 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/content/1.getting-started/1.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Introduction to `nuxt-pdf`." 3 | --- 4 | 5 | # Introduction 6 | 7 | `nuxt-pdf` is an open source Nuxt 3 PDF toolkit, that allows you to easily render PDFs application or server side. 8 | 9 | ::list{type="success"} 10 | - Client side exporting of Vue components to HTML 11 | - Server side rendering of complex PDFs 12 | - Simple encryption of your PDFs, by allowing you to set a password and permissions 13 | - Editable forms inside your PDFs 14 | - Pre-build components to quickly develop your PDF 15 | - documentation, recipes and example code to get you started 16 | :: 17 | 18 | ::callout 19 | #summary 20 | Show me the code! 21 | 22 | #content 23 | Visit the [quick start](/nuxt-pdf/getting-started/quick-start) page to see code examples. 24 | :: 25 | 26 | ## Which method is right for me? 27 | 28 | Generating PDFs on the client or through the server fundamentally change how the PDF is compiled and created. We try and ensure that our feature set matches both methods equally, however there are a few method-specific features. These are listed below. 29 | 30 | | | Application Side | Server Side | 31 | |-----------------------------------------------------|------------------|-------------| 32 | | **Features** | | | 33 | | Convert Vue components to PDFs | ✅ | ❌ | 34 | | Add Encryption to your PDFs | ✅ | ✅ | 35 | | Define Layouts for your PDFs | ❌ | ✅ | 36 | | Use your Tailwind Styles | ✅ | ❌ | 37 | | Use pre-build components to create your PDF | ❌ | ✅ | 38 | | Define your default PDF options in `nuxt.config.ts` | ❌ | ✅ | 39 | 40 | One factor that this table does not take into account is the reliablity and consistency, that server side generated PDFs will add to your application. Generating a PDF from HTML code, may result in different appearences, based on the browser, screen size and other settings on an individuals PC, limiting the control you have over the finished product. Rendering your PDFs on the server side, will allow you to control the output to a mich higher degree. 41 | 42 | ### Use cases 43 | 44 | #### Application side 45 | 46 | - You need to quickly develop your PDF integration 47 | - You already have Vue components developed, that can be reused to generate the PDFs 48 | - You are not concerned about minor layout shifts and differences between browsers 49 | 50 | #### Server side 51 | 52 | - You require a higher degree of control over the output 53 | - You are developing complex PDFs that may require large amounts of data, you do not want to directly expose to a users client 54 | -------------------------------------------------------------------------------- /playground/components/TestingStage.vue: -------------------------------------------------------------------------------- 1 | 104 | --------------------------------------------------------------------------------