Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ This is a fork of @snowdog/vuepress-plugin-pdf-export. All changes are available
- Applies styles to hide UI elements like navigation or sidebar
- Doesn't require other runtimes like Java to operate
- Designed to work well in headless environments like CI runners
- Can filter and sort pages.
- Can generate a rudimentary table of contents

## Config options
- `theme` - theme name (default `@vuepress/default`)
- `sorter` - function for changing pages order (default `false`)
- `filter` - function for filtering the pages (default `false`)
- `tocLevel` - function returning a TOC level for the pages, i.e. zero or one (default `false`)
- `outputFileName` - name of output file (default `site.pdf`)
- `puppeteerLaunchOptions` - [Puppeteer launch options object](https://github.com/puppeteer/puppeteer/blob/v2.1.1/docs/api.md#puppeteerlaunchoptions) (default `{}`)
- `pageOptions` - [Puppeteer page formatting options object](https://github.com/puppeteer/puppeteer/blob/v2.1.1/docs/api.md#pagepdfoptions) (default `{format: 'A4'}`)
Expand All @@ -35,6 +38,30 @@ Then run:
vuepress export [path/to/your/docs]
```

#### The filter function

The `filter` function takes a `pages` object and returns `true` or `false`. Only pages where the function returns `true` are rendered to the pdf. The function is invoked as follows:

```
exportPages = exportPages.filter(filter);
```

#### The sorter function

The `sorter` function takes two `pages` objects and return `-1`, `0`, or `1` to indicate the sort order. The function is invoked as follows:

```
exportPages = exportPages.sort(sorter)
```

The sorting happens after the filtering, so you only have to handle the pages that pass your filter function.

#### The tocLevel function

The `tocLevel` function takes a `pages` object returns a TOC level, either zero (`0`, top level) or one (`1`, secondary level), or minus one (`-1`, leave out of TOC). If the entire TOC is empty, e.g. every page is on level `-1`, no TOC is rendered.

The TOC generation is invoked after the filtering and sorting. So the list of pages can be assumed to be filtered.

### Tips
To run this plugin on Gitlab CI you may want to run Chrome with `no-sandbox` flag. [Details](https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md#setting-up-chrome-linux-sandbox)

Expand All @@ -49,3 +76,7 @@ module.exports = {
]
}
```

## Known Issues

- At the moment, pdfjs cannot inject footers on the rendered pages, and the individual pages do not know their page number, so the page numbers in the TOC relates to the page numbers in the PDF, but no page number is rendered on the actual PDF pages.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@e8johan/vuepress-plugin-pdf-export",
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT",
"repository": "e8johan/vuepress-plugin-pdf-export",
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions src/extendCli.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = options => {
const theme = options.theme || '@vuepress/default'
const sorter = options.sorter || false
const filter = options.filter || false
const tocLevel = options.tocLevel || false
const outputFileName = options.outputFileName || 'site.pdf'
const puppeteerLaunchOptions = options.puppeteerLaunchOptions || {}
const pageOptions = options.pageOptions || {}
Expand All @@ -35,6 +36,7 @@ module.exports = options => {
host: nCtx.devProcess.host,
sorter,
filter,
tocLevel,
outputFileName,
puppeteerLaunchOptions,
pageOptions
Expand Down
114 changes: 100 additions & 14 deletions src/generatePdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,41 @@ const { join } = require('path')
const { fs, logger, chalk } = require('@vuepress/shared-utils')
const { yellow, gray } = chalk

function _createToc(doc, toc, tocPageCount) {
doc.text('Table of Contents', { fontSize: 20 });
doc.text(' ', { fontSize: 8 });
const table = doc.table({
widths: [5 * pdf.mm, (210-85.8) * pdf.mm, 30 * pdf.mm],
padding: 0,
borderWidth: 0
});
let currentPage = tocPageCount;
if (currentPage == -1)
currentPage = 9998;

toc.forEach(t => {
const row = table.row();
if (t.tocLevel == 0) {
row.cell(t.title, {fontSize: 11, textAlign: 'left', colspan: 2});
row.cell((currentPage+1).toString(), {fontSize: 11, textAlign: 'right'});
} else if (t.tocLevel == 1) {
row.cell('', {fontSize: 11, textAlign: 'left'});
row.cell(t.title, {fontSize: 11, textAlign: 'left'});
row.cell((currentPage+1).toString(), {fontSize: 11, textAlign: 'right'});
}
// Other toc levels mean skipping the entry

if (tocPageCount != -1)
currentPage += t.pageCount;
});
}

module.exports = async (ctx, {
port,
host,
sorter,
filter,
tocLevel,
outputFileName,
puppeteerLaunchOptions,
pageOptions
Expand All @@ -17,6 +47,11 @@ module.exports = async (ctx, {
const tempDir = join(tempPath, 'pdf')
fs.ensureDirSync(tempDir)

// Default toc level if not specified
if (typeof tocLevel !== 'function') {
tocLevel = function() { return -1; }
}

let exportPages = pages.slice(0)

if (typeof filter === 'function') {
Expand All @@ -32,7 +67,8 @@ module.exports = async (ctx, {
url: page.path,
title: page.title,
location: `http://${host}:${port}${page.path}`,
path: `${tempDir}/${page.key}.pdf`
path: `${tempDir}/${page.key}.pdf`,
relativePath: page.relativePath
}
})

Expand Down Expand Up @@ -64,24 +100,74 @@ module.exports = async (ctx, {
}

await new Promise(resolve => {
const mergedPdf = new pdf.Document()

exportPages
.map(({ path }) => fs.readFileSync(path))
.forEach(file => {
// Build the TOC (collect page numbers, etc)
var toc = []
for (let i = 0; i < exportPages.length; i++) {
const {
relativePath,
path,
title
} = exportPages[i]
const file = fs.readFileSync(path)
const page = new pdf.ExternalDocument(file)
mergedPdf.addPagesOf(page)
})
const tl = tocLevel(exportPages[i])
if (tl == 0 || tl == 1) {
toc.push({tocLevel: tl, title: title, pageCount: page.pageCount})
}
}

mergedPdf.asBuffer((err, data) => {
// Generate and TOC without page numbers to count pages
const tocPdf = new pdf.Document({
paddingLeft: 25.4 * pdf.mm,
paddingRight: 25.4 * pdf.mm,
paddingTop: 25.4 * pdf.mm,
paddingBottom: 37.6 * pdf.mm,
});
if (toc.length > 0) {
_createToc(tocPdf, toc, -1);
}
let tocPageCount = -1;
tocPdf.asBuffer((err, data) => {
if (err) {
throw err
throw err;
} else {
fs.writeFileSync(outputFileName, data, { encoding: 'binary' })
logger.success(`Export ${yellow(outputFileName)} file!`)
resolve()
const tocPages = new pdf.ExternalDocument(data);
tocPageCount = tocPages.pageCount;
}
})
}).finally(x => {
// Merge the pages, but first, insert the TOC
const mergedPdf = new pdf.Document({
paddingLeft: 25.4 * pdf.mm,
paddingRight: 25.4 * pdf.mm,
paddingTop: 25.4 * pdf.mm,
paddingBottom: 37.6 * pdf.mm,
});

if (toc.length > 0) {
_createToc(mergedPdf, toc, tocPageCount);
} else {
tocPageCount = 0;
}

for (let i = 0; i < exportPages.length; i++) {
const {
path,
} = exportPages[i]
const file = fs.readFileSync(path)
const page = new pdf.ExternalDocument(file)
mergedPdf.addPagesOf(page)
}

mergedPdf.asBuffer((err, data) => {
if (err) {
throw err
} else {
fs.writeFileSync(outputFileName, data, { encoding: 'binary' })
logger.success(`Export ${yellow(outputFileName)} file!`)
resolve()
}
})
});
})

await browser.close()
Expand Down