Upstream now counts the parsed tree and keys in held_bytes and tracks the cache total itself,
which replaces this branch's copy of the cache accounting and its owned_bytes. write_full's
retention budget uses held_bytes; the separate node budget is dropped since held_bytes counts nodes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
pa-Arab-PK is Punjabi in Arabic script, which Word keeps as right-to-left
text, but word_lang looked only at the language and wrote w:val. A script
subtag (Arab, Hebr, Syrc, Thaa; Hans, Hant, Hani, Jpan, Kore, Hang, Hira,
Kana; Latn, Cyrl, Grek) now decides over the language. It is read only in
its own place, after the language and any extended languages, so private
use such as en-x-arab names no script.
A singleton (one letter or digit) starts an extension, or with x private
use, and BCP 47 needs a subtag after it. en-x and en-a were written as the
document's language. They and a singleton straight after another are now
left out; one-character subtags inside private use stay allowed.
BCP 47 sets no maximum length; 35 characters is only the least a protocol
must hold. A well formed tag such as en-US-u-ca-gregory-nu-latn-co-phonebk
was left out of both exports. The bound is now 255, kept only so a PDF's
/Lang can't put an unbounded string in the file.
Word export named no language, so Word checked the spelling of every
export as English and underlined a whole document in any other
language. The PDF already says its language in the catalog's /Lang.
When that is a well formed language tag, Word gets it as the default
language of the text (w:docDefaults), in the attribute Word keeps for
its script: w:eastAsia for Chinese, Japanese and Korean, w:bidi for
Arabic, Hebrew, Persian and Urdu, w:val for the rest. Both HTML
exports, with and without CSS, get <html lang>. Without /Lang, or with a
malformed one, the files are exactly as before.
docx_with_language, html_with_language and html_simple_with_language
take the language; docx, html and html_simple stay as they are and call
them with none. The engine reads /Lang through the existing initial
view.
The new export and engine tests fail without the change.
Review fixes for Copy with formatting.
- ui-egui: the HTML copy goes through one arboard Clipboard, created on
first use and kept in the app (os_clipboard::with_kept, under the same
process-wide lock), instead of a fresh one per copy. On X11 the copied
data is served only while its Clipboard lives, so a fresh one dropped
at once could leave nothing to paste without a clipboard manager. A
failed create or set falls back to egui's plain-text copy.
- ui-egui: html_clipboard_override stands in for the system clipboard in
tests. With the system clipboard on, Copy sets HTML and plain text in
one set and emits no CopyText; when the set is refused it emits
CopyText with the plain text. The HTML is built only when a clipboard
will take it (system clipboard on, not the web build). The
last_copied_html field is gone.
- export: clipboard HTML text is escaped in one pass and also drops C1
controls, a bare carriage return and the bidirectional overrides and
isolates. Font sizes are written within 1 to 1000 pt.
- engine: the glyph-to-block step budget is a parameter, tested; the copy
says styles_incomplete only when matching actually ran out of steps.
- automation: text_extract format "html" has one 32 MiB HTML budget
across all pages; a page past it gets none, and the result carries
html_truncated. The tool description says so.
- parity: the new tests are evidence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
main's 3D-embed change (#5808) added crates/annot/src/threed.rs with its own
pub MAX_MODEL_BYTES (same 256 MiB value), so both were re-exported from the
crate root and pdfcraft-annot no longer compiled after merging main. model3d
now uses threed's constant.
Found in review: the PR lists these and nothing pinned them.
- With Prepare open, a drag from a highlight draws the selection rectangle.
- A double-click on a paragraph selected with others opens it.
- In crop mode, a right-click on the next page goes to what lies there,
though a clipped artwork's whole frame reaches over it.
- A crop grip takes a press within its reach, 9 px off.
- A comment's menu opens over a picture and over a paragraph.
- A right-click with the button held opens the selected comment's menu.
Each seen failing against a planted violation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review. No change in behaviour.
- The guard's `PAGE_HIT_CALLS` refused nothing once `main`'s press-handler
check went; it only failed when it was stale, so it goes.
- The guard's notes said `canvas/image_pick.rs` uses none of the marks (it
takes `PagePointer` now), that page modules read only
`PagePointer::drag_started` (some ask for the primary button themselves),
and left out the point tests that tell a tool a press began on its page.
- `content_ui`'s and the comments' notes said added text is its own with
every tool, and that added content decides for itself.
- `object_ui`'s `!double_clicked()` can't fail (the page view re-routes
that press), and Fill & Sign's preview recomputed `hovered`.
- `Capture` is private, and `top` is `canvas`'s.
- Test notes gave as history bugs that only this branch ever had.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review. `fix(ui): a right-button drag on the pages starts nothing
a left one does` covered drag starts. A right-button drag still moved the
end of a selection already made, as `update_dragged_selection` tested a
drag by any button (`main` replaced the selection instead). And its end
finished a selection whichever button it was, so with a markup tool, Replace
Text or Redact a selection left from another tool became a comment. Both
read the primary button now.
Tests, each seen failing with its fix reverted (Redact's on its own):
`a_right_button_drag_leaves_a_text_selection_alone`,
`a_right_button_drag_makes_no_mark_of_a_text_selection`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review, each against `main`:
- With Add text holding text it can't draw, a drag from a paragraph moved
the paragraph; only its click was refused. Neither acts now (#125's
draft stays where it is being typed).
- A right-click reached a Prepare field whatever the field tool and while
a table is set up (a review ruling), but also on a document that can't
be changed, where Prepare stays open after a tab switch: it selected the
field and opened a menu of entries the engine would refuse. It selects
nothing there.
Tests, each seen failing with its fix reverted:
`add_text_moves_no_paragraph_while_it_holds_a_draft_it_cant_draw`,
`with_prepare_on_a_locked_document_a_right_click_on_a_field_selects_nothing`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review. On `main` the selected picture's grips and rotate knob
were drag-only widgets of their own, so their drag began at the press.
Their widgets went, and the page's response is click-and-drag, which calls
it a drag only past egui's click distance: a move of a few pixels resized
nothing. They take their drag from the press again. A click that doesn't
move edits nothing, as before; a drag of the picture itself still waits,
since a click selects.
Tests: `a_short_drag_from_a_picture_grip_resizes_it` (seen failing with
the fix reverted), `a_click_on_a_picture_grip_changes_nothing` (a pin,
seen failing with the release planted to edit always).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review. The selected picture's frame is its own on `main`: a
drag from it moves it, though another picture lies over it there. The
routed press was the top picture's, and the drag only started for the
selected one's, so nothing moved. `PicturePart::OverSelected` says so: a
click there selects the picture on top, as before, and a drag moves the
selected one.
Test, seen failing with the part planted away:
`a_drag_from_the_selected_picture_under_another_moves_the_selected_one`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review. Delete, Backspace and crop's Enter and Escape moved to
the app's keys, which run before anything is drawn and don't look at what
is on screen. So with Edit text and a picture selected:
- Delete in the page grid removed the hidden picture and kept the pages;
- Delete with the pointer on the Pages panel did the same;
- with the picture's page scrolled away or in single-page view on another
page, Delete removed a picture the reader couldn't see, and Enter
applied its crop.
`main` read these keys in the picture's own page pass, which none of
those cases reach. `picture_keys` runs there again, from
`page_tool_input`, under Edit text's gate (so no dialog gets them), and
before the group acts. Delete still works wherever on the pages the
pointer is.
Tests, each seen failing with the fix reverted:
`delete_in_the_page_grid_deletes_the_page_and_not_the_hidden_picture`,
`a_selected_pictures_keys_wait_for_its_page_to_be_on_screen`,
`delete_with_the_pointer_on_the_pages_panel_deletes_a_page`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review. With Edit text, a right-click on added text or on an
open paragraph's frame went to that, which has no menu: the picture under
it was unselected and the page's menu opened. `main` opens the picture's.
Those give way to a picture under them, as paragraphs do, and to nothing
else: a comment under added text stays unselected, as before.
A comment in `draw_pages` said a selected Prepare field keeps its menu
off the pages. Since the page menu follows the right-click, it stays
selected and the menu is the page's.
Tests, each seen failing with the fix reverted:
`a_right_click_on_added_text_over_a_picture_selects_the_picture`,
`a_right_click_on_an_open_paragraphs_frame_over_a_picture_selects_the_picture`.
`a_right_click_with_edit_text_on_added_text_over_a_comment_selects_no_comment`
is a pin, seen failing with added text dropped from the hits outright.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review:
- Rule (f) missed `pub(in …)` functions, the visibility the guard's commit brought
in (`declares` strips any `pub(…)`), and flagged names that merely
end in its words (`dropdowns`): it now matches whole words of the name.
- Inside `canvas` the narrowed visibility doesn't hold, and a router
taken whole (`let r = &view.router;`) got past the checks by method
name; such a use now counts as the router's (`takes_the_router`).
- Docs: the router's module doc and `PagePress::Free` say a declined
drag is nobody's once it starts; Prepare's note says a highlight's drag
is Prepare's; Add text's paragraph note names `ParagraphCx::adding`;
a test's doc claimed crop-mode keys it doesn't press.
- One source each for the objects' menu test (`object_ui::menu_open`)
and Edit text's gesture cancel (`cancel_pointer_gestures` calls
`cancel_edit_text_gestures`); the page-menu test asserts an entry
only the page's menu has.
Each guard addition seen failing against a violation planted in
`#[cfg(any())]` code in `canvas/area.rs`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review, each against `main`'s behaviour:
- A double-click on one of the objects selected together only left the
group: the second press was the group's. It is the object's under it,
so added text opens for typing and a picture is selected, as before;
a paragraph opens too (the user's ruling).
- On a page whose objects can't be selected together, a click on the
empty page left the picture selected: no area selection starts there
to drop it, and the picture's own test that did is gone. A click nobody
holds on the page unselects it.
- Crop mode's grips stopped at the page's edge, so those of a picture
filling its page could be taken from their inner half only. They reach
past it, cursor, press and right-click: no page took a right-click
there, which left crop mode and opened the page menu (#5900 keeps crop
mode there). The box and the picture stop at the page's edge, as a
clipped artwork's whole frame can reach over the next pages.
- A press past a page's edge was the page's whenever it had any hit
there: the objects selected together, or the open paragraph's frame,
took presses on the next page and did nothing with them. Past the edge
only the selected picture's knob and grips (crop mode's too) take a
press, and the objects' hit stays on its page.
- A drag from a hole in the selected picture (a clip with a hole) fell
through, started an area selection and unselected it. The selected
picture's frame is its own, as before.
- Image pick: a drag or double-click from a link let the picked picture
go before ruling 5, and does again; the link's click still picks none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`refactor(ui): crop mode is a hit of its picture` moved the picture's Delete to the app's key handling, which
runs before the page view, so its edit applied before added content read
the keys. Added content's selection mirrors the picture's when the
picture is added content (`sync_image_selection`), and once the picture
was gone that index named the next added item on the page: added
content's own Delete, seeing the same key press, deleted it too.
The picture's Delete consumes the key, and drops the mirrored selection
with the picture's.
Found in review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found in review of the right-clicks 4b routes:
- With Edit text, a right-click on a picture under a link went to the
link, which Edit text doesn't follow: the page menu opened and the
picture was unselected. A right-click counts links only for the tools
that reach them (Select; the Link tool).
- Edit text's paragraphs have no menu, and give way to the picture
under them (the user's ruling): text over a picture, an OCR'd scan's
among it, no longer hides the picture's menu.
- With a Prepare field tool, or while a table is set up, Prepare's hit
leaves fields out, so a right-click on the selected field opened the
page menu. A right-click reaches fields whatever the field tool.
- In crop mode, a right-click off the crop went to crop mode's whole-page
hit and unselected every picture. Only one on the picture being
cropped, its box or its grips is the crop's (#5900's area), which
opens no menu; elsewhere it goes to what lies there, as without crop
mode: another picture is selected for its menu.
- Under a held press, the menu was the page's on anything but a link. It
is the routed owner's when that owner is the one selected (the held
press leaves the selections alone), the page's otherwise.
`DocView.menu_for` is `pub(in crate::canvas)`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`fix(ui): a comment's move, resize or reshape ends` ended a comment's move, resize or reshape once the button was
up, and applied it where the pointer then was: when the pages missed
the release (the page scrolled out of range, a tab switch, a tool
switched mid-drag), the comment jumped to wherever the pointer next
showed up over its page, and while the pointer stayed off the pages the
gesture waited for it.
Only a drag that stops on the page applies its gesture now; one that
ends with the button up and no drag stopped there ends with nothing
applied, whether the pointer is on the page or not. The test hovers away
from the move's start, where the jump shows.
Found in review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pages' presses are the primary button's (`PagePointer.edges`, the
router's `began`), so a right-button drag was a press nobody held, and
every tool that started on egui's `drag_started()`, which counts every
button, started on it: the Rectangle tool drew a rectangle, Prepare's
selection rectangle selected the fields in it, the Link tool opened Link
Properties for a new link, the camera moved its quadrilateral to the page,
and the Select tool selected text. On `main` too.
`PagePointer.drag_started` is a primary drag of a press the pages hold,
started this frame. The page modules read it instead of egui's (comments'
shapes, callout and text box, Prepare, the Link tool, the camera, the text
layer, image pick, and the Edit text group, whose owned presses were
primary already); zoom, crop, Sign area and Redact already asked for the
primary button. The guard refuses a bare `.drag_started()` in a page
module (`NOT_PAGE_DRAGS` lists the widgets of their own: Organize's
thumbnails, the signature pad, the Scan tab), and the router's
`drag_started` is `drag_began`, so the rule reads cleanly.
Tests, one per family, each seen failing with the fix reverted:
`a_right_button_drag_draws_no_rectangle`, `…_selects_no_field`,
`…_draws_no_link`, `…_leaves_the_camera_quadrilateral`,
`…_selects_no_text`; the guard rule against a planted `#[cfg(any())]`
call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It hovered fifty times with a 10 ms sleep between, so a busy runner
could run out of tries before the page's text or paragraph boxes
arrived from their worker threads. It now steps frames with
`support::step_until`, as the other waits for background work do,
until the cursor is the one wanted or 30 seconds pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the Edit text group, the text layer and link following told what
the press is to their page, only the page view reads the router.
- The router's methods and `DocView.router` are `pub(in crate::canvas)`:
outside `canvas` the compiler refuses a read or a write, aliases
included, which the guard's text match could miss. `ROUTER_READERS`
keeps only the page view's dispatcher (`page_tool_input`).
- Rule (f): no function in a page module is named with "grabs" or
"owns", which decide for themselves whether a press is theirs.
- CLAUDE.md's "Page presses" rule says so: modules never read the
router; they are told.
Each rule seen failing against a violation planted in `#[cfg(any())]`
code (a `router.keeps_drag()` read in `content_ui`, a `fn fill_grabs` in
`fill_sign`), and a real router read from `content_ui` no longer
compiles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A comment's gesture ended only on egui's `drag_stopped()`. When the pages
missed its release (the button let go where they didn't see it), the
gesture stayed live: it kept the page's frames from Fill & Sign and the
shape tools, and the next drag that stopped on the page committed it,
taking the comment there.
It now ends, as the other page gestures do, on `drag_stopped()` or once
the primary button is up, where the pointer is.
Two tests planted a live move with the button up to check that a live
move keeps its page's presses (`main`'s
`active_comment_gesture_keeps_highlighted_link_click` and 4a's
`a_fill_and_sign_click_places_nothing_while_a_mark_is_moved`). That
state now ends at once, so both hold the press through the planted move
instead: the intent stands, and each still fails with the live-move
guard taken out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The text layer asked the router whether the press's owner kept its drag,
link following asked it which link held the press, and the Link tool
started a new link from a press any link held, since the router kept a
declined drag's capture.
- `PageRouter::drag_started`: a drag the owner declines (text markup;
with the Link tool, a link) is nobody's once it starts, on the page it
began on. The Link tool draws a new link only from a drag nobody
holds.
- `PageInput` carries the press (`PagePress`) and whether its drag is the
page's text's (`text_drag`): the text layer and link following read
them, and `PageRouter::owner` goes. Only the page view reads the
router now.
- Image pick acts on a press nobody holds: a link's click follows the
link only, and picks no picture under it (the user's ruling).
- The I-beam drops over a link only where a press would go to it, the
top hit under the pointer (the user's ruling): the tools that select a
link's text show it there.
- `PageText::glyph_at` is the one test for the text under a point, for
the text layer and Redact's boxes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit text ignores input while a dialog, the palette, another window or
another tab has it. Only a drag of the objects selected together ended
then; a paragraph's move or rewrap, a picture's move, resize, turn or
crop and added content's move waited, and once the input came back with
the button up they landed wherever the pointer was.
While Edit text's input is blocked, `DocView::cancel_edit_text_gestures`
abandons them all, as the objects' already was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page's menu chose its entries from the selections, in a fixed order:
a selected Prepare field's menu, then a link's, then the picture's, then
a comment's. A selection left from before outranked what was just
right-clicked, so with a field selected, a right-click on the empty page
(or on a comment) opened the field's menu.
The right-click remembers what it went to (`DocView.menu_for`, from the
routed hits), and the menu shows that owner's entries when it has a
menu, or else the page's (the user's ruling). Add text's right-click is
the page's, as before; while a press is held only a link takes its menu.
The picture menu's flag and its geometric test go with it.
#5900's menu check (no image menu over the picture being cropped, #5835)
is the routed owner's too: a right-click that went to the crop (its
picture, box or grips) opens no menu, and `crop_contains_point` and
`crop_controls_contain`, its second point test, go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Edit text group still asked the router for itself and passed answers
from one module to the next: `others_own_pointer`, `text_used`,
`over_source`, `source_blocked` and the pictures' `allow_interaction`
decided who acted, so what the pointer happened to be over at the release,
or an earlier module's answer, could take a click from the module its
press was on.
- Paragraphs (`ParagraphCx`), pictures (`ImageCx`) and added content
(`ContentCx`) are told the press (`PagePress`) and Edit text's gate (its
input blocked by a dialog, the palette, another window or tab, as
before), and act on their own presses; the paragraph outline comes from
the top hit under the pointer (`hovered`), hidden while the objects
selected together or added content being moved hold the page. The flags
go, and the three modules no longer read the router.
- Add text acts on the paragraph its press was on, or the one under the
pointer; its click on a paragraph refuses a blocked draft and finishes
the one being typed in the paragraph's own act.
- Clicks act where they were pressed (the user's ruling): a paragraph
opens though the button came up just past its box, added text is
selected, and opened for typing, by the item its press was on, and Add
text's new box starts where it was pressed (a press beside a paragraph
released inside it starts a box; it placed nothing).
- The picture's grips and rotate knob lose their egui widgets: a drag
takes what the press was on. A press past a page's edge is the page's
that has something under it there (the knob of a picture filling its
page), so the knob still turns the picture there.
- Edit text's right-click goes to what lies on top under it, as every
tool's does (ruling C): a picture is selected for its menu through
`edit_text_ui::picture_context`, and a comment over a paragraph or a
picture gets its own menu. The page view's own picture scan goes.
- #5900's part of that scan (a right-click on the picture being cropped,
its box or its grips keeps crop mode, over a picture on top of it too)
is the crop hit's: `CropPart::Cut`, the picture outside the box kept,
shown or cut away, joins the grips and the box, so a right-click there
keeps crop mode. Its menu check stays for now (the menu commit takes
it). Test: `a_right_click_on_the_part_a_crop_cuts_away_keeps_crop_mode`,
seen failing without `Cut`.
- With the Edit panel open, the tools that don't pick added content up
still select added text by its own test at the pointer, as before: a
press-time hit would take the press from the stamps and the other tools
that place on any press.
- `main`'s press-handler check guards nothing now, and goes; rule (e)
(`ROUTER_READERS`) keeps the modules from reading the router again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`object_ui` took a Shift- or ⌘-press before anyone else, whoever owned it,
through its own inventory test at the press point, and declined a plain
press where that test found an object the router didn't (just outside a
picture's edge), so such a press started nothing at all.
- The page view reads the press's modifiers from `PagePointer`
(`modifiers`: those held at the frame's first primary press). With
Edit text, a press that adds (Shift, Ctrl or ⌘) is the objects' whole:
`object_ui::modified_hit` gives `Objects { Toggle(target) }` for the
object under it, or none (an area selection that adds), `Held` while
text being typed is open, and `Refused` on a page whose objects can't
be selected together. Only the press asks: hover and cursors are as
before.
- `object_ui` is told what the press is to its page (`PagePress`) and acts
on its own parts; a press nobody holds starts the area selection. The
inventory test and its decline go, and so does `blocked_page`: the held
press keeps the editor open until the button is up through its capture.
- A plain press beside a picture, within the 2 px the objects are found
in but outside the picture, now starts an area selection.
- The guard: `object_ui::input` no longer reads the router.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Crop mode held every press on its page through a hit with no point test,
so its claim won the cursor on every visible page (its Default arrow over
the next page's paragraphs), and its own grip test at the drag's start and
its cursor test used different reaches (10 and 8 px).
- `edit_text_ui::crop_hit` says what of the picture being cropped lies
under a point: a grip of the box kept, the box itself, or the rest of
its page (`PicturePart::Crop`, on `Layer::Editor`), and the hand over
its Done, Cancel and Remove Crop controls, past the page's edge too.
A drag takes what its press was on (`ImageCx.press`), and the cursor is
the hit's: one reach for both.
- Delete and Backspace on a selected picture, and crop mode's Enter and
Escape, move to the app's key handling (`edit_text_ui::picture_keys`),
which no key reaches under a dialog. Delete works wherever the pointer
is (the user's ruling: before, it did nothing over a paragraph).
Escape while a button is down is still the press's: it cancels a crop
drag, and crop mode stays.
- #5900's `crop_controls_contain` (the right-click check for #5835)
measures with `crop_frames` and the grips' reach, so the crop's frame
and reach have one source; its `crop_bounds` was `crop_frames`' frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>