Commit Graph
189 Commits
Author SHA1 Message Date
RiceDee c9abf15e7c fix(edit): only a row aligned on its own is rewritten
A nonempty aligns counted as a style change, so every row of the
paragraph was rebuilt from the text state: rows left unselected lost
their source operators, and with TJ word gaps their words moved by
about a point and a half while the row's outer width stayed the same.
aligns no longer makes an edit non-plain; a row whose own line has an
alignment override is rewritten, and the others keep their operators.
2026-10-25 16:57:41 +11:00
RiceDee a8ea7d06cd Merge main into feat/align-selected-lines
Main brought #4178 (already on this branch), #4217 (edited text keeps
every space typed), #4266 (added text takes character spacing) and
#3546 (Save commits the open text boxes).

Conflicts:
- edit/added.rs: AddedText has both aligns and char_spacing. Lines are
  measured with the spaced width (latin_width) and placed by their own
  paragraph's alignment, with Tc set before them; both are read back.
- edit/text.rs: wrap keeps #4217's spacing and still returns each
  line's row, so a row aligned on its own keeps its alignment; a
  justified row stretches over its interior spaces only.
- ui-egui/content_ui.rs: the box keeps its per-paragraph layouter,
  which now draws character spacing (spaced_job), with input_id() and
  the per-document typing history.
- automation: aligns and char_spacing are both text-only options.
- Tests from both sides kept; #4178's two editor tests, present on both
  sides, kept once, and mode_tab's doc comment put back.

