Skip to content

Releases: romizone/pdfromeo

v2.2.2 — Edit Text worked exactly once

Choose a tag to compare

@romizone romizone released this 09 Aug 01:19

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.app

tests/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

Choose a tag to compare

@romizone romizone released this 09 Aug 00:36

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.app

tests/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

Choose a tag to compare

@romizone romizone released this 08 Aug 08:47

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.app

Under 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

Choose a tag to compare

@romizone romizone released this 08 Aug 00:28

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.app

Then 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

Choose a tag to compare

@romizone romizone released this 26 Jul 10:09

The app now shows the document it is working on, and the editor is driven by
clicking the page instead of typing coordinates.

Installing

  1. Open the .dmg and drag PdfRomeo into Applications.
  2. Clear the download quarantine flag:
xattr -dr com.apple.quarantine /Applications/PdfRomeo.app
  1. 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

Choose a tag to compare

@romizone romizone released this 26 Jul 07:58

Fixes the .dmg published with 1.1.3, which macOS refused with
"PdfRomeo is damaged and can't be opened."

Installing

  1. Open the .dmg and drag PdfRomeo into Applications.
  2. Clear the download quarantine flag:
xattr -dr com.apple.quarantine /Applications/PdfRomeo.app
  1. 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. codesign rejects any
    bundle carrying extended attributes, and they cannot be stripped in
    place: com.apple.provenance is kernel-managed, and a synced project
    folder keeps re-stamping the bundle with com.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

Choose a tag to compare

@romizone romizone released this 26 Jul 07:32

⚠️ The .dmg originally 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 Pixmap was required, so every run failed on the first page with pixmap 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 AttributeError was swallowed. Named destinations and /GoTo actions 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 TypeError on every run: insert_image has no opacity parameter, and its rotate only accepts multiples of 90.
  • Watermark (text) was never rotated — the default 45° rounded to 0°.
  • Bates Numbering crashed with NameError: name 'Path' is not defined whenever the output folder was left empty.
  • Organize Pages silently did nothing: the new order was sorted before use, so 3,1,2,4 became 1,2,3,4 and 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 PATH and 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 pytesseract binding, 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 strip pass 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

Choose a tag to compare

@romizone romizone released this 26 Jul 03:38

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 libffi

Affects v1.1.0 and v1.1.1 — no API changes.

v1.1.1 — Worker fix

Choose a tag to compare

@romizone romizone released this 26 Jul 03:34

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

Choose a tag to compare

@romizone romizone released this 26 Jul 03:27

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 threadRun no 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.name vs parameter name)
  • Resolved a circular import between main_window.py and home.py
    by moving TOOL_NEEDS_DOC into a new tool_registry.py module

📦 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