Commit Graph
419 Commits
Author SHA1 Message Date
mtlcl410 b8d9497b93 M5: cloudy squares, circles and text boxes are drawn (comment.cloud) (#4195) 2026-11-09 18:32:44 +08:00
oracola91 476effc1c8 Text from a justified line keeps one space between words (#5496) 2026-11-09 18:31:47 +08:00
NetterSilver 69571065b9 Split edit-text paragraphs at headings and blank lines (#3642) 2026-11-09 18:23:57 +08:00
mtlcl410 a2ad6b77f8 M7: replacing a line keeps the source font for the words it can show (#2378) (#2955) 2026-11-09 18:22:25 +08:00
mtlcl410 8d9f8d5f30 M5: a reply is not drawn on the page or flattened into it (comment.reply) (#5632) 2026-11-09 18:15:44 +08:00
jonathanwxh-cellandjonathanwxh-cell ab502255cb edit: preserve gap-drawn spaces and indents in subset fonts via /ActualText (#4229) (#4514)
Co-authored-by: jonathanwxh-cell <jonathanwxh-cell@users.noreply.github.com>
2026-11-09 18:13:34 +08:00
regendorf-ux e63736d2e7 Print production: Fix hairlines (dialog, edit, tool) (#1906) 2026-11-09 18:08:46 +08:00
bflatastic 08c0a59212 Merge main into merge/redact-forms-misc
# Conflicts:
#	ROADMAP.md
#	crates/render/src/text/tests.rs
2026-11-09 09:56:59 +07:00
Ozt-y 9792328d67 fix(edit): preserve annotation optional-content membership in marked content (#3213) (#3275) 2026-11-09 10:14:10 +08:00
midasmatterhorn 98b4761934 edit: a variation selector after existing text draws the base glyph instead of failing the edit (#4899) 2026-11-09 10:09:30 +08:00
bflatastic 8a39399b80 Format merged import lists 2026-11-09 08:22:26 +07:00
bflatastic cf10038885 Merge #6650: fix(edit): editing an OCR phrase redraws only the words it changes (refs #3313)
# Conflicts:
#	crates/edit/src/text.rs
#	crates/edit/src/text_regions.rs
2026-11-09 08:21:42 +07:00
mtlcl410 83a6a8394e M7: Edit text lines set in a Type 3 font are as high as their glyphs (edit.text-edit) (#6571) 2026-11-09 08:57:56 +08:00
Oliver H. 616f245b04 fix(edit): break the row on every Unicode row break (#6295) 2026-11-09 08:54:37 +08:00
Fatih Kunak 2ef2e2b822 fix(fonts): a subset that gives the space no width can't encode one (#6131) (#6158) 2026-11-09 08:44:48 +08:00
Dylan M. Taylor ae21773fef fix(edit): stop rescanning what a loop already holds in wrapping, grouping and flattening (#5963) 2026-11-09 08:40:50 +08:00
Tiago KielandClaude Opus 5.5 19450d7cdf fix(edit): an OCR word edit can't land on the wrong word (refs #3313)
A rewrite also takes away copies of its words. With a copy earlier on the
page, the words after it moved up the list, so the next change rewrote the
word after the one it meant: deleting John removed Smith and left John.

Before each change, the words up to the next one that stays must still be
where they were. If they aren't, the edit falls back to the whole-phrase
rewrite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:41:17 -05:00
Tiago KielandClaude Opus 5.5 9f5166f297 fix(edit): spaces a deleted word leaves don't redraw the OCR phrase (refs #3313)
Backspacing over a word leaves the spaces either side of it, and the rule
that sent spacing to the whole-phrase rewrite took that for spacing someone
asked for, so the most ordinary deletion redrew the whole line again.

Spaces beside a change are nothing to draw: the words around it stay where
the scan has them, with the gap already between them. Only what has to be
drawn goes to the whole rewrite: spacing inside the new words, between two
words nothing else changed, or any whitespace other than spaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:10:47 -05:00
Tiago KielandClaude Opus 5.5 a6229de16d fix(edit): review follow-ups to keeping OCR words (refs #3313)
- A draft with the same words (a space typed after the last) changes
  nothing and is no undo step. It used to redraw the whole phrase.
- Rewriting words alone is refused when it would move any other text on
  the page, visible text too, not only other OCR words.
- A cover stops at the words either side that stay: the padding of a
  large word could reach over the ink of a small one beside it.
- Spacing other than one space between words goes to the whole-phrase
  rewrite, which keeps it, instead of being dropped.
- A squeeze is relative to the scale the words already have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 15:43:19 -05:00
Tiago KielandClaude Opus 5.5 4731036cdb fix(edit): editing an OCR phrase redraws only the words it changes (refs #3313)
Deleting one word from a recognised line covered the whole line and
redrew it in Helvetica, though nothing but that word had changed.

A text edit of an OCR phrase now covers and redraws only the words it
changes; the rest stay as the scan shows them, still invisible text. Each
changed stretch is rewritten as a phrase of its own, on a copy of the
document, and the edit falls back to the whole-phrase rewrite when words
can't be told apart or a kept word would move. A deleted word leaves its
gap. A replacement a little too wide is squeezed across to fit, by up to
a quarter; wider than that it takes over the next word. A change of
formatting, or a typed line break, still redraws the whole phrase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 14:51:11 -05:00
BFlat 43f24aa169 Draw Thai in Fill & Sign, text boxes, form fields, headers, footers and watermarks (#6632)
Refs #2354. Thai typed into these tools is shaped and drawn from the Thai face through Add text's writer (now pdfcraft_fonts::shaped_text), searchable and copyable, instead of being written as ?????; a build without a Thai face refuses it with a message.
2026-11-09 02:56:34 +08:00
BFlat e0f90d5f2d Edit a PDF: select all, copy, cut and paste page objects within and between documents (#6639)
Refs #5989. Ctrl/Cmd+A selects every object on the page; copied objects paste as page content that draws the same, within a document or into another, with organize's bounded deep copy across documents. Headless: object_copy, object_paste, object_move all.
2026-11-09 02:32:50 +08:00
BFlat c07e092ff5 Merge pull request #6637 from storytold/merge/3492-thai-line-breaks
M7: break Thai rows between dictionary words (#3492, merged with main)
2026-11-09 02:25:04 +08:00
bflatastic c7fc7560eb Test Thai word grouping and row filling without a Thai face
thai_lines' row filling moves into wrap_words, so both it and thai_words are
tested with synthetic advances where no Thai face is built in.
2026-11-09 00:54:58 +07:00
bflatastic f6831d58ea Merge remote-tracking branch 'origin/main' into merge/3492-thai-line-breaks 2026-11-09 00:15:27 +07:00
bflatastic 8f5a5aeb89 Merge remote-tracking branch 'origin/main' into fix/thai-grouped-text-v2
# Conflicts:
#	docs/roadmap.md
2026-11-09 00:13:03 +07:00
bflatastic c8248e3b6d Merge #3492: M7: break Thai rows between dictionary words
Brings the Thai word-breaking branch up to date with main. The only conflict was the edit.add-text parity entry: main's evidence and notes (print inks) are kept, with the new Thai test added and the Thai wrapping sentence updated.
2026-11-08 23:58:18 +07:00
bflatastic 5f265960ad Let only a same-style complete program keep the bundled Thai face out
A complete program of the source's family in another style, such as one
its descriptor marks bold, stopped the bundled Regular Thai outlines from
completing a Regular subset, and the text was then drawn in that bold
program. Count only a program in the source's style, as the subset
completion itself does.
2026-11-08 23:51:51 +07:00
bflatastic d0c56fdfee Refuse the form whose merged resources pass the page's limit
The resource limit was checked before a form's resources were merged into
the page's, so one form could take the page past it and every later form
was then refused, even one whose resources were all the page's already.
Check the merged dictionary instead.
2026-11-08 23:51:51 +07:00
bflatastic f4d669b31f Pass over a complete Thai program that may not be embedded again
The first program of the face that could shape a span was chosen even when
its embedding permissions forbid embedding it, and the edit then failed
while a later program of the same face could have been used. Check that a
program may be embedded before choosing it.
2026-11-08 23:51:51 +07:00
bflatastic 5f356ade12 Give an unencoded space in legacy Thai the source font's space fallback
A space that no subset of the face encodes was drawn as a quarter-em gap,
narrower than the text-space fallback the source font uses elsewhere.
Keep each subset's source space with it and use that for the gap.
2026-11-08 23:51:51 +07:00
bflatastic fbc2dc77a7 Read each THSarabunPSK subset of an edit once, within one budget
Every resource name of a page's Thai subsets decoded and kept its own copy
of the font program, so many names of one large program could hold about
2 GiB for a single edit. Names of one font object now share its program,
the subsets of an edit are decoded within a shared 64 MiB allowance, and
an edit that needs more is refused with the reason, changing nothing.
2026-11-08 23:51:51 +07:00
bflatastic 2f492db9a3 Merge remote-tracking branch 'origin/main' into merge/5454-ocr-phrase-size 2026-11-08 23:39:29 +07:00
bflatastic ae7a21d2b0 Edit grouped text with one click instead of preparing pages on sight
Edit text queued Prepare grouped text for every page that came into view, after a full
preparation on a copy of the document on the UI thread to see whether it could. Scrolling cost
that work per page, marked the document changed, added undo steps and inlined shared forms
although nothing was edited.

Now nothing happens while pages are shown. A click (press and release, no drag) on grouped
artwork, routed to the picture by the page router as before, asks once whether that artwork
shows text preparation would open (text::forms::grouped_text_at, which reads the page without
copying or changing it). If it does, the page is prepared as one undoable step and the
paragraph at the point clicked opens for typing. A logo or a group preparation would refuse is
selected as before, with no message and no change. A drag still moves the selected group, and
while a paragraph is open or an edit is waiting the click selects as before. text_prepare is
unchanged.

The docs and the parity entry no longer say text is prepared on entering Edit text.
2026-11-08 22:31:14 +07:00
bflatastic cc9f7977bf Repair a page's damaged THSarabunPSK map without grouped text
The repair reads the page's own fonts. It ran only after a group was expanded, so the same
damaged map on text that was never grouped kept reading wrong. Preparation now repairs it
whenever it finds it, recorded in the repair log as before, and PrepareText (text_prepare)
succeeds when it repaired a map even with nothing to expand. A repaired map no longer matches
the damage's fingerprint, so preparing again changes nothing.
2026-11-08 22:31:14 +07:00
bflatastic 90b88ddcb6 Ungroup reads the Do it replaces and refuses one inside text or a path
Ungroup and text preparation now check the placement in one place, ungroup_ops, which is given
the page's operators and the Do's index:

- The form is looked up by the name the Do writes, byte for byte. Ungroup looked it up by the
  picture's display name, a lossy UTF-8 copy, so a form named /F#FF was 'missing'.
- A Do with more than one operand is refused: the picture was read for its first, the renderer
  draws its last.
- A Do inside BT…ET or an unfinished path is refused. Inline, the form's text objects would be
  nested in the page's, and its clip would take in the page's path. Preparation already refused
  these; Ungroup didn't.
2026-11-08 22:31:14 +07:00
bflatastic 233bc6c932 Ungroup keeps the translucency an unmatched Q doesn't restore
A Q with no q before it restores nothing; the renderer ignores it. The check for translucency at
the Do took it as restoring the opaque default, so a transparency group drawn after /H gs Q was
inlined although it is drawn translucent.
2026-11-08 22:31:14 +07:00
bflatastic 7169b0d6ad Ungroup refuses a form the page's default colour space would remap
A form with resources of its own and no /DefaultRGB (likewise Gray, CMYK) draws its device
colours as they are. Inline, the page's resources apply, and a /DefaultRGB there would remap
them (ISO 32000-2 §8.6.5.6). Only the form having an entry the page lacks was refused; now either
side having one the other doesn't share is.
2026-11-08 22:31:14 +07:00
bflatastic 5850000e41 Prepare grouped text with Ungroup's own form expansion
text/forms/expand.rs was a drifted copy of ungroup.rs. It is gone: text preparation now opens each
placement with crate::ungroup, so Ungroup and preparation share one implementation.

What the copy added is now in ungroup.rs: a Refusal that says whether the form itself or only
this placement is the reason (intrinsic) and whether a rename alone was the problem (renaming),
the shows_text result, merges_without_renaming, refusals for unreadable /Resources and /Group
entries and arrays with a non-number, a cap on resource counts and q nesting, a total size for
pattern copies, and ending an unfinished path with the form. Ungroup's newer behaviour stays:
names are read from the operand the renderer uses, so a surplus operand is renamed correctly
rather than refused; default colour spaces are compared with the page's; and a group blending
in DeviceRGB on a page that names no blending space is drawn inline, as Ungroup does, rather
than refused as the copy did.

What a form draws is now read within one decode allowance shared with the form itself, so a
crafted group can no longer make one Ungroup decode 64 MiB for each of 1024 nested forms. A
group whose content can't be checked is refused; for a form without a group, blend modes inside
don't matter, and content that can't be checked only means preparation can't tell whether it
has text (shows_text is None), which it reports.
2026-11-08 22:31:14 +07:00
bflatastic 4444355446 Merge grouped Thai text editing (#6249)
# Conflicts:
#	parity/acrobat-features.toml
2026-11-08 22:31:13 +07:00
FredApps 981193ad41 Merge pull request #6619 from FredApps/fix/rules-content-stream-api
fix(edit): restore page rules after the content stream API change
2026-11-08 15:30:47 +01:00
mtlcl410 7d1dea0517 M7: Ungroup draws Form artwork in place, so its text can be edited (#3067)
Refs #2964, #3005
2026-11-08 22:14:30 +08:00
FredApps cb705f73de M0: restore page rules after the content stream API change 2026-11-08 14:59:05 +01:00
FredApps 8287ad0d06 Merge pull request #6289 from Piohrun/fix/partial-content-rewrite
M2.6: never rewrite a content stream from the part before its damage
2026-11-08 13:10:56 +01:00
NetterSilver f750589812 Keep ruled tables when exporting a PDF to Word (#5154)
Fixes #5149
2026-11-08 19:28:07 +08:00
Grzegorz MartensandClaude Opus 5.5 3639fed12f M2.6: text a stream not read whole goes on from isn't edited
A text object can run on from one content stream into the next. When it is still open at a
stream damaged partway, or one that doesn't decode, the text that stream shows starts where the
text before it ends: editing that text moved what the hidden stream shows, its advance changed.
An edit in the text object before such a stream went ahead, as edits before the damage do.

`rewritten_contents` now refuses a change from the start of a text object still open at the
first stream not read whole (`changes_text_open_into_damage`); a change before that text object
goes on. `text_a_stream_not_read_whole_goes_on_from_is_not_edited` (the hidden stream damaged
from its start and one that doesn't decode, between the text and a stream closing it or last on
the page; a line in an earlier text object of the same stream is still edited).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-07 20:18:07 +01:00
Grzegorz MartensandClaude Opus 5.5 569f631f2b M2.6: an image's move changes no state a stream not read whole draws in
Moving or resizing an image in a stream before one damaged partway, or one that doesn't decode,
goes on, as the edit changes no stream past it. But the image's `q cm … Q` can close past that
stream, and what the stream draws inside the scope is unknown. When the image looked alone there,
the move changed the scope's `cm` in place, and anything the stream draws in it moved too: a
square drawn there in the image's `cm` moved with the image, as on `main`.

A scope still open after such a stream now counts as one that paints something else
(`first_op_after_damage`, read by `image_scopes` and `enclosing_transform`). An operator counts
by its keyword there: a page may split its content between an operator's operands and its
keyword, so a `Q` whose operands come before the hidden stream still closes after it. The move
gives the `Do` a `cm` of its own and leaves the scope's as it was; with a clip on the image, the
clip stays, as one the stream may share. A crop goes around the `Do` alone rather than at the
scope's start, where it would clip what the stream draws. A crop PdfCraft put in such a scope
isn't removed, and the refusal says why rather than that there is no crop. Arrange is
unchanged: it takes out the scope it finds, which changes the stream past it and is refused.

`an_image_whose_scope_closes_past_a_stream_not_read_whole_moves_alone`: a stream that doesn't
decode and one damaged, each also with the square's operands before what is hidden (ending the
stream before, or in the damaged stream before its damage); the image with no clip, a clip, and a
PdfCraft crop; moved, cropped, and its crop removed. A scope that closes in a damaged stream
before its damage still takes the move in its `cm`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-07 20:18:07 +01:00
Tiago KielandClaude Opus 5.5 7cb8ca3c01 fix(edit): OCR phrase size counts words and keeps widths past 2x (refs #3313)
Review follow-ups:
- The phrase's median counts each word once: neighbouring words OCR
  sized alike share a run, so 15, 9 and 9 pt gave 12 pt, not 9.
- The styled rewrite's width calibration is not clamped again after the
  phrase ratio, so a word sized at more than twice the phrase keeps the
  width the scan shows it at.
- The single-style rewrite (a new family or bold) scales the first
  word's Tz by its own size over the phrase's, so the line is as wide
  whatever size OCR guessed for that word.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 13:35:47 -05:00
Tiago KielandClaude Opus 5.5 82116a5c7f fix(edit): an edited OCR phrase is drawn at one size (refs #3313)
Recognize text sizes each word from its own box and a guess at how much
of the em its letters fill, so one phrase's words come out several
points apart (9 to 15.6 pt on a LaTeX scan). Editing a phrase drew and
saved every word at its own size.

An invisible block's size is now the median of its words' sizes
(TextBlock::phrase_size). The styled rewrite draws every run at it and
scales each word's width calibration by its own size over the phrase's,
so words keep the width the scan shows them at; the single-style
rewrite (a new family or bold) uses it in place of the first word's
size; and the inline editor lays each word out at it while typing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 13:35:47 -05:00
Grzegorz MartensandClaude Opus 5.5 86cb375869 M2.6: edits stop at a content stream that doesn't decode, as at damage
An edit was refused after a content stream damaged partway, since what the damage hides can
change how the later streams read, the graphics state they draw in among it. A stream that
doesn't decode at all hides all of its operators, and an edit after one went ahead. It now stops
there too: text lines and blocks, and images (arrange, crop, transform, move), as selecting
objects on `main` already does for a page with such a stream. An edit in a stream before it goes
on, and the stream is kept as stored.

A file may give a stream's /Filter by reference, which isn't read here, so the case needs no
damage: `content_after_a_stream_that_does_not_decode_is_not_edited` has such a stream move the
text and image after it with a `cm`. `a_content_stream_that_does_not_decode_is_kept_and_edited_around`
becomes `..._kept_and_edited_before`, its edit now in the stream before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
2026-11-07 18:41:02 +01:00