* M3: gate watchdog tests without timed sleeps
* test(render): replace watchdog timing delays with gates
* Render tests: the panic test doesn't wait on the gate's finished barrier
Only an abandoned render reaches it, so waiting there hung under load.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* M8: redact lists of words or phrases
Find text and redact gains "Multiple words or phrases": a list with one word
or phrase per line, typed or imported from a .txt file (Import list…), whose
every match is marked in one undo step. Lists are trimmed, deduplicated and
bounded (1,000 entries of 256 characters; files up to 1 MB are read).
redact_mark takes words and reports matched_words, the marks each entry made.
protect.redact-word-lists is shipped; whole-word and case-sensitive matching
and exporting the list are still missing.
* M8: translate word-list labels into Arabic
The Arabic catalog arrived on main after this branch, so the new Find
text and redact strings were still English and failed
arabic_covers_ui_literals.
* Tests: drop sharp grid renders when the thumbnail will cover the cell
Zooming the organize grid back out left the sharp page raster in place
until a thumbnail texture existed, so the zoom test failed when that
texture had not arrived yet.
* M8: Czech rows for the word-list redaction labels
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Parity: merge the duplicate form.field-list evidence and notes
#649 and #650 each added `evidence` and `notes` to the same entry, so the
TOML has duplicate keys and `cargo xtask parity` fails on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M4: bookmarks from tagged headings (organize.bookmarks-from-structure)
Bookmark options > New bookmarks from structure (command
bookmark.from_structure, tool bookmark_from_structure) turns a tagged
document's headings into bookmarks, as Acrobat's New Bookmarks From
Structure does for headings: H1-H6 through the role map, a plain H by its
Sect/Part/Art nesting, nested by level under a new first "Untitled"
bookmark, each linked to its structure element (/SE, ISO 32000-2
§12.3.3). Titles come from /ActualText, the marked text drawn on the page
(decoded through the font's ToUnicode or encoding), /Alt or /T.
The structure walk is bounded (depth, visits, heading and title caps,
seen-set) and survives self-referencing elements; an untagged document
gets an actionable error; the edit is one undo step under the assemble
permission. Not yet: choosing other element types, and destinations at the
heading's position rather than its page.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Tests: size the shared raster allowance from thumbnail demand
The canvas test planted a 132×171 thumbnail whose tag did not match the
request finish_render_frame computes. On FreeBSD that real render finished
in the same frame, so the retained raster exceeded 132×171×4. Plant a
thumbnail that already satisfies demand and compare against that size.
* M4: a heading's text stops gathering once it is far past the title length
A heading whose marked-content list repeats one mark up to the visit cap could otherwise collect
about a gigabyte before the title is cut to 256 characters.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Extract pages: a File name field names the extracted files (#737)
The Extract pages dialog had no way to name the result: separate files
were always "<document> (page N).pdf" in the chosen folder, and a new
tab was "<document> (extract).pdf".
The dialog now has a File name field, filled in with the document's
name when it opens. Separate files are "<name> (page N).pdf"; a single
renamed page is "<name>.pdf"; extracting into a new tab names the tab
(and so the Save As suggestion) "<name>.pdf". The name is made safe for
every platform (separators, reserved and control characters, trailing
dots and spaces, any-case ".pdf"), and falls back to the document's
name when nothing usable is left.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Extract pages: Windows device names get a prefix, and the name is capped at 200 bytes
"NUL.pdf" writes nowhere on Windows, and 200 CJK characters overflow the 255-byte name limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: DWAA1660 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M9: sign and timestamp encrypted documents
Signing a password-protected PDF failed with "not supported: signing
encrypted documents". The writer encrypted every string, including the
signature's /Contents placeholder, which the signer patches in place
after hashing.
- cos: a signature dictionary's /Contents is never encrypted or
decrypted (ISO 32000-2 §7.6.2), identified by /Type /Sig|/DocTimeStamp
or by /Contents + /ByteRange. Existing signatures in encrypted files
now read their real CMS and survive rewrites of their dictionary.
- sign: the blanket refusal is replaced by the document's permissions
(Table 22) when opened with the user password: existing fields and
document timestamps need fill-forms, new fields need annotate +
modify, certifying needs modify. Refusals name the permissions
password.
- Tests for all four algorithms, counter-signing, tampering,
permissions, document timestamps and the sign_document tool; parity
entry sign.encrypted-documents; ROADMAP log.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Hadi Chokr <[email protected]>
* M9: signing an encrypted document waits for a pending security change to be saved
Otherwise the incremental save becomes a full rewrite that applies or removes protection while
signing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Signed-off-by: Hadi Chokr <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Parity: merge the duplicate form.field-list evidence and notes
#649 and #650 each added `evidence` and `notes` to the same entry, so the
TOML has duplicate keys and `cargo xtask parity` fails on main.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M9: the signer's certificate must allow document signing (key usage, EKU, critical extensions)
A trusted signer whose certificate may not sign documents was reported
Valid. Validation now checks the signer certificate (RFC 5280 §4.2.1.3,
§4.2.1.12, §4.2): a keyUsage needs digitalSignature or nonRepudiation, an
extended key usage needs a document-signing purpose (any, emailProtection,
codeSigning, RFC 9336 documentSigning, Adobe Authentic Documents, Microsoft
document signing), and no critical extension may be one PdfCraft doesn't
process. Otherwise the verdict is Unknown and the details say why.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M9: a critical qcStatements extension doesn't make a qualified certificate unusable
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M4: keep the render pool across metadata edits
Metadata edits (SetInfo) no longer rebuild the render pool or the display
bytes. The engine refreshes the document-information fields, the file size,
the signatures (when present) and the working bytes, and keeps the renderer.
Undo and redo of metadata edits reuse the same pool.
Comment and form edits still rebuild the renderer and their view data.
Tests:
- metadata_edits_and_history_reuse_the_render_pool
- field_and_comment_edits_still_rebuild_the_render_pool
- metadata_edit_keeps_the_renderer_without_changing_page_content (the
metadata invalidation audit; its assertions now expect the kept renderer)
Supersedes #711 and #712.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
* M4: a metadata edit reads the other Info entries as the full inspection does
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Haiku 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M14: Arabic interface language
A 2,136-entry Arabic catalog (six CLDR plural forms, Arabic month and
weekday names), selected by `ar` and every `ar-*` locale. egui joins
Arabic letters but places words left to right, so the catalog is put
into display order when it loads (bidi::display_rtl); placeholders,
keyboard shortcuts and caption colons stay in place. The command
search matches a multi-word Arabic query word by word.
The craft-fonts pin moves to the revision with Noto Sans Arabic.
crates/fonts/build.rs now refuses to embed Adobe type designs, so the
Noto Sans CJK SC in that revision is never embedded (AGENTS.md §1.1).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M14: Arabic catalog follows the {key} shortcut labels and has Redaction code
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
View ▸ Zoom and Page navigation were hand-written with "⌘1", "⌘[" … while
the registered items below them were formatted by Shortcut::label, so
Linux showed both ⌘1 and Ctrl+K in one menu. Ten tooltips and the view-key
rows of Help ▸ Keyboard shortcuts hard-coded ⌘ the same way.
- The keys the document view handles itself are Shortcut constants in
ui-egui commands.rs, written with the same Shortcut::label.
- Labels follow egui's detected OS (ctx.os()) instead of cfg!, so the web
build in a Linux or Windows browser shows Ctrl too, and tests can check
both platforms.
- Translated tooltips take a {key} placeholder (13 catalogs; only the
shortcut token changed, hashes updated in ATTRIBUTION.toml).
- Fit visible is listed once in View (in Zoom), not also in the generated
list; Ctrl+3 and the palette still run it.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M8: redaction code sets (U.S. FOIA, Privacy Act) as overlay text
Redaction properties offers "Custom text" or "Redaction code" under "Use overlay
text". Picking the U.S. FOIA (5 U.S.C. 552(b)) or U.S. Privacy Act (552a) set and
ticking codes makes new marks carry them as overlay text, in the set's order.
redact_mark takes code_set and codes and rejects unknown sets or codes with the
allowed list.
Also merges a duplicated evidence/notes pair in form.field-list that made
parity/acrobat-features.toml fail to parse on main.
* M8: say why the default redaction code set can't fail
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Tests: pin the clock in form_bulk (saves a second apart differed in /ModDate on Windows CI)
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Windows: open files in the running PdfCraft instead of a new window each time (#282, #317)
Outlook attachments, Explorer double-clicks and Open With start pdfcraft.exe with the file,
so every file got its own process and window. Now the first PdfCraft listens on a Unix domain
socket in %LOCALAPPDATA%\PdfCraft (or a portable copy's data folder), one per Windows session.
A later launch that only names files sends their absolute paths, waits for "ok" and exits; the
running app opens them as tabs through the existing OsEvent::Open path (the one macOS Apple
events use) and comes to the front.
Anything else keeps today's behaviour: --new-window, any other option, a launch without files,
or any failure to hand over within 5 seconds opens a window of its own. Other platforms are
unchanged.
The socket lives in the user's profile, so other accounts can't create, replace or reach it.
uds_windows (already in the lockfile through zbus) provides it: std has no Unix sockets on
Windows and the workspace forbids unsafe. A socket left by a crash is replaced on the next start.
* Windows: launches started together share one window (#282, #317)
Several files selected in Explorer and opened at once start one process
per file at the same moment. A launch could then find the socket bound
by another launch that was not listening yet: connecting failed, clearing
the socket failed with "Access is denied", and the launch opened a window
of its own.
Launches now take turns through a lock on a file beside the socket, held
only while a launch finds out whether it is the first. A socket that
cannot be cleared because another launch holds it is tried again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_016es5tszobbb1yZ7JeozJXL
* Windows: a portable copy doesn't hand files to another instance
Its data folder may be writable by other accounts, so a socket there could leak or inject file
paths. Builds on art804's single-instance work in #602.
Co-authored-by: art804 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: art804 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* PDF/A: no sRGB output intent for CMYK documents, and verify checks the intent's colour space (#667)
pdfa_convert always added an sRGB output intent when there was none,
even for documents whose only content is DeviceCMYK, and pdfa_verify
then called them compliant: it only checked that some PDF/A output
intent existed. ISO 19005-2 6.2.4.3 allows DeviceCMYK only with a CMYK
output intent, and DeviceRGB only with an RGB one.
- verify reads the PDF/A output intent's destination profile (/N, else
the ICC header's data colour space) and reports DeviceCMYK under a
non-CMYK intent and DeviceRGB under a non-RGB one (6.2.4.3, not
fixable).
- convert adds the generated sRGB intent only when the document has no
DeviceCMYK; a CMYK document keeps the existing "needs a CMYK output
intent (not added automatically)" issue instead of a wrong intent.
- Device colour detection tells gray, RGB and CMYK apart (gray works
under any intent).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* PDF/A: DefaultRGB/DefaultCMYK resources satisfy the output-intent colour check
ISO 19005-2 6.2.4.3 accepts device colour drawn through a default colour space under any output
intent.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: DWAA1660 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* render: cancel obsolete in-flight requests
* #623: recheck cancellation after busy publication
* #623: record rendering session in roadmap
* M2: a request cancelled during the parse doesn't leave its worker unable to render
The abort was cached as the worker's parse result, so every later request on that worker failed as
unparsable. Also drops ROADMAP edits that described other PRs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* M2: text turned at any angle reads as lines along its own baseline
The text layer sorted every glyph into the nearest of four quarter turns
and laid it out by its axis-aligned box. Text at 30-60 degrees lost its
word gaps, split into one line per word in reverse order, or interleaved
two neighbouring lines letter by letter; text set along a curve came out
in fragments.
Each glyph now keeps its drawn em box as a quad and its writing
direction. Glyphs within the layout's word-gap tolerance (sin 0.15) of a
quarter turn take the existing path unchanged. Other glyphs are laid out
in a frame along their own baseline, one flow per direction; runs that
continue each other while turning (stamps, seals) are unrolled into one
line. Turned text keeps the place in the reading order that its first
glyph had before.
Horizontal, quarter-turn, skewed and near-axis text is bit-identical
(text, rect, line_of, space_before) over pdf_engine_test.pdf, the
crates' test PDFs and 300 synthetic pages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M2: look up a turned glyph's direction among directions only, not every curved run
Curved runs can number about half the glyphs, which made grouping quadratic on hostile pages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* M2: pages with /UserUnit are shown at their size in points, as in Acrobat
/UserUnit gives the size of a page's user-space unit in points; PdfCraft
ignored it everywhere except the measuring tools, so a /UserUnit 2 page
was shown, extracted, printed and edited at half its size, and the
measuring tools disagreed with the rest of the app.
The value is now read as Acrobat reads it (the page's own entry, a number
from 1 up, at most 75,000; anything else counts as 1) in two places:
- vendored hayro-syntax Page (patch 13): render_dimensions and
initial_transform give the page in points, so rendering, tiles,
PageInfo and the text layer follow; a page without it keeps exactly
the transform it had.
- pdfcraft-model Page: user_unit(), and display_size/view_matrix in
points, so print, header/footer, watermark, background, added content
and measuring agree with the screen. The measure crate drops its own
arithmetic. Added items record the /UserUnit they were written for;
items without the record (written before) read back converted.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* M2: an added item's recorded /UserUnit is clamped like the page's
An infinite or huge recorded /UserUnit made the scale factor 0 and collapsed the item.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Sign: decide Windows store keys in code every platform builds
#646 fixed the Windows build after #386 with `_` arms in the two
matches in windows.rs. That module is cfg(windows), so Linux and macOS
CI never compile it, and the next PublicKey variant will either break
the Windows build again or, now, fall into `_` without anyone choosing.
The store's two decisions move into keys.rs, which every platform
builds, with every variant named and no `_` arm:
- PublicKey::store_signing_key: RSA, or ECDSA with 256/384 bits, or
None for the kinds PdfCraft only verifies with (P-521, brainpool,
Ed25519, Unsupported).
- PublicKey::store_signature_der: CNG's raw r||s as DER (RSA as is);
the verify-only kinds return SignError::Unsupported, as since #646.
windows.rs calls both, with no `_` arms left. Behaviour is unchanged.
tests/store_keys.rs runs everywhere: every variant's answer (with an
exhaustive match, so a new variant must be added to it), the P-256 and
P-384 conversion round-tripped through PublicKey::verify (code that
used to compile only on Windows), and malformed CNG signatures erroring
rather than panicking.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Sign: smart cards and tokens in the Windows store ask for their PIN (#694)
Signing with a Windows store identity whose key lives on a smart card or
USB token failed with NTE_SILENT_CONTEXT (0x80090022) and no PIN dialog.
`identities()` acquires each key with CRYPT_ACQUIRE_SILENT_FLAG so that
listing never prompts, and signing reused that handle after
`set_silent(false)`. That only drops NCRYPT_SILENT_FLAG from
NCryptSignHash: a provider handle that was opened silently stays silent,
so a card that needs its PIN refuses instead of asking.
`WindowsKey` now keeps the certificate context and acquires a fresh,
non-silent key handle when it signs, so the provider can show its own
dialog. Listing is unchanged and still silent.
Reproduced and checked by hand with a qualified certificate on a SafeNet
token (SafeNet Smart Card Key Storage Provider, RSA-2048): 0.5.0 fails
with 80090022, this build shows the SafeNet PIN dialog and signs. The
software-key round trip (`signing_with_windows_store_identities`,
--ignored) still passes on Windows 11.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Sign: a Windows store identity signs with its issuers
A digital ID file brings its certificate chain and the signature embeds
it; a Windows store identity signed with its own certificate only. A
certificate issued by an intermediate CA (the usual case on a token)
then validates only where the reader already has that CA.
`windows::find`, which fetches the identity to sign with, now fills
`chain` with the certificates Windows chains it to, without the root.
`identities()` still builds no chains, so listing stays fast and local.
A chain that can't be built leaves `chain` empty, as before.
Test `a_windows_store_identity_signs_with_its_issuers` (--ignored, like
the other store test): a temporary root, issuer and signer; the
signature validates against the root alone. The cleanup guard now also
removes the public copy New-SelfSignedCertificate leaves in Intermediate
Certification Authorities, which the existing test left behind.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: James Locke <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: mmmaarian03-crypto <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
A resizable egui panel stores the width its content used at the end of
every frame and starts the next frame there, so a row wider than the panel
widens it on every repaint (any pointer move) until it reaches 520 px.
- Bookmarks: the search row reserved 32 px for an 8 px gap and a 26 px
clear button, overflowing by 2 px. The field now reserves the button's
size plus the theme's item spacing.
- Comments: the reply row reserved 56 px for the gap and "Post", which only
fits English. The field now fills its line and Post sits right-aligned
under it, keeping the field first in Tab order.
- Tests read egui's PanelState and hold each panel's width across 30
repaints (Bookmarks with a bookmark; Comments with a reply open in
Russian with Post hovered). On main they grow 358 → 418 and to 520 px.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Create from images (doc_create, and image_xobject for stamps, signatures and
inserted images) dropped a JPEG's or PNG's embedded ICC profile, so a CMYK
JPEG became untagged /DeviceCMYK.
- JPEG: the APP2 ICC_PROFILE chunks are joined by sequence number; a profile
with a missing or repeated chunk is ignored.
- PNG: the iCCP profile is read.
- A profile whose header is sound (acsp) and whose colour space matches the
image (GRAY/RGB/CMYK for 1/3/4 components) becomes [/ICCBased <stream>]
with /N and the device space as /Alternate. Anything else keeps the device
space, as before. JPEG data and an Adobe CMYK /Decode are unchanged.
- Images in one document that carry the same profile share one profile
object.
TIFF profiles are not read yet.
Co-authored-by: lloyd <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
#646 fixed the Windows build after #386 with `_` arms in the two
matches in windows.rs. That module is cfg(windows), so Linux and macOS
CI never compile it, and the next PublicKey variant will either break
the Windows build again or, now, fall into `_` without anyone choosing.
The store's two decisions move into keys.rs, which every platform
builds, with every variant named and no `_` arm:
- PublicKey::store_signing_key: RSA, or ECDSA with 256/384 bits, or
None for the kinds PdfCraft only verifies with (P-521, brainpool,
Ed25519, Unsupported).
- PublicKey::store_signature_der: CNG's raw r||s as DER (RSA as is);
the verify-only kinds return SignError::Unsupported, as since #646.
windows.rs calls both, with no `_` arms left. Behaviour is unchanged.
tests/store_keys.rs runs everywhere: every variant's answer (with an
exhaustive match, so a new variant must be added to it), the P-256 and
P-384 conversion round-tripped through PublicKey::verify (code that
used to compile only on Windows), and malformed CNG signatures erroring
rather than panicking.
Co-authored-by: Claude Opus 5.5 <[email protected]>
* create: lift TIFF decoding buffer limit to allow print-sized images (#665)
By default, the tiff crate limits decoded buffer size to 256 MiB.
Set Decoder::with_limits(Limits::unlimited()) to match PNG decoding
behavior and prevent 'decoder limits exceeded' on large images.
* create: cap the TIFF decode buffer at 2 GiB instead of lifting it entirely (#665)
A few bytes claiming enormous dimensions must still be refused, not allocated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Help ▸ Check for updates showed a bare "couldn't reach GitHub
(http status: 403)" when the anonymous hourly quota ran out. Read
error statuses in latest_release instead: a 403 now reports the
shared-quota limit with the retry time from X-RateLimit-Reset
("try again in about N minutes"), falling back to "try again later"
when the header is missing or spent. Other statuses keep the old
message. Adds rate_limit_messages_name_the_wait.
Co-authored-by: ShauryaSwarup <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
The rows of Sign with a Digital ID painted the name and the "Issued by"
line unclipped, so a long subject or issuer (a Windows store
certificate named after a GUID, for example) ran past the dialog's
right edge.
Both lines are now cut with "…" at the row's edge, and a cut row shows
its whole text on hover.
Test `long_digital_id_names_stay_inside_the_picker` fails before the
change.
Co-authored-by: mmmaarian03-crypto <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
The About dialog is a modal centred on its own size. Switching to the
Contributors tab grows it, and for a few frames it recentres: here the
Table toggle sits at y=378, then 355, then 341. The test waited a fixed
four frames (#656) before clicking Table. Since the 0.5.0 contributors
refresh that is not always enough (deterministic on some Debian
machines, intermittent on the GitHub runners), so the click landed where
the button had been and the table never opened: "No nodes found matching
the query: By { label: \"PRs\" }" at links.rs:78.
Clicking after one frame reproduces exactly that failure. A `settled`
helper now runs frames until the target node exists and its rect is the
same on two frames in a row, so the test no longer depends on how many
frames the modal needs. The dialog itself is fine: a real click comes
long after the two or three frames the modal takes to settle.
Co-authored-by: DWAA1660 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Parity: document the Page Display resolution gap at fixed zoom
view.zoom-to-percent and view.zoom-actual-size are shipped with empty
notes. Record that PdfCraft's 100% is its fixed 96-dpi basis, that
Acrobat's fixed-percentage display also depends on its Page Display
Resolution preference, and that PdfCraft does not offer that preference
yet (tracked with misc.prefs-page-display). Status, commands, tools and
evidence are unchanged. No runtime behavior changed.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
* Parity: state the Acrobat fixed-zoom formula in the zoom notes
The zoom percentage and Actual Size notes now give the measured rule
(logical px per PDF point = ppi / 72 × zoom), name the Custom
resolutions checked black-box, and say that Use System Setting is not
reproduced. Status, commands, tools and evidence are unchanged. No
runtime behavior changed.
Co-Authored-By: Claude Haiku 5.5 <[email protected]>
---------
Co-authored-by: Claude Haiku 5.5 <[email protected]>
* macOS: IME text committed after the marked text is cleared isn't lost (winit#4650)
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* macOS: the first Apple Korean key in a fresh PdfCraft composes (winit#4744)
On the first key after PdfCraft starts, Apple Korean 2-Set can answer
interpretKeyEvents with insertText and the raw compatibility jamo instead of
setMarkedText. winit sends that key to the app as an ordinary key, and the
next keys compose without it: g k s Space typed ㅎㅏㄴ instead of 한 (Search,
form fields, comments; rust-windowing/winit#3095).
vendor/winit now ports rust-windowing/winit#4744: keyDown interprets that
one key a second time, synchronously, before any KeyboardInput is sent, and
only when every condition holds: Apple Korean, IME allowed and not yet
composing, no pending commit or marked text, no Command/Control/Option, not
a repeat, the key window's first responder, the only callback is one
insertText of that same single jamo, and focus and input source unchanged
afterwards. The decision is ImeStartup (ime_startup.rs, copied with its
seven tests); no timer, no key queue, no text rewriting. It sits on top of
the previous macOS IME change (pending_commit, in_key_down); the retry runs
inside in_key_down. Command maps to ModifiersState::SUPER in winit 0.30
(META on winit master).
Checked with the real input method on macOS 14.7.1 (scratch winit app, fresh
process each time): without the patch g k s Space gave ㅎ as a key, then
ㅏ and ㄴ committed separately, 3 runs out of 3; with it, ㅎ → 하 → 한 → 한
committed, 3 out of 3. All other traces (Japanese, Cangjie, ABC, held
keys, later Korean keys) are unchanged. ImeStartup's tests pass with
rustc --test. A test fails if a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* macOS: IME text committed after the marked text is cleared isn't lost (winit#4650)
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* macOS: Space in Apple Korean with no syllable in progress types one space (winit#4478)
With Apple Korean 2-Set on macOS 14, Space with no syllable in progress
(a second space, or a space after a digit or punctuation) makes AppKit
call setMarkedText(" "), insertText(" "), then setMarkedText(""). winit
reports the insertText as Ime::Commit(" "), but the trailing empty
setMarkedText returns its state to the ground state, so keyDown also sends
the key as KeyboardInput with text " ". egui inserts both, and PdfCraft's
text fields got two spaces (the double space of alacritty/alacritty#8079).
vendor/winit now records that the key being handled committed IME text and
then doesn't also send it as KeyboardInput, unless AppKit forwards it as a
command through doCommandBySelector (Enter after a syllable still commits
the syllable and sends Enter). keyDown consumes the record on every key,
so keys typed after IME is disallowed are unaffected.
rust-windowing/winit#4478 aims at the same double space, but its changes
(clearing the marked text on commit, forwarding a later insertText, and
dropping doCommandBySelector's guard) don't reach this call order, and the
key loss it also fixes is already fixed by the previous change
(pending_commit). This is the narrow form for the call order observed
here; it should go upstream with #4478.
Checked with the real input methods on macOS 14.7.1 (scratch winit app, on top
of the previous change): the duplicate KeyboardInput Space is gone and
nothing else changes (Korean syllables, 한+Enter, 한+5, Japanese, Cangjie,
ABC, held keys, keys after IME is disallowed). A test fails if a
re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Keep #678's emoji-picker branch: view.rs is main's plus this PR's Space fix (54b3d3c)
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* macOS: IME text committed after the marked text is cleared isn't lost (winit#4650)
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* macOS: emoji from the emoji picker reach text fields (winit#4749)
The macOS emoji picker (Fn/Globe-E, Control-Command-Space) inserts its text
with insertText:replacementRange: outside any key press and without a
composition. winit 0.30.13 reports insertText only as part of a key press or
a composition, so an emoji picked into Search, a form field or a comment
was dropped.
vendor/winit now reports text inserted outside a key press, with IME
allowed and no composition, as Ime::Commit, as rust-windowing/winit#4749
(the 0.30 backport of #4748) does. Builds on the pending_commit and
in_key_down state of the previous macOS IME change.
#4748's selectedRange change, which lets dictation start, is left out:
checked on macOS 14.7.1, a valid selection switches on press-and-hold for
every held key in a winit window, so holding e kept sending e key events
under an accent menu, and the accent picked with 1 arrived as a separate
commit after "eeee…1". Without it, held keys repeat as before.
Checked with a scratch winit app on macOS 14.7.1: an insertText outside a key
press now arrives as Ime::Commit; held keys, ABC, Japanese, Cangjie and
Korean typing give the same events as before. A test fails if a re-vendored
winit loses the patch or gains the selectedRange change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
egui disallows IME when a text field loses focus, for example when the user
clicks the page while composing. winit 0.30.13 then clears its own marked
text but never tells the input method, which keeps the composition and
continues it once IME is allowed again. Checked with the real input methods
on macOS 14.7.1: Korean 하, IME off and on, then r k gave 학, then 하 and 가
(하 committed a second time if the app already kept it); Japanese か, IME
off and on, then s a gave 傘.
vendor/winit now calls NSTextInputContext::discardMarkedText when IME is
disallowed with marked text present, before its existing reset, as
rust-windowing/winit#4745 does on winit master. With the patch the same
sequences give 가 and さ. AppKit may call unmarkText from
discardMarkedText while IME is still enabled, so its Preedit("") comes
before Disabled; the RefCell borrow in the check ends before the call.
Everything else in the scratch app's traces (ABC, Japanese, Cangjie,
Korean) is unchanged. A test fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
winit 0.30.13 reports insertText as Ime::Commit only while marked text is
left, and after any commit it swallows the next key. Real paths where that
loses text:
- input methods that end a composition with unmarkText, then insertText
(rust-windowing/winit#3925): the committed text was dropped;
- Apple Korean 2-Set, for the key typed after a syllable: on 한 then 5 it
calls setMarkedText("한"), insertText("한"), setMarkedText(""),
insertText("5"), so the 5 (or ;, or any key that ends the syllable) was
dropped;
- a commit made outside a key press (a candidate clicked with the mouse,
dictation): no key follows to reset the state, so the next key (for
example Backspace) was swallowed.
vendor/winit now commits when a composition was started (pending_commit,
set in setMarkedText, cleared by the commit and when IME is disallowed);
other insertText calls still reach the app as KeyboardInput. A commit
outside keyDown clears the marked text and returns to the ground state.
validAttributesForMarkedText lists the underline and marked-clause
attributes, which some input methods need before they compose.
This is rust-windowing/winit#4650, the 0.30 backport of #4651 (merged on
winit master), plus the part of #4748 that fixes#4651's out-of-key-press
case (#4748's other parts are not included). One correction: upstream
spells the underline attribute "NSUnderlineStyle", but AppKit's
NSUnderlineStyleAttributeName is "NSUnderline" (printed on macOS 14), so
the AppKit value is used.
Checked with the real input methods on macOS 14.7.1 (a scratch winit app fed
key events through AppKit): with the patch 한+5 and 한+; commit the 5 and ;,
unmarkText-then-insertText commits, and Backspace after a commit outside a
key press is delivered; ABC typing, held keys, Japanese (日本, あ), Cangjie
(日) and Korean composition give the same events as before. A test fails if
a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
egui calls Window::set_ime_allowed(true) when a text field gains focus and
set_ime_allowed(false) when it loses it. On X11, winit 0.30.13 handles each
change by destroying the window's X input context and creating a new one, and
leaves focusing the new context to the next FocusIn. PdfCraft's window keeps
the focus while the user moves between Search, form fields and the canvas, so
no FocusIn comes and the new context stays unfocused: the input method
(IBus, fcitx5) doesn't follow the field until the window loses and regains
focus.
vendor/winit's Ime::set_ime_allowed now returns create_context's result, and
the event processor, after draining the queued IME requests, focuses the last
context replaced for the active window, once, as
rust-windowing/winit#4727 does on winit master. No public API changes. A
test fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
eframe creates PdfCraft's window hidden, restores the saved state
(app.ron, persist_window) and shows the window after the first frame is
drawn. For a window closed maximized, winit 0.30.13 applies the maximized
state with ShowWindow(SW_MAXIMIZE), which shows a hidden window: PdfCraft
appeared unpainted (white), was hidden again, then shown with SW_SHOW and
SW_MAXIMIZE once more. rust-windowing/winit#4586 also shows the window's
focus state going wrong on this path.
vendor/winit now skips the maximize while the window is hidden and shows a
hidden window that is maximized with one SW_MAXIMIZE, as
rust-windowing/winit#4587 (approved upstream) does on winit master. That is
the later, focus-safe form of #4575; #4575's other change (re-sending
SW_MINIMIZE on every flag change while minimized) is not taken. Transitions
between two visible states make the same ShowWindow calls as before. A test
fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
When a composition has no target clause, winit 0.30.13 reads the cursor with
ImmGetCompositionStringW(GCS_CURSORPOS) without checking that the
WM_IME_COMPOSITION lParam has GCS_CURSORPOS set. The Microsoft Korean IME
never sets it (lParam 0x6018), so the read gives 0 and winit reports
Ime::Preedit("한", Some((0, 0))). egui 0.36.2 recognises that exact report on
Windows and drops the range, so PdfCraft draws no caret while Korean is being
composed.
vendor/winit now reads GCS_CURSORPOS only when the flag says the IME set it
and otherwise puts the cursor at the end of the composition, as
rust-windowing/winit#4746 does on winit master (Firefox and SDL do the same).
IMEs that report a cursor or a target clause (Japanese, Chinese) are
unchanged. A test fails if a re-vendored winit loses the patch.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>