@percy/cli-pdf 1.32.10-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/assets.js ADDED
@@ -0,0 +1,14 @@
1
+ import path from 'path';
2
+ import { createRequire } from 'module';
3
+ const cjsRequire = createRequire(import.meta.url);
4
+ export function pdfjsAssets() {
5
+ let root = path.dirname(cjsRequire.resolve('pdfjs-dist/package.json'));
6
+ return {
7
+ root,
8
+ buildDir: path.join(root, 'legacy/build'),
9
+ standardFontsDir: path.join(root, 'standard_fonts'),
10
+ cmapsDir: path.join(root, 'cmaps'),
11
+ libPath: path.join(root, 'legacy/build/pdf.js'),
12
+ workerFile: 'pdf.worker.js'
13
+ };
14
+ }
@@ -0,0 +1,110 @@
1
+ export const MIN_DIMENSION = 10;
2
+ export const MAX_DIMENSION = 2000;
3
+ export const DEFAULT_SCALE = 2;
4
+ export const MAX_SCALE = 5;
5
+
6
+ // Wall-clock ceiling for a single page's in-page work (open, measure, render).
7
+ // Page#eval resolves off `Runtime.callFunctionOn` with `awaitPromise: true`,
8
+ // which has no timeout of its own -- Page.TIMEOUT only covers navigation. A PDF
9
+ // that wedges pdf.js would otherwise hang the HTTP request forever while
10
+ // holding a browser page and a listening asset server.
11
+ export const PAGE_RENDER_TIMEOUT = 30000;
12
+ export function fitScale(requestedScale, {
13
+ width,
14
+ height
15
+ }) {
16
+ return Math.min(requestedScale, MAX_DIMENSION / width, MAX_DIMENSION / height);
17
+ }
18
+ export function assertRasterDimensions(pageNumber, width, height) {
19
+ if (width < MIN_DIMENSION || height < MIN_DIMENSION) {
20
+ throw new Error(`Page ${pageNumber} rasterized to ${width}x${height}px, below Percy's ` + `${MIN_DIMENSION}px minimum. Increase \`scale\`.`);
21
+ }
22
+ }
23
+ export async function openDocument(_, {
24
+ origin
25
+ }) {
26
+ let lib = window['pdfjs-dist/build/pdf'] || window.pdfjsLib;
27
+ if (!lib) {
28
+ throw new Error('pdf.js did not initialise in the page');
29
+ }
30
+ lib.GlobalWorkerOptions.workerSrc = `${origin}/pdfjs/pdf.worker.js`;
31
+ let doc = await lib.getDocument({
32
+ url: `${origin}/doc.pdf`,
33
+ isEvalSupported: false,
34
+ standardFontDataUrl: `${origin}/standard_fonts/`,
35
+ cMapUrl: `${origin}/cmaps/`,
36
+ cMapPacked: true
37
+ }).promise;
38
+ window.__percyPdf = {
39
+ lib,
40
+ doc
41
+ };
42
+ return {
43
+ pageCount: doc.numPages
44
+ };
45
+ }
46
+ export async function measurePages(_, {
47
+ pageNumbers
48
+ }) {
49
+ let {
50
+ doc
51
+ } = window.__percyPdf;
52
+ let sizes = [];
53
+ for (let pageNumber of pageNumbers) {
54
+ let page = await doc.getPage(pageNumber);
55
+ let {
56
+ width,
57
+ height
58
+ } = page.getViewport({
59
+ scale: 1
60
+ });
61
+ sizes.push({
62
+ pageNumber,
63
+ width,
64
+ height
65
+ });
66
+ page.cleanup();
67
+ }
68
+ return sizes;
69
+ }
70
+ export async function renderPage(_, {
71
+ pageNumber,
72
+ scale
73
+ }) {
74
+ let {
75
+ doc
76
+ } = window.__percyPdf;
77
+ let page = await doc.getPage(pageNumber);
78
+ try {
79
+ let viewport = page.getViewport({
80
+ scale
81
+ });
82
+ let width = Math.ceil(viewport.width);
83
+ let height = Math.ceil(viewport.height);
84
+ let canvas = document.createElement('canvas');
85
+ canvas.width = width;
86
+ canvas.height = height;
87
+ let context = canvas.getContext('2d');
88
+ context.fillStyle = '#ffffff';
89
+ context.fillRect(0, 0, width, height);
90
+ await page.render({
91
+ canvasContext: context,
92
+ viewport
93
+ }).promise;
94
+ return {
95
+ width,
96
+ height,
97
+ dataUrl: canvas.toDataURL('image/png')
98
+ };
99
+ } finally {
100
+ page.cleanup();
101
+ }
102
+ }
103
+ export async function destroyDocument() {
104
+ let state = window.__percyPdf;
105
+ if (state !== null && state !== void 0 && state.doc) {
106
+ await state.doc.destroy();
107
+ delete window.__percyPdf;
108
+ }
109
+ return true;
110
+ }
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { pdfjsAssets } from './assets.js';
2
+ export { resolvePages, MAX_PAGES } from './pages.js';
3
+ export { openDocument, measurePages, renderPage, destroyDocument, fitScale, assertRasterDimensions, DEFAULT_SCALE, MAX_SCALE, PAGE_RENDER_TIMEOUT, MIN_DIMENSION, MAX_DIMENSION } from './browser-scripts.js';
package/dist/pages.js ADDED
@@ -0,0 +1,67 @@
1
+ // Ceiling on how many pages one request may rasterize. Every page is held in
2
+ // memory as a PNG twice over -- once in the rasterizer's result array, once in
3
+ // the resource closure the snapshot queue keeps -- and each additionally crosses
4
+ // CDP as a base64 data URL. A 50MB PDF can carry thousands of pages, so without
5
+ // a cap a single request can exhaust the heap. Callers who genuinely want more
6
+ // can narrow with `pages` and issue several requests.
7
+ export const MAX_PAGES = 250;
8
+ function parseSelection(value, pageCount) {
9
+ if (value == null) return range(1, pageCount);
10
+ if (typeof value === 'number') return [toPageNumber(value)];
11
+ if (Array.isArray(value)) return value.map(toPageNumber);
12
+ if (typeof value !== 'string') {
13
+ throw new Error(`Invalid page selection: expected a number, array or string, got ${typeof value}`);
14
+ }
15
+ let selected = [];
16
+ for (let part of value.split(',')) {
17
+ part = part.trim();
18
+ if (!part) continue;
19
+ let match = /^(\d+)\s*-\s*(\d+)?$/.exec(part);
20
+ if (match) {
21
+ let from = toPageNumber(match[1]);
22
+ let to = match[2] == null ? pageCount : toPageNumber(match[2]);
23
+ if (to < from) throw new Error(`Invalid page range "${part}": end page is before start page`);
24
+ selected.push(...range(from, to));
25
+ } else if (/^\d+$/.test(part)) {
26
+ selected.push(toPageNumber(part));
27
+ } else {
28
+ throw new Error(`Invalid page selection "${part}": expected a page number or a range like "2-5"`);
29
+ }
30
+ }
31
+ return selected;
32
+ }
33
+ function toPageNumber(value) {
34
+ let n = Number(value);
35
+ if (!Number.isInteger(n) || n < 1) {
36
+ throw new Error(`Invalid page number "${value}": page numbers are 1-based integers`);
37
+ }
38
+ return n;
39
+ }
40
+ function range(from, to) {
41
+ let out = [];
42
+ for (let i = from; i <= to; i++) out.push(i);
43
+ return out;
44
+ }
45
+ export function resolvePages({
46
+ pages,
47
+ excludePages
48
+ } = {}, pageCount) {
49
+ if (!Number.isInteger(pageCount) || pageCount < 1) {
50
+ throw new Error(`Invalid page count: ${pageCount}`);
51
+ }
52
+ let selected = parseSelection(pages, pageCount);
53
+ let excluded = new Set(excludePages == null ? [] : parseSelection(excludePages, pageCount));
54
+ let outOfRange = [...new Set(selected.filter(p => p > pageCount))];
55
+ if (outOfRange.length) {
56
+ throw new Error(`Requested page${outOfRange.length > 1 ? 's' : ''} ${outOfRange.join(', ')} ` + `but the document has only ${pageCount} page${pageCount > 1 ? 's' : ''}`);
57
+ }
58
+ let resolved = [...new Set(selected)].filter(p => !excluded.has(p)).sort((a, b) => a - b);
59
+ if (!resolved.length) {
60
+ throw new Error('No pages left to snapshot after applying `pages` and `excludePages`');
61
+ }
62
+ if (resolved.length > MAX_PAGES) {
63
+ throw new Error(`Requested ${resolved.length} pages but the maximum per request is ${MAX_PAGES}. ` + 'Narrow the selection with `pages` (for example "1-100") and issue several requests.');
64
+ }
65
+ return resolved;
66
+ }
67
+ export { parseSelection as _parseSelection };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@percy/cli-pdf",
3
+ "version": "1.32.10-beta.1",
4
+ "license": "MIT",
5
+ "description": "Renders PDF documents into per-page images for Percy snapshots",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/percy/cli",
9
+ "directory": "packages/cli-pdf"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "tag": "beta"
14
+ },
15
+ "engines": {
16
+ "node": ">=14"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "main": "./dist/index.js",
22
+ "type": "module",
23
+ "exports": "./dist/index.js",
24
+ "scripts": {
25
+ "build": "node ../../scripts/build",
26
+ "lint": "eslint --ignore-path ../../.gitignore .",
27
+ "test": "node ../../scripts/test",
28
+ "test:coverage": "yarn test --coverage"
29
+ },
30
+ "dependencies": {
31
+ "@percy/logger": "1.32.10-beta.1",
32
+ "pdfjs-dist": "^2.16.105"
33
+ },
34
+ "gitHead": "b90cd9fe29b14fdb7a1e383609654b227adacf17"
35
+ }