Skip to content

Instantly share code, notes, and snippets.

@ccorcos
Last active November 13, 2025 18:56
Show Gist options
  • Select an option

  • Save ccorcos/525ba4523ffedb95711f3d63c2c3174e to your computer and use it in GitHub Desktop.

Select an option

Save ccorcos/525ba4523ffedb95711f3d63c2c3174e to your computer and use it in GitHub Desktop.
Download GDrive PDFs
// 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!");
})();
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