Skip to content

JamesbbBriz/viewerclip

Repository files navigation

viewerclip

Capture multiple screenshots of a region you select, stitch them into a single PDF — locally, in your browser, no server, no LLM.

License: MIT Manifest V3 Chrome Network No LLM Release

Languages: English · 简体中文


viewerclip is an open-source Chrome extension (Manifest V3) for paginated embedded document viewers — the kind that show a multi-page document inside an iframe or canvas widget but don't expose a "download as PDF" button. Pick a region once, click Add page on every page you scroll through inside the viewer, then click Save PDF. The extension stitches your screenshots into a single PDF file on disk.

It is not a scraper, not an automation tool, and not an LLM-based extractor. Every screenshot is a single user-initiated click. There is no outbound HTTP request of any kind.

Table of contents

How it works

 ┌──────────────┐  1. set region (auto or manual drag)
 │  Active tab  │ ────────────────────────────────────▶ floating panel
 └──────┬───────┘
        │ 2. you flip the viewer's pages yourself
        │    and click "+ Add page" each time
        ▼
 ┌────────────────────────────────────────┐
 │  Service worker (MV3, ephemeral)       │
 │   • chrome.tabs.captureVisibleTab      │
 │   • OffscreenCanvas crop to your rect  │
 │   • appended to chrome.storage.session │
 └──────┬─────────────────────────────────┘
        │ 3. click "Save PDF"
        ▼
 ┌────────────────────────────────────────┐
 │  pdf-lib (in content script)           │
 │   • embed each PNG as one PDF page     │
 │   • A4 / Letter / fit, auto-orient     │
 │   • aspect-preserving fit + 24pt margin│
 └──────┬─────────────────────────────────┘
        │ 4. blob URL → chrome.downloads
        ▼
   Local file: viewerclip_YYYYMMDD_HHMMSS.pdf

There is no LLM, no OCR, no text extraction. The output PDF contains rasterised images.

Highlights

Set region two ways Auto — generic DOM heuristics offer up to 3 candidate regions to pick from. Manual — drag a rectangle anywhere. Manual always works as fallback.
One click per page After region is set, click Add page on each page of the embedded viewer. The extension never auto-paginates and never makes a network request.
N pages → 1 PDF pdf-lib stitches all captures into a single PDF. Per-page orientation auto-flips landscape if the captured aspect ratio is wide.
Zero outbound HTTP The extension makes no fetch / XHR / WebSocket / beacon requests at runtime. No analytics, no telemetry, no LLM, no API. (host_permissions: ["<all_urls>"] is declared because Chrome requires it for chrome.tabs.captureVisibleTab — it grants screenshot/DOM read access on the active tab, not network access.)
Site-agnostic No hardcoded domains, no vendor selectors, no URL allowlist. The same code runs identically on every page.
Session-scoped state Captures live in chrome.storage.session and clear when the browser exits. Reset is one click.
MIT licensed No closed-source server component, no proxy, no bundled key.

Install

Chrome Web Store

Listing pending review.

Manual install (signed .crx)

Each tagged release attaches a signed .crx and .zip to the Releases page. Drag the .crx onto chrome://extensions (Developer mode enabled).

Developer mode (unpacked)

See Build from source.

Usage

  1. Open the page that contains the embedded paginated viewer.
  2. In the floating panel (bottom-right), click Auto region — the extension highlights up to 3 candidate areas; click the one that contains the document. If nothing is highlighted, click Manual and drag a rectangle around the viewer instead.
  3. Navigate the viewer's internal pagination yourself. On each page, click + Add page in the panel. The counter increments.
  4. When you've captured every page you want, click Save PDF. The extension assembles a PDF and triggers a download.
  5. To start over, click Reset. Captures and region clear.

The downloaded filename is viewerclip_YYYYMMDD_HHMMSS.pdf. No host-page text is included in the filename or PDF metadata.