The TextDraft literals from #3546 take selection and typing_from, and
an engine test's AddedText takes aligns. New test:
added_text_paragraphs_align_by_their_spaced_width, seen failing with
the unspaced width.
2026-10-25 01:02:24 +11:00
aiongg 692f34fce2 Merge track r3-t04 2026-10-24 06:40:21 +00:00
aiongg 880723e3af Merge PR #4266: M7: added text takes character spacing from Format text
crates/engine/src/lib.rs: union of the added:: re-exports (paste_place/paste_rect from #4230, CHAR_SPACING_LIMIT).
crates/ui-egui/src/content_ui.rs: editor keeps #4178's anchored width and horizontal_align and #4266's spacing layouter; spaced_job takes the alignment so the custom layouter keeps it.
crates/ui-egui/tests/it/content.rs: kept both tests.
2026-10-24 06:24:10 +00:00
aiongg f71a305915 Merge PR #4230: M7: paste added text boxes and images in the engine and through content_copy (#2636)
ROADMAP.md, docs/roadmap.md: kept both sides' status and progress-log lines; did not restore the older M14 status line that the branch had already trimmed.
2026-10-24 06:23:24 +00:00
aiongg 8dda3d50b9 Merge PR #4316: M7: a bold paragraph's stand-in font no longer turns earlier edits bold (#4280) 2026-10-24 06:22:51 +00:00
aiongg 5cb74eeb69 Merge PR #4217: M7: edited text keeps every space typed (#4199, #3797)
crates/edit/src/tests.rs: both sides appended tests at the end; kept both.
Cargo.lock: unchanged from base (the PR's ctutils bump did not apply).
2026-10-24 06:22:45 +00:00
aiongg 7c005aed5b Merge PR #3551: Edit text: keep Tr, stroke, line width and gs set inside BT when rewriting a paragraph (#3508, #3509) 2026-10-24 06:21:29 +00:00
aiongg fa3a0221d9 Merge PR #3000: fix(edit): batch styled paragraph fallback glyphs 2026-10-24 06:21:12 +00:00
nlevaandOpus 5.5 medium ultracode 302ad68502 M7: paste added text boxes and images in the engine and through content_copy (#2636)
Edit::PasteContent pastes an added text box or image copied in the same
document or another, as it was (a text box's style; an image's picture,
turns, flips and crop), as one undo step. A pasted image draws its picture
from the copied object while that is still the picture in the target, else
from an added image there that already holds it (an earlier paste from
another document), else the file is embedded. Document::added_image_file
gives a copied image's picture as a file and honours the copy permission.

pdfcraft_edit::added::paste_rect / paste_place place a paste where the item
was, 18 pt down and to the right past each copy of it on the page (an image
with the same box and transforms counts whichever object holds its
picture), or at a point, kept on the page.

The content_copy tool drives it headlessly, across pages and documents.

The desktop keys and menus come in follow-up PRs.

Co-Authored-By: Opus 5.5 medium ultracode <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEgu9NcTcPqKovsLyfPxK
2026-10-24 01:17:34 +02:00
Lcoding15 0b3b243e9a M7: a bold paragraph's stand-in font no longer turns earlier edits bold (#4280)
Edit text set a paragraph whose font lacks a typed character (an accent
missing from a subset) in a standard font, always under the resource
name PCEdHelv, whatever face it stood for. Rewriting a bold paragraph
after a regular one replaced PCEdHelv with Times-Bold, so every
paragraph set in the earlier stand-in turned bold, and the editor's
draft measurement saw those paragraphs change and grew its frame over
them.

The stand-in is now named after its face (PCEdTimesBold, ...), as the
line and region rewrites already name theirs.

Fixes #4280
2026-10-23 22:46:56 +02:00
RiceDee ea6420cde5 M7: a lone row aligns across the column under it, not the page's busiest
alignment_frame found the column by the left edge most of the page's other
rows share. On a page of two columns, a title over the column with fewer rows
took the other column's edge, failed to fit in it, and was aligned in its own
box. Only rows reaching across the title now count.

Tests: the two-column case; a two-colour title (the styled-region path) is
framed too; a justified paragraph sent with its rows split by line breaks, as
the editor sends it, still fills every row but the last.
2026-10-24 02:34:48 +11:00
RiceDee 1f29575d34 M7: align a one-row paragraph across the column it sits in
Centring one highlighted line of a paragraph moves it out of the
paragraph's rows, so the next alignment of it saw a one-row paragraph
and aligned it in its own box: Left left it where it was, and since a
single row grows to the page's margin, each Centre moved it further
right. An alignment for a one-row paragraph is now laid out across the
column of text it sits in (the left edge the page's other text uses, to
the furthest right those rows reach), or its own box when no column
holds it.
2026-10-24 02:34:48 +11:00
RiceDee 5c1c3462eb M7: align the selected lines of a paragraph or text box on their own
Format text's alignment always re-aligned the whole paragraph or added
text box, and clicking it closed an added text box, losing the
selection, so one line couldn't be aligned. With text selected,
alignment now applies to the lines (text between line breaks) the
selection touches; with only the caret, to the whole box as before.

- Added text stores each line's alignment in its /PCAdded parameters
  (/Aligns, null where a line follows /Align). A justified line's last
  row is not stretched.
- An edited paragraph sends its rows' alignments with each rewrite
  while it is open (BlockStyle::aligns), in both the single-style and
  the styled-region rewrite.
- Both editors draw each line by its own alignment, and the alignment
  buttons show the selected line's. An added text box stays open while
  Format text is used, its style applied at once; clicks are judged by
  where they were pressed, so a touch or pen tap on the panel doesn't
  close either editor.
- Automation: `aligns` on page_add_text, content_update and text_edit.
2026-10-24 02:34:48 +11:00
Marco Arnulfo cd6f031b82 M7: added text takes character spacing from Format text (fixes #4245) 2026-10-23 17:31:02 +02:00
Ridwan 9b5624afc1 fix: Issue #4017. Flattening removes empty fields it cannot draw (#4033)
* M6: flattening removes empty fields it cannot draw (#4017)

Flattening kept every widget without an appearance so that its value
was not lost with nothing drawn in its place (#1794). A field without a
value has nothing to lose, yet it stayed in /AcroForm /Fields and on the
page, so a flattened file could still be filled in.

A widget without an appearance now stays only when its field has a
value: its /V, or the nearest one inherited through /Parent. A missing
value, an empty string or an empty array is no value. Comments and
valued widgets are unchanged.

Fixes #4017

* M6: flattening keeps a widget whose value is past the parent limit (#4017)

has_value walks at most 64 levels up /Parent. When it ran out it said
"no value", so a widget whose /V sat on the 64th ancestor was removed.
Running out now counts as a value: one that wasn't checked must not be
removed.
2026-10-23 10:42:08 -04:00
Matej VrzalaandBrandon Thomas 75207dc8ed fix(edit): setting a paragraph's width no longer panics on text larger than a page (#4110)
rewrite_block clamps a requested paragraph width between the text's
size in user space (size * k) and the largest page PDF allows
(14 400 pt). f64::clamp panics when its minimum is the larger, so text
set above 14 400 pt crashed the app as soon as its paragraph was given
a width. The size comes from the file (`20000 Tf`, or `1 Tf` scaled up
by `Tm`) or from the request (the automation tools pass `size`
through).

The lower bound is now capped at 14 400 pt too, so such a paragraph
gets the widest width allowed. `f64::min` also turns a NaN size into
the cap.

Test: a 20000 pt Tf, a 1 pt Tf scaled 20000x by Tm, and a requested
size of 20000 pt each take a width of 200 without panicking (all three
panicked before).

Reported-by: BS.ehlas.cz

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-23 10:20:35 -04:00
Jayesh-Prajapati9 3e9988954a M7: CR and CRLF break rows on the styled rewrite path too (#4199)
A multi-line or multi-style draft is laid out by rewrite_styled_block, which broke rows only at \n: a CRLF left a stray CR before the break and a lone CR broke nothing. The CR of a CRLF is now skipped and a lone CR breaks the row, keeping the draft's character positions, and a draft whose only break is a lone CR takes the same path as one with \n.
2026-10-23 14:15:17 +05:30
luis.pereiraandClaude Opus 5.5 60985e119c Edit text: a dash or line cap partway through an outlined line starts a run (#3508)
Review follow-up for #3551: two ExtGStates at the same opacity are the same
Paint, so a dashed outline after a solid one joined its line and was
rewritten with the first run's selections. Ts tracks the effective dash
pattern and line cap, from d and J and from an ExtGState's /D and /LC, and
same_paint compares them as values for stroked text, so states with the
same settings under other names still continue a line. A dash array longer
than 32 entries, or with an operand that isn't a finite number, and a cap
outside 0-2 are ignored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 09:35:03 +01:00
luis.pereiraandClaude Opus 5.5 9163f29090 Edit text: per-run q/Q, /LW from gs, and a Tr partway through a line starts a run (#3508, #3509)
Review follow-up for #3551, rebased on the gs grouping of #3310:
- The styled rewrite draws each run in its own q/Q from the state where
  the rewrite is drawn: a gs has no inverse, so one run's ExtGState stayed
  in force for the runs after it. Two ExtGStates at the same opacity share
  a paragraph (Paint), but either may set what Paint doesn't compare.
- The gs arm applies an ExtGState's /LW to the line width; paint_ops
  restates the effective width after any replayed gs.
- A run whose visible Tr (or, for stroked text, stroke colour and width)
  differs no longer joins the line before it, so a line is not rewritten
  in its first run's state. Clipping and invisible modes still join, as
  the #3447 refusal reads the whole line; gs changes are already kept
  apart by Paint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 09:27:47 +01:00
cadelassandClaude Opus 5.5 6d767cc6fc Edit text: keep Tr, stroke, line width and gs set inside BT when rewriting a paragraph (#3508, #3509)
A rewritten paragraph is drawn before its text object and restated only the
font, spacing and fill. Anything the text object set before its first show was
lost: `1 Tr 1 0 0 RG 2 w` inside BT turned red outline text into filled black
text, and `/GS1 gs` inside BT made semi-transparent text opaque, even when the
text was unchanged.

The interpreter now tracks stroke colour, line width and the ExtGStates
selected (bounded to 32 distinct names), and the rewrite restates whatever
differs from the state in force where it is drawn. Styled runs restate it per
run, too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:27:46 +01:00
Jayesh-Prajapati9 d9115bb90b M7: edited text keeps every space typed (#4199, #3797)
Rewriting a paragraph in one style split its text at white space and
joined the words with one space, so a run of spaces, an indent or a
trailing space typed in the editor was saved as one space or none. The
text is now kept as typed (other white space becomes a space, and CR or
CRLF become line breaks), and the wrapper keeps each run of spaces,
dropping only the spaces where a row wraps.

A justified (stretched) row still drops its indent and the spaces after
its last word, as before, so word spacing reaches only the spaces
between its words; keeping an indent on a justified row is a possible
follow-up. Centred and right-aligned rows, and the editor's draft box,
are measured to the last word, and a paragraph at 0 Tz is not justified.

Fixes #4199
Fixes #3797
2026-10-23 13:50:17 +05:30
aiongg ba024eb0dd fix(edit): flatten on a copy so a page refused part way changes nothing 2026-10-23 00:02:33 +00:00
aiongg 7a466ff5fc fix(edit): prune a field again when a shorter path reaches it past the depth cutoff 2026-10-22 23:32:11 +00:00
aiongg a52e39a5e2 Merge #4114 (fix/coderabbit-b-shared): shared DecodeBudget
The tests each branch appends to the audit, edit and organize test files are
kept side by side; prune.rs merges cleanly, with the form memo on top of the
budgeted page content.
2026-10-22 21:17:55 +00:00
aiongg fdcfe16b7d fix(edit): bound the text one page's extraction can expand into
Text extraction copies a ToUnicode destination for every glyph showing its
code, and the page's total text was not bounded, so a small page could expand
into gigabytes. One page's extraction (its forms included) stops with an error
past 16 MiB of text.
2026-10-22 21:17:15 +00:00
aiongg dc7a195dda fix(edit): prune each form-field node once after flattening
Removing flattened widgets from the field tree recursed into every /Kids
entry with only a depth limit, so a node listing its child twice per level
(or itself) made the walk exponential. Each node's answer is now remembered;
a node met again while it is still being visited survives, as it did when
the depth limit stopped a cycle.
2026-10-22 21:16:21 +00:00
aiongg f1a89a4842 fix(edit): wrap added text once per write, not once per line
Writing an added text item re-wrapped the whole text inside the loop over its
lines to learn the line count, so multiline text cost time and temporary
memory quadratic in its length. The wrapped lines are computed once.
2026-10-22 21:16:21 +00:00
aiongg f980b34ef3 Text and image reading charge a page's content and its forms to one decode budget 2026-10-22 19:49:44 +00:00
PerchAnton d323e86706 M7 PR #3000: retain batched styled fallbacks with current main 2026-10-22 20:11:21 +03:00
aiongg a37a908f7b fix(edit): page content, forms and image records share per-page limits
The editor's page readers decoded every /Contents entry and every form
entry with a fresh 1 GiB allowance, and the reading-order image
inventory added a record per image draw in every form entered, so a few
shared forms drawing each other produced millions of records. Content
streams and form entries are decoded against one per-page DecodeBudget
(content past it is an error, so no edit rewrites a page from part of
its content; forms past it are reported truncated), and an inventory
past 100,000 image draws is refused.
2026-10-22 16:57:55 +00:00
aiongg 55e32759b1 Integration: subset fallback test names the face the build leads with (#2609 × #1760)
#2609 put the Japanese faces ahead of the bundled Inter face and rewrote
the #1760 test to expect Inter, which holds only without craft-fonts.
With CRAFT_FONTS_DIR, BIZ UDPGothic has ő and draws it. The test now
expects the Japanese sans face when the build has one, Inter otherwise.
2026-10-22 04:15:17 +00:00
aiongg 2c13fd9373 Merge r2 track t16: mixed: automation-mcp, crate:misc, crypt-security, export-convert, optimize, preflight-pdfa, save-io
# Conflicts:
#	ATTRIBUTION.toml
#	contributors/people.toml
#	crates/automation/src/comments.rs
#	crates/edit/src/tests.rs
#	crates/engine/src/tests.rs
#	parity/acrobat-features.toml
2026-10-22 02:23:58 +00:00
aiongg dcf2802f96 Merge r2 track t15: mixed: accessibility-tags, dialogs-prefs, javascript, organize-pages
# Conflicts:
#	crates/edit/src/tests.rs
#	crates/engine/src/tests.rs
2026-10-22 02:23:22 +00:00
aiongg 3cf35888d7 Merge r2 track t12: i18n, i18n: new language catalogues
# Conflicts:
#	ATTRIBUTION.toml
2026-10-22 02:21:42 +00:00
aiongg 15f558aaa8 Merge r2 track t11: redaction, mixed: combine, compare, crate:edit, measure, ocr
# Conflicts:
#	ATTRIBUTION.toml
#	crates/automation/src/tools.rs
#	crates/edit/src/lib.rs
#	crates/ui-egui/tests/compare.rs
#	crates/ui-egui/tests/ui_group/marks.rs
2026-10-22 02:21:38 +00:00
aiongg 69e755c439 Merge r2 track t08: fonts
# Conflicts:
#	crates/automation/src/tools.rs
#	vendor/README.md
2026-10-22 02:20:50 +00:00
aiongg 4d5025c6da Merge r2 track t07: text-extract-select
# Conflicts:
#	crates/edit/src/tests.rs
#	crates/render/src/raster.rs
#	vendor/README.md
2026-10-22 02:20:03 +00:00
aiongg a2c46a9298 Merge r2 track t06: annotations-comments
# Conflicts:
#	crates/automation/tests/automation.rs
#	crates/render/src/raster.rs
2026-10-22 02:19:23 +00:00
aiongg 7bf122eea8 Merge r2 track t02: edit-content
# Conflicts:
#	crates/edit/src/tests.rs
#	crates/edit/src/text.rs
#	crates/ui-egui/src/canvas.rs
#	crates/ui-egui/src/edit_text_ui.rs
2026-10-22 02:17:34 +00:00
aiongg 456b96d06b Merge PR #2454: M5: year-first slash dates (yyyy/mm/dd, yyyy/m/d) for Fill & Sign and headers 2026-10-22 01:49:17 +00:00
aiongg 1544fae727 Merge PR #2481: M7: show a shaped run in one text object instead of one per glyph 2026-10-22 01:48:40 +00:00
aiongg 3378e660aa Merge PR #2612: M7: let a list marker open an item and a hanging indent continue it (#2564) 2026-10-22 01:48:38 +00:00
aiongg 41ec3f370e Merge PR #3289: Lay out vertical (Identity-V) text as columns in the edit layer
crates/edit/src/text.rs: interpret takes the page resources (#3310) with #3289's CachedFont cache and Result return; walk/enter return Result and use main's form budget; call sites pass resources and propagate with ?. TextLine and the text state keep both sides' fields (clips, gs; unreadable_mode, vertical, unreadable). The shown run keeps main's tracking: 0.0 with #3289's vertical advance and tm. replace_line checks ensure_editable before main's clipping-text refusal.
crates/edit/src/tests.rs: kept both sides' new tests.
2026-10-22 01:48:38 +00:00
aiongg f58254b57f Merge PR #3310: Keep text rows in other layers, gs states or Tz out of a paragraph
crates/edit/src/text.rs: interpret takes the page resources (#3310) and the named property lists for /ActualText (main's #3301); every call site passes both. In BDC handling, #3310's layer push runs before main's property-list lookup; carry.layers is saved before positioned_tracking.
crates/edit/src/tests.rs: kept both sides' new tests.
2026-10-22 01:47:54 +00:00
aiongg fa1095350a Merge PR #3697: Edit text: keep a line together across subsets of the same font (#3661) 2026-10-22 01:47:18 +00:00
aiongg 3f9b41246a Merge PR #1829: M10: keep dotted-leader contents entries together
ROADMAP.md: progress-log line added at the top
docs/roadmap.md: status line, M10 row note and revision row applied onto main's
parity/acrobat-features.toml: export-docx evidence and notes insertions applied onto main's
crates/edit/src/tests.rs, crates/engine/src/tests.rs: new tests appended after main's
2026-10-22 01:46:30 +00:00
aiongg f4f9e009ad Merge PR #3861: M7: keep a word on its line when its spacing differs by less than a thousandth of an em
crates/edit/src/tests.rs: kept both new tests (#3571's surplus-TJ test and this PR's spacing test).
2026-10-22 01:45:49 +00:00
aiongg 64e49eac48 Merge PR #2487: M7: drop the fallback fonts a text edit leaves unused 2026-10-22 01:45:38 +00:00
aiongg a0e8c2faa2 Merge PR #2609: fix(edit): draw the Cyrillic the Japanese faces lack with the bundled Inter face (#2593)
parity/acrobat-features.toml: edit.edit-text evidence and notes take both sides' additions
2026-10-22 01:45:35 +00:00