mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-01 05:58:12 -06:00
crates/ui-egui/src/edit_text_ui.rs: kept the per-paragraph draft_rect and the fade_in(false) Area builder; added #3070's page_clip and the clip on the editor's ui.
This commit is contained in:
@@ -1162,7 +1162,8 @@ pub(crate) fn page_input(
|
||||
return true;
|
||||
}
|
||||
let boxes: Vec<Rect> = lines.iter().map(|l| xf.user_rect(info, page, l.rect.map(|v| v as f32)).expand(2.0)).collect();
|
||||
let painter = ui.painter();
|
||||
// Outlines stay on their page, like its text (#3061).
|
||||
let painter = ui.painter().with_clip_rect(ui.clip_rect().intersect(xf.rect.expand(1.0)));
|
||||
let active = view.line_editor.as_ref().filter(|e| e.page == page).map(|e| e.block);
|
||||
for (i, b) in boxes.iter().enumerate() {
|
||||
if active != Some(i) {
|
||||
@@ -1504,6 +1505,10 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) -
|
||||
.as_ref()
|
||||
.and_then(|ed| view.text_background.draft_rect_for(ed.page, ed.block))
|
||||
.map(|r| xf.user_rect(info, page, r.map(|v| v as f32)));
|
||||
// The editor draws on its page only, as the page itself does: a paragraph running past the
|
||||
// page's edge (a converter relying on the page clip) must not cover the next page or the
|
||||
// tool bars (#3061).
|
||||
let page_clip = xf.rect.intersect(view.viewport_rect());
|
||||
let ed = view.line_editor.as_mut()?;
|
||||
if hold {
|
||||
ed.focus = true;
|
||||
@@ -1537,6 +1542,7 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) -
|
||||
.fade_in(false)
|
||||
.fixed_pos(Pos2::new(ed.rect.left(), ed.rect.top()))
|
||||
.show(ctx, |ui| {
|
||||
ui.set_clip_rect(ui.clip_rect().intersect(page_clip));
|
||||
// The editor opens at the source size. Once text changes, show the real draft
|
||||
// extent: single rows grow toward the page edge, paragraphs rewrap at their
|
||||
// source width, and the visible frame grows/shrinks vertically with their rows.
|
||||
|
||||
@@ -5339,3 +5339,123 @@ fn read_mode_shortcut_applies_the_open_paragraph() {
|
||||
assert!(s.views[0].line_editor.is_none(), "no editor stays open in Read mode");
|
||||
assert_eq!(s.session.get(s.views[0].id).unwrap().text_lines(0)[0].text, "Chapter One", "the edit is applied");
|
||||
}
|
||||
|
||||
/// #3061: a paragraph running past its page's bottom edge (an HTML converter relying on the
|
||||
/// page clip) opens in an editor that draws on its page only. It covered the next page.
|
||||
#[test]
|
||||
fn a_paragraph_running_off_its_page_edits_without_covering_the_next_page() {
|
||||
let text = |first: &str, count: usize, leading: usize| {
|
||||
let mut s = String::from("BT /F1 14 Tf\n");
|
||||
for i in 0..count {
|
||||
s.push_str(&format!("1 0 0 1 72 {} Tm ({first} line {}: the client may cancel this) Tj\n", 720 - (leading * i) as i64, i + 1));
|
||||
}
|
||||
s.push_str("ET");
|
||||
s.into_bytes()
|
||||
};
|
||||
// Page 1's single paragraph goes down to y = -270; page 2 is ordinary.
|
||||
let (one, two) = (text("Page one", 56, 18), text("PAGE TWO", 3, 40));
|
||||
let stream = |d: &[u8]| [format!("<< /Length {} >>\nstream\n", d.len()).into_bytes(), d.to_vec(), b"\nendstream".to_vec()].concat();
|
||||
let page = |contents: usize| {
|
||||
format!("<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents {contents} 0 R /Resources << /Font << /F1 7 0 R >> >> >>")
|
||||
.into_bytes()
|
||||
};
|
||||
let objs: Vec<Vec<u8>> = vec![
|
||||
b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
|
||||
b"<< /Type /Pages /Kids [3 0 R 5 0 R] /Count 2 >>".to_vec(),
|
||||
page(4),
|
||||
stream(&one),
|
||||
page(6),
|
||||
stream(&two),
|
||||
b"<< /Type /Font /Subtype /Type1 /BaseFont /Times-Roman >>".to_vec(),
|
||||
];
|
||||
let mut bytes = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (i, o) in objs.iter().enumerate() {
|
||||
offsets.push(bytes.len());
|
||||
bytes.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
|
||||
bytes.extend_from_slice(o);
|
||||
bytes.extend_from_slice(b"\nendobj\n");
|
||||
}
|
||||
let xref = bytes.len();
|
||||
bytes.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
|
||||
for o in offsets {
|
||||
bytes.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
|
||||
}
|
||||
bytes.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objs.len() + 1).as_bytes());
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1200.0, 1400.0)).build_eframe(move |_cc| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
app.open_bytes("off-page.pdf", None, bytes).unwrap();
|
||||
app
|
||||
});
|
||||
h.run_steps(4);
|
||||
h.state_mut().views[0].fit = pdfcraft_ui_egui::canvas::Fit::Page;
|
||||
h.run_steps(4);
|
||||
let rects =
|
||||
|h: &Harness<'static, PdfCraftApp>| (h.state().views[0].page_screen_rect(0).unwrap(), h.state().views[0].page_screen_rect(1).unwrap());
|
||||
// The same pages without Edit text, so without the paragraph's outline: what the gap looks
|
||||
// like there, pixel by pixel (review on #3070: it isn't one colour on every platform).
|
||||
let (first, second) = rects(&h);
|
||||
let plain = h.render().unwrap();
|
||||
assert!(h.state_mut().execute("edit.edit_text"));
|
||||
h.run_steps(4);
|
||||
assert_eq!(rects(&h), (first, second), "Edit text keeps the pages where they were");
|
||||
let id = h.state().views[0].id;
|
||||
let info = h.state().session.get(id).unwrap().info.pages[0].clone();
|
||||
shown_on_its_page_only(&mut h, &plain, first, second, "the paragraph's outline");
|
||||
let v = info.user_to_view(200.0, 378.0);
|
||||
let p = first.min + egui::vec2(v[0], v[1]) * (first.width() / info.width);
|
||||
h.hover_at(p);
|
||||
h.run_steps(1);
|
||||
h.drag_at(p);
|
||||
h.run_steps(1);
|
||||
h.drop_at(p);
|
||||
for _ in 0..60 {
|
||||
h.run_steps(2);
|
||||
if !h.state().render_pending() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
assert!(
|
||||
h.state().views[0].line_editor.as_ref().is_some_and(|e| e.page == 0 && e.text.contains("line 50")),
|
||||
"the paragraph's off-page lines are in it"
|
||||
);
|
||||
shown_on_its_page_only(&mut h, &plain, first, second, "the open editor");
|
||||
// Scrolled up past the tool bar, the editor stays inside the document area.
|
||||
let viewport = h.state().views[0].viewport_rect();
|
||||
let above = viewport.top() - 3.0;
|
||||
let before = h.render().unwrap();
|
||||
h.hover_at(egui::pos2(first.right() - 20.0, first.center().y));
|
||||
h.event(egui::Event::MouseWheel {
|
||||
unit: egui::MouseWheelUnit::Point,
|
||||
delta: egui::vec2(0.0, -300.0),
|
||||
phase: egui::TouchPhase::Move,
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
});
|
||||
h.run_steps(4);
|
||||
let scrolled = h.state().views[0].page_screen_rect(0).unwrap();
|
||||
assert!(scrolled.top() < viewport.top() - 100.0, "scrolled: {scrolled:?} in {viewport:?}");
|
||||
assert!(h.state().views[0].line_editor.is_some(), "still editing");
|
||||
let after = h.render().unwrap();
|
||||
for x in (first.left() as u32 + 70)..(first.left() as u32 + 420) {
|
||||
assert_eq!(after.get_pixel(x, above as u32), before.get_pixel(x, above as u32), "above the document area at x = {x}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Between pages `first` and `second`, and on `second` above its own text, across where page
|
||||
/// 1's paragraph is: what is there without it (the gap as `plain` shows it without Edit text,
|
||||
/// white paper).
|
||||
fn shown_on_its_page_only(h: &mut Harness<'static, PdfCraftApp>, plain: &image::RgbaImage, first: egui::Rect, second: egui::Rect, what: &str) {
|
||||
let shot = h.render().unwrap();
|
||||
let pixel = |x: f32, y: f32| shot.get_pixel(x as u32, y as u32).0;
|
||||
let gap_y = (first.bottom() + second.top()) / 2.0;
|
||||
for x in (first.left() as u32 + 70)..(first.left() as u32 + 420) {
|
||||
let x = x as f32;
|
||||
assert_eq!(pixel(x, gap_y), plain.get_pixel(x as u32, gap_y as u32).0, "{what}: between the pages at x = {x}");
|
||||
for dy in [4.0, 20.0, 40.0] {
|
||||
let [r, g, b, _] = pixel(x, second.top() + dy);
|
||||
assert!(r > 240 && g > 240 && b > 240, "{what}: page 2 at {x}, +{dy}: {r},{g},{b}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user