mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-26 03:06:56 -06:00
Integration: #825 PDF table-cell and footnote text lays a gradient across its own lines, as on screen
(cherry picked from commit 14e98419ad)
This commit is contained in:
@@ -2612,4 +2612,75 @@ mod gradient_text_tests {
|
||||
let d = p.doc().unwrap().doc.clone();
|
||||
check(&rr.render_page(&d, &p.cache, 0, 1.0, false, &Default::default()).unwrap(), "pdf");
|
||||
}
|
||||
|
||||
/// The columns the red → blue ink spans (black table edges left out).
|
||||
fn ink_span(img: &designcraft_render::Rendered) -> (u32, u32) {
|
||||
let cols: Vec<u32> = (0..img.width)
|
||||
.filter(|&x| {
|
||||
(0..img.height).any(|y| {
|
||||
let p = img.pixel(x, y);
|
||||
p[1] < 100 && p[0].max(p[2]) > 100
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
(*cols.first().unwrap(), *cols.last().unwrap())
|
||||
}
|
||||
|
||||
/// Mean red and blue of the red → blue ink in columns `x0..x1`.
|
||||
fn coloured_ink(img: &designcraft_render::Rendered, x0: u32, x1: u32) -> (f64, f64) {
|
||||
let (mut r, mut b, mut n) = (0.0, 0.0, 0.0);
|
||||
for x in x0..x1 {
|
||||
for y in 0..img.height {
|
||||
let p = img.pixel(x, y);
|
||||
if p[1] < 100 && p[0].max(p[2]) > 100 {
|
||||
(r, b, n) = (r + p[0] as f64, b + p[2] as f64, n + 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(n > 20.0, "no ink in {x0}..{x1}");
|
||||
(r / n, b / n)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gradient_on_table_cell_text_spans_the_cells_text_in_pdf_as_on_screen() {
|
||||
let mut s = crate::Session::new();
|
||||
s.execute("file.new", &json!({"width": 300, "height": 120, "facingPages": false, "margins": 0})).unwrap();
|
||||
let g = s
|
||||
.execute("swatch.create", &json!({"gradient": {"stops": [{"location": 0, "color": "#ff0000"}, {"location": 100, "color": "#0000ff"}]}}))
|
||||
.unwrap()["name"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let r = s.execute("frame.create", &json!({"rect": [0, 0, 300, 120], "content": "text", "text": ""})).unwrap();
|
||||
s.execute("text.select", &json!({"story": r["story"], "anchor": 0, "focus": 0})).unwrap();
|
||||
s.execute("table.insert", &json!({"rows": 1, "cols": 3})).unwrap();
|
||||
// A third of the frame's width: the cell's text, red → blue.
|
||||
let mut d: designcraft_doc::Document = (*s.doc().unwrap().doc).clone();
|
||||
let sid = d.stories.values().find(|st| !st.tables.is_empty()).unwrap().id;
|
||||
let t = std::sync::Arc::make_mut(d.story_mut(sid).unwrap().tables.values_mut().next().unwrap());
|
||||
let text = &mut t.cell_mut(0, 0).unwrap().text;
|
||||
text.insert(0, "HHHH");
|
||||
text.format_chars(0..4, |f| {
|
||||
f.over.fill = Some(g.clone());
|
||||
f.over.size = Some(24.0);
|
||||
});
|
||||
let mut rr = designcraft_render::Renderer::new();
|
||||
let cache = designcraft_compose::Cache::new();
|
||||
let screen = rr.render_page(&d, &cache, 0, 1.0, false, &Default::default()).unwrap();
|
||||
let pdf = designcraft_pdf::export_pdf(&d, &cache, &Default::default()).unwrap();
|
||||
let mut p = crate::Session::new();
|
||||
p.execute("file.new", &json!({"width": 300, "height": 120, "facingPages": false})).unwrap();
|
||||
p.execute("file.place", &json!({"base64": super::super::file::base64_encode(&pdf), "name": "cell.pdf", "x": 0, "y": 0, "width": 300}))
|
||||
.unwrap();
|
||||
let placed = rr.render_page(&p.doc().unwrap().doc.clone(), &p.cache, 0, 1.0, false, &Default::default()).unwrap();
|
||||
let (x0, x1) = ink_span(&screen);
|
||||
assert!(x1 < 120, "the text stays in the first column: {x0}..{x1}");
|
||||
let q = (x1 - x0) / 4;
|
||||
for (what, img) in [("screen", &screen), ("pdf", &placed)] {
|
||||
let (lr, lb) = coloured_ink(img, x0, x0 + q);
|
||||
let (rr, rb) = coloured_ink(img, x1 + 1 - q, x1 + 1);
|
||||
assert!(lr > lb + 50.0, "{what}: the left ink is red ({lr:.0}, {lb:.0})");
|
||||
assert!(rb > rr + 50.0, "{what}: the right ink is blue ({rr:.0}, {rb:.0})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,22 @@ impl Exporter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The box a gradient on a cell's or a note's text spans: its lines, each padded by its ascent as
|
||||
/// the screen renderer pads them, so both lay the gradient out alike.
|
||||
fn lines_area(ft: &FrameText) -> Option<Rect> {
|
||||
ft.lines
|
||||
.iter()
|
||||
.filter_map(|l| {
|
||||
let first = l.glyphs.first()?;
|
||||
let x0 = l.glyphs.iter().map(|g| g.x).fold(first.x, f64::min);
|
||||
let x1 = l.glyphs.iter().map(|g| g.x + g.adv.max(0.0)).fold(l.end_x, f64::max);
|
||||
let pad = l.ascent.max(1.0);
|
||||
let bottom = l.drop_cap.map_or(l.baseline + l.descent.max(0.0), |dc| dc.rect.y1.max(l.baseline + l.descent.max(0.0)));
|
||||
Some(Rect::new(x0 - pad, l.baseline - l.ascent - pad, x1 + pad, bottom + pad))
|
||||
})
|
||||
.reduce(|a, b| a.union(b))
|
||||
}
|
||||
|
||||
/// An opaque nonzero fill with `paint`.
|
||||
fn paint_fill(paint: Paint) -> Fill {
|
||||
Fill { paint, opacity: NormalizedF32::ONE, rule: FillRule::NonZero }
|
||||
@@ -195,7 +211,9 @@ impl Exporter<'_> {
|
||||
for n in &ft.notes {
|
||||
if let Some(nft) = n.text.frames.first() {
|
||||
s.push_transform(&tf(Affine::translate(n.origin.to_vec2())));
|
||||
let area = std::mem::replace(&mut me.text_area, lines_area(nft));
|
||||
me.frame_text(s, &n.text, nft, &n.source);
|
||||
me.text_area = area;
|
||||
s.pop();
|
||||
}
|
||||
}
|
||||
@@ -223,7 +241,9 @@ impl Exporter<'_> {
|
||||
}
|
||||
} else if let Some(cft) = c.text.frames.first() {
|
||||
s.push_transform(&tf(Affine::translate(c.origin.to_vec2())));
|
||||
let area = std::mem::replace(&mut self.text_area, lines_area(cft));
|
||||
self.frame_text(s, &c.text, cft, &c.source);
|
||||
self.text_area = area;
|
||||
s.pop();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user