mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-06 18:52:36 +00:00
fix(edit): reach the styled rewrite's rows at the step they are written
The styled path writes rows `line_spacing` (1.5 by default) times the size apart when a size or spacing is set, but its Reach assumed 1.2, so an image over only the lower rows of a larger rewrite was missed. The pitch now comes from the row writer's own step (row_step, shared by both), at the largest size among the paragraph's lines and ranges. The landing test gains a six-row rewrite at twice the size over an image covering only its last row; it fails with the 1.2 step. A case with only line_spacing set covers the other branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
This commit is contained in:
1 parent
705d862328
commit
8fd151ff79
2 files changed
+22
-8
No files matched your search
@@ -4577,6 +4577,8 @@ fn revealed_text_moves_on_top_only_when_painted_over() {
|
||||
fn an_ocr_rewrite_goes_on_top_of_paint_over_where_it_lands() {
|
||||
let scan = "q 200 0 0 100 100 100 cm /Im0 Do Q";
|
||||
let moved = text::BlockStyle { offset: Some([-290.0, -450.0]), ..Default::default() };
|
||||
let sized = text::BlockStyle { size: Some(24.0), ..Default::default() };
|
||||
let spaced = text::BlockStyle { line_spacing: Some(3.0), ..Default::default() };
|
||||
let cases = [
|
||||
("BT /F1 12 Tf 3 Tr 400 600 Td (Away) Tj ET", "Moved", &moved),
|
||||
("BT /F1 12 Tf 3 Tr 400 600 Td (Away) Tj ET", "Moved\nTwice", &moved),
|
||||
@@ -4584,7 +4586,12 @@ fn an_ocr_rewrite_goes_on_top_of_paint_over_where_it_lands() {
|
||||
("BT /F1 12 Tf 3 Tr 40 150 Td (Hi) Tj ET", "Hello there,\nthis line now runs well past the scan's left edge", &text::BlockStyle::default()),
|
||||
("BT /F1 12 Tf 3 Tr 110 230 Td (Above) Tj ET", "One\nTwo\nThree\nFour\nFive\nSix", &text::BlockStyle::default()),
|
||||
];
|
||||
for (content, edit, style) in cases {
|
||||
let low = "q 200 0 0 30 100 300 cm /Im0 Do Q";
|
||||
let cases = cases.into_iter().map(|(c, e, s)| (c, e, s, scan)).chain([
|
||||
("BT /F1 12 Tf 3 Tr 110 500 Td (Above) Tj ET", "One\nTwo\nThree\nFour\nFive\nSix", &sized, low),
|
||||
("BT /F1 12 Tf 3 Tr 110 500 Td (Above) Tj ET", "One\nTwo\nThree\nFour\nFive\nSix", &spaced, low),
|
||||
]);
|
||||
for (content, edit, style, scan) in cases {
|
||||
let mut doc = image_page_with(&format!("{content} {scan}"));
|
||||
let before = text::text_blocks(&doc, 0).unwrap().remove(0);
|
||||
let ops = pdfcraft_content::parse(&page_content_bytes(&doc, 0)).ops;
|
||||
|
||||
@@ -402,6 +402,16 @@ fn paragraph_geometry(doc: &Document, page: &pdfcraft_model::Page, block: &TextB
|
||||
Ok((dx, dy, style.width.unwrap_or(natural).clamp(1.0, 14_400.0)))
|
||||
}
|
||||
|
||||
/// How far below a row of `size` the next one is written: the source's leading unless a size
|
||||
/// or line spacing is chosen.
|
||||
fn row_step(style: &BlockStyle, size: f64, source_leading: f64) -> f64 {
|
||||
if style.line_spacing.is_none() && style.size.is_none() {
|
||||
source_leading
|
||||
} else {
|
||||
style.line_spacing.unwrap_or(1.5).clamp(0.5, 5.0) * style.size.unwrap_or(size)
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_styled_rows(
|
||||
chars: &[StyledChar],
|
||||
width: f64,
|
||||
@@ -812,8 +822,9 @@ pub(crate) fn rewrite_styled_block(
|
||||
.unwrap_or(first.size * 1.5);
|
||||
// Revealed OCR text that something is painted over goes on top of the page, where it starts
|
||||
// from the initial state.
|
||||
let largest = style.ranges.iter().filter_map(|r| r.size).chain(style.size).fold(first.size, f64::max);
|
||||
let pitch = source_leading.max(largest * style.line_spacing.unwrap_or(1.2));
|
||||
let largest =
|
||||
block.lines.iter().filter_map(|i| lines.get(*i)).map(|l| l.size).chain(style.ranges.iter().filter_map(|r| r.size)).fold(first.size, f64::max);
|
||||
let pitch = source_leading.max(row_step(style, largest, source_leading));
|
||||
let widest = measured.iter().copied().fold(width, f64::max);
|
||||
// This path lays rows out along user-space baselines, so the paragraph is upright here.
|
||||
let reach = crate::Reach { rect: block.rect, offset: [dx, dy], width: widest, rows: rows.len(), pitch, upright: true };
|
||||
@@ -972,11 +983,7 @@ pub(crate) fn rewrite_styled_block(
|
||||
x += segment_width;
|
||||
pos = end;
|
||||
}
|
||||
baseline -= if style.line_spacing.is_none() && style.size.is_none() {
|
||||
source_leading
|
||||
} else {
|
||||
style.line_spacing.unwrap_or(1.5).clamp(0.5, 5.0) * style.size.unwrap_or(anchor.size)
|
||||
};
|
||||
baseline -= row_step(style, anchor.size, source_leading);
|
||||
}
|
||||
block_ops.push(Op::new("Q", vec![]));
|
||||
let on_top = crate::lift_on_top(over, &mut block_ops);
|
||||
|
||||
Reference in new issue
Block a user