CodeRabbit's review of 674a20899:
- The Thai edit refused when nothing was left of its allowance, so an
edit whose reads spent it to the byte was refused. It now refuses when
a read was refused for want of it (`DecodeBudget::refused`).
- `LegacyThaiFont::from_pdf` read its metrics with `from_dict`, outside
the edit's allowance, and so did the edit for each subset it measures.
Both now read them within it (`from_dict_within`), and metrics read
past it adapt nothing: `from_pdf` refuses the font before reading its
program, and the edit leaves the subset unread and is refused. The
edit's scan of every font on the page for its face name stays outside
the allowance, as on `main`: charged, each alias of a font would pay
again, and other faces would spend what the face's subsets need.
`MAX_THAI_PROGRAMS` says what it pays for.
- When the `/CIDToGIDMap` a THSarabunPSK subset's Thai mappings are
rebuilt from was left unread for want of the editor's allowance,
`composite_glyph_maps` gave nothing, and a subset with a Latin-only
cmap of its own was listed as it was, without its Thai mappings. The
editor now lists no copy of it, as for metrics read past the
allowance.
Tests: `a_thai_edit_that_spends_its_allowance_to_the_byte_goes_through`
(`pdfcraft-edit`: a subset costing exactly the 64 MiB is edited, and
one three bytes past it is refused),
`a_legacy_fonts_metrics_are_paid_for_from_the_edits_allowance` and
`a_map_left_unread_for_the_allowance_lists_no_copy` (`pdfcraft-fonts`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
The Claims review of the description at b7224cbc9. `Reads::decode` said a
stream past its limit is unread, not refused, "when what is left can pay
for its stages". What `DecodeBudget::decode_stages_within` does, and
`a_stage_past_its_limit_charges_what_came_before_and_the_limit` pins:
with more left than what the stages before the stopped one made and the
limit, it is charged those two; otherwise, with more left than the
stages' total (behind one filter, the limit), it is charged that total;
with no more left than that total, it spends all of it and counts as
refused.
`MAX_THAI_PROGRAMS` also names the second read of a subset's program,
for which of its glyphs have outlines, among what it pays for.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
The Consumers review of ba28458ad..b7224cbc9:
- `recovered_thai_map` read the descendant's /CIDToGIDMap with
`Dict::name`, on the line after e9142b03b's /Encoding check, so a map
given by reference made Repair Thai maps pass the font over, and so
did a missing one, which is Identity (ISO 32000-2 Table 115), as
`composite_glyph_maps`, `LegacyThaiFont` and the renderer read it. It
now resolves the map and takes a missing one as Identity.
- `MAX_THAI_PROGRAMS` says what it pays for: the programs, and the CMaps
and glyph maps read to adapt their cmaps. The metrics read to find and
measure the subsets aren't charged to it. Its refusal ("MiB of font
data") still says what it counts.
Test: `recovered_thai_map_checks_every_kept_glyph_against_the_font_program`
recovers the same map with the font's /CIDToGIDMap given by reference and
with none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
The Resources review of 8eead8ecd:
- `embedded_sfnt_within` read a composite font's metrics within the
editor's allowance before `composite_glyph_maps` refused any `/Encoding`
but Identity-H, so an Identity-V or UniJIS font spent its ToUnicode and
`/CIDToGIDMap` for nothing. Its `/Encoding` is now checked first; a
program with a cmap of its own is kept as it is, as when the rebuild
finds nothing.
- The Thai edit's allowance, `MAX_THAI_PROGRAMS`, now also pays for the
CMaps and glyph maps its subsets' metrics and codes are read from: its
doc and its refusal say "font data", not "font programs".
- `a_symbolic_truetype_font_without_an_encoding_reads_its_glyph_names`
also reads its font, whose `post` table names its glyphs, within an
allowance: the program is paid for by the glyph-name reading, by the
Mac OS Roman one and for the widths, and a byte short of the first the
names go unread.
Tests: `a_composite_font_that_cant_be_adapted_spends_only_its_program`,
and `adapting_a_programs_cmap_is_paid_for` again expects a composite font
whose codes aren't Identity-H to pay for its program only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
main's #3962 (symbolic TrueType glyph names, 63a6f8ace and nearby) reads a
symbolic TrueType font's codes by the `post` names of the glyphs its cmap
selects, before the Mac OS Roman reading:
- crates/fonts/src/pdf.rs, `unicode_map`: both readings stay, in main's
order, each given the metrics read's allowance. The program read main
added (`symbolic_glyph_texts`) goes through `Reads::decode`, as
`symbolic_mac_roman_texts` does since the last merge: paid for from the
allowance, `past_allowance` set when it can't be, behind several
filters within the stream's own limit.
- crates/fonts/src/pdf/tests.rs: both sides added tests at the same place;
all are kept.
Tests: `adapting_a_programs_cmap_is_paid_for` now expects the program to
be paid for a fourth time, and `the_symbolic_mac_roman_reading_is_paid_for`
becomes `the_symbolic_readings_of_a_program_are_paid_for`, paying for both
readings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
main's Thai work (#6249, #3492) and the Type 3 and symbolic TrueType
metrics changed the code this PR bounds. Each conflict, and each read main
added that this PR's allowance must cover:
- crates/fonts/src/pdf.rs, the editor's program read: this PR's read is
kept, its stage-scaled limit and its check for an allowance already
spent; main's read had /DecodeParms by reference too, which it has.
- The editor's loop: main rebuilds a cmap for a composite subset without
one from its ToUnicode and /CIDToGIDMap, where this PR skipped Type 0.
main's composite support stays, with the metrics read within the
editor's allowance (`from_dict_within`) and no copy adapted from metrics
read past it, as for a simple font. The /CIDToGIDMap the cmap is rebuilt
from (`composite_glyph_maps`) reads as the font's other streams do
(`Reads::decode`): within the allowance, behind several filters too.
- Reads main added to the metrics read: a Type 3 font's glyph
descriptions (`type3_glyph_box`), and the program read that tells a
symbolic TrueType font's codes as Mac OS Roman
(`symbolic_mac_roman_texts`). Both are now paid for from the allowance
the metrics are read within, and a refusal sets `past_allowance`. The
glyph descriptions keep their own limits (64 KiB each, 4 MiB a font).
- Constants and helpers both sides added beside one another, and the
tests both appended, are all kept. Callers main added of
`unicode_map` pass a reading without a total, as `from_dict` does.
- crates/edit/src/rules.rs: the refusal of a page whose content didn't
all decode stays, and the operators come from main's `text::joined`.
Tests: `adapting_a_programs_cmap_is_paid_for` now expects a composite
font's ToUnicode, and a simple font's program read again for the Mac OS
Roman reading, to be paid for. Four are new:
`adapting_a_composite_subsets_cmap_is_paid_for`,
`a_full_cid_map_rebuilds_a_composite_subsets_cmap_behind_two_filters`,
`type3_glyph_boxes_are_paid_for_from_the_allowance` and
`the_symbolic_mac_roman_reading_is_paid_for`. The fixture of main's test
of a composite subset without a cmap moves into `cmapless_composite`,
which both use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
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>
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>
- 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>
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>
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.
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.
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.
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.
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.
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.
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.
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.