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.
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.
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.
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.
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.
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.
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.
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>
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>
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>
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>
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.
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.
The dirty mark now follows has_unsaved_work, the same test the close
prompt uses, so text typed into a field or text box shows the mark before
it is applied. The web page's title, which an embedding host reads to tell
which file opened, now always names the file, even when the document asks
to show its own title; the native window title is unchanged.
From the review of #5228. The finished CMYK group was drawn through
whatever paint transform a preceding shading pattern left on the
context, so its cyan half showed magenta. Hayro patch (27) now resets
the paint transform before drawing the group's pixels.
Two regression tests, both reported in that review:
- a pattern painted before the group (fails without the reset);
- the Color blend mode over an empty CMYK group, swept over all 256 K
levels (failed on nine levels before #5199's vello_cpu fix was merged
in; passes with it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
The title now reads "<name> • — PdfCraft" while the active document
has unsaved changes, as the other Craft apps write it, and goes back to
"<name> — PdfCraft" once saved.
eframe's web runner ignores ViewportCommand::Title, so the web build
never named the open document. It now also sets the page's
document.title, which an embedding host reads to confirm which document
opened and whether it has unsaved changes.
The comment nudge matched arrows with egui's key patterns, which let an
extra Alt through, so with a comment selected Alt+Left moved it a pixel
instead of going back. It now takes only arrows pressed alone or with
Cmd/Ctrl or Shift, judged by each key press's own modifiers, as the
picture and object nudge does.
Previous view and Next view had only Cmd+[ / Cmd+] (Ctrl elsewhere), and
Alt+Left turned the page, since the arrow keys ignore Alt. The document
view now takes Alt+Left / Alt+Right (Option on macOS) for them, as Acrobat
and browsers do, before the arrows turn the page. Following a link
already records where it was followed from.
With a picture or objects selected in Edit text & images, the arrow-key
nudge ran first and also matched a press with Alt, so Alt+Left moved the
selection. It now takes only arrows pressed alone or with Shift, judged
by each key press's own modifiers rather than the frame's.
Help > Keyboard shortcuts lists the pair under the existing Previous view
/ Next view labels.
Resolves test-file conflicts where main and this branch both appended tests:
main's file plus this branch's appended tests, unchanged.
fmt, clippy -p pdfcraft-forms -p pdfcraft-automation, and both crates' tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
Resolves test-file conflicts where main and this branch both appended tests:
main's file plus this branch's appended tests, unchanged.
fmt, clippy -p pdfcraft-forms -p pdfcraft-automation, and both crates' tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
Resolves test-file conflicts where main and this branch both appended tests:
main's file plus this branch's appended tests, unchanged.
fmt, clippy -p pdfcraft-forms -p pdfcraft-automation, and both crates' tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
main's knockout patch (hayro (22)) draws each element of a knockout group
once in erase mode, to clear what the group drew under the element's shape.
An isolated DeviceCMYK group nested in such an element still took the CMYK
path in that pass, and drew its finished pixels with Normal blending over
what should have been cleared. draw_isolated_cmyk_group now declines while
erasing, so the group's contents are interpreted in erase mode as before.
The new test fails without the fix (the centre shows red through the
magenta: [240, 63, 168] against [245, 127, 197] for the DeviceRGB control).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
main has taken hayro-interpret patches up to (66) and hayro up to (25)
since #3777 was opened, and open PRs claim hayro-interpret (67)-(69) and
hayro (26). The isolated DeviceCMYK group patches become hayro-interpret
(70) and hayro (27). vendor/README.md names the tests by their new path,
raster::tests::isolated_cmyk_groups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9