Last active
November 13, 2025 18:56
-
-
Save ccorcos/525ba4523ffedb95711f3d63c2c3174e to your computer and use it in GitHub Desktop.
Download GDrive PDFs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // started from: https://github.com/zavierferodova/Google-Drive-View-Only-PDF-Script-Downloader | |
| (async function () { | |
| console.log("Loading script ..."); | |
| const load = (url) => | |
| new Promise((res, rej) => { | |
| const s = document.createElement("script"); | |
| s.onload = () => res(); | |
| s.onerror = rej; | |
| // Trusted Types safe-ish path | |
| if (window.trustedTypes && trustedTypes.createPolicy) { | |
| const policy = trustedTypes.createPolicy("jspdfPolicy", { | |
| createScriptURL: (input) => input, | |
| }); | |
| s.src = policy.createScriptURL(url); | |
| } else { | |
| s.src = url; | |
| } | |
| document.body.appendChild(s); | |
| }); | |
| await load("https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"); | |
| const { jsPDF } = window.jspdf; | |
| // Collect blob: images (e.g., Google Drive preview) | |
| const imgs = Array.from(document.getElementsByTagName("img")) | |
| .filter((img) => img.src.startsWith("blob:")) | |
| // Ensure the image actually decoded so naturalWidth/Height are valid | |
| .filter((img) => { | |
| // Some browsers support decode(); guard and fallback | |
| return typeof img.decode === "function" | |
| ? true | |
| : img.naturalWidth > 0 && img.naturalHeight > 0; | |
| }); | |
| console.log(`Scanning content ... found ${imgs.length} images`); | |
| // Ensure all images are decoded to get correct natural sizes | |
| for (const img of imgs) { | |
| if (typeof img.decode === "function") { | |
| try { await img.decode(); } catch {} // ignore decode errors; we'll try anyway | |
| } | |
| } | |
| if (imgs.length === 0) { | |
| console.warn("No blob: images found."); | |
| return; | |
| } | |
| // Helper: image -> dataURL at native size | |
| async function imgToDataURL(img) { | |
| const c = document.createElement("canvas"); | |
| c.width = img.naturalWidth; | |
| c.height = img.naturalHeight; | |
| const ctx = c.getContext("2d"); | |
| ctx.drawImage(img, 0, 0, c.width, c.height); | |
| // PNG preserves exact pixels (no JPEG artifacts) | |
| const url = c.toDataURL("image/png"); | |
| // help GC | |
| c.width = c.height = 0; | |
| return url; | |
| } | |
| // Build page descriptors (orientation + size in px) | |
| const pages = []; | |
| for (let i = 0; i < imgs.length; i++) { | |
| const img = imgs[i]; | |
| const w = img.naturalWidth || 1; | |
| const h = img.naturalHeight || 1; | |
| const orientation = w >= h ? "l" : "p"; | |
| const dataURL = await imgToDataURL(img); | |
| pages.push({ dataURL, w, h, orientation }); | |
| console.log(`Prepared page ${i + 1}/${imgs.length} (${w}×${h}px)`); | |
| } | |
| console.log("Generating PDF ..."); | |
| // Use px units so we can pass native pixel dimensions as format | |
| const first = pages[0]; | |
| const pdf = new jsPDF({ | |
| orientation: first.orientation, | |
| unit: "px", | |
| format: [first.w, first.h], | |
| compress: true, | |
| putOnlyUsedFonts: true, | |
| }); | |
| // Draw first page | |
| pdf.addImage(first.dataURL, "PNG", 0, 0, first.w, first.h, undefined, "SLOW"); | |
| // Remaining pages: set size BEFORE drawing | |
| for (let i = 1; i < pages.length; i++) { | |
| const p = pages[i]; | |
| // Add a page with the exact size/orientation | |
| pdf.addPage([p.w, p.h], p.orientation); | |
| pdf.addImage(p.dataURL, "PNG", 0, 0, p.w, p.h, undefined, "SLOW"); | |
| console.log(`Placed page ${i + 1}/${pages.length}`); | |
| } | |
| // Title fallback logic | |
| let title = "download.pdf"; | |
| try { | |
| const meta = document.querySelector('meta[itemprop="name"]'); | |
| const candidate = (meta?.content || document.title || "download").trim(); | |
| title = candidate.toLowerCase().endsWith(".pdf") ? candidate : candidate + ".pdf"; | |
| } catch {} | |
| console.log("Downloading PDF file ..."); | |
| await pdf.save(title, { returnPromise: true }); | |
| console.log("PDF downloaded!"); | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| fixpdf() { | |
| if [[ $# -ne 3 ]]; then | |
| echo "Usage: fixpdf <landscape|portrait> <input.pdf> <output.pdf>" >&2 | |
| return 1 | |
| fi | |
| local orient="$1" | |
| local in_pdf="$2" | |
| local out_pdf="$3" | |
| if [[ "$orient" != "landscape" && "$orient" != "portrait" ]]; then | |
| echo "Orientation must be 'landscape' or 'portrait'" >&2 | |
| return 1 | |
| fi | |
| if [[ ! -f "$in_pdf" ]]; then | |
| echo "Input file not found: $in_pdf" >&2 | |
| return 1 | |
| fi | |
| if ! command -v magick >/dev/null 2>&1; then | |
| echo "Error: ImageMagick 7 ('magick') not found in PATH." >&2 | |
| return 1 | |
| fi | |
| local dpi=300 | |
| local tmpdir | |
| tmpdir="$(mktemp -d -t fixpdf.XXXXXXXX)" || return 1 | |
| local _cleanup | |
| _cleanup() { rm -rf "$tmpdir"; } | |
| trap _cleanup EXIT | |
| # Letter size at given DPI | |
| local page_w_px page_h_px | |
| if [[ "$orient" == "portrait" ]]; then | |
| # 8.5" x 11" | |
| page_w_px=$(( dpi * 17 / 2 )) # 8.5 * 300 = 2550 | |
| page_h_px=$(( dpi * 11 )) # 11 * 300 = 3300 | |
| else | |
| # 11" x 8.5" | |
| page_w_px=$(( dpi * 11 )) # 11 * 300 = 3300 | |
| page_h_px=$(( dpi * 17 / 2 )) # 8.5 * 300 = 2550 | |
| fi | |
| echo "=== fixpdf ===" | |
| echo "Orientation: $orient" | |
| echo "Input: $in_pdf" | |
| echo "Output: $out_pdf" | |
| echo "DPI: $dpi" | |
| echo "Page pixels: ${page_w_px}x${page_h_px}" | |
| # | |
| # 1. Rasterize PDF pages → PNGs | |
| # | |
| echo "[1] Rasterizing PDF pages to PNG..." | |
| magick -density "$dpi" -units PixelsPerInch \ | |
| "$in_pdf" \ | |
| -background white -alpha remove -alpha off +repage \ | |
| "$tmpdir/page-%04d.png" || { | |
| echo "Error rasterizing PDF." >&2 | |
| return 1 | |
| } | |
| local pages=( "$tmpdir"/page-*.png ) | |
| if [[ ! -e "${pages[1]}" ]]; then | |
| echo "No pages generated from PDF." >&2 | |
| return 1 | |
| fi | |
| local num_pages=${#pages[@]} | |
| echo " Rasterized pages: $num_pages" | |
| echo " Sample first page file: ${pages[1]}" | |
| # | |
| # 2–4. For each page: | |
| # - Normalize width | |
| # - Slice into page-height tiles | |
| # - Pad each tile to exact page size | |
| # | |
| echo "[2] Processing each raster page (normalize, slice, pad)..." | |
| local slice_idx=0 | |
| # Enable null_glob locally so globs that don't match expand to nothing | |
| setopt local_options null_glob | |
| local img | |
| for img in "${pages[@]}"; do | |
| local base="${img##*/}" | |
| echo " -> Page image: $base" | |
| # Log original size | |
| if command -v identify >/dev/null 2>&1; then | |
| local orig_size | |
| orig_size=$(identify -format "%wx%h" "$img" 2>/dev/null) | |
| echo " Original size: $orig_size" | |
| fi | |
| # 2. Normalize width; height scales to keep aspect ratio | |
| magick "$img" -resize "${page_w_px}x" "$img" || { | |
| echo "Error resizing $img." >&2 | |
| return 1 | |
| } | |
| if command -v identify >/dev/null 2>&1; then | |
| local new_size | |
| new_size=$(identify -format "%wx%h" "$img" 2>/dev/null) | |
| echo " Normalized size: $new_size" | |
| fi | |
| # 3. Crop current page into vertical tiles of page_h_px | |
| local stem="${base%.*}" | |
| local chunk_pattern="$tmpdir/${stem}-chunk-%02d.png" | |
| echo " Cropping into tiles of ${page_w_px}x${page_h_px}..." | |
| magick "$img" -crop "${page_w_px}x${page_h_px}" +repage "$chunk_pattern" || { | |
| echo "Error cropping $img into tiles." >&2 | |
| return 1 | |
| } | |
| local chunks=( "$tmpdir"/${stem}-chunk-*.png ) | |
| if [[ ${#chunks[@]} -eq 0 ]]; then | |
| echo " WARNING: No chunks produced for $base (page may be blank or tiny?)." | |
| continue | |
| fi | |
| echo " Chunks from ${base}: ${#chunks[@]}" | |
| # 4. Pad each chunk to full page size and rename to global slice-%06d sequence | |
| local chunk | |
| for chunk in "${chunks[@]}"; do | |
| if command -v identify >/dev/null 2>&1; then | |
| local chunk_size | |
| chunk_size=$(identify -format "%wx%h" "$chunk" 2>/dev/null) | |
| echo " -> Chunk $chunk (size: $chunk_size) -> padding to ${page_w_px}x${page_h_px}" | |
| fi | |
| magick "$chunk" \ | |
| -background white -gravity north \ | |
| -extent "${page_w_px}x${page_h_px}" \ | |
| "$chunk" || { | |
| echo "Error padding $chunk." >&2 | |
| return 1 | |
| } | |
| local slice_name | |
| printf -v slice_name "%s/slice-%06d.png" "$tmpdir" "$slice_idx" | |
| mv "$chunk" "$slice_name" || { | |
| echo "Error renaming $chunk -> $slice_name." >&2 | |
| return 1 | |
| } | |
| slice_idx=$((slice_idx + 1)) | |
| done | |
| done | |
| echo "[3] Total output slices (pages) prepared: $slice_idx" | |
| if [[ $slice_idx -eq 0 ]]; then | |
| echo "No slices generated; nothing to write to PDF." >&2 | |
| return 1 | |
| fi | |
| # | |
| # 5. Reassemble slices into final PDF | |
| # | |
| echo "[4] Assembling slices into final PDF..." | |
| local slices=( "$tmpdir"/slice-*.png ) | |
| echo " Number of slice images: ${#slices[@]}" | |
| magick "${slices[@]}" \ | |
| -units PixelsPerInch -density "$dpi" \ | |
| "$out_pdf" || { | |
| echo "Error writing output PDF." >&2 | |
| return 1 | |
| } | |
| echo "=== Done. Wrote $slice_idx pages to: $out_pdf ===" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment