Commit Graph
4058 Commits
Author SHA1 Message Date
alehelandClaude Opus 5.5 4b3033c869 fix(ui): review fixes for the page router: Escape on text drags, the open frame's sides, a stale owner
From a review of this branch:
- Escape mid-drag with a text markup tool (Highlight, Underline, Strikeout, Squiggly, Replace
  Text) or Redact over text left the half-made selection on the page: the cancel took the Escape
  that would have cleared it. It is dropped now. The marks made from a selection at its end
  (after the page tools, which the release blocking did not reach) also wait for a cancelled
  press's button; egui ends its own drag on Escape without a drag stop, so they were not made
  in practice, but nothing but egui stopped them.
- The open paragraph's frame took the press above and below the text only: its sides still gave
  edge grips (a closed paragraph's rewrap handles) on the Editor layer, over the frame. Edges now
  belong to closed paragraphs only.
- A press in the frame right after a release kept the last press's owner until a page claimed
  it, so a drag from the margin beside a page could move the comment just clicked. A new press
  now drops the last owner before the pages give it theirs.
- A release and a new press in one frame no longer leave the new press cancelled.
- Added content is picked up with the tools that act on it (Select, Add text, Edit text), not with
  every tool while Edit a PDF is open.
- The layer docs in page_cursor.rs say what lies on each layer now.

New tests an_open_paragraphs_frame_takes_the_box_beside_its_text and
a_drag_from_beside_the_page_right_after_a_click_leaves_the_comment_alone (the release and the
next press in one frame) fail on the previous commit. escape_cancels_a_highlight_being_dragged_over_text
(with a control: the drag highlights without Escape) passes there too, for the reason above, and
stays as a guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:01:17 +01:00
alehelandClaude Opus 5.5 bcc71601d4 docs: page modules decide a press by one hit test and act on the router's owner
A "Page presses" non-negotiable in CLAUDE.md, beside "Page input" and "Page cursor": a page
module decides what of its own is under a point in one hit test (a Hit from
canvas/page_router.rs) that gives its cursor, its tip and its press, and acts on a press only
when the router says it is its own, never by testing the press point again or by asking
another module; Escape cancels any press the pages hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:53:44 +01:00
alehelandClaude Opus 5.5 a4c5fbed64 fix(ui): Edit text's paragraphs, pictures, objects and added content decide a press by one hit test
Edit text's paragraph edges were tested twice: by edit_text_ui for the cursor, on the page's
paragraphs, and by object_ui for the press, on its own object inventory (grabs_edge). Where the
two inventories disagree, the resize arrow shows and a press starts an invisible area selection
instead (#2276, the review's step-3 probe). The open paragraph's frame showed the Move cursor
4 px out and took the press 6 px out.

Each of these parts now has one hit test, in the page router's table, and acts only on the
press it was captured on:
- edit_text_ui::hit: a paragraph's edges (Handle), its body (Text; Editor for the open one).
- edit_text_ui::picture_hit and picture_handle_hit: a picture's body on the Text layer, asked
  before the paragraphs, so text on top of artwork takes the press; the selected picture's
  corners and rotate knob on Handle, asked after them, so they win over text.
- object_ui::hit: the objects selected together (Placed) and the open paragraph's frame
  (Handle), with one reach (FRAME_REACH, 6 px) for the cursor and the press. An area selection
  starts only where nobody's hit claims the press.
- content_ui::hit: added text and pictures (Placed; the selected one's corners on Handle), over
  form fields; Add text starts a new box only where nobody's hit claims the press.
image_owns_pointer and content_ui::select_grabs go, with the edge test object_ui kept a copy of
(grabs_edge). The page's hits move into canvas::PageHits.

Un-ignores a_paragraph_edge_resizes_wherever_it_shows_the_resize_cursor: it passes on main
already (the inventories agree for its paragraph since the probe was written), so it stays as a
guard rather than as proof. New test an_open_paragraphs_frame_shows_move_wherever_a_press_takes_the_box
fails on the previous commit (the cursor is Default there).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:53:23 +01:00
alehelandClaude Opus 5.5 63fa5010d8 fix(ui): form fields and Prepare decide a press by one hit test
Forms: forms_ui::hit is the fields' only hit test (cursor and press). The press opens a text
field (#2213) and its click acts on the field the press was on, from the page router, instead
of the field under the release point: a press on one check box released over the one beside it
ticked the second. Now it ticks neither, as a button pressed and released off itself does
nothing. Forms no longer wait on comments::select_grabs: a comment that moves (a note, a shape,
a signature, a Fill & Sign mark) hits on the Placed layer, over form fields, so it still takes a
press over a field (a signature can be dragged off the field it covers), and text markup stays
on the Comment layer, under them, so a click on a box under a highlight ticks the box.
select_grabs goes.

Prepare Form: prepare::hit (the selected field's corner handles, then a field, on Placed so a
field being moved stays over the comments as before) gives the cursor, and the drag, click and
double click act on what the press was on; a press held by something else is not Prepare's.

New tests: a_press_on_one_check_box_released_over_another_ticks_neither (fails without this
commit: the second box is ticked) and a_check_box_under_a_highlight_takes_the_click (fails with
markup on the Placed layer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:29:57 +01:00
alehelandClaude Opus 5.5 7d72f21da3 fix(ui): the area and placement tools act only on their own presses, of the primary button
The Sign area, marquee zoom and snapshot, crop and redaction boxes, and the stamps, act where
nothing drawn over the page holds the press (the page router has no owner for it), rather than
wherever the press lands on the page. Fill & Sign already asks the router (fill_grabs).

Their drags start on the primary button and end with its release, however egui reported it:
its drag stop, or the button found up. They started on any button, so a right-drag drew a
signature rectangle and opened the signing dialog (and drew a crop or a marquee), and the end
waited for a pointer position, so a drag whose release came without one never ended.

New test a_right_drag_draws_no_signature_rectangle, seen failing with the drag started on any
button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:21:35 +01:00
alehelandClaude Opus 5.5 35d28a60cc fix(ui): comments, links and text selection decide a press by one hit test
The page router (canvas/page_router.rs): a page module answers "what of mine is under this
point?" in one hit test, which gives a Hit (an owner, a layer, a cursor, a tip). On the frame of
a press, the hits at the press point decide who it belongs to, by PR 2's table (the highest
layer the tool reaches), and the router holds that capture until the button is up and its
release has been seen; automation's cancel and Escape drop it. The pointer's hits claim the
cursor and the tip, after the tools that are not converted yet, as before.

Comments: comments::hit is the module's only hit test, with each tool's filter (Select: point
handles, resize handles, then the comment; Fill & Sign: the same over its marks only; shape
tools: the selected comment's handles only). The Select tool's drag, click and double click act
on the comment and part the press was captured on, instead of testing the press origin and the
release point again; fill_grabs and select_grabs ask the router while the button is down. The
topmost comment is the one drawn last (/Annots order), not the last in inspection's top-edge
order: over stacked comments the tip named the top one while a click selected the one
underneath. Text markup declines the drag, so a click on a highlight selects it and a drag
across it still selects the text.

Links: the link hover is a hit (hand and target), and a click follows the link its press was
on. Text selection starts unless the press is held by something that keeps its drag (a link, a
comment that moves); it tested for a link under the pointer at the moment egui called the press
a drag, so a drag from text onto a link selected nothing.

Un-ignores a_click_on_stacked_comments_selects_the_one_drawn_on_top; new test
a_drag_from_text_onto_a_link_selects_the_text. Both fail with their fix reverted (inspection
order for the topmost comment; the link test at the pointer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:15:07 +01:00
alehelandClaude Opus 5.5 9f483490c7 fix(ui): Escape cancels any drag the pages hold, and its release does nothing
Escape cancelled a comment gesture, Prepare's marquee, measuring and the camera tool, each in its
own module; with the Sign area, crop, marquee zoom, a redaction box, the Link tool's rectangle,
a paragraph or picture drag, added content, an object move or a text selection it did nothing,
and the release then drew, placed or opened what the drag had begun (the step-4 probe: Escape
mid-rectangle still opened the signing dialog).

document_area now handles Escape once, before the pages: while the pages hold a press, it drops
every pointer gesture (DocView::cancel_pointer_gestures, as automation's cancel does) and marks
the press cancelled. Until the button is up the page tools take no input from it, so its
release neither clicks (a stamp, a Fill & Sign mark) nor ends a drag. Escape without a press
keeps its meanings (close the editor, clear the selection, back to the Select tool).

Un-ignores escape_cancels_a_signature_rectangle. Two new tests: Escape mid-press keeps the
release from placing a stamp, and Escape mid-drag abandons a crop rectangle. All three fail
with the cancel taken out; the stamp test also fails with the cancel kept and the release let
through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 12:01:18 +01:00
RiceDee 5a86f35210 fix(edit, ui): alignments follow the right rows; Format by keyboard keeps the box
Four holes CodeRabbit found on 648dd428:

- alignment_frame framed a one-row paragraph when any alignment entry
  was set, including one past the draft's rows, and then moved rows
  with no alignment of their own to the column's edge. It now takes the
  draft and frames only when every draft row is aligned, by its own
  alignment or the paragraph's.
- remap could not tell which of two same paragraphs an edit took
  ("A\nA" to "A"), and kept the deleted one's alignment. remap_at takes
  the caret where the edit ended, and the typing path (follow_typing,
  both editors) passes it from the box's state. The caret only chooses
  between readings that fit both texts.
- Both editors judged a focus loss by where a click landed, so focusing
  a Format text control by keyboard or a screen reader (no pointer)
  closed the box before the alignment was chosen. They now also judge
  by where the keyboard went (focus_pos): focus outside the document
  view, or on the added-text box's own buttons, keeps the box open as a
  click there does.

Each change has a test seen failing before it.
2026-10-29 21:30:21 +11:00
ihsan mokhlisse 55a933800b M7: keep a typed space run through a saved width change (#2517)
The wrap's space count has to survive an incremental save and a full rewrite, and a width-only text_edit has to reflow with that count. A recorded editor session narrows the box and widens it again.
2026-10-29 10:42:20 +01:00
ihsan mokhlisse bb7186d87c M7: restore the exact gap a wrap consumed, not a guessed space (#2517)
wrap() already dropped the whole gap at a wrap point (#4199); rejoining it always guessed a single space back, so two or more typed spaces silently became one, and a Chinese/Japanese word split (which has no space of its own) could gain a spurious one. wrap() now records how many separator characters it consumed at each wrap (0 for a CJK split); the /PCSoft marker carries it as /N (a file saved before this, or by another writer, reads as 1); reflow_text and the mixed-style row editor insert exactly that many spaces back instead of assuming one.
2026-10-29 10:13:01 +01:00
ihsan mokhlisse 8a78fb21cf M3: translate the menu bar's own overflow sentinel to a glyph every font has
craft_menu writes "⋯" (U+22EF) itself for the menu of titles that don't fit, through the app's own label callback. No font this app ships or falls back to has that glyph, and egui 0.37's MissingGlyphPolicy::Panic crashes on it as soon as the bar is narrow enough to need it: every editing test that narrows the window while a paragraph is open hit this. The ordinary ellipsis the rest of the app already uses everywhere else is available and reads the same.
2026-10-29 10:13:01 +01:00
ihsan mokhlisse dbeb9fa963 M7: move a paragraph instead of rewriting it to trigger a stale-number regroup
wrap()'s own test for mixed exotic whitespace fed through it directly, bypassing the caller's CR/CRLF normalization, needed its expected value updated for #4199's keep-every-space design, landed on main after this branch's base.
2026-10-29 10:11:19 +01:00
ihsan mokhlisse b282ec26be M7: check a shared text object through the tools (#2517)
Editing either end of one text object, then saving and editing again, has to leave the other paragraph in place. The library tests did not drive text_edit and doc_save.
2026-10-29 10:11:19 +01:00
ihsan mokhlisse 59513aa887 M7: keep a preceding paragraph out of a shared text object's marker (#2517)
A partial rewrite of the last paragraph opened the marker at the shared BT, so the paragraph before it was inside the region.
2026-10-29 10:11:19 +01:00
ihsan mokhlisse 62af4f87ca M7: compile the paragraph fixture on current main (#2517)
The paragraph-key helper has to carry reflow text, which a width change uses. ClosePath on an empty subpath is a match guard so the edit crate still passes clippy.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 21b911ae42 M7: close a shared text object before the next paragraph (#2517)
A partial rewrite put the paragraph marker around the shared ET, so the neighbour in that text object joined the paragraph and the next edit deleted it.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse cb832b0206 M7: close a partial rewrite before the next marked region (#2517)
An unmarked paragraph was closing its marker at the following line's BT, which is after that line's BDC. The close now follows this paragraph's last ET.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 2bc2933988 M7: rejoin a wrapped line when the box is widened (#2517)
A newline that was typed stays. A wrap is marked so a later width change can reflow it, and a short unmarked line stays broken.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse a84ae042d1 M7: close a partial rewrite's marker after the paragraph (#2517)
A row left in the source stays in the last paragraph. An extra marker is not placed across two marked-content regions.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 4ebb575a88 M7: keep a repeated step without TL, and a partial rewrite's neighbour (#2517)
Three or more rows at one repeated step stay one paragraph when the file has no TL, which is how LibreOffice writes each line. A rewrite that leaves a source row still marks the whole paragraph, so the neighbour survives a second edit. Dragging the box width keeps the line breaks.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 53e6d832af M7: keep an explicit leading, and a grown paragraph's neighbour (#2517)
The join limit follows the text object's TL when it has one, checked against the measured baseline gap. A rewrite of every row is marked so a new row one leading above the next paragraph does not absorb it.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse e228e0edfc M7: keep a blank line as a paragraph break (#2517)
A gap of 2.4 times the font size was still under the 2.5 times join limit, so two paragraphs became one and editing the first deleted the second. Once a paragraph has two rows, a wider gap than its own leading stays the next paragraph.
2026-10-29 10:09:18 +01:00
RiceDee 648dd42810 fix(edit): a column is measured by its whole rows; an isolated row keeps its width
Two holes in alignment_frame:

- A column's right edge came from the runs that start at its left
  edge, so a row of several styles ended at its first run, and the
  column could fail to hold a title wider than that run. It now takes
  the right edge of each complete row (row_rect).
- A row with no column to align in was framed by its own box, which
  the rewrite also used as the width its text wraps at: changing an
  isolated row's text together with an alignment wrapped it at its old
  width, word by word. When the text changes and no column holds the
  row, alignment_frame now returns None, so the row keeps the width it
  has without the alignment. With the text unchanged the own box is
  still the frame, so aligning twice is still a no-op.
2026-10-29 19:12:11 +11:00
RiceDee b9940d7d81 fix(edit): a lone row aligns in its own column, not a closer page-wide heading
alignment_frame measured a one-row paragraph's alignment in the frame
of the nearest overlapping block. A full-width heading closer to the
title than the title's own body column gave it a page-wide frame, so
centring or right-aligning the title moved it out of its column. It now
considers every overlapping block whose column holds the row and takes
the narrowest, the nearest on a tie, so a page-wide heading gives the
title no frame while a narrower column that holds it does. When no
column holds the row it is still measured in its own box.

The comment about page-wide headers named an exclusion the filter
never made; the doc comment now says what is chosen and why.
2026-10-29 18:38:05 +11:00
Claude 5ada1249e9 Merge remote-tracking branch 'upstream/main' into fix/page-render-warnings
# Conflicts:
#	crates/automation/tests/it/main.rs
2026-10-29 05:01:33 +00:00
Oliver HagmannandClaude Opus 5.5 f0cd96e90b Review: isolate_annotations says that an index off the page's list is an error
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:54:15 +01:00
Cursor Agent 2a7e5bda26 Merge remote-tracking branch 'upstream/main' into prep/2960-merge-main
Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>

# Conflicts:
#	crates/render/src/text/tests.rs
2026-10-29 04:52:43 +00:00
Oliver HagmannandClaude Opus 5.5 22c08fb65b A test that Fill & Sign's typed text is drawn where it goes while it is dragged (#4619)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:50:58 +01:00
Oliver HagmannandClaude Opus 5.5 b68362bdc2 Rebase: isolate_annotations is exported from main's lookup module
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:50:58 +01:00
Oliver HagmannandClaude Opus 5.5 b4f13fd8ba Review: a moved comment's layer that can't be made is asked for once per drag
When Document::comment_layer gave nothing (a comment with nothing to draw, or
a document that can't be read), prepare_move asked again on every pointer
frame of the drag, and each time it copies and writes out the whole
document. The failure is now remembered for that drag (edit generation, page,
index); the next drag asks again.

A render that fails no longer drops the layer either: only its pixels go, so
the frame alone follows the pointer as before, the failed scale isn't waited
for, and another scale renders the same layer again without making it anew.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:50:58 +01:00
Oliver HagmannandClaude Opus 5.5 a793400ff5 Rebase: the move preview follows PagePointer's position
main now gives page modules one pointer per frame (#2685); the comment drawn where it goes
follows `cx.ptr.pos`, as its frame does, instead of egui's hover position.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:50:58 +01:00
Oliver HagmannandClaude Opus 5.5 158318c09b Review: the move preview cuts a comment kept upright on a turned page from the box it is drawn in
A NoRotate comment on a quarter-turned page isn't drawn in its /Rect; its layer's tile and
its offset now come from the box it is shown in, as the frame it follows does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:50:58 +01:00
Oliver HagmannandClaude Opus 5.5 67b5b2dcf6 A moved comment is drawn where it goes while it is dragged
Moving a rectangle, an oval or any other comment showed only its blue
frame going, the shape staying where it was until release. The comment is
now rendered alone once as the drag starts (Document::comment_layer: a
read-only copy with only that comment, no page content) and laid over the
page where it goes; the page beneath keeps showing it where it was until
the move is applied, as a CAD program's move does. Rendering the page
without it would cost a whole page's render at every drag on a heavy plan.
Stamps and Fill & Sign marks keep their own layers. annot: isolate_annotations
for several comments of a page, so a group can be drawn together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:50:58 +01:00
Cursor Agent d58d121170 Merge remote-tracking branch 'upstream/main' into prep/4596-merge-main
Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>
2026-10-29 04:49:23 +00:00
Adrian 3850c9f52e M3: the titles follow a document opened while the window or tab is hidden (#3640)
eframe runs only App::logic while a window or browser tab is hidden, so a
?file= download that finished then opened the document without the titles
changing. The titles are now set at the end of logic as well as in ui.
2026-10-29 00:37:47 -04:00
aiongg 3109bb2359 Merge pull request #5236 from FredApps/integration/kenm76-2026-10-29
Integrate 13 KenM76 fixes for forms, text and rendering
2026-10-28 22:32:00 -04:00
FredApps d99538145c Clarify integrated vendor regression coverage 2026-10-29 02:43:29 +01:00
Adrian 08f3991ab9 M5: Alt+Left / Alt+Right go back through the view history with a form field selected, judged by each press's modifiers (#3632)
Prepare's arrow nudge read the frame's Alt, so an Alt+arrow whose Alt was
let go in the same frame nudged the field instead. Each press's own
modifiers now decide, as for objects, pictures and comments.
2026-10-28 21:41:33 -04:00
FredApps 97c062a14b Complete additive integration fields and test boundaries 2026-10-29 02:40:41 +01:00
FredApps f280f3a282 Integrate KenM76 PR #5228
# Conflicts:
#	vendor/README.md
#	vendor/hayro-interpret/src/device.rs
2026-10-29 02:39:15 +01:00
FredApps 7fc2033421 Integrate KenM76 PR #5199
# Conflicts:
#	crates/render/tests/it/main.rs
#	vendor/README.md
2026-10-29 02:38:56 +01:00
FredApps 1b0e3b6110 Integrate KenM76 PR #5178
# Conflicts:
#	vendor/README.md
2026-10-29 02:38:44 +01:00
FredApps e34cb1d1cc Integrate KenM76 PR #4991
# Conflicts:
#	crates/automation/tests/it/automation.rs
#	crates/forms/src/author/edit.rs
#	crates/forms/src/tests.rs
#	crates/forms/src/values.rs
#	parity/acrobat-features.toml
2026-10-29 02:38:25 +01:00
FredApps 07edd05aea Integrate KenM76 PR #4987
# Conflicts:
#	crates/forms/src/author/edit.rs
#	crates/forms/src/tests.rs
2026-10-29 02:38:03 +01:00
FredApps 9e30913d6e Integrate KenM76 PR #4977 2026-10-29 02:36:20 +01:00
FredApps d1fcc12e07 Integrate KenM76 PR #4943 2026-10-29 02:36:20 +01:00
FredApps ebd7578cc3 Integrate KenM76 PR #4913
# Conflicts:
#	crates/render/tests/it/main.rs
2026-10-29 02:36:20 +01:00
FredApps d9ddd8a8b0 Integrate KenM76 PR #4906 2026-10-29 02:35:58 +01:00
FredApps b0555cb918 Integrate KenM76 PR #4898
# Conflicts:
#	vendor/README.md
2026-10-29 02:35:58 +01:00
FredApps a66686cb0d Integrate KenM76 PR #4862 2026-10-29 02:34:57 +01:00