Releases: romizone/pdfromeo
Release list
v2.2.2 — Edit Text worked exactly once
A bug-fix release. If you are on v2.2.1, update — text editing there only works for the first paragraph of a session.
What was broken
After editing one paragraph, the double-click that should have started the next edit — on the same page or any other — did nothing at all, with no message.
Qt delivers a double-click as press, release, double-click. With an overlay already open, the press moves the focus, the focus change commits that overlay, and the commit raised a latch meant to stop a stale gesture from opening an editor the pending refresh would tear down.
The double-click then arrives in the same event burst, before any timer can lower that latch. So the guard never caught a stale gesture — it caught the live one, every single time. The first edit worked only because there was no earlier overlay to commit.
Why the guard could not simply be removed
Measured with the latch disabled: the second overlay does open, and the previous edit's own refresh destroys it microseconds later — taking whatever was typed with it.
The gesture genuinely has to be held. It just has to be replayed once the page is whole again, rather than dropped.
The held click remembers the paragraph's text rather than its position on the page, because committing an edit renumbers a page's paragraphs and a stored index would replay into the wrong words. If a grow-down push has moved the target it is matched by text; if nothing matches, nothing opens, rather than editing a neighbour by mistake.
The replacement state also cannot wedge: it is cleared both when the mutation lands and unconditionally at the start of the next click, so no missed timer can leave the viewer permanently refusing to edit.
Installing
xattr -dr com.apple.quarantine /Applications/PdfRomeo.apptests/test_edit_text_ui.py grows from 39 to 57 checks, including three consecutive edits driven by real synthetic double-clicks across two pages — verified to fail against v2.2.1 and pass here. Full suite: 57 + 49 + 43 tool panels + 256 + 139 + 23 + engine smoke, all green.
Full changelog: https://github.com/romizone/pdfromeo/blob/main/CHANGELOG.md
v2.2.1 — Edit Text that shows you what it can do
A polish release for Edit Text, in response to it not feeling finished. Two separate problems, both fixed.
The mode now changes the page
Choosing Edit Text used to change nothing on screen — it set a mode and wrote a line to the status strip, so there was no way to tell a paragraph the tool would re-wrap from a scan it would refuse, except by trying.
- Every editable paragraph is outlined the moment the mode opens, and the one under the cursor is picked out more strongly.
- The I-beam is earned per paragraph rather than worn over the whole page, so the cursor itself tells you where editing is possible.
- Blocks that cannot be re-wrapped are left bare — tables, contents pages, rotated text. Outlining them and then refusing would be a lie.
- A page with nothing to edit says so, instead of leaving you double-clicking a scan that will never respond.
The editor looks like the page
The overlay had inherited the dark theme's input styling: a dark rounded field with near-white text sitting on white paper, and 12 pt of padding that shunted the words sideways the moment it opened.
It now takes the paper's colour and the paragraph's own ink, with no padding — the text does not move at all when you double-click, and a hairline is the only sign that typing is live.
Performance
Paragraph detection costs 18 ms a page and is deliberately uncached, so calling it while painting would have spent more than a frame's budget on a single visible page. The outlines are found on an idle timer, visible pages only: a full repaint with outlines drawn is 1.1 ms, and fifty scroll steps trigger no detection at all.
That cache holds geometry for drawing and hit-testing only. Which paragraph an edit actually lands on is still resolved from the document on every double-click, because committing an edit renumbers the page's paragraphs.
Installing
xattr -dr com.apple.quarantine /Applications/PdfRomeo.apptests/test_edit_text_ui.py adds 39 checks. Full suite: 256 + 139 + 49 + 39 + 43 tool panels + 23 regression + engine smoke, all green.
Full changelog: https://github.com/romizone/pdfromeo/blob/main/CHANGELOG.md
v2.2.0 — Retype a paragraph, and let the page make room
Editing text in a PDF stops being "replace this fragment" and becomes "retype this paragraph". Double-click a paragraph, type, and the whole paragraph re-wraps — in the document's own font, keeping its justification and its inline bold and italic. If it needs more lines, everything below it moves down to make room.
This release folds in both 2.1.0 (reflow) and 2.2.0 (making room).
Editing text
- Word-like re-wrapping. Add or remove words and the lines break again around them, instead of the replacement being squeezed into the box the old text happened to occupy.
- The document's own font. Text is measured from the PDF's own width tables and drawn with the page's existing font resources, so nothing is re-embedded and the file does not grow with each edit. Previously every edit was redrawn in a substituted base-14 face — on a document set in Georgia that was 9% narrower and broke every line in a different place.
- Justification that actually justifies, and inline styling that survives: fixing a typo no longer flattens the bold phrase three words later.
- Content below moves. Grow a paragraph and the text, bullets, tables, images, links, form fields and annotations beneath it shift by exactly that much; shrink it and the space closes back up. On a two-column page only the edited column moves.
What it refuses, and says so
A paragraph is only offered for reflow when re-wrapping it is safe. Tables, dot-leader contents pages, rotated pages, multi-column layouts, invisible OCR text layers, letter-tracked display type, and paragraphs whose font cannot be measured are all declined with a plain-English reason.
An edit that would push content past the bottom of the page is refused too — PDF discards that content without warning, so it is never attempted. There is no flow onto the next page; PDF has no page-flow model, and neither does Acrobat.
Typing a character the document's font does not contain is reported rather than silently substituted, and nothing is written.
Safety
Every edit is verified before it commits: each word outside the edited paragraph must keep its text and land exactly where it should, within 0.05 pt. Anything else rolls the whole edit back. One reflow is one undo step.
Installing
macOS blocks the app on first launch because it is ad-hoc signed rather than notarised. After dragging it to Applications:
xattr -dr com.apple.quarantine /Applications/PdfRomeo.appUnder the hood
New engine modules: fontmetrics.py (exact measurement from the PDF's own tables — 0.0001 pt across base-14, fully-embedded, subsetted and simple TrueType fonts), textblocks.py (paragraph reconstruction — a PyMuPDF "block" is not a paragraph, in either direction), reflow.py (line breaking and content-stream emission), and pageroom.py (free-space detection and banded page shifting).
tests/test_reflow.py adds 256 checks; the full suite is 256 + 139 + 49 + 43 tool panels + 23 regression + engine smoke, all green.
Design and the measurements behind every decision: docs/paragraph-reflow-spec.md
Full changelog: https://github.com/romizone/pdfromeo/blob/main/CHANGELOG.md
v2.0.0 — A document workspace, not a rack of tools
Opening a PDF used to mean remembering a path for the next tool to read. It now opens a live document workspace — you see the file, search it, annotate it, redact it and reorganise it, with the page on screen the whole time.
All 43 batch tools keep working, unchanged, and are reachable from the right-hand pane without leaving the document.
What's new
The workspace
- Document tabs, with an unsaved-changes dot and a save prompt before closing or quitting
- Continuous viewer — all pages on one dark canvas, threaded rendering, sharp on Retina for the first time, zoom 10–640%
- Text selection across pages (⌘C to copy)
- Side panels on an Acrobat-style rail: page thumbnails, bookmarks, search, comments
Comment & review
- Eleven annotation types: highlight, underline, strikethrough, squiggly, sticky note, text box, freehand ink, rectangle, ellipse, line, arrow
- Colour and line-width pickers; comments panel listing every annotation by author, page and date
Search & redact
- Find across the document (⌘F) with every match highlighted and a result list
- Redaction that genuinely removes content rather than covering it — and clears the undo history so it cannot be brought back
Document handling
- Undo/redo across annotations, pages and bookmarks
- Atomic in-place save that re-encrypts password-protected files (a plain save silently stripped the protection)
- Print, document properties, and recent files with first-page thumbnails
- Password-protected PDFs open at last — they used to be rejected at the door, which also made the Unlock tool unreachable
Also: a genuinely dark theme (apply_dark_theme had been applying a light one since 1.1.3), full File/Edit/View/Tools menus, and "Open With PdfRomeo" now works while the app is running.
Installing
macOS blocks the app on first launch because it is ad-hoc signed rather than notarised. After dragging it to Applications:
xattr -dr com.apple.quarantine /Applications/PdfRomeo.appThen open it normally.
Under the hood
New modules: app/engine/session.py (the stateful document session — Qt-free, like the rest of the engine), plus workspace, docview, panels, commenting, docprops and printing in the UI layer.
Two new test gates joined the suite: tests/test_session.py (139 checks) and tests/smoke_workspace.py (49 checks), alongside the existing engine, regression and UI gates — all five green.
Full changelog: https://github.com/romizone/pdfromeo/blob/main/CHANGELOG.md
v1.2.0 — Show the page, and edit by clicking it
The app now shows the document it is working on, and the editor is driven by
clicking the page instead of typing coordinates.
Installing
- Open the
.dmgand drag PdfRomeo into Applications. - Clear the download quarantine flag:
xattr -dr com.apple.quarantine /Applications/PdfRomeo.app
- Launch normally. The step is needed because the app is ad-hoc signed
rather than notarised with an Apple Developer ID.
Every tool shows the page
Until now nothing in the app displayed a PDF. A complete viewer existed in
app/ui/viewer.py but was never instantiated, so all 43 tools were operated
blind — fill in a form, press Run, then open the result somewhere else to
find out what happened.
- A rendered page now sits beside the options, with page navigation, zoom
and fit-to-width. - Tool pages are laid out in two panes: options on the left, document on the
right. Tools with no PDF to show (HTML → PDF, Images → PDF) collapse the
pane and centre their options. - After a run, the pane switches to the file that was just produced.
The editor is a canvas
Click the page to place text. The click sets the text baseline, so the
Page, X position (pt) and Y position (pt) fields are gone. Placed
items are listed, can be removed before saving, and are marked on the page.
Click existing text to rewrite it. Editable text is outlined; picking a
piece of it opens the current wording for editing. This is new — the engine
previously had no way to alter text already in a document, only to stamp
more on top.
- The original glyphs are removed with a redaction rather than covered, so
the old wording cannot be recovered by copy-paste. - Size, colour and font carry over from the text being replaced. Embedded
fonts cannot be reused for new glyphs without the original font file, so
the nearest standard face is chosen from the font's name and flags —
serif, monospace, bold and italic are all preserved. - A replacement longer than the original is shrunk until it fits.
Several placements and rewrites can be queued and applied in one pass.
Under the hood
New engine functions PdfEngine.text_spans, add_text_items,
replace_text_spans and substitute_font, each covered by
tests/regression.py (now 23 checks). tests/smoke_ui.py additionally
asserts that PDF tools render their source and that the editor asks for no
coordinates.
Full changelog: v1.1.4...v1.2.0
v1.1.4 — Fix the "damaged and can't be opened" disk image
Fixes the .dmg published with 1.1.3, which macOS refused with
"PdfRomeo is damaged and can't be opened."
Installing
- Open the
.dmgand drag PdfRomeo into Applications. - Clear the download quarantine flag:
xattr -dr com.apple.quarantine /Applications/PdfRomeo.app
- Launch normally.
That step is required because the app is signed ad-hoc rather than
notarised with an Apple Developer ID. Right-click → Open on its own is
often not enough for an ad-hoc signature on Apple Silicon.
What was wrong
The 1.1.3 build pruned unused Qt frameworks after py2app had already
signed the bundle. The signature seals a manifest of every file inside the
app, so removing files afterwards invalidated it. Gatekeeper reported
a sealed resource is missing or invalid, and macOS presents that as
"damaged" — with no override available to the user.
- The bundle is re-signed after pruning, and the build now fails loudly if
the signature does not verify, so a broken bundle cannot be published. - Signing happens on a copy in a scratch directory.
codesignrejects any
bundle carrying extended attributes, and they cannot be stripped in
place:com.apple.provenanceis kernel-managed, and a synced project
folder keeps re-stamping the bundle withcom.apple.FinderInfo. - The disk image is staged the same way and ships an
Applications
symlink.
No application code changed. Every fix from
v1.1.3 — OCR,
Split by Bookmarks, Split by Size, Watermark, Bates Numbering, Organize
Pages, Compress, and the dependency detection — is present.
Full changelog: v1.1.3...v1.1.4
v1.1.3 — Tools that never worked, and a dependency gate that was backwards
⚠️ The.dmgoriginally attached here was rejected by macOS as "damaged" — the build invalidated its own code signature. It has been removed. Download the fixed disk image from v1.1.4; it contains every fix listed below.
A bug-fix release. Several tools had never worked, and the dependency
detection added in 1.1.2 was inverted in places — hiding tools that were
fine and offering tools that were not.
Tools that were broken and now work
- OCR produced nothing at all. The page image was handed to PyMuPDF as a PIL image where a
Pixmapwas required, so every run failed on the first page withpixmap must be a Pixmap, for every input. - Split by Bookmarks always reported "Bookmarks found but no valid page targets". Page targets were resolved through a pikepdf method that does not exist, and the resulting
AttributeErrorwas swallowed. Named destinations and/GoToactions are now resolved properly. - Split by Size failed as soon as a split was actually needed. It only ever succeeded when the whole document fitted in one chunk.
- Watermark (image) raised
TypeErroron every run:insert_imagehas noopacityparameter, and itsrotateonly accepts multiples of 90. - Watermark (text) was never rotated — the default 45° rounded to 0°.
- Bates Numbering crashed with
NameError: name 'Path' is not definedwhenever the output folder was left empty. - Organize Pages silently did nothing: the new order was sorted before use, so
3,1,2,4became1,2,3,4and the output matched the input. - Compress silently skipped grayscale and transparent images, reporting success with no size reduction.
Dependency detection
- Tesseract, cairo and pango are now found under the Homebrew prefixes. Launched from Finder, an app bundle gets a
PATHand a library search path that exclude/opt/homebrew, so 1.1.2 dimmed OCR, Deskew and HTML → PDF on machines where those dependencies were installed and working. - Word → PDF was gated on "is this macOS", which is neither necessary nor sufficient. It is now available whenever Apple Pages or the WeasyPrint fallback can do the job — including on Linux and Windows, where 1.1.2 blocked it despite the fallback working.
- OCR and Deskew also check for the
pytesseractbinding, not just the binary. - Detection no longer imports WeasyPrint at startup, which cost over three seconds on every launch.
- Dependencies are re-checked when you return to the home page, so installing one no longer requires restarting the app.
Interface
- Colours follow the PdfRomeo palette, defined in one place in
app/ui/styles.py. - Closing a document with ⌘W re-dims the tools that need one.
- Tools that write several files now ask for a folder. Picking a file meant the app created a directory literally named
output.pdf. - Leaving a tool while it is still working now asks first. It used to destroy the running thread, which aborts the process.
- Options are locked while a job runs, so its inputs cannot change underneath it.
- Split's default range
1-means "to the end of the document" again, rather than page 1 only. - Fill & Sign no longer writes an output file when no field matched, and reports the real error when saving fails.
- The About box shows the running version instead of a hardcoded 1.0.
Build
- py2app's
strippass corrupted the Qt plugins, leaving a bundle that could not load a platform plugin. Two invalid options (plist_strings,install_requires) aborted the build outright. - The app now depends on PySide6-Essentials and prunes Qt frameworks outside its dependency closure: 1.3 GB down to 400 MB.
- Building requires Python 3.11 — Qt's platform plugins do not load under the Python 3.9 that ships with Xcode.
tests/regression.py covers each fix above.
Full changelog: v1.1.2...v1.1.3
v1.1.2 — Graceful System-Dep Handling
Detects missing optional system deps at startup. Affected tools are dimmed in the home page and show a tooltip + dialog with the install command.
Detected deps:
tesseract(OCR, Deskew)weasyprint(HTML → PDF)pages(Word → PDF, macOS only)
Install with:
brew install tesseract
brew install cairo pango gdk-pixbuf libffiAffects v1.1.0 and v1.1.1 — no API changes.
v1.1.1 — Worker fix
Hotfix: clicking Run produced 'run() got multiple values for argument log'.
Root cause: the background Worker was injecting log/progress/is_cancelled as keyword arguments while BaseTool was also passing them positionally via _args, causing a duplicate-argument error.
Fix: simplified Worker to just call fn(*args, **kwargs) without auto-injection. BaseTool now wraps the user's run() in a closure that captures the log/progress/cancel hooks and passes them as explicit parameters to the wrapped function.
Affects: v1.1.0 only. v1.0 was unaffected (no background thread). v1.1.1 is fully backward compatible.
Workaround for v1.1.0 users: clone v1.0 or wait — this fix is already in main.
PdfRomeo 1.1.0 — Sejda-Smooth UX
PdfRomeo 1.1.0 — Sejda-Smooth UX
A focused UX release that brings PdfRomeo's flow much closer to Sejda's
signature "one tool, one page, zero friction" experience.
✨ What's new
Drop & pick files
- DropZone v2 — large, centered empty state with a Browse button
- After upload the drop zone collapses to a compact FileChip showing
the file's icon, name, human-readable size, and page count —
with an × to remove - Multi-file inputs show a vertical list of chips with per-file remove
Run without blocking the UI
- Real background thread —
Runno longer freezes the window for
long operations - Inline progress bar appears in the page
- The Run button shows a spinner + "Processing…" label
- The button is disabled while work is in progress
Clear feedback after success
- Green success banner with the output path
- Three quick actions: Open, Show in Finder, Process another
Recent files
- The home page now shows a Recent files strip (up to 5)
- Persisted to
~/Library/Application Support/PdfRomeo/recent.json - Click any chip to open that PDF
Global drag-onto-window
- Drop a PDF anywhere on the window — it auto-opens
- No need to navigate to a specific tool first
Tool gating
- Tools that need an open PDF are dimmed and disabled until a file
is loaded - Cursor changes to a "forbidden" symbol so it's clear which tools
are blocked - Updates in real-time as you open/close documents
Polish
- Auto-focus the first input on every tool page
- A "drag a PDF anywhere" hint in the home subtitle
- Drag-highlight overlay on every DropZone
🐛 Fixes
- FileChip shadowing bug (
Path.namevs parametername) - Resolved a circular import between
main_window.pyandhome.py
by movingTOOL_NEEDS_DOCinto a newtool_registry.pymodule
📦 Installation
git pull
.venv2/bin/pip install --force-reinstall --no-cache-dir "PySide6>=6.7.0,<6.9.0"
.venv2/bin/python main.py⚙️ Compatibility
Same as v1.0:
- macOS Apple Silicon (M1 / M2 / M3)
- Python 3.9+ from Xcode (or any modern Python 3)
- PySide6 6.7.0 – 6.8.x (6.10+ breaks on Python 3.9)
Full diff: v1.0...v1.1