PageSourceSearch

https://pixlane.media/assets/js/pdf-ops/resize-pdf-pages.js?v=1786400000

js pixlane.media collected 2026-10-09 03:21:02 UTC 5,946 bytes, 153 lines download raw bytes

1// resize-pdf-pages.js. Change PDF page dimensions (A4/Letter/etc or custom),
2// scaling content to fit. Rotation-proof approach: each selected page is
3// embedded as a form XObject and redrawn onto a fresh page of the target size,
4// so /Rotate flags, offset media boxes, and crop boxes are all handled.
5import {
6  allPages,
7  clamp,
8  exportPdfDocument,
9  loadPdfSource,
10  parsePageSequence,
11} from "./_shared.js?v=1787810615&runtime=bf3904c1cd16";
12
13const PRESETS = {
14  a4: [595.28, 841.89],
15  letter: [612, 792],
16  legal: [612, 1008],
17  a5: [419.53, 595.28],
18  a3: [841.89, 1190.55],
19  square: [595.28, 595.28],
20};
21
22const MIN_DIMENSION = 72;
23const MAX_DIMENSION = 14400;
24
25// cos/sin of -rotation (drawPage's rotate is counter-clockwise positive, while
26// the PDF /Rotate attribute is clockwise). Exact values avoid float fuzz.
27const ROTATION_TRIG = {
28  0: { cos: 1, sin: 0 },
29  90: { cos: 0, sin: -1 },
30  180: { cos: -1, sin: 0 },
31  270: { cos: 0, sin: 1 },
32};
33
34function normalizeRotation(angle) {
35  const wrapped = ((Math.round(Number(angle) || 0) % 360) + 360) % 360;
36  // /Rotate must be a multiple of 90; snap defensively for malformed files.
37  return (Math.round(wrapped / 90) * 90) % 360;
38}
39
40function parseDimension(raw, fallback) {
41  const value = Number.parseFloat(raw);
42  return clamp(Number.isFinite(value) ? value : fallback, MIN_DIMENSION, MAX_DIMENSION);
43}
44
45// Visible area of the page: crop box intersected with media box (viewers do
46// the same); falls back to the media box when the intersection is degenerate.
47function visibleBox(page) {
48  const media = page.getMediaBox();
49  const crop = page.getCropBox();
50  const left = Math.max(media.x, crop.x);
51  const bottom = Math.max(media.y, crop.y);
52  const right = Math.min(media.x + media.width, crop.x + crop.width);
53  const top = Math.min(media.y + media.height, crop.y + crop.height);
54  if (right - left > 0 && top - bottom > 0) {
55    return { left, bottom, right, top };
56  }
57  return { left: media.x, bottom: media.y, right: media.x + media.width, top: media.y + media.height };
58}
59
60export async function processResizePdfPages(sourceUrl, settings = {}) {
61  const { PDFLib, doc, totalPages, nameBase } = await loadPdfSource(sourceUrl, settings);
62  if (!totalPages) {
63    throw new Error("The PDF has no pages to resize.");
64  }
65
66  const presetKey = String(settings.preset || "a4").toLowerCase();
67  let baseWidth;
68  let baseHeight;
69  if (presetKey === "custom") {
70    baseWidth = parseDimension(settings.customWidth, 595);
71    baseHeight = parseDimension(settings.customHeight, 842);
72  } else {
73    [baseWidth, baseHeight] = PRESETS[presetKey] || PRESETS.a4;
74  }
75
76  const orientation = String(settings.orientation || "auto").toLowerCase();
77  const fitMode = String(settings.fitMode || "fit").toLowerCase();
78  const resizeSet = new Set(
79    settings.pageRange
80      ? parsePageSequence(settings.pageRange, totalPages, { preserveOrder: false })
81      : allPages(totalPages),
82  );
83
84  const out = await PDFLib.PDFDocument.create();
85  for (const pageNumber of allPages(totalPages)) {
86    if (!resizeSet.has(pageNumber)) {
87      const [copied] = await out.copyPages(doc, [pageNumber - 1]);
88      out.addPage(copied);
89      continue;
90    }
91
92    const sourcePage = doc.getPage(pageNumber - 1);
93    const box = visibleBox(sourcePage);
94    const sourceWidth = box.right - box.left;
95    const sourceHeight = box.top - box.bottom;
96    if (!(sourceWidth > 0) || !(sourceHeight > 0)) {
97      throw new Error(`Page ${pageNumber} has an invalid size and cannot be resized.`);
98    }
99
100    const rotation = normalizeRotation(sourcePage.getRotation().angle);
101    const swapped = rotation === 90 || rotation === 270;
102    // Size of the page as a viewer displays it (after /Rotate).
103    const displayWidth = swapped ? sourceHeight : sourceWidth;
104    const displayHeight = swapped ? sourceWidth : sourceHeight;
105
106    let targetWidth = baseWidth;
107    let targetHeight = baseHeight;
108    const wantLandscape =
109      orientation === "landscape" ||
110      (orientation !== "portrait" && displayWidth > displayHeight);
111    if (targetWidth !== targetHeight) {
112      targetWidth = wantLandscape ? Math.max(baseWidth, baseHeight) : Math.min(baseWidth, baseHeight);
113      targetHeight = wantLandscape ? Math.min(baseWidth, baseHeight) : Math.max(baseWidth, baseHeight);
114    }
115
116    let displayScaleX;
117    let displayScaleY;
118    if (fitMode === "stretch") {
119      displayScaleX = targetWidth / displayWidth;
120      displayScaleY = targetHeight / displayHeight;
121    } else {
122      const pick = fitMode === "fill" ? Math.max : Math.min;
123      displayScaleX = displayScaleY = pick(targetWidth / displayWidth, targetHeight / displayHeight);
124    }
125    // drawPage scales along the embedded page's own axes (pre-rotation); a
126    // 90/270 rotation maps the source x-axis to the display's vertical axis.
127    const xScale = swapped ? displayScaleY : displayScaleX;
128    const yScale = swapped ? displayScaleX : displayScaleY;
129
130    // The transform anchor is drawPage's (x, y): translate, then rotate, then
131    // scale. Find the drawn bounding rect relative to the anchor from the four
132    // transformed corners, then center it on the target page.
133    const { cos, sin } = ROTATION_TRIG[rotation];
134    let minX = Infinity;
135    let minY = Infinity;
136    for (const [px, py] of [[0, 0], [sourceWidth, 0], [0, sourceHeight], [sourceWidth, sourceHeight]]) {
137      minX = Math.min(minX, xScale * px * cos - yScale * py * sin);
138      minY = Math.min(minY, xScale * px * sin + yScale * py * cos);
139    }
140
141    const embedded = await out.embedPage(sourcePage, box);
142    const target = out.addPage([targetWidth, targetHeight]);
143    target.drawPage(embedded, {
144      x: (targetWidth - displayScaleX * displayWidth) / 2 - minX,
145      y: (targetHeight - displayScaleY * displayHeight) / 2 - minY,
146      xScale,
147      yScale,
148      rotate: PDFLib.degrees(-rotation),
149    });
150  }
151
152  return exportPdfDocument(out, `${nameBase}-resized.pdf`);
153}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.