TreeMap
무료 오픈소스 디스크 사용량 시각화 도구로, 사각형 트리맵·중복 파일 탐색·디스크 가득 예측·정리 시 파일을 휴지통으로만 이동한다
브라우저에서 PDF 주석·양식 입력·병합·분할·변환, 워드·엑셀·파워포인트를 편집 가능한 PDF로
Annotate, fill forms, merge, split and convert PDFs — and turn Word, Excel or PowerPoint files into editable PDFs — right in your browser. No uploads, no accounts.
생산성TypeScript · JavaScriptGitHub @shixzie온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A real PDF editor that runs entirely in your browser. No uploads, no accounts — your documents never leave your device. The only exception is a signing request you choose to send, and that is encrypted in the browser first (see below).README 전문 보기 ↗A real PDF editor that runs entirely in your browser. No uploads, no accounts — your documents never leave your device. The only exception is a signing request you choose to send, and that is encrypted in the browser first (see below).
Edit & annotate
Signing (certificate-based, like DocuSign or Acrobat Sign)
CN=<name>, OU=Email verified by RealPDF, E=<email>). The key pair is generated in the browser as non-extractable and only the public key is sent; the signer and their signature are remembered on the device, so next time Sign goes straight to placing. Only the email is verified, not the name.p12 / .pfx, RSA or ECDSA P-256/384/521) with its password in the browser; the certificate, its private key and the password never leave the deviceETSI.CAdES.detached, SHA-256, with the signer's certificate chain and the signing-certificate-v2 attribute), which Adobe Acrobat/Reader and other validators check as digitally signed documentshttps://realpdf.app/sign/<id>#<key> plus an email draft addressed to them. The document is encrypted in the browser with AES-256-GCM before upload and the key exists only in the link's , which browsers never send to the server, so the server stores ciphertext it cannot read (file name and message included). Whoever opens the link presses , sees their tags, creates a signature at the first one and fills them, and the signed copy is uploaded back to the same link (encrypted again); the sender opens the link to see who signed and download the result. Links expire after 30 days, and the sender can delete them earlier from the home screen or the dialogLocal library
Pages
Forms
Document tools
1-3,5,8-) or split every page into a ZIP.txt.docx), Excel (.xlsx) and PowerPoint (.pptx) files and convert them into editable PDFs: paragraphs, headings, lists, tables, merged cells, multiple sheets/slides and inline images are rebuilt with pdf-lib (no server, no native engine).docx), Excel (.xlsx) or PowerPoint (.pptx) file: positioned text runs, page breaks, one sheet per page and one slide per pageThe More PDF tools catalog adds compression, AES-256 password protection, unlocking, rotation, watermarks, page numbers, embedded-image extraction, overlays, visual comparison, local English OCR, web optimization, repair, redaction, rasterization, flattening, blank documents, saved-HTML conversion, cropping, page sizing, pages per sheet, page halving, metadata controls, bookmarks, text search, viewer preferences, and fillable-field creation. See docs/pdf-tools.md for usage and limits.
Everything the tools produce uses your current edited state (annotations, page order, filled forms), not just the original file.
Light (warm, paper-like) and dark themes, following your system preference by default, with a one-click toggle in the toolbar.
The interface ships in English, Spanish, French and German, with a picker in
the toolbar. The browser language is detected on the first visit, the chosen
language is remembered, and <html lang> / dir, the document title and
number/date formatting follow it. Adding a language is one dictionary file plus
one registry entry — see docs/localization.md.
npm install
npm run dev # http://localhost:5173
Production build (fully static, deploy anywhere):
npm run build # outputs dist/ (includes the pdf.js worker + wasm/fonts/cmaps assets)
npm run preview
| Layer | Technology |
|---|---|
| PDF rendering | pdf.js — Web Worker + WebAssembly decoders for JPEG 2000/ICC/JBIG2 (wasm/), plus CMaps and standard font data, all served from your own origin |
| Editing surface | fabric.js canvas overlays (one per visible page), coordinates stored in PDF points |
| Signing service | The Worker's /api/ routes (worker/api.ts): encrypted signing requests in an R2 bucket (worker/signRequests.ts), email verification codes sent with Cloudflare Email Sending and a small CA that issues signing certificates (worker/signingIdentity.ts, worker/certificateAuthority.ts). The Vite dev and preview servers serve the same routes from memory (worker/devServer.ts), with the emailed codes readable at /api/dev/outbox |
| Digital signatures | node-forge reads the PKCS#12 file and encodes the CMS/ASN.1 structures; hashing and the private-key operation run in WebCrypto. The signature, its field and appearance are written as a hand-rolled incremental update (xref table or xref stream, matching the file) over pdf-lib objects (src/lib/signing/), loaded only when the signing dialog opens |
| Saving | pdf-lib — annotations are re-drawn as native PDF operators, images embedded, new text mapped to Standard-14 with Noto fallback subsets for other scripts and emoji, edited text embedded with its original font (subset) after rewriting the page's content streams to delete the original glyphs, highlights exported with a real Multiply blend mode. The same content-stream rewrite is applied to a scratch page and re-rendered with pdf.js while editing, so the on-screen preview matches the saved file |
| State | zustand store with snapshot-based undo/redo |
| ZIP export | dependency-free STORE-method zip writer (src/lib/zip.ts), with optional DEFLATE via pako for Office packages |
Export works by walking every stored annotation, inverting the pdf.js viewport transform to get PDF user-space coordinates, and drawing lines/rectangles/ellipses/text/images with pdf-lib. Rotation and non-standard page boxes are handled by the same inverse transform, so what you see is what gets written.
| Key | Action |
|---|---|
V T X P H R O L A W I S E | select, text, edit text, draw, highlight, rectangle, ellipse, line, arrow, cover, image, signature, eraser |
Ctrl/Cmd + Z / Ctrl/Cmd + Shift + Z | undo / redo |
Ctrl/Cmd + S | save (download) the PDF |
Ctrl/Cmd + O | open a PDF or Office file |
Ctrl/Cmd + scroll | zoom |
Delete | delete selection |
Esc | deselect / back to select tool |
The editor is static, so it deploys as a Workers static-assets project — no
origin server, and the pdf.js WebAssembly decoders, fonts and CMaps (plus the
Noto fallback font slices, emitted as hashed assets) are served
from your own domain (there is no CDN call to a third party). A small Worker
(worker/index.ts) sits in front of the assets to log/trace requests, to
redirect www to the apex domain, and to serve the signing service under
/api/ (see Signing service setup below).
npm run deploy # builds and runs `wrangler deploy`
# or, to test the Cloudflare runtime locally first:
npm run cf:dev # builds and runs `wrangler dev`
npm run types # regenerate worker-configuration.d.ts after config changes
wrangler.jsonc points the Worker at ./dist, attaches the realpdf.app and
www.realpdf.app custom domains on deploy, serves assets through the ASSETS
binding (run_worker_first), turns on Workers Logs and Traces, and declares an
empty previews block so Workers Builds can create branch previews with
npx wrangler preview:
{
"name": "realpdf",
"main": "worker/index.ts",
"assets": {
"directory": "./dist",
"not_found_handling": "single-page-application",
"binding": "ASSETS",
"run_worker_first": true
},
"previews": {},
"observability": {
"enabled": true,
"logs": { "enabled": true },
"traces": { "enabled": true }
},
"routes": [
{ "pattern": "realpdf.app", "custom_domain": true },
{ "pattern": "www.realpdf.app", "custom_domain": true }
]
}
Remove the routes block if you prefer to attach the domains from the
Cloudflare dashboard. public/_headers adds long-lived caching for hashed
assets and a week for pdfjs-assets/, plus nosniff / frame / referrer
hardening, HSTS and a Content-Security-Policy that allows scripts only from
the app's origin and frames only ko-fi.com (so index.html has no inline scripts;
the pre-paint theme and language code is public/boot.js) — because
run_worker_first bypasses _headers, the Worker re-applies those rules to
every asset response.
Signing links and email-verified signing IDs need three things the static app does not:
R2 bucket realpdf-signing (binding SIGN_STORE). wrangler deploy
creates it if it does not exist. A daily cron (17 3 * * *) deletes expired
requests and verification codes.
Email Sending for the m.realpdf.app subdomain, so the EMAIL binding can send the
codes from [email protected] (Cloudflare dashboard → Email → Email Sending,
add the domain and its DNS records).
The CA that issues the certificates, as two Worker secrets:
node scripts/make-signing-ca.mjs # writes .signing-ca/ca-key.pem and ca-cert.pem (gitignored)
npx wrangler secret put SIGNING_CA_KEY < .signing-ca/ca-key.pem
npx wrangler secret put SIGNING_CA_CERT < .signing-ca/ca-cert.pem
Keep ca-key.pem somewhere safe offline and delete it from disk: anyone
with it can issue RealPDF certificates.
Until email and the CA are set up, /api/identity/* answers 503 and the app
says signing IDs are unavailable (certificate files and signing links keep
working). Uploads and certificate requests are rate-limited per IP
(WRITE_LIMIT), and verification emails per IP and per address
(EMAIL_LIMIT). For local testing with the real runtime, put the two secrets
in .dev.vars and run npm run cf:dev; emails are then written to
.wrangler/tmp/email/ instead of being sent.
Workers Logs and Traces are visible in the Cloudflare dashboard under
Workers & Pages → realpdf → Logs and Traces. The Worker emits one
structured JSON line per request (event, method, host, path, status,
durationMs, colo, country) and wraps request handling in a
realpdf:request span with http.response.status_code / http.response.duration_ms
attributes. Every request is sampled (head_sampling_rate: 1); lower the rate
in wrangler.jsonc if volume grows. Query strings are never persisted
(redact_query_string), and document contents are never logged — everything
still happens in the browser. www.realpdf.app 301-redirects to
https://realpdf.app, preserving the path and query string.
The suites run on Vitest: headless-browser end-to-end tests (Playwright) drive real pointer input and verify the exported PDFs by re-rendering them, and unit tests exercise the content-stream rewriting without a browser. The global setup writes the shared fixtures and starts its own Vite server, so a single command is enough:
npm test # vitest run: unit + all end-to-end suites
npm run test:watch # the same, in watch mode
npm test -- tests/e2e/text-edit.test.mjs # one suite
# or target a running server (for example the production build):
APP_URL=http://localhost:4173/ npm test
tests/unit/content-edit.test.mjs — text-run matching and content-stream rewriting (form XObjects, rotated text, split TJ arrays)tests/e2e/editor.test.mjs — load, annotate with every tool, eraser, undo/redo, virtualization, page ops, export → reopen verification, non-embedded standard font renderingtests/e2e/tools.test.mjs — merge, split range, PDF→PNG, PDF→text, images→PDF, text→PDF, form filling → exported value verificationtests/e2e/ui.test.mjs — theme toggle, homepage tool cards, signature image upload, Ko-fi button, language switch (persistence, translated strings, <html lang>/title)tests/e2e/library.test.mjs — save to the local library, rename, persistence across a reload, restore annotations, rebuild the PDF, deletetests/unit/sign.test.mjs — PKCS#12 loading (3DES, AES, OpenSSL-made RSA/ECDSA files), wrong passwords, PAdES signatures over xref tables and xref streams, countersigning, filling an empty signature field, certification — every signature checked with Node's crypto, independently of the app's codetests/unit/verify.test.mjs — signature verification: signer, chain and trust anchors, tampered bytes, content appended after signing, countersignatures and certification, ECDSAtests/unit/mcp.test.mjs, tests/unit/mcp-requests.test.mjs — the /mcp endpoint (JSON-RPC, errors, batches, CORS) and signing requests created by an agent, signed through the app's client with an email-verified certificate, then checked and downloaded by the agenttests/e2e/digital-sign.test.mjs — Sign yourself with a certificate file and a drawn signature: place, review, download and verify the signed PDF (byte range, CMS signature, embedded signature image, field position, rendered appearance), then countersign with a typed signature and check both signatures; remembered signers (stored keys not exportable) are reused after a reload and forgottentests/unit/signing-api.test.mjs — signing requests (versions, write/owner tokens, concurrent uploads, expiry, rate limiting) and email-verified certificates (codes, attempt limits, proof of key possession, issued certificate checked with Node's crypto)tests/e2e/sign-request.test.mjs — a sender lists two signers and places a field for each, creates a signing link (only ciphertext is uploaded, the key never is), a signer without a certificate adopts a typed signature confirmed by email and fills their field, and the sender downloads the signed copy through the same link and deletes the requesttests/e2e/unicode-text.test.mjs — added text outside WinAnsi (extended Latin, Greek, Cyrillic, CJK, Hangul, emoji) exports with embedded Noto subsets, WinAnsi-only text embeds nothing extra, typed line breaks surviveAll suites pass against the Vite dev server, the production build and the local
Cloudflare Workers runtime (npm run cf:dev).
TJ arrays that pdf.js splits, encrypted streams), the exporter falls back to the older behaviour: the original is covered with the colour sampled from the page behind it.?..doc/.xls/.ppt, OpenDocument and macro-enabled variants are not supported.?. Existing text rendering supports embedded fonts and CJK via pdf.js CMaps.src/
components/ UI: toolbar, tool rail, viewer, page sidebar, modals, form overlay
i18n/ locale registry, detection, plurals, t() and the language picker
lib/
pdfjs.ts pdf.js setup + local wasm/font/cmap assets
export.ts fabric → pdf-lib annotation drawing
fonts/ Noto fallback fonts for characters the Standard-14 fonts lack (loaded on demand)
textEdit.ts existing-text hit-testing, font reuse, colour sampling
annotationStyle.ts selected-result kind, style read/apply (covers, shapes, paths)
contentEdit.ts content-stream walk that deletes edited text runs
forms.ts AcroForm discovery + value writing
pdfOps.ts merge, extract, image/text conversion
currentDocument.ts "bake" the edited document for tools/export
signing/ PKCS#12 identities, CMS (PAdES) signatures, the incremental-update writer and verification
signController.ts signing flow: which bytes to sign, placement, download
signRequests.ts signing links: encryption, the link format, the /api/sign-requests client
signRequestController.ts opening a link in the editor and sending signed copies back
serialize.ts annotation JSON (assets kept out of undo snapshots)
zip.ts minimal ZIP writer (STORE, optional DEFLATE)
zipRead.ts ZIP reader for Office packages
ooxml.ts OOXML helpers: XML, relationships, units, WinAnsi text
pdfLayout.ts shared wrapped-text layout over pdf-lib Standard-14 fonts
docxToPdf.ts WordprocessingML → PDF (paragraphs, lists, tables, images)
xlsxToPdf.ts SpreadsheetML → PDF (sheets, styles, merges, pagination)
pptxToPdf.ts PresentationML → PDF (slides, placeholders, tables, images)
officeToPdf.ts format detection + dispatcher
pdfToOffice.ts PDF text extraction + Word/Excel/PowerPoint exporters
store.ts single zustand store (pages, history, tools, forms)
scripts/ sample generator, screenshots, visual checks
tests/ vitest suites: helpers, unit tests, browser end-to-end tests
#fragmenthttps://realpdf.app/mcp lets AI agents send a PDF for signature, see who has signed, download the signed copy and verify any PDF's signatures; see docs/mcp.md| Office import | a dependency-free ZIP reader over pako (src/lib/zipRead.ts) + OOXML parsing with DOMParser (ooxml.ts, docxToPdf.ts, xlsxToPdf.ts, pptxToPdf.ts); shared text layout re-uses pdf-lib Standard-14 fonts (pdfLayout.ts) |
| Office export | OOXML parts generated directly and packed with the zip writer (pdfToOffice.ts), using pdf.js text content with positions |
| Localization | dependency-free typed dictionaries (src/i18n/) with Intl.PluralRules plurals and locale-aware number/date formatting |
tests/e2e/text-edit.test.mjs — edit embedded-font and standard-font text: text layer deletion, exported font programs, coloured backgroundstests/e2e/text-select.test.mjs — selecting text activates the text tool, its options reflect and restyle the selected text, and the changes survive exporttests/e2e/selection-options.test.mjs — selecting covers, shapes, arrows, highlights and drawings shows their tool options, restyles the selection (arrow head included), survives undo/redo and exporttests/e2e/office.test.mjs — Word/Excel/PowerPoint → PDF (text, tables, merged cells, embedded images) and PDF → Word/Excel/PowerPoint (OOXML parts, page breaks, one sheet/slide per page)tests/unit/zip.test.mjs — ZIP round-trips for stored and deflated Office packages/api/identity/ca.pem); RealPDF is not on the Adobe Approved Trust List. A signing ID lives in one browser: clearing site data or switching devices means creating a new one (the key cannot be exported, by design)..p12/.pfx file with its private key. Whether a reader shows the signature as trusted depends on the certificate: one issued by a CA on the Adobe Approved Trust List or the EU trusted lists validates out of the box, a self-signed one only after the recipient trusts it. Signing a document you edited signs the edited copy, which invalidates signatures already in the original; sign before editing to keep them. Certifying signatures always allow form filling and signing (DocMDP level 2), and new visible signature boxes added after a certification may be reported as changes by strict validators, so later signers should use the document's empty signature fields (their “Sign here” tags).ko-fi.com in a cross-origin iframe; no Ko-fi script runs in the app's own page.