diff --git a/crates/render/src/blendspace.rs b/crates/render/src/blendspace.rs index ea14caaad..73855cea9 100644 --- a/crates/render/src/blendspace.rs +++ b/crates/render/src/blendspace.rs @@ -446,8 +446,7 @@ mod tests { doc, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }], - 200, - 140, + (200, 140), Affine::translate((30.0, 10.0)), &RenderOptions { blend_space_view: true, background: Some([180, 180, 180, 255]), ..Default::default() }, ) @@ -589,8 +588,7 @@ mod tests { &doc, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }], - 200, - 140, + (200, 140), Affine::translate((30.0, 10.0)), &cyan, ); @@ -649,10 +647,11 @@ mod tests { renderer.threads = 0; let placements = [Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }]; let options = RenderOptions { paper: false, blend_space_view: true, ..Default::default() }; - let out = renderer.render(&doc, &Cache::new(), &placements, 160, 120, Affine::IDENTITY, &options); + let out = renderer.render(&doc, &Cache::new(), &placements, (160, 120), Affine::IDENTITY, &options); assert!(out.pixel(30, 40)[3].abs_diff(64) <= 1, "half-opacity object inside a half-opacity group is quarter opaque"); - let cmy = renderer.render(&doc, &Cache::new(), &placements, 160, 120, Affine::IDENTITY, &RenderOptions { plate: Some(4), ..options.clone() }); - let k = renderer.render(&doc, &Cache::new(), &placements, 160, 120, Affine::IDENTITY, &RenderOptions { plate: Some(3), ..options }); + let cmy = + renderer.render(&doc, &Cache::new(), &placements, (160, 120), Affine::IDENTITY, &RenderOptions { plate: Some(4), ..options.clone() }); + let k = renderer.render(&doc, &Cache::new(), &placements, (160, 120), Affine::IDENTITY, &RenderOptions { plate: Some(3), ..options }); for ((out, cmy), k) in out.pixels.as_chunks::<4>().0.iter().zip(cmy.pixels.as_chunks::<4>().0).zip(k.pixels.as_chunks::<4>().0) { assert_eq!(cmy[3], k[3], "all ink passes have the same coverage"); assert_eq!(out[3], cmy[3], "conversion preserves coverage"); diff --git a/crates/render/src/color.rs b/crates/render/src/color.rs index c37193bed..c4406b73c 100644 --- a/crates/render/src/color.rs +++ b/crates/render/src/color.rs @@ -1,5 +1,5 @@ use super::types::{Frame, Rendered}; -use super::*; +use super::{Affine, BlendMode, Compose, DcBlend, Document, Item, Mix, Rect, RenderContext, peniko, vello_cpu}; const CMYK_N: usize = 17; diff --git a/crates/render/src/damage.rs b/crates/render/src/damage.rs index 31f9757e0..3c11a5c20 100644 --- a/crates/render/src/damage.rs +++ b/crates/render/src/damage.rs @@ -257,8 +257,8 @@ mod tests { let mut r = Renderer::new(); r.threads = 0; let opts = RenderOptions::default(); - let mut img = r.render(old, &cache, &placed, w, h, view, &opts); - let full = r.render(new, &cache, &placed, w, h, view, &opts); + let mut img = r.render(old, &cache, &placed, (w, h), view, &opts); + let full = r.render(new, &cache, &placed, (w, h), view, &opts); // Patch the damaged pixels. let regions = damage(old, new).unwrap(); let u = regions.iter().map(|(_, r)| *r).reduce(|a, b| a.union(b)).unwrap(); @@ -266,7 +266,7 @@ mod tests { let y0 = ((u.y0 * zoom).floor().max(0.0)) as u32; let x1 = ((u.x1 * zoom).ceil() as u32).min(w); let y1 = ((u.y1 * zoom).ceil() as u32).min(h); - let patch = r.render(new, &cache, &placed, x1 - x0, y1 - y0, Affine::translate((-(x0 as f64), -(y0 as f64))) * view, &opts); + let patch = r.render(new, &cache, &placed, (x1 - x0, y1 - y0), Affine::translate((-(x0 as f64), -(y0 as f64))) * view, &opts); for y in y0..y1 { for x in x0..x1 { let pi = (((y - y0) * (x1 - x0) + (x - x0)) * 4) as usize; diff --git a/crates/render/src/decode.rs b/crates/render/src/decode.rs index ec4f515a5..135dbf108 100644 --- a/crates/render/src/decode.rs +++ b/crates/render/src/decode.rs @@ -1,5 +1,5 @@ use super::pdf_raster::{is_pdf, pdf_page_size, render_pdf_page}; -use super::*; +use super::{Pixmap, vello_cpu}; /// Decode encoded image bytes into a premultiplied pixmap. pub fn decode_pixmap(bytes: &[u8]) -> Option { diff --git a/crates/render/src/draw.rs b/crates/render/src/draw.rs index 6aa81086e..9780e820e 100644 --- a/crates/render/src/draw.rs +++ b/crates/render/src/draw.rs @@ -1,6 +1,9 @@ use super::color::{HIDDEN_LAYERS, blend_mode, color_of, overprints, rect_overlaps, screen_color, set_fill_paint, spread_blends}; use super::types::{DisplayQuality, Frame}; -use super::*; +use super::{ + Affine, Arc, BezPath, BlendMode, Compose, ComposedStory, Content, DcBlend, Item, Mix, RenderContext, Renderer, Shape, ShownOn, SpreadRef, + StoryId, Vec2, blendspace, corners, fx, grid, kurbo, peniko, +}; /// An item's fill under its content; an overprinting fill multiplies into what is beneath. fn draw_body_fill(ctx: &mut RenderContext, f: &Frame, it: &Item, bp: &BezPath, xf: Affine) { diff --git a/crates/render/src/driver.rs b/crates/render/src/driver.rs index 08d1680a5..a5dd2b86b 100644 --- a/crates/render/src/driver.rs +++ b/crates/render/src/driver.rs @@ -1,7 +1,9 @@ use super::color::{PLATE, RICH_BLACK, blend_space_view, rect_overlaps}; use super::decode::now; use super::types::{Frame, FrameStats, MAX_PAGE_PIXELS, MAX_SIDE, Placed, RenderOptions, Rendered, default_threads}; -use super::*; +use super::{ + Affine, Cache, Document, HashMap, PageSide, Rect, RenderContext, Renderer, Resources, SpreadRef, Vec2, blendspace, kurbo, peniko, text, vello_cpu, +}; impl Renderer { pub fn new() -> Self { @@ -37,17 +39,9 @@ impl Renderer { } /// Render the given spreads into a `width`×`height` image with `view` (canvas → pixels). - pub fn render( - &mut self, - doc: &Document, - cache: &Cache, - spreads: &[Placed], - width: u32, - height: u32, - view: Affine, - opts: &RenderOptions, - ) -> Rendered { + pub fn render(&mut self, doc: &Document, cache: &Cache, spreads: &[Placed], size: (u32, u32), view: Affine, opts: &RenderOptions) -> Rendered { let start = now(); + let (width, height) = size; RICH_BLACK.with(|r| r.set(opts.rich_black)); PLATE.with(|p| p.set(opts.plate)); if let Some(hit) = self.cmyk_view.as_ref().filter(|entry| entry.matches(doc, spreads, width, height, view, opts, self.threads)) { @@ -59,7 +53,7 @@ impl Renderer { // Release the old snapshot before rendering an edited document or resized viewport. self.cmyk_view = None; self.cmyk_ran = false; - let image = self.render_uncached(doc, cache, spreads, width, height, view, opts); + let image = self.render_uncached(doc, cache, spreads, size, view, opts); // Kept only when the ink passes ran: other views render at their usual cost anyway. if self.cmyk_ran && image.pixels.len() <= blendspace::CachedView::MAX_BYTES { self.cmyk_view = Some(blendspace::CachedView::new(doc, spreads, view, opts, &image, self.stats, self.threads)); @@ -68,18 +62,17 @@ impl Renderer { image } - #[allow(clippy::too_many_arguments)] fn render_uncached( &mut self, doc: &Document, cache: &Cache, spreads: &[Placed], - width: u32, - height: u32, + size: (u32, u32), view: Affine, opts: &RenderOptions, ) -> Rendered { let start = now(); + let (width, height) = size; let w = width.clamp(1, MAX_SIDE) as u16; let h = height.clamp(1, MAX_SIDE) as u16; let mut ctx = match self.ctx.take() { @@ -156,6 +149,6 @@ impl Renderer { let view = Affine::scale(scale) * Affine::translate(-r.origin().to_vec2()); let mut o = opts.clone(); o.background = Some([255, 255, 255, 255]); - Some(self.render(doc, cache, &[Placed { spread: SpreadRef::Doc(si), xf: Affine::translate(Vec2::ZERO) }], w, h, view, &o)) + Some(self.render(doc, cache, &[Placed { spread: SpreadRef::Doc(si), xf: Affine::translate(Vec2::ZERO) }], (w, h), view, &o)) } } diff --git a/crates/render/src/pdf_raster.rs b/crates/render/src/pdf_raster.rs index bb3caeb43..76160be96 100644 --- a/crates/render/src/pdf_raster.rs +++ b/crates/render/src/pdf_raster.rs @@ -1,5 +1,5 @@ use super::types::Rendered; -use super::*; +use super::{Affine, Arc, HashMap, Pixmap, Rect, pdf_bounds, pdf_hide_layers, pdf_layers, vello_cpu}; /// MIME type guess for encoded image bytes. /// Is this a PDF file (placed PDFs are graphics)? diff --git a/crates/render/src/stroke.rs b/crates/render/src/stroke.rs index bff888b09..c9d8b6acb 100644 --- a/crates/render/src/stroke.rs +++ b/crates/render/src/stroke.rs @@ -1,7 +1,10 @@ use super::color::{HIDDEN_LAYERS, PLATE, color_of, overprints, plate_pixmap, screen_color, set_fill_paint}; use super::pdf_raster::{is_pdf, shown_box}; use super::types::{DisplayQuality, Frame}; -use super::*; +use super::{ + Affine, BezPath, BlendMode, Compose, Item, Mix, Rect, RenderContext, Renderer, Shape, StrokeAlign, StrokeType, colorize, images, kurbo, + pdf_layers, peniko, vello_cpu, +}; impl Renderer { pub(crate) fn draw_stroke(&mut self, ctx: &mut RenderContext, f: &Frame, it: &Item, bp: &BezPath, xf: Affine) { diff --git a/crates/render/src/tests.rs b/crates/render/src/tests.rs index 708b6bd43..295a0da5f 100644 --- a/crates/render/src/tests.rs +++ b/crates/render/src/tests.rs @@ -64,11 +64,11 @@ fn very_wide_renders_do_not_panic() { let doc = Document::new(&NewDocument::default()); let cache = Cache::new(); let mut r = Renderer::new(); - let img = r.render(&doc, &cache, &[], 70_000, 4, Affine::IDENTITY, &RenderOptions::default()); + let img = r.render(&doc, &cache, &[], (70_000, 4), Affine::IDENTITY, &RenderOptions::default()); assert_eq!(img.width, MAX_SIDE); let page = [Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }]; let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() }; - let img = r.render(&doc, &cache, &page, 70_000, 64, Affine::scale(120.0), &opts); + let img = r.render(&doc, &cache, &page, (70_000, 64), Affine::scale(120.0), &opts); assert_eq!(img.width, MAX_SIDE); assert!(r.render_page(&doc, &cache, 0, 500.0, true, &RenderOptions::default()).is_none()); } diff --git a/crates/render/src/text.rs b/crates/render/src/text.rs index c29107c36..3797f508e 100644 --- a/crates/render/src/text.rs +++ b/crates/render/src/text.rs @@ -250,6 +250,117 @@ fn line_box(l: &Line) -> Option { Some(Rect::new(x0 - pad, l.baseline - l.ascent - pad, x1 + pad, bottom + pad)) } +fn draw_text_indicators(ctx: &mut RenderContext, f: &Frame, cs: &ComposedStory, ft: &FrameText, m: Affine) { + // Text in fonts that aren't installed: pink behind the glyphs (screen only). + if f.opts.highlight_missing_fonts && cs.styles.iter().any(|s| s.missing_font) { + ctx.set_transform(m); + ctx.set_paint(screen_color([255, 166, 214], 255)); + for l in &ft.lines { + for g in l.glyphs.iter().filter(|g| g.adv > 0.0 && cs.styles.get(g.style as usize).is_some_and(|s| s.missing_font)) { + ctx.fill_rect(&kurbo::Rect::new(g.x, l.baseline - l.ascent, g.x + g.adv, l.baseline + l.descent)); + } + } + } + if f.opts.highlight_keeps && ft.lines.iter().any(|l| l.keep_violation) { + ctx.set_transform(m); + ctx.set_paint(screen_color([255, 236, 120], 255)); + for l in ft.lines.iter().filter(|l| l.keep_violation) { + ctx.fill_rect(&kurbo::Rect::new(l.x0, l.baseline - l.ascent, l.x1, l.baseline + l.descent)); + } + } + if f.opts.highlight_hj && ft.lines.iter().any(|l| l.hj > 0) { + ctx.set_transform(m); + for l in ft.lines.iter().filter(|l| l.hj > 0) { + let shade = [0u8, 0xF6, 0xEA, 0xD8][l.hj.min(3) as usize]; + ctx.set_paint(screen_color([255, shade, [0u8, 0xB0, 0x70, 0x30][l.hj.min(3) as usize]], 255)); + ctx.fill_rect(&kurbo::Rect::new(l.x0, l.baseline - l.ascent, l.end_x.max(l.x0 + 1.0), l.baseline + l.descent)); + } + } + if f.opts.highlight_custom_tracking && cs.styles.iter().any(|s| s.custom_tracking) { + ctx.set_transform(m); + ctx.set_paint(screen_color([176, 236, 176], 255)); + for l in &ft.lines { + for g in l.glyphs.iter().filter(|g| g.adv > 0.0 && cs.styles.get(g.style as usize).is_some_and(|s| s.custom_tracking)) { + ctx.fill_rect(&kurbo::Rect::new(g.x, l.baseline - l.ascent, g.x + g.adv, l.baseline + l.descent)); + } + } + } + if f.opts.tag_markers && cs.styles.iter().any(|s| s.xml_tag.is_some()) { + ctx.set_transform(m); + let px = 1.0 / m.determinant().abs().sqrt().max(1e-9); + for l in &ft.lines { + let tag_of = |g: &designcraft_compose::PlacedGlyph| cs.styles.get(g.style as usize).and_then(|s| s.xml_tag.clone()); + let gs: Vec<_> = l.glyphs.iter().filter(|g| g.len > 0).collect(); + let mut i = 0; + while i < gs.len() { + let Some(tag) = tag_of(gs[i]) else { + i += 1; + continue; + }; + let mut j = i + 1; + while j < gs.len() && tag_of(gs[j]).as_deref() == Some(tag.as_str()) { + j += 1; + } + let c = f.doc.xml.tags.iter().find(|t| t.name == tag).map_or([100, 100, 220], |t| t.color); + ctx.set_paint(screen_color([c[0], c[1], c[2]], 255)); + let (x0, x1) = (gs[i].x, gs[j - 1].x + gs[j - 1].adv); + let (top, bot) = (l.baseline - l.ascent, l.baseline + l.descent); + let (w, t) = (2.5 * px, px.max(0.3)); + // [ before the element, ] after it. + for (x, dir) in [(x0, 1.0), (x1, -1.0)] { + ctx.fill_rect(&kurbo::Rect::new(x - t / 2.0, top, x + t / 2.0, bot)); + for y in [top, bot - t] { + let (a, b) = if dir > 0.0 { (x, x + w) } else { (x - w, x) }; + ctx.fill_rect(&kurbo::Rect::new(a, y, b, y + t)); + } + } + i = j; + } + } + } + if f.opts.change_markup && cs.styles.iter().any(|s| s.inserted) { + ctx.set_transform(m); + for l in &ft.lines { + for g in l.glyphs.iter().filter(|g| g.adv > 0.0 && cs.styles.get(g.style as usize).is_some_and(|s| s.inserted)) { + ctx.set_paint(screen_color([120, 190, 255], 110)); + ctx.fill_rect(&kurbo::Rect::new(g.x, l.baseline - l.ascent, g.x + g.adv, l.baseline + l.descent)); + } + } + } + if f.opts.note_indicators + && let Some(st) = f.doc.story(cs.story).filter(|st| !st.editorial.is_empty()) + { + // A small flag at each note anchor, in the notes' amber. + ctx.set_transform(m); + ctx.set_paint(screen_color([232, 160, 32], 255)); + for l in &ft.lines { + for g in l.glyphs.iter().filter(|g| st.text.get(g.byte..).is_some_and(|t| t.starts_with(designcraft_doc::NOTE_MARK))) { + let h = l.ascent.max(4.0); + let mut p = BezPath::new(); + p.move_to((g.x - h * 0.25, l.baseline - h)); + p.line_to((g.x + h * 0.25, l.baseline - h)); + p.line_to((g.x, l.baseline - h * 0.55)); + p.close_path(); + ctx.fill_path(&p); + ctx.fill_rect(&kurbo::Rect::new(g.x - h * 0.03, l.baseline - h * 0.6, g.x + h * 0.03, l.baseline + l.descent * 0.5)); + } + } + } + if f.opts.condition_indicators && !f.doc.conditions.is_empty() && cs.styles.iter().any(|s| s.condition.is_some()) { + ctx.set_transform(m); + let px = 1.0 / m.determinant().abs().sqrt().max(1e-9); + for l in &ft.lines { + for g in l.glyphs.iter().filter(|g| g.visible && g.adv > 0.0) { + let Some(name) = cs.styles.get(g.style as usize).and_then(|s| s.condition.as_deref()) else { continue }; + let Some(c) = f.doc.conditions.iter().find(|c| c.name == name) else { continue }; + ctx.set_paint(screen_color([c.color[0], c.color[1], c.color[2]], 255)); + let y = l.baseline + l.descent * 0.6; + ctx.fill_rect(&kurbo::Rect::new(g.x, y, g.x + g.adv, y + 2.0 * px)); + } + } + } +} + impl Renderer { pub(crate) fn draw_text(&mut self, ctx: &mut RenderContext, f: &Frame, cs: &Arc, ft: &FrameText, xf: Affine) { let doc = f.doc; @@ -276,114 +387,7 @@ impl Renderer { if !ft.tables.is_empty() { self.draw_tables(ctx, f, ft, xf); } - // Text in fonts that aren't installed: pink behind the glyphs (screen only). - if f.opts.highlight_missing_fonts && cs.styles.iter().any(|s| s.missing_font) { - ctx.set_transform(m); - ctx.set_paint(screen_color([255, 166, 214], 255)); - for l in &ft.lines { - for g in l.glyphs.iter().filter(|g| g.adv > 0.0 && cs.styles.get(g.style as usize).is_some_and(|s| s.missing_font)) { - ctx.fill_rect(&kurbo::Rect::new(g.x, l.baseline - l.ascent, g.x + g.adv, l.baseline + l.descent)); - } - } - } - if f.opts.highlight_keeps && ft.lines.iter().any(|l| l.keep_violation) { - ctx.set_transform(m); - ctx.set_paint(screen_color([255, 236, 120], 255)); - for l in ft.lines.iter().filter(|l| l.keep_violation) { - ctx.fill_rect(&kurbo::Rect::new(l.x0, l.baseline - l.ascent, l.x1, l.baseline + l.descent)); - } - } - if f.opts.highlight_hj && ft.lines.iter().any(|l| l.hj > 0) { - ctx.set_transform(m); - for l in ft.lines.iter().filter(|l| l.hj > 0) { - let shade = [0u8, 0xF6, 0xEA, 0xD8][l.hj.min(3) as usize]; - ctx.set_paint(screen_color([255, shade, [0u8, 0xB0, 0x70, 0x30][l.hj.min(3) as usize]], 255)); - ctx.fill_rect(&kurbo::Rect::new(l.x0, l.baseline - l.ascent, l.end_x.max(l.x0 + 1.0), l.baseline + l.descent)); - } - } - if f.opts.highlight_custom_tracking && cs.styles.iter().any(|s| s.custom_tracking) { - ctx.set_transform(m); - ctx.set_paint(screen_color([176, 236, 176], 255)); - for l in &ft.lines { - for g in l.glyphs.iter().filter(|g| g.adv > 0.0 && cs.styles.get(g.style as usize).is_some_and(|s| s.custom_tracking)) { - ctx.fill_rect(&kurbo::Rect::new(g.x, l.baseline - l.ascent, g.x + g.adv, l.baseline + l.descent)); - } - } - } - if f.opts.tag_markers && cs.styles.iter().any(|s| s.xml_tag.is_some()) { - ctx.set_transform(m); - let px = 1.0 / m.determinant().abs().sqrt().max(1e-9); - for l in &ft.lines { - let tag_of = |g: &designcraft_compose::PlacedGlyph| cs.styles.get(g.style as usize).and_then(|s| s.xml_tag.clone()); - let gs: Vec<_> = l.glyphs.iter().filter(|g| g.len > 0).collect(); - let mut i = 0; - while i < gs.len() { - let Some(tag) = tag_of(gs[i]) else { - i += 1; - continue; - }; - let mut j = i + 1; - while j < gs.len() && tag_of(gs[j]).as_deref() == Some(tag.as_str()) { - j += 1; - } - let c = f.doc.xml.tags.iter().find(|t| t.name == tag).map_or([100, 100, 220], |t| t.color); - ctx.set_paint(screen_color([c[0], c[1], c[2]], 255)); - let (x0, x1) = (gs[i].x, gs[j - 1].x + gs[j - 1].adv); - let (top, bot) = (l.baseline - l.ascent, l.baseline + l.descent); - let (w, t) = (2.5 * px, px.max(0.3)); - // [ before the element, ] after it. - for (x, dir) in [(x0, 1.0), (x1, -1.0)] { - ctx.fill_rect(&kurbo::Rect::new(x - t / 2.0, top, x + t / 2.0, bot)); - for y in [top, bot - t] { - let (a, b) = if dir > 0.0 { (x, x + w) } else { (x - w, x) }; - ctx.fill_rect(&kurbo::Rect::new(a, y, b, y + t)); - } - } - i = j; - } - } - } - if f.opts.change_markup && cs.styles.iter().any(|s| s.inserted) { - ctx.set_transform(m); - for l in &ft.lines { - for g in l.glyphs.iter().filter(|g| g.adv > 0.0 && cs.styles.get(g.style as usize).is_some_and(|s| s.inserted)) { - ctx.set_paint(screen_color([120, 190, 255], 110)); - ctx.fill_rect(&kurbo::Rect::new(g.x, l.baseline - l.ascent, g.x + g.adv, l.baseline + l.descent)); - } - } - } - if f.opts.note_indicators - && let Some(st) = f.doc.story(cs.story).filter(|st| !st.editorial.is_empty()) - { - // A small flag at each note anchor, in the notes' amber. - ctx.set_transform(m); - ctx.set_paint(screen_color([232, 160, 32], 255)); - for l in &ft.lines { - for g in l.glyphs.iter().filter(|g| st.text.get(g.byte..).is_some_and(|t| t.starts_with(designcraft_doc::NOTE_MARK))) { - let h = l.ascent.max(4.0); - let mut p = BezPath::new(); - p.move_to((g.x - h * 0.25, l.baseline - h)); - p.line_to((g.x + h * 0.25, l.baseline - h)); - p.line_to((g.x, l.baseline - h * 0.55)); - p.close_path(); - ctx.fill_path(&p); - ctx.fill_rect(&kurbo::Rect::new(g.x - h * 0.03, l.baseline - h * 0.6, g.x + h * 0.03, l.baseline + l.descent * 0.5)); - } - } - } - if f.opts.condition_indicators && !f.doc.conditions.is_empty() && cs.styles.iter().any(|s| s.condition.is_some()) { - ctx.set_transform(m); - let px = 1.0 / m.determinant().abs().sqrt().max(1e-9); - for l in &ft.lines { - for g in l.glyphs.iter().filter(|g| g.visible && g.adv > 0.0) { - let Some(name) = cs.styles.get(g.style as usize).and_then(|s| s.condition.as_deref()) else { continue }; - let Some(c) = f.doc.conditions.iter().find(|c| c.name == name) else { continue }; - ctx.set_paint(screen_color([c.color[0], c.color[1], c.color[2]], 255)); - let y = l.baseline + l.descent * 0.6; - ctx.fill_rect(&kurbo::Rect::new(g.x, y, g.x + g.adv, y + 2.0 * px)); - } - } - } + draw_text_indicators(ctx, f, cs, ft, m); let scale = m.determinant().abs().sqrt(); let greek_px = f.opts.greek_below_px; let mut greek = BezPath::new(); diff --git a/crates/render/src/types.rs b/crates/render/src/types.rs index 634435806..6476c735b 100644 --- a/crates/render/src/types.rs +++ b/crates/render/src/types.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{Affine, Cache, Document, Item, Rect, SpreadRef, fx}; /// How far (points) an object's soft effects reach outside its geometry. pub fn effect_outset(it: &Item) -> f64 { diff --git a/crates/render/tests/it/blend_over_paper.rs b/crates/render/tests/it/blend_over_paper.rs index 0fd257027..6136e59ef 100644 --- a/crates/render/tests/it/blend_over_paper.rs +++ b/crates/render/tests/it/blend_over_paper.rs @@ -28,7 +28,7 @@ fn pixel(d: &Document, x: u32, y: u32) -> [u8; 4] { let mut r = Renderer::new(); r.threads = 0; let placed = [Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }]; - let img = r.render(d, &Cache::new(), &placed, 200, 100, Affine::IDENTITY, &RenderOptions::default()); + let img = r.render(d, &Cache::new(), &placed, (200, 100), Affine::IDENTITY, &RenderOptions::default()); let i = ((y * 200 + x) * 4) as usize; [img.pixels[i], img.pixels[i + 1], img.pixels[i + 2], img.pixels[i + 3]] } diff --git a/crates/render/tests/it/effects.rs b/crates/render/tests/it/effects.rs index 230913173..92d04bc44 100644 --- a/crates/render/tests/it/effects.rs +++ b/crates/render/tests/it/effects.rs @@ -30,7 +30,7 @@ fn render_with(d: &Document, threads: u16) -> Rendered { let mut r = Renderer::new(); r.threads = threads; let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() }; - r.render(d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::translate(Vec2::ZERO) }], SIZE, SIZE, Affine::IDENTITY, &opts) + r.render(d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::translate(Vec2::ZERO) }], (SIZE, SIZE), Affine::IDENTITY, &opts) } fn render(d: &Document) -> Rendered { @@ -207,7 +207,7 @@ fn render_zoomed(d: &Document) -> Rendered { let mut r = Renderer::new(); let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() }; let view = Affine::scale(8.0) * Affine::translate(Vec2::new(-70.0, -70.0)); - r.render(d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::translate(Vec2::ZERO) }], SIZE, SIZE, view, &opts) + r.render(d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::translate(Vec2::ZERO) }], (SIZE, SIZE), view, &opts) } #[test] diff --git a/crates/render/tests/it/gradient_inks.rs b/crates/render/tests/it/gradient_inks.rs index 0eb7f0f15..8fec16ac4 100644 --- a/crates/render/tests/it/gradient_inks.rs +++ b/crates/render/tests/it/gradient_inks.rs @@ -23,8 +23,7 @@ fn a_cmyk_gradient_is_c50_m50_halfway() { &d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }], - 100, - 20, + (100, 20), Affine::IDENTITY, &RenderOptions { paper: false, background: Some([255; 4]), ..Default::default() }, ); diff --git a/crates/render/tests/it/gradients.rs b/crates/render/tests/it/gradients.rs index 477e2c746..3a1c4bcd4 100644 --- a/crates/render/tests/it/gradients.rs +++ b/crates/render/tests/it/gradients.rs @@ -32,7 +32,7 @@ fn render(d: &Document) -> Rendered { let mut r = Renderer::new(); r.threads = 0; let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() }; - r.render(d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::translate(Vec2::ZERO) }], 200, 240, Affine::IDENTITY, &opts) + r.render(d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::translate(Vec2::ZERO) }], (200, 240), Affine::IDENTITY, &opts) } fn grey(img: &Rendered, x: u32, y: u32) -> u8 { diff --git a/crates/render/tests/it/perf.rs b/crates/render/tests/it/perf.rs index 46f33ab62..b6bdce5b4 100644 --- a/crates/render/tests/it/perf.rs +++ b/crates/render/tests/it/perf.rs @@ -61,22 +61,22 @@ fn glyph_paths_are_cached_and_offscreen_text_is_skipped() { // 200% view of the top-left of the first spread: only a few frames are visible. let view = Affine::scale(2.0); let opts = RenderOptions::default(); - let a = r.render(&d, &cache, &all, 800, 600, view, &opts); + let a = r.render(&d, &cache, &all, (800, 600), view, &opts); let drawn = r.stats.glyphs; assert!(drawn > 100, "glyphs drawn: {drawn}"); assert!(r.cached_frames() <= 4, "only visible frames get glyph paths: {}", r.cached_frames()); // Same pixels from the cache. - let b = r.render(&d, &cache, &all, 800, 600, view, &opts); + let b = r.render(&d, &cache, &all, (800, 600), view, &opts); assert!(a.pixels == b.pixels); // Culling: far fewer glyphs than the visible frames hold. - let whole = r.render(&d, &cache, &all, 2400, 1600, Affine::scale(1.0), &opts); + let whole = r.render(&d, &cache, &all, (2400, 1600), Affine::scale(1.0), &opts); assert!(r.stats.glyphs > drawn * 2, "{} vs {drawn}", r.stats.glyphs); drop(whole); // A new composition (edited story) gets fresh paths. let mut d2 = d.clone(); let sid = *d2.stories.keys().next().unwrap(); d2.set_story_text(sid, "Edited").unwrap(); - let c = r.render(&d2, &cache, &all, 800, 600, view, &opts); + let c = r.render(&d2, &cache, &all, (800, 600), view, &opts); assert!(r.stats.glyphs < 10); assert!(c.pixels != a.pixels); } @@ -88,7 +88,7 @@ fn greeked_text_builds_no_glyph_paths() { let mut r = Renderer::new(); r.threads = 0; let opts = RenderOptions { greek_below_px: 6.0, ..Default::default() }; - let img = r.render(&d, &cache, &placed(&d), 400, 300, Affine::scale(0.3), &opts); + let img = r.render(&d, &cache, &placed(&d), (400, 300), Affine::scale(0.3), &opts); assert_eq!(r.stats.glyphs, 0); assert_eq!(r.cached_frames(), 0); // The grey bars are drawn. @@ -106,11 +106,11 @@ fn render_text_heavy_spread_at_100_percent_within_budget() { // 100% on a HiDPI 2880×1800 canvas = 2 px/pt, centred on the second spread. let view = Affine::scale(2.0) * Affine::translate((0.0, -828.0)); let opts = RenderOptions { greek_below_px: 6.0, ..Default::default() }; - r.render(&d, &cache, &all, 2880, 1800, view, &opts); + r.render(&d, &cache, &all, (2880, 1800), view, &opts); let mut v: Vec = (0..9) .map(|_| { let t = Instant::now(); - drop(r.render(&d, &cache, &all, 2880, 1800, view, &opts)); + drop(r.render(&d, &cache, &all, (2880, 1800), view, &opts)); t.elapsed().as_secs_f64() * 1000.0 }) .collect(); diff --git a/crates/render/tests/it/stroke_alignment.rs b/crates/render/tests/it/stroke_alignment.rs index eee8450ab..e1c233c8a 100644 --- a/crates/render/tests/it/stroke_alignment.rs +++ b/crates/render/tests/it/stroke_alignment.rs @@ -30,8 +30,7 @@ fn render_with(path: PathData, fill: bool, stroke: Stroke, xf: Affine, threads: &d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }], - 96, - 96, + (96, 96), Affine::IDENTITY, &RenderOptions { paper: false, background, ..Default::default() }, ) diff --git a/crates/render/tests/it/text_frame_bottom.rs b/crates/render/tests/it/text_frame_bottom.rs index 3716902f8..56e162c67 100644 --- a/crates/render/tests/it/text_frame_bottom.rs +++ b/crates/render/tests/it/text_frame_bottom.rs @@ -21,8 +21,7 @@ fn text_image(height: f64) -> Rendered { &d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::IDENTITY }], - 160, - 100, + (160, 100), Affine::IDENTITY, &RenderOptions { background: Some([255; 4]), ..Default::default() }, )