mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-30 21:26:48 -06:00
Refactor render for strict Clippy checks
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Pixmap> {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
+112
-108
@@ -250,6 +250,117 @@ fn line_box(l: &Line) -> Option<Rect> {
|
||||
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<ComposedStory>, 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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]]
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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() },
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<f64> = (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();
|
||||
|
||||
@@ -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() },
|
||||
)
|
||||
|
||||
@@ -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() },
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user