Privacy

  • Zero outbound HTTP requests at runtime. The extension makes no fetch / XHR / WebSocket / beacon calls. No API, no LLM, no telemetry, no error reporting, no update beacons (beyond Chrome's own extension update check). host_permissions: ["<all_urls>"] is required by Chrome for the screenshot API but does not enable network access.
  • All state lives in chrome.storage.session, scoped to the current browser session. Closing every browser window clears it.
  • No site-specific code — no domain allowlist, no vendor selectors. The extension behaves identically on every page.
  • No PII in the PDF metadata. Title / Creator / Producer fields are hardcoded to the literal string viewerclip.

Tech stack

Layer Choice
Build Vite + @crxjs/vite-plugin (MV3)
UI React 18 + TypeScript (strict mode)
Styling Tailwind CSS, scoped via shadow DOM
PDF pdf-lib — pure JS, MIT
Storage chrome.storage.session
Tests vitest
CI GitHub Actions — tag → build → signed .crx + .zip draft Release

No backend, no analytics SDK, no LLM SDK, no OCR. The extension makes zero outbound HTTP requests.

Project status

Source version 0.1.0 (MVP)
Manifest V3
Minimum Chrome 116
Web Store Listing pending

Build from source

git clone https://github.com/JamesbbBriz/viewerclip.git
cd viewerclip
npm install
npm run build

Then in Chrome:

  1. Open chrome://extensions.
  2. Enable Developer mode.
  3. Click Load unpacked and select the generated dist/ folder.

Quality gates

npm run typecheck    # tsc --noEmit, strict mode
npm run test         # vitest run
npm run build        # vite build → dist/
npm run package      # produce release/viewerclip-v<version>.{zip,crx}

Contributing

viewerclip is MIT-licensed and welcomes contributions. Before opening a PR:

  1. Fork and create a feature branch from main.
  2. Run npm install, then npm run build to confirm the extension builds cleanly.
  3. Pass all quality gates (typecheck, test, build).
  4. Use Conventional Commits prefixes (feat: / fix: / chore: / docs: / refactor: / test: / ci:).

Hard rules — PRs that violate any of these will be rejected

  • Inline scripts, eval, Function(...), or any remote code execution. Manifest V3 forbids them and so does this project.
  • Any outbound HTTP request (fetch/XHR/WebSocket/beacon), any LLM/API integration, any analytics SDK. host_permissions is ["<all_urls>"] only to allow chrome.tabs.captureVisibleTab; do not piggyback any other API on it.
  • Hardcoded domain names, vendor-specific CSS selectors, or DOM probes targeting any specific website. Auto-detect must remain a set of generic DOM heuristics.
  • A clipcv-operated server, proxy, or any non-LLM outbound HTTP request. (There is no LLM here either.)
  • User-facing copy that names a specific destination service, ATS, CRM, or recruiting platform.
  • AI attribution lines in commits, PR descriptions, or code comments.

For larger changes (new dependency, architectural shift), open an issue first to discuss the design.

Releasing

Releases are produced by GitHub Actions on every v* tag push.

  1. Bump version in src/manifest.json and commit.
  2. Tag the commit: git tag v<version> (e.g. git tag v0.2.0).
  3. Push the tag: git push origin v<version>.
  4. The release.yml workflow checks out the tag, runs npm ci, npm run build, npm run package, then creates a draft GitHub Release with release/viewerclip-v<version>.zip and release/viewerclip-v<version>.crx attached. The release stays in draft so a maintainer can review the artifacts and write user-facing notes before publishing.
  5. The CRX signing key comes from the CRX_PRIVATE_KEY repository secret — a PEM-encoded RSA-2048 private key. Generate it once with openssl genrsa -out viewerclip.pem 2048 and paste the contents into the secret. The same key must be used for every subsequent release so the extension's .crx retains a stable id.

License

MIT © viewerclip contributors.

About

Capture multiple screenshots of a region you select, stitch them into a single PDF — locally, no server, no LLM.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors