Commit Graph

1451 Commits

Author SHA1 Message Date
DWAA1660 89896af68b Fix saved signature replacement and long-name clipping (#163) (#178)
* #163: allow replacing saved signatures and render long names fully

Add saved signature and initials previews with change/remove/add controls in
Fill & Sign. Preserve placed annotations and saved values on cancellation.
Use bounded line outlines for long names and fit typed placements to 150 pt.

Co-authored-by: Codex <noreply@openai.com>

* cargo fmt after the rename

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:37:28 -04:00
HG.Studio d0991a8cd5 macOS: File ▸ Open and file pickers no longer crash the app (#145)
* macOS: File ▸ Open and file pickers no longer crash the app

A blocking rfd::FileDialog runs -[NSOpenPanel runModal], a nested run loop
inside winit's event handler. A mouse or gesture event that arrives while it
spins re-enters the handler, winit panics ("tried to handle event while
another event is currently being handled"), and because the panic can't
unwind out of the AppKit block callback the app aborts.

Open, Combine, Insert pages, Replace pages and OCR ▸ multiple files now use
rfd::AsyncFileDialog, which starts the panel as a sheet and returns at once.
A worker thread waits for the choice and the app uses it on a later frame.
Insert and Replace remember their document and drop a pick that arrives after
the user switched tabs. pick_override lets tests and automation drive these
commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cargo.lock: pollster for pdfcraft-ui-egui

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:37:07 -04:00
Blake Gower 54df6b933e Persist default workspace mode for newly opened PDFs (#172)
* M14: persist the default PDF workspace mode

Keep the saved workspace preference separate from current mode and explicit session overrides. Preserve CLI ordering and PDF Initial View behavior, and cover persistence, Preferences, and control-channel use.

Co-authored-by: Codex <noreply@openai.com>

* Opening a PDF keeps a closed left panel and an unchanged workspace's tool panel

The default workspace is applied only when it differs from the current mode, and never
reopens a left panel the user or `--left closed` closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: James B. Gower <gowerblake2012@gmail.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:36:47 -04:00
Frumpy9 5387c346a5 M3: Add middle-click autoscroll on Linux (#111)
* M3: add middle-click autoscroll

Co-Authored-By: Codex <noreply@openai.com>

* M3: keep middle-click autoscroll toggled after release

Keep scrolling enabled after releasing the wheel, including when the
pointer moves before release. Anchor speed to the initial press event,
so movement later in the same frame cannot shift the starting point.

Cover toggle behavior and increasing speed with distance in both views.

Co-Authored-By: Codex <noreply@openai.com>

* M3: increase middle-click autoscroll speed

Double scrolling speed at the same pointer displacement and raise the
speed cap proportionally, preserving the dead zone and click toggle.
Keep the sustained-scroll fixture visible at the faster response.

Co-Authored-By: Codex <noreply@openai.com>

* M3: match Chromium middle-click scrolling

Use a 15-point dead zone and Chromium's nonlinear distance-to-speed curve
in the document viewer and Organize Pages. Request continuous redraws so
scrolling follows elapsed frame time and preserves fractional movement.
Keep click toggling, cancellation, and the existing marker unchanged.

Cover reference speeds, pathological inputs, pause/resume, and consistent
travel at 30/60/120/144 fps with different display scales and document zoom.

Co-Authored-By: Codex <noreply@openai.com>

* M3: leave README unchanged in autoscroll PR

Remove the feature bullet so README matches upstream main. Keep the
feature explanation in the pull request description.

Co-Authored-By: Codex <noreply@openai.com>

* M3: limit custom middle-click scrolling to Linux

Return before handling input on other targets so their existing
middle-button behavior, widgets, cursor, and Escape shortcuts stay intact.
Restrict the autoscroll UI regressions to Linux and add unsupported-platform
input coverage plus a non-Linux UI regression for the native CI matrix.

Co-Authored-By: Codex <noreply@openai.com>

* ROADMAP: drop the log line about merging the rename

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:16:26 -04:00
HEOJUNFO 9a1bf44e07 Render: round page rasters up so the last partial row and column are drawn (#102) (#114)
An A4 page (595.28×841.89 pt) rendered 595×841 at 72 dpi, where
pdftoppm gives 596×842: the whole-page render passed hayro no size, and
hayro floors the scaled page size. Content touching the right or bottom
edge lost its last partial pixel at every resolution.

- render: new device_pixels(pt, scale) rounds up (ignoring float noise
  up to 1/100 px, so Letter at 150 dpi stays 1275 px). The untiled
  render passes it to hayro as the width and height, still within
  MAX_SIDE. The partial edge pixels are anti-aliased against the white
  page, as the clip to the crop box already does.
- ui-egui: the canvas tile grid uses the same helper instead of
  round(), so tiled pages get tiles for the partial row and column too.
- No change to vendored hayro.

Regression test: a fractional MediaBox renders 596×842 with partly
covered edge pixels, 2481 px wide at 300 dpi, and a corner tile matches
the whole-page render. It failed (595×841) before the fix.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:16:17 -04:00
Frumpy9 4454af3742 Select all pages with Ctrl+A in Organize Pages (#113)
* M4: select all pages in Organize with Ctrl+A

Route Select all to the page grid in Organize while preserving viewer text
selection and keyboard ownership for text inputs, dialogs and the palette.
Keep the current page as the selection anchor for subsequent range selection.

Co-Authored-By: Codex <noreply@openai.com>

* Use the pdfcraft crate names after the rename

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:14:41 -04:00
Eri Bastos e15e5ebce0 Checkboxes with an indirect /AP /N state dictionary are drawn (#180) (#181)
hayro-syntax recovered a stream with a wrong /Length by scanning ahead
for the bytes `stream`. An indirect appearance-state dictionary
(`<< /Yes … /Off … >>`, as Quartz writes a checkbox's `/AP /N`) was
read as whatever stream came later in the file. That dictionary has no
`/BBox`, so the appearance was dropped and the widget was skipped.

`stream` now has to be the next token. A wrong `/Length` still
recovers, and the renderer already selects the `/AS` state once `/N`
is a dictionary.

Fixes #180

Co-authored-by: Eri Bastos <eb@fluxmail.xyz>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:14:34 -04:00
munzir 85e8e34547 CLI: text exits non-zero when a selected page cannot be read (#131) (#139)
* CLI: text exits non-zero when a selected page cannot be read (#131)

printcraft-cli text --page N reported the failure on stderr and still exited 0, so a script checking the status could not tell a failed extraction from a successful one. The failing pages are collected and the command now fails with them named, pinned by a test for the nonexistent-page case and one for a normal extraction.

* Move the text exit-code test to the renamed pdfcraft-cli app

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Mohammed Munzir <Mohammed.Munzir@iga.gov.bh>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:14:25 -04:00
munzir 38c62bda3e Automation: file-writing tools no longer advertise read-only (#130) (#138)
doc_export_data, accessibility_report and image_save each write to their required path and replace an existing file there, but the tool table marked all three read-only, so MCP clients were told the calls change no file. They now carry the destructive flag the table defines for a tool that overwrites files, and a test pins the three names.

Co-authored-by: Mohammed Munzir <Mohammed.Munzir@iga.gov.bh>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:14:16 -04:00
EthanSuquet ff1a8b85dc Edit text: keep the tokens a content stream shares with its neighbours (#154)
A page's /Contents array is one content stream in pieces, and a writer
may split it between any two tokens (ISO 32000-2 §7.8.2): a TJ array
can end one piece with its operator starting the next, and a
marked-content dictionary can close at the start of the next piece.

Replacing a line or rewriting a paragraph re-serialized the parsed
operators of the line's piece, and the parser keeps neither operands
left at the end of a piece nor a dictionary's closing >> at its start.
The edit dropped them: the text after the edited line vanished, and the
page was left with an operator without operands (TJ, BDC), which other
readers reject.

The piece is now rewritten by splicing: only the replaced, inserted and
removed operators change, and every other byte is copied as it was.

Co-authored-by: EthanSuquet <292885762+EthanSuquet@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:13:04 -04:00
Checkov23 d63bc7ad98 Edit text: the graphics state carries over between a page's content streams (#150)
A page's /Contents array is one content stream in pieces (ISO 32000-2
§7.8.2), so a cm, a q that is still open, the font and the colour set in
one piece still apply in the next. Edit text read every piece with a
fresh state. AutoCAD scales the whole page in its first stream
(0.12 0 0 0.12 0 0 cm) and draws in the following ones, so the text
boxes of its plans landed far off the page and no label could be edited.

Co-authored-by: Oliver Schiemann <228913879+Checkov23@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:12:59 -04:00
Carlos Moro 0636b9ca46 Issue #96: on Linux, draw on the GPU that drives the monitors (#105)
* Issue #96: on Linux, draw on the GPU that drives the monitors

Preferring the low-power GPU (issue #8) picked the integrated GPU on
desktops whose monitors are all on the discrete one, so the window was
black (Hyprland) or the app panicked with "surface isn't supported"
(GNOME). On Linux, pick an adapter that supports the window's surface
and whose PCI ids match a GPU with a connected monitor (from
/sys/class/drm); integrated still wins among those, so hybrid laptops
keep the issue #8 behaviour. WGPU_POWER_PREF still overrides.

* Use the PdfCraft name for the fake sysfs test folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:12:55 -04:00
Shaurya Swarup bfa17ff3b9 macOS: declare PDF by UTI so Open With recommends PrintCraft (#176)
CFBundleDocumentTypes listed PDF by extension and MIME type only.
LaunchServices keeps those as plain tags (.pdf, application/pdf)
instead of resolving them to com.adobe.pdf, so Finder greys the app
out under Open With > Recommended Applications and a forced open
fails with 'cannot open files in the PDF document format'.

Add LSItemContentTypes com.adobe.pdf (keeping extension and MIME for
backward compatibility). Role stays Editor, rank stays Alternate, so
the app is offered without hijacking the default. Verified with
lsregister -dump: bindings change from '.pdf, application/pdf' to
'com.adobe.pdf' with claimed UTIs com.adobe.pdf; plutil -lint and
xmllint --noout pass.

Follow-up to #73.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:12:50 -04:00
Jakub 6c89837541 fix: print: read lpstat untranslated so printers show on non-English macOS (#101)
* Print: read lpstat untranslated so printers show on non-English macOS

On a Mac whose interface language isn't English, the Print dialog
listed no printers. `lpstat -p -d` follows the UI language (here
Polish: "drukarka … jest bezczynna", "domyślny cel systemowy: …"), and
parse_lpstat only knows the English "printer " and "system default
destination:" prefixes, so it matched nothing.

- lpstat now runs with LC_ALL=C and LANG=C, which is enough on Linux.
  macOS CUPS ignores both and follows AppleLanguages unless SOFTWARE
  is set, in which case it uses LANG, so SOFTWARE is set too.
- New test: localized output parses to nothing, and the command sets
  all three variables.

Checked on macOS with a Polish UI: only SOFTWARE=1 LANG=C gives English
output, and the dialog now lists every printer with the right default.
lp's error messages stay localized; they are shown, never parsed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Use the PdfCraft name in lpstat's SOFTWARE variable

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:12:45 -04:00
storybored379 a2d9a9480c M1: validate object-number ranges before reading cross references (#92)
Reject invalid xref ranges before arithmetic or narrowing casts, allowing the existing reconstruction path to preserve intact objects and record the repair reason. Cover both tables and streams through Document::open.

Co-authored-by: storybored379 <storybored379@users.noreply.github.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:12:41 -04:00
jcl-bit cc7694be99 Security: ask before opening links from documents (#90, #91) (#104)
A document could open any address with no confirmation: a link click, a
push button's URI action, a one-line button script mapped to a URI action,
or app.launchURL from JavaScript (on by default). That allowed sending
form data to an outside site, or handing file:, smb: and app-handler
addresses to the operating system.

All four now go through PdfCraftApp::request_document_url:

- engine::links::document_url accepts only http, https and mailto, up to
  2048 characters, and refuses control, zero-width, bidi-override and
  non-space whitespace characters, malformed schemes and web addresses
  without a host.
- Accepted addresses wait in an "Open this web page?" dialog that shows
  the real host (engine::links::host drops user:password@, and treats \
  like / as browsers do, so https://a.example\@b.example shows a.example)
  and the full address. Cancel is the highlighted button; Escape and
  clicking outside cancel. While one request waits, further ones are
  dropped, so a script can't stack dialogs.
- Refused addresses are reported in a notice and never opened.

PdfCraft's own links (Help, About, updates) still use open_url.
Preferences > JavaScript now says scripts need permission to open a web
page. Tests: engine unit tests for the policy and host parsing, and
crates/ui-egui/tests/document_links.rs driving the link, button, button
script, app.launchURL and Escape paths (all fail against the old code).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:12:36 -04:00
jcl-bit 70da0179a6 Security: --root confinement leaks and escapes (#136, #137) (#164)
* Automation: --root refuses every outside path the same way (#136)

With a root set, `resolve` canonicalized a path before checking it against
the root, so an existing outside path got "is outside the allowed
directory" while a missing one got the OS error ("No such file or
directory", "Not a directory", "invalid path"): a confined MCP client or
script could probe which files and folders exist anywhere on the disk.

Containment is now decided before anything about an outside path is
reported:
- `..` is resolved by name first (as Windows does), so a path can't pass
  through an outside folder on its way back in;
- on Windows another network share or device namespace (`\\host\share`,
  `\\?\UNC\...`, `\\.\...`) is refused by name, without contacting it;
- the deepest existing ancestor, links followed, must be inside the root
  before anything is said about the rest;
- a broken link is refused the same way, so whether its target exists
  stays hidden too.

Every outside path now gets `<path> is outside the allowed directory
<root>`, existing or not, file or folder, read or write. Filesystem errors
such as "not found" are still reported for paths inside the root.

Behaviour change: on Unix, `..` now means the parent by name, as on
Windows (`link/../x` is `x` beside `link`), and a write such as
`new/../x.pdf` that stays in the root now succeeds instead of failing with
"'..' is not allowed here".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* CLI: script images stay inside --root (#137)

`pdfcraft-cli run --script steps.json --root DIR` wrote each step's
`"out"` PNG with `std::fs::write`, never through the root check the tools
use: an absolute `"out"` created or replaced any file the process could
write, and a relative one landed in the working directory instead of the
root.

The image is now saved through the new `Automation::write_output`, which
uses the same resolver and atomic write as a tool's own output: outside
paths get the usual "is outside the allowed directory" refusal (the step
fails, nothing is written), relative paths resolve inside the root, and
without --root the path is written as given, in place as before (so
`/dev/stdout` still works). `run <tool> --out` goes the same way.

Found while testing this, the same "." problem in two tools:
- a write path naming a folder, such as "." (the root itself), staged its
  temporary file beside that folder, i.e. outside the root for ".",
  overwriting and then deleting any file there called
  `.root.pdfcraft-tmp`. `write_atomic` now refuses a folder before
  writing anything;
- `image_save` adds an extension to a path without one, so "." became
  `root.png` beside the root. It now refuses a folder first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Automation: files named after a document stay in the output folder

Found while reviewing the --root fixes for #136 and #137. Folder outputs
name their files after the document and joined that name straight onto the
resolved folder, without checking the result against the root:

- `doc_export_images` and `doc_export_all_images` used the whole document
  name, so `doc_create` with `name: "../../escape"` followed by
  `doc_export_images` wrote `escape_page_1.png` outside the root (an
  absolute name went anywhere);
- `page_extract` (separate files), `doc_split`, `ocr_recognize_files` and
  `action_run` took the last component, but on Windows a name such as
  `x/C:evil.pdf` gives `C:evil`, and `folder.join("C:evil-page1.pdf")`
  replaces the folder with a path relative to the current directory on C:.

Every such name now goes through `child()`, which turns separators,
colons and control characters into `_` (and prefixes a name of only dots),
so the file is always a direct child of the folder. `with_root` also
refuses a root that is not a folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:12:32 -04:00
EthanSuquet 7920f99f7a Redact: operators split across a page's content streams are redacted whole (#153)
* Redact: operators split across a page's content streams are redacted whole

A page's /Contents array is one content stream in pieces, and a writer
may split it between any two tokens (ISO 32000-2 §7.8.2). Some office
and layout exporters do: a TJ array ends one piece and its TJ operator
starts the next, or a marked-content dictionary closes in the next piece.

Redaction parsed each piece on its own, so such an operator was cut off
from its operands. Glyphs shown by a split TJ were never checked against
the marks: Apply drew the black box but left the text in the file, and
Verify reported nothing under the box. When the piece holding the
operands was rewritten for another mark, they were dropped, so the text
after it vanished and the next piece began with an operator without
operands, which other readers reject.

process() now parses the pieces joined, gives each operator back to the
piece its keyword is in, and rewrites every piece a split operator spans.
Pages whose pieces split only between operators are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Use the pdfcraft crate names after the rename

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: EthanSuquet <292885762+EthanSuquet@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-07 21:12:27 -04:00
Brandon Thomas d3db99818f Rename PrintCraft to PdfCraft (#174)
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:03:57 -04:00
BFlat 1c78ff479c Merge pull request #122 from storytold/fix/107-windows-pdf-association
Windows: register PrintCraft as a PDF handler, so it can be the default (#107)
2026-10-07 11:38:35 +07:00
BFlat 64fc90498b Merge pull request #120 from storytold/docs/97-98-100
Docs: automation README, internal craftrules links, docs/releasing.md (#97, #98, #100)
2026-10-07 11:38:30 +07:00
BFlat 26a3e63bb9 Merge pull request #118 from storytold/fix/94-check-box-styles
Forms: check box and radio button styles (#94)
2026-10-07 11:38:22 +07:00
bflatastic 91dc8718b8 Windows: register PrintCraft as a PDF handler, so it can be the default (#107)
The MSI registered no file association. The only PrintCraft entry a
user could pick in "Choose an app on your PC" was the advertised Start
Menu shortcut, which Windows can't run as a file handler ("This app
can't run on your PC").

The MSI now uses Windows' Default Programs registration: a
PrintCraft.Document ProgID (open command, icon), .pdf OpenWithProgids,
Applications\printcraft.exe (friendly name, supported types), and
Capabilities\FileAssociations listed under RegisteredApplications.
PrintCraft appears under "Open with" and in Settings > Default apps.
It never takes over an existing default: .pdf's own default value is
left alone, and the user chooses.

Checked locally with WiX 5.0.2: the MSI builds, passes ICE validation
(only the existing ICE61 note on same-version upgrades), and its
Registry table holds exactly these values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:52:19 +07:00
BFlat 3751978d0e Merge pull request #116 from storytold/fix/93-95-prepare-form
Prepare a form: Detect form fields in the panel, delete several fields at once (#93, #95)
2026-10-07 09:46:48 +07:00
bflatastic 032373b2a9 Docs: docs/releasing.md, which release.yml has always pointed to (#100)
release.yml's comments say "See docs/releasing.md", but the workflow came
over from PhotoCraft without its document. It now describes PrintCraft's
pipeline as it is: cutting a release and test runs, every artifact
(including Windows on ARM64), the craft-fonts pin, each platform's
packaging, signing and notarization, the release-environment secrets,
and the packaging checks.

Written from release.yml and the packaging scripts. It says plainly that
the binaries don't embed the commit yet, and packaging/env.sh no longer
points to a build_info.rs that doesn't exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:46:23 +07:00
bflatastic bddc800c87 Docs: say the craftrules standards are internal, and that the rules here are the binding ones (#98)
AGENTS.md, CLAUDE.md and crates/fonts/README.md linked to storytold/craftrules, which outside contributors can't open. The links stay for team members, now labelled internal, and AGENTS.md says up front that every rule a contributor here must follow is stated in AGENTS.md and CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:44:11 +07:00
bflatastic 5e74f95017 Docs: the automation README describes the tools and MCP resources that ship (#97)
Its Status line said 38 tools and listed form authoring, redaction tools and MCP page-image resources as planned; 123 tools and all three ship. It now names the areas the tools cover and points to printcraft-cli tools for the current list instead of a count that goes stale, and the MCP section lists the resources/* methods and resource templates. ROADMAP's M13 row said 91 tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:43:26 +07:00
bflatastic cdb3f3d106 Forms: check box and radio button styles (#94)
Field Properties > Options > Check Box Style (Button Style for radio
buttons): Check, Circle, Cross, Diamond, Square or Star, as Acrobat
offers. The choice is stored in each widget's /MK /CA with Acrobat's
ZapfDingbats codes (4 l 8 u n H), so other viewers draw the same mark,
and PrintCraft draws it as paths, needing no symbol font. Styles set
by other apps are read back too; the defaults stay a check for check
boxes and a circle for radio buttons.

- forms: CheckStyle, check_style(), FieldProps::check_style; set_props
  writes /MK /CA (resolving an indirect /MK so its colours survive) and
  redraws; text and choice fields refuse a style.
- engine: Document::field_check_style.
- automation: form_set_props takes check_style, form_fields reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:42:39 +07:00
bflatastic e4191e0d2c Prepare a form: Detect form fields in the panel, delete several fields at once (#93, #95)
- #93: field detection already ran when Prepare a form starts on a
  document without fields, and as the form.detect command, but nothing in
  the tool panel offered it. Prepare a form > Fields now lists Detect
  form fields; on a form that already has fields it only adds new ones.
- #95: with several fields selected (Shift/Cmd-click), Delete and the
  context menu's Delete removed only the right-clicked one. They now
  remove every selected field as one undoable step ("Delete 3 fields").
  One prepare::delete_selected serves both.

Editing the shared properties of several fields at once is still to do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:31:35 +07:00
BFlat 55c581760a Merge pull request #88 from storytold/docs/roadmap-session-15
ROADMAP: session 15 log
2026-10-07 02:30:45 +07:00
bflatastic de3ab3e087 ROADMAP: session 15 log
The session's log line: issues #70, #72, #73, #74 and #76 fixed, and
#79, #80 and #81 noted as open. The parity tables are refreshed from
`cargo xtask parity` (805 features, 50.2% shipped, 53.7% weighted),
with the by-area shares and the 0.2.1 release. Docs only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:18:04 +07:00
BFlat 376ffcd8e8 Merge pull request #86 from storytold/fix/74-add-text-keeps-boxes
Add text: clicking elsewhere keeps the text and starts a new box (#74)
2026-10-07 02:17:26 +07:00
BFlat f3e22b738d Merge pull request #82 from storytold/fix/73-macos-open-from-finder
macOS: open PDFs from Finder, Open With and the Dock (#73)
2026-10-07 02:17:21 +07:00
BFlat edaa4445e4 Merge pull request #84 from storytold/fix/76-windows-text
UI: sharper, higher-contrast text, most visible on Windows (#76)
2026-10-07 02:07:59 +07:00
bflatastic 5b44162871 Add text: clicking elsewhere keeps the text and starts a new box (#74)
With the Text tool, a click on the page replaced the box being typed
with a new empty one, throwing the text away. A real mouse click
usually arrives as press and release in one frame, so the page saw the
click before the editor saw its text box lose focus.

- The page click now finishes the open box first (one finish() shared
  with the editor), then starts a new box where you clicked. Both input
  timings end the same way, so the tool no longer drops back to Select
  after one box.
- The box being typed gets Done (keep it, back to Select) and Discard
  (as Escape) buttons beside it, as the reporter suggested. Pressing
  them doesn't count as a click away.
- The box just kept is not selected while the next one is typed, so the
  Format panel styles the new text.
- content_ui::page_input returns a bool; its Outcome::done was never
  set.

Reproduced and checked in the real app on Windows with OS mouse and
keyboard input (SendInput): before, the first box vanished; now both
boxes stay and a third opens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:05:02 +07:00
Brandon Thomas 282a12cc89 ROADMAP: session 14 log
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:56:31 -04:00
BFlat b26010858d Merge pull request #78 from storytold/fix/72-word-export
Export to Word: files open in Word again (#72)
2026-10-07 01:50:55 +07:00
bflatastic 71fcf5be8e UI: sharper, higher-contrast text, most visible on Windows (#76)
- Glyphs are rendered on whole pixels: egui's sub-pixel binning draws
  each glyph at quarter-pixel offsets, which it notes makes text
  blurrier, most visibly at the 100-150 % scaling common on Windows.
- Light theme: a mild gamma (0.75) on glyph coverage darkens the
  antialiased edges of dark text, which egui renders linearly (thin and
  grey next to the system's text). The dark theme keeps egui's curve.
- The faintest text colour met only 3.3:1 (light) and 3.9:1 (dark).
  It is now #6B6B73 / #97979E, so every text colour meets WCAG AA
  (4.5:1) on every surface in both themes, enforced by a test.

Compared in headless renders at 1x and 2x: crisper stems at 1x, little
change at 2x.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:50:38 +07:00
BFlat 90c56e9ff0 Merge pull request #77 from storytold/fix/70-two-page-next
View: next page in two-page view moves a whole spread (#70)
2026-10-07 01:40:19 +07:00
bflatastic a6af3bc6c1 Packaging: describe PrintCraft, not the image editor it was copied from
The Linux package description, AppStream metadata (categories,
keywords, a screenshot that doesn't exist), the MSI's comment and the
web embedding example still described PhotoCraft. They now describe a
PDF workbench, AppStream lists application/pdf, and the screenshot is
one of ours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:39:33 +07:00
bflatastic f50c5a4e88 macOS: open PDFs from Finder, Open With and the Dock (#73)
Finder hands files to an app as an Apple event, not on the command
line. winit doesn't handle it, so AppKit answered "PrintCraft cannot
open files in the PDF document format". The bundle also declared no PDF
document type: its Info.plist still carried the sibling image editor's
types.

- Info.plist declares PDF (by extension and MIME type) as a document
  type, role Editor, rank Alternate: offered under Open With and
  settable as the default, without taking over on install.
- apps/printcraft registers open-documents and quit Apple event
  handlers through fmv-macos-events (=0.1.0, the audited safe wrapper
  PhotoCraft uses, since the Objective-C interop needs unsafe), before
  the launch event arrives.
- printcraft-ui-egui gains OsEvent and PrintCraftApp::os_events, polled
  every frame: Open opens each file as File > Open does; Quit closes the
  window like its close button, so unsaved changes are asked about.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:39:33 +07:00
bflatastic 8b25d88450 Export to Word: files open in Word again (#72)
Text extracted from a PDF can hold C0 control characters (fonts without
a usable /ToUnicode), which XML forbids, and Word refuses the whole
document over one ("Word experienced an error trying to open the
file"). The Word, HTML and RTF writers now drop the characters XML
forbids. Also:
- the page size stays within Word's 22 in (a till receipt is taller,
  and Word read it as a negative height), and margins shrink on small
  pages so text keeps room; images fit the real text width;
- a non-finite font size falls back to 12 pt;
- zip entries get a valid DOS date (1980-01-01) instead of zero.

Checked by opening the exports in Microsoft Word: control characters,
U+FFFE/U+FFFF, a 33 in receipt with a 40-row table, narrow, tiny and
wide pages all open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:31:37 +07:00
bflatastic c341a5c929 View: next page in two-page view moves a whole spread (#70)
Next page went to the right-hand page of the spread already on screen,
so the view didn't scroll and the current page snapped back to the
left-hand one. Next and previous page (Ctrl/Cmd+arrows, the rail's
buttons and the NextPage/PrevPage actions) now step a spread at a time
in two-page view, with or without a cover page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:21:42 +07:00
Brandon Thomas 8197e60eb7 Merge pull request #75 from storytold/pr/craft-fonts
Fonts: use craft-fonts as an optional build input; move the Japanese fonts out
2026-10-06 14:13:50 -04:00
Brandon Thomas 1d67833599 Fonts: use craft-fonts as an optional build input; move the Japanese fonts out
Shippori Mincho (and its OFL file) leaves assets/fonts/: the Japanese fonts now
live in storytold/craft-fonts. printcraft-fonts gets craft-fonts' build.rs recipe:
with CRAFT_FONTS_DIR=<checkout> it embeds the manifest's fonts as CRAFT_FONTS
(wasm32: only BIZ UDPGothic Regular); without it CRAFT_FONTS is empty and the
app builds, tests and runs as before, minus the Japanese face.

- SHIPPORI_MINCHO is now Option<&[u8]> from CRAFT_FONTS; japanese_glyph
  (was shippori_glyph) uses document_japanese_font() (Shippori Mincho, then
  BIZ UDMincho, then any Jpan face) and returns GlyphError::NoFont without one.
- Editing PDF text into Japanese without craft-fonts is a clear EditError that
  leaves the page untouched.
- UI: every Jpan craft font (BIZ UDPGothic first) is the last fallback of
  every egui family, sharing the same static bytes.
- Tests: Japanese glyph/edit tests skip without craft-fonts; new UI tests for
  no-tofu Japanese with craft-fonts and working fonts without it.
- CI: the Linux test job checks out craft-fonts at a pinned SHA and runs the
  tests with CRAFT_FONTS_DIR. Releases build every platform with it
  (CRAFT_FONTS_REQUIRED=1) and ship each font's OFL as OFL-<family>.txt.
- ATTRIBUTION.toml gains [[build_input]] (checked by xtask assets); NOTICE,
  AGENTS.md, CLAUDE.md, README and docs say fonts live in craft-fonts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:36:23 -04:00
Brandon Thomas 2e49a84ace CI: remove the temporary ARM64 crash-diagnosis job
It was only for finding the WARP crash (fixed in the previous commit) and
was merged with #66 by accident; it ran a 60-iteration crash-dump loop and
edited the runner's registry on every push.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:43:49 -04:00
Brandon Thomas d341651b20 Tests: one wgpu device compiles shaders at a time in view.rs (WARP ARM64 crash)
On GitHub's Windows 11 ARM64 runner (no GPU, so wgpu uses Microsoft's WARP)
tests/view.rs died with STATUS_ACCESS_VIOLATION in about 1 run in 4. A crash
dump puts it inside WARP's pixel-shader JIT
(d3d10warp!PixelJitProgram::ClassifyVars, on WARP's own thread pool) when the
two tests that render pixels compile shaders on separate devices at once.
Single-threaded, or each test alone, it never crashed (180 runs); bigger
thread stacks didn't help (6/30 vs 7/30), so it's that race, not our code.
The app has one device on a real GPU and can't hit it.

Those two tests now hold a per-binary lock for their whole run (taken before
the harness, so released after its device drops). Other test binaries render
from at most one test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 10:43:49 -04:00
Brandon Thomas 05031c81e9 Merge pull request #66 from storytold/feat/61-windows-arm64
Windows on ARM64: release builds, tested on ARM64 hardware (#61)
2026-10-06 09:26:40 -04:00
Brandon Thomas 5112e34f37 TEMP: print the faulting thread and the loaded graphics modules 2026-10-06 09:08:36 -04:00
Brandon Thomas c3007277ef TEMP: capture a crash dump of the ARM64 view-test crash and print its stack 2026-10-06 08:55:59 -04:00