mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-28 12:36:56 -06:00
Refactor render for strict Clippy checks
This commit is contained in:
+593
-343
File diff suppressed because it is too large
Load Diff
@@ -62,9 +62,23 @@ fn funnel(
|
||||
ctx.set_paint(color(series_color(rctx, c, 0), 1.0));
|
||||
ctx.fill_rect(&r);
|
||||
if let Some(name) = c.categories.get(i) {
|
||||
label(ctx, rctx, m, name, Rect::new(area.x0, y, plot.x0 - base * 0.4, y + band), base, Align::Right, Anchor::Middle, text_col, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
name,
|
||||
Rect::new(area.x0, y, plot.x0 - base * 0.4, y + band),
|
||||
crate::chart::LabelStyle { size: base, align: Align::Right, anchor: Anchor::Middle, col: text_col, bold: false },
|
||||
);
|
||||
}
|
||||
label(ctx, rctx, m, &value_label(*v), Rect::new(cx - 60.0, y, cx + 60.0, y + band), base, Align::Center, Anchor::Middle, Rgba::WHITE, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
&value_label(*v),
|
||||
Rect::new(cx - 60.0, y, cx + 60.0, y + band),
|
||||
crate::chart::LabelStyle { size: base, align: Align::Center, anchor: Anchor::Middle, col: Rgba::WHITE, bold: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +132,20 @@ fn region_tiles(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area:
|
||||
}
|
||||
let inner = t.inset(-base * 0.25);
|
||||
if inner.width() > base && inner.height() > base * 0.8 {
|
||||
label(ctx, rctx, m, &text, inner, base * 0.9, Align::Center, Anchor::Middle, if dark { Rgba::WHITE } else { text_col }, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
&text,
|
||||
inner,
|
||||
crate::chart::LabelStyle {
|
||||
size: base * 0.9,
|
||||
align: Align::Center,
|
||||
anchor: Anchor::Middle,
|
||||
col: if dark { Rgba::WHITE } else { text_col },
|
||||
bold: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,9 +163,23 @@ fn region_tiles(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area:
|
||||
ctx.fill_rect(&Rect::new(x0, bar.y0, x1.min(bar.x1), bar.y1));
|
||||
}
|
||||
let ly = Rect::new(bar.x0 - base * 8.0, bar.y0 - base * 0.2, bar.x0 - base * 0.4, bar.y1 + base * 0.2);
|
||||
label(ctx, rctx, m, &c.data_label(s, min), ly, base, Align::Right, Anchor::Middle, text_col, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
&c.data_label(s, min),
|
||||
ly,
|
||||
crate::chart::LabelStyle { size: base, align: Align::Right, anchor: Anchor::Middle, col: text_col, bold: false },
|
||||
);
|
||||
let ry = Rect::new(bar.x1 + base * 0.4, bar.y0 - base * 0.2, bar.x1 + base * 8.0, bar.y1 + base * 0.2);
|
||||
label(ctx, rctx, m, &c.data_label(s, max), ry, base, Align::Left, Anchor::Middle, text_col, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
&c.data_label(s, max),
|
||||
ry,
|
||||
crate::chart::LabelStyle { size: base, align: Align::Left, anchor: Anchor::Middle, col: text_col, bold: false },
|
||||
);
|
||||
}
|
||||
let note = Rect::new(area.x0, area.y1 - note_h, area.x1, area.y1);
|
||||
label(
|
||||
@@ -147,11 +188,13 @@ fn region_tiles(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area:
|
||||
m,
|
||||
"Map preview unavailable: regions shown as tiles",
|
||||
note,
|
||||
base * 0.85,
|
||||
Align::Center,
|
||||
Anchor::Middle,
|
||||
mix(text_col, Rgba::WHITE, 0.4),
|
||||
false,
|
||||
crate::chart::LabelStyle {
|
||||
size: base * 0.85,
|
||||
align: Align::Center,
|
||||
anchor: Anchor::Middle,
|
||||
col: mix(text_col, Rgba::WHITE, 0.4),
|
||||
bold: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -248,7 +291,14 @@ fn treemap(
|
||||
}
|
||||
let inner = t.inset(-base * 0.3);
|
||||
if inner.width() > base && inner.height() > base {
|
||||
label(ctx, rctx, m, &text, inner, base, Align::Left, Anchor::Top, Rgba::WHITE, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
&text,
|
||||
inner,
|
||||
crate::chart::LabelStyle { size: base, align: Align::Left, anchor: Anchor::Top, col: Rgba::WHITE, bold: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,11 +339,7 @@ fn sunburst(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area: Rec
|
||||
m,
|
||||
name,
|
||||
Rect::new(lp.x - 50.0, lp.y - base, lp.x + 50.0, lp.y + base),
|
||||
base,
|
||||
Align::Center,
|
||||
Anchor::Middle,
|
||||
Rgba::WHITE,
|
||||
false,
|
||||
crate::chart::LabelStyle { size: base, align: Align::Center, anchor: Anchor::Middle, col: Rgba::WHITE, bold: false },
|
||||
);
|
||||
}
|
||||
a0 = a1;
|
||||
@@ -344,7 +390,14 @@ fn tree_treemap(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area:
|
||||
}
|
||||
let inner = t.inset(-base * 0.3);
|
||||
if inner.width() > base && inner.height() > base {
|
||||
label(ctx, rctx, m, &text, inner, base * 0.9, Align::Left, Anchor::Top, Rgba::WHITE, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
&text,
|
||||
inner,
|
||||
crate::chart::LabelStyle { size: base * 0.9, align: Align::Left, anchor: Anchor::Top, col: Rgba::WHITE, bold: false },
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -355,7 +408,14 @@ fn tree_treemap(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area:
|
||||
ctx.fill_rect(&t);
|
||||
if t.width() > base * 2.0 && banner > base * 0.8 {
|
||||
let head = Rect::new(t.x0 + base * 0.3, t.y0, t.x1 - base * 0.3, t.y0 + banner);
|
||||
label(ctx, rctx, m, &n.name, head, base, Align::Left, Anchor::Middle, Rgba::WHITE, true);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
&n.name,
|
||||
head,
|
||||
crate::chart::LabelStyle { size: base, align: Align::Left, anchor: Anchor::Middle, col: Rgba::WHITE, bold: true },
|
||||
);
|
||||
}
|
||||
let inner = Rect::new(t.x0 + 1.0, t.y0 + banner, t.x1 - 1.0, t.y1 - 1.0);
|
||||
let k = lighten(fill, 0.12 * (depth + 1) as f64);
|
||||
@@ -437,11 +497,7 @@ fn tree_sunburst(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area
|
||||
m,
|
||||
&n.name,
|
||||
Rect::new(lp.x - half, lp.y - base, lp.x + half, lp.y + base),
|
||||
base * 0.9,
|
||||
Align::Center,
|
||||
Anchor::Middle,
|
||||
Rgba::WHITE,
|
||||
false,
|
||||
crate::chart::LabelStyle { size: base * 0.9, align: Align::Center, anchor: Anchor::Middle, col: Rgba::WHITE, bold: false },
|
||||
);
|
||||
}
|
||||
if !n.children.is_empty() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{DateTime, Fields, Rgba, ShapeId, Vec2, datetime};
|
||||
|
||||
/// Animation state of one shape during a slide show or preview.
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
+10
-11
@@ -67,7 +67,14 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area: Rec
|
||||
Align::Right => q.x - w + base * 0.3,
|
||||
_ => q.x - w / 2.0,
|
||||
};
|
||||
label(ctx, rctx, m, name, Rect::new(x0, q.y - base * 0.7, x0 + w, q.y + base * 0.7), base, align, Anchor::Middle, text_col, false);
|
||||
label(
|
||||
ctx,
|
||||
rctx,
|
||||
m,
|
||||
name,
|
||||
Rect::new(x0, q.y - base * 0.7, x0 + w, q.y + base * 0.7),
|
||||
crate::chart::LabelStyle { size: base, align, anchor: Anchor::Middle, col: text_col, bold: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
// Value labels up the first spoke.
|
||||
@@ -83,11 +90,7 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area: Rec
|
||||
m,
|
||||
&t,
|
||||
Rect::new(q.x - w - base * 0.3, q.y - base * 0.6, q.x - base * 0.3, q.y + base * 0.6),
|
||||
base * 0.9,
|
||||
Align::Right,
|
||||
Anchor::Middle,
|
||||
text_col,
|
||||
false,
|
||||
crate::chart::LabelStyle { size: base * 0.9, align: Align::Right, anchor: Anchor::Middle, col: text_col, bold: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -130,11 +133,7 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, area: Rec
|
||||
m,
|
||||
&t,
|
||||
Rect::new(q.x - 40.0, q.y - base * 1.4, q.x + 40.0, q.y - base * 0.2),
|
||||
base * 0.9,
|
||||
Align::Center,
|
||||
Anchor::Bottom,
|
||||
text_col,
|
||||
false,
|
||||
crate::chart::LabelStyle { size: base * 0.9, align: Align::Center, anchor: Anchor::Bottom, col: text_col, bold: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+242
-76
@@ -60,6 +60,50 @@ pub(crate) struct Frame<'a> {
|
||||
pub(crate) fields: SlideFields,
|
||||
}
|
||||
|
||||
/// A shape being drawn: its resolved geometry, fill, line and effects, in shape space `m` at size
|
||||
/// `w` x `h`, shared by the stages of `Renderer::geometry_shape`.
|
||||
struct ShapeDraw<'a, 'ctx> {
|
||||
f: &'a Frame<'a>,
|
||||
rctx: &'a Ctx<'ctx>,
|
||||
s: &'a Shape,
|
||||
st: &'a ShapeState,
|
||||
m: Affine,
|
||||
w: f64,
|
||||
h: f64,
|
||||
depth: usize,
|
||||
geo: Geometry,
|
||||
fades: Vec<deckcraft_geom::morph::Fade>,
|
||||
fill: Option<Fill>,
|
||||
fill_ph: Option<Rgba>,
|
||||
line: Line,
|
||||
line_ph: Option<Rgba>,
|
||||
effects: Option<Effects>,
|
||||
fx_ph: Option<Rgba>,
|
||||
outline: BezPath,
|
||||
is_pic: bool,
|
||||
empty_ph: bool,
|
||||
}
|
||||
|
||||
impl ShapeDraw<'_, '_> {
|
||||
/// How much of the fill and stroke of path `i` shows (morphing outlines fade).
|
||||
fn fade(&self, i: usize) -> deckcraft_geom::morph::Fade {
|
||||
self.fades.get(i).copied().unwrap_or(deckcraft_geom::morph::Fade { fill: 1.0, stroke: 1.0 })
|
||||
}
|
||||
}
|
||||
|
||||
/// What casts a shape's shadow, glow and reflection: its geometry, fill and outline, in shape
|
||||
/// space `m` at size `w` x `h`.
|
||||
struct FxSource<'a> {
|
||||
geo: &'a Geometry,
|
||||
fill: &'a Option<Fill>,
|
||||
fill_alpha: Option<f64>,
|
||||
outline: Option<Caster<'a>>,
|
||||
content: Option<&'a deckcraft_model::style::PictureFill>,
|
||||
m: Affine,
|
||||
w: f64,
|
||||
h: f64,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
fn canvas(pres: &Presentation, opts: &RenderOpts, filters: bool) -> (RenderContext, u16, u16, Affine) {
|
||||
let (w, h) = output_size(pres, opts);
|
||||
@@ -541,7 +585,6 @@ impl Renderer {
|
||||
Some(pm) => self.morphs.geometry(pm, s, w, h),
|
||||
None => (self.scaled_geometry(s, m, depth, w, h), Vec::new()),
|
||||
};
|
||||
let fade = |i: usize| fades.get(i).copied().unwrap_or(deckcraft_geom::morph::Fade { fill: 1.0, stroke: 1.0 });
|
||||
let (fill, fill_ph) = resolve::fill(rctx, s);
|
||||
let (line, line_ph) = resolve::line(rctx, s);
|
||||
let (effects, fx_ph) = resolve::effects(rctx, s);
|
||||
@@ -552,6 +595,56 @@ impl Renderer {
|
||||
// Placeholders in the editor: dashed outline and prompt, nothing else when empty.
|
||||
let empty_ph = s.ph.is_some() && s.text.as_ref().is_none_or(TextBody::is_empty) && !is_pic && !matches!(s.kind, ShapeKind::Media(_));
|
||||
let fill = if let Some(t) = st.tint { Some(Fill::solid(deckcraft_model::ColorRef::rgb(t))) } else { fill };
|
||||
let d = ShapeDraw { f, rctx, s, st, m, w, h, depth, geo, fades, fill, fill_ph, line, line_ph, effects, fx_ph, outline, is_pic, empty_ph };
|
||||
self.cast_effects(ctx, &d);
|
||||
let soft = d.effects.as_ref().and_then(|e| e.soft_edge).filter(|r| *r > 0.0);
|
||||
if let Some(r) = soft {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, None, None, None, None);
|
||||
let _ = r;
|
||||
}
|
||||
let pt = self.point_scale(&d);
|
||||
self.draw_extrusion(ctx, &d, pt);
|
||||
self.draw_contour(ctx, &d, pt);
|
||||
self.draw_fill(ctx, &d);
|
||||
self.draw_fill_overlay(ctx, &d);
|
||||
self.draw_bevel(ctx, &d, pt);
|
||||
self.draw_outline(ctx, &d);
|
||||
if let Some(r) = soft {
|
||||
self.draw_feather(ctx, &d, r);
|
||||
}
|
||||
self.draw_inner_shadow(ctx, &d);
|
||||
self.draw_text_body(ctx, &d);
|
||||
}
|
||||
|
||||
/// Shape-space units per point for the 3-D sizes, measured without the camera's turn (which
|
||||
/// `shape_at` puts in `m`): its foreshortening is not a child-coordinate scale.
|
||||
fn point_scale(&self, d: &ShapeDraw) -> f64 {
|
||||
let turn = d
|
||||
.effects
|
||||
.as_ref()
|
||||
.and_then(|e| e.raw3d.as_deref())
|
||||
.and_then(camera::from_raw)
|
||||
.and_then(|c| camera::affine(&c, d.w, d.h))
|
||||
.filter(|t| t.determinant().abs() > 1e-9);
|
||||
self.per_point(turn.map_or(d.m, |t| d.m * t.inverse()), d.depth)
|
||||
}
|
||||
|
||||
fn cast_effects(&mut self, ctx: &mut RenderContext, d: &ShapeDraw) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let depth = d.depth;
|
||||
let fill_ph = d.fill_ph;
|
||||
let line_ph = d.line_ph;
|
||||
let fx_ph = d.fx_ph;
|
||||
let is_pic = d.is_pic;
|
||||
let s = d.s;
|
||||
let rctx = d.rctx;
|
||||
let geo = &d.geo;
|
||||
let fill = &d.fill;
|
||||
let effects = &d.effects;
|
||||
let line = &d.line;
|
||||
// Effects below the shape.
|
||||
if let Some(e) = &effects {
|
||||
// A picture (or picture fill) casts its shadow from its pixels, not its frame.
|
||||
@@ -560,7 +653,7 @@ impl Renderer {
|
||||
_ => None,
|
||||
};
|
||||
// What casts the shadow: the fill as see-through as it is, and the outline.
|
||||
let fill_alpha = fill_opacity(rctx, &fill, fill_ph);
|
||||
let fill_alpha = fill_opacity(rctx, fill, fill_ph);
|
||||
let outline = line.fill.as_ref().filter(|f| !f.is_none()).map(|lf| {
|
||||
let a = match lf {
|
||||
Fill::Solid { color: c } => rctx.color(c, line_ph).a as f64 / 255.0,
|
||||
@@ -568,28 +661,22 @@ impl Renderer {
|
||||
};
|
||||
let (base, rel) = self.line_space(m, depth);
|
||||
let lw = line.width.unwrap_or(0.75).max(0.0);
|
||||
let outside = if border_outside(is_pic, effects.as_ref()) { outside_line(&line, lw) } else { None };
|
||||
Caster { line: outside.map_or(std::borrow::Cow::Borrowed(&line), std::borrow::Cow::Owned), ph: line_ph, alpha: a, base, rel }
|
||||
let outside = if border_outside(is_pic, effects.as_ref()) { outside_line(line, lw) } else { None };
|
||||
Caster { line: outside.map_or(std::borrow::Cow::Borrowed(line), std::borrow::Cow::Owned), ph: line_ph, alpha: a, base, rel }
|
||||
});
|
||||
self.effects_below(ctx, rctx, e, fx_ph, &geo, &fill, fill_alpha, outline, content.as_ref(), m, w, h);
|
||||
self.effects_below(ctx, rctx, e, fx_ph, FxSource { geo, fill, fill_alpha, outline, content: content.as_ref(), m, w, h });
|
||||
}
|
||||
let soft = effects.as_ref().and_then(|e| e.soft_edge).filter(|r| *r > 0.0);
|
||||
if let Some(r) = soft {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, None, None, None, None);
|
||||
let _ = r;
|
||||
}
|
||||
// Shape-space units per point for the 3-D sizes, measured without the camera's turn (which
|
||||
// `shape_at` puts in `m`): its foreshortening is not a child-coordinate scale.
|
||||
let pt = {
|
||||
let turn = effects
|
||||
.as_ref()
|
||||
.and_then(|e| e.raw3d.as_deref())
|
||||
.and_then(camera::from_raw)
|
||||
.and_then(|c| camera::affine(&c, w, h))
|
||||
.filter(|t| t.determinant().abs() > 1e-9);
|
||||
self.per_point(turn.map_or(m, |t| m * t.inverse()), depth)
|
||||
};
|
||||
}
|
||||
|
||||
fn draw_extrusion(&mut self, ctx: &mut RenderContext, d: &ShapeDraw, pt: f64) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let fill_ph = d.fill_ph;
|
||||
let rctx = d.rctx;
|
||||
let fill = &d.fill;
|
||||
let effects = &d.effects;
|
||||
let outline = &d.outline;
|
||||
// A 3-D extrusion under a turned camera: the sides, swept behind the face.
|
||||
if let Some(raw) = effects.as_ref().and_then(|e| e.raw3d.as_deref())
|
||||
&& let Some(ex) = extrude::from_raw(raw)
|
||||
@@ -604,8 +691,17 @@ impl Renderer {
|
||||
let pre = m * turn.inverse();
|
||||
let lit = light::Lighting::from_raw(raw);
|
||||
let d = ex.depth * pt;
|
||||
extrude::draw(ctx, &outline, w, h, &cam, d, pre, turn, extrude::side_color(rctx, &ex, face), &lit);
|
||||
extrude::draw(ctx, outline, w, h, &cam, d, pre, turn, extrude::side_color(rctx, &ex, face), &lit);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_contour(&mut self, ctx: &mut RenderContext, d: &ShapeDraw, pt: f64) {
|
||||
let m = d.m;
|
||||
let fill_ph = d.fill_ph;
|
||||
let rctx = d.rctx;
|
||||
let fill = &d.fill;
|
||||
let effects = &d.effects;
|
||||
let outline = &d.outline;
|
||||
// A 3-D contour: a band around the outline, behind the face (and in front of any sides).
|
||||
if let Some(raw) = effects.as_ref().and_then(|e| e.raw3d.as_deref())
|
||||
&& let Some(c) = extrude::contour_from_raw(raw)
|
||||
@@ -615,8 +711,23 @@ impl Renderer {
|
||||
_ => None,
|
||||
};
|
||||
let col = c.color.as_ref().map(|k| rctx.color(k, face)).or(face).unwrap_or(Rgba::rgb(0xff, 0xff, 0xff));
|
||||
extrude::draw_contour(ctx, &outline, m, c.width * pt, col, &light::Lighting::from_raw(raw));
|
||||
extrude::draw_contour(ctx, outline, m, c.width * pt, col, &light::Lighting::from_raw(raw));
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_fill(&mut self, ctx: &mut RenderContext, d: &ShapeDraw) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let fill_ph = d.fill_ph;
|
||||
let empty_ph = d.empty_ph;
|
||||
let s = d.s;
|
||||
let f = d.f;
|
||||
let rctx = d.rctx;
|
||||
let geo = &d.geo;
|
||||
let fill = &d.fill;
|
||||
let outline = &d.outline;
|
||||
let fade = |i: usize| d.fade(i);
|
||||
// Fill.
|
||||
match &s.kind {
|
||||
ShapeKind::Picture { fill: pf } => {
|
||||
@@ -625,11 +736,11 @@ impl Renderer {
|
||||
if s.fill.as_ref().is_some_and(|f| !f.is_none())
|
||||
&& let Some(fl) = &fill
|
||||
{
|
||||
self.fill_geometry(ctx, f, rctx, fl, fill_ph, &geo, (w, h), m, &fade);
|
||||
self.fill_geometry(ctx, f, rctx, fl, fill_ph, geo, (w, h), m, &fade);
|
||||
}
|
||||
ctx.set_transform(m);
|
||||
let bounds = Rect::new(0.0, 0.0, w, h);
|
||||
ctx.push_clip_layer(&outline);
|
||||
ctx.push_clip_layer(outline);
|
||||
let t = f.opts.gif_times.iter().find(|g| g.0 == s.id).map(|g| g.1);
|
||||
paint::picture(ctx, f.pres, &paint::resolved(rctx, pf, None), bounds, m, t);
|
||||
ctx.pop_layer();
|
||||
@@ -642,7 +753,7 @@ impl Renderer {
|
||||
if s.fill.as_ref().is_some_and(|f| !f.is_none())
|
||||
&& let Some(fl) = &fill
|
||||
{
|
||||
self.fill_geometry(ctx, f, rctx, fl, fill_ph, &geo, (w, h), m, &fade);
|
||||
self.fill_geometry(ctx, f, rctx, fl, fill_ph, geo, (w, h), m, &fade);
|
||||
}
|
||||
ctx.set_transform(m);
|
||||
let pf = deckcraft_model::style::PictureFill { media: *media, ..Default::default() };
|
||||
@@ -653,7 +764,7 @@ impl Renderer {
|
||||
let bounds = Rect::new(0.0, 0.0, w, h);
|
||||
if let Some(p) = mc.poster {
|
||||
let pf = deckcraft_model::style::PictureFill { media: p, crop: mc.crop, ..Default::default() };
|
||||
ctx.push_clip_layer(&outline);
|
||||
ctx.push_clip_layer(outline);
|
||||
paint::picture(ctx, f.pres, &pf, bounds, m, None);
|
||||
ctx.pop_layer();
|
||||
} else if mc.video {
|
||||
@@ -668,10 +779,23 @@ impl Renderer {
|
||||
if let Some(fl) = &fill
|
||||
&& !(empty_ph && f.opts.edit && s.fill.is_none() && s.line.is_none() && s.style.is_none())
|
||||
{
|
||||
self.fill_geometry(ctx, f, rctx, fl, fill_ph, &geo, (w, h), m, &fade);
|
||||
self.fill_geometry(ctx, f, rctx, fl, fill_ph, geo, (w, h), m, &fade);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_fill_overlay(&mut self, ctx: &mut RenderContext, d: &ShapeDraw) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let fx_ph = d.fx_ph;
|
||||
let f = d.f;
|
||||
let rctx = d.rctx;
|
||||
let geo = &d.geo;
|
||||
let fill = &d.fill;
|
||||
let effects = &d.effects;
|
||||
let fade = |i: usize| d.fade(i);
|
||||
// `a:fillOverlay`: a fill blended over the shape's fill (PowerPoint's "Flow" themes multiply
|
||||
// a gradient over a tiled picture), inside the filled outline, under the line.
|
||||
if let Some(ov) = effects.as_ref().and_then(|e| e.fill_overlay.as_ref())
|
||||
@@ -691,13 +815,19 @@ impl Renderer {
|
||||
ctx.pop_layer();
|
||||
}
|
||||
}
|
||||
// Some presets' outlines default to sharp corners (PowerPoint's own per-shape default).
|
||||
let line = match (&s.geom, line.join) {
|
||||
(Geom::Preset { name, .. }, None) if preset::mitred_by_default(name) => {
|
||||
Line { join: Some(deckcraft_model::style::LineJoin::Miter), ..line }
|
||||
}
|
||||
_ => line,
|
||||
};
|
||||
}
|
||||
|
||||
fn draw_bevel(&mut self, ctx: &mut RenderContext, d: &ShapeDraw, pt: f64) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let fill_ph = d.fill_ph;
|
||||
let is_pic = d.is_pic;
|
||||
let rctx = d.rctx;
|
||||
let geo = &d.geo;
|
||||
let fill = &d.fill;
|
||||
let effects = &d.effects;
|
||||
let outline = &d.outline;
|
||||
// A 3-D bevel: the face and a band inside the outline lit as surfaces.
|
||||
if let Some(bv) = effects.as_ref().and_then(|e| e.raw3d.as_deref()).and_then(bevel::from_raw) {
|
||||
let bv = bevel::Bevel { w: bv.w * pt, h: bv.h * pt, ..bv };
|
||||
@@ -717,6 +847,29 @@ impl Renderer {
|
||||
bevel::draw(ctx, &bv, if is_pic { None } else { solid }, &area, Rect::new(0.0, 0.0, w, h), m, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_outline(&mut self, ctx: &mut RenderContext, d: &ShapeDraw) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let depth = d.depth;
|
||||
let line_ph = d.line_ph;
|
||||
let is_pic = d.is_pic;
|
||||
let s = d.s;
|
||||
let rctx = d.rctx;
|
||||
let geo = &d.geo;
|
||||
let effects = &d.effects;
|
||||
let outline = &d.outline;
|
||||
let line = d.line.clone();
|
||||
let fade = |i: usize| d.fade(i);
|
||||
// Some presets' outlines default to sharp corners (PowerPoint's own per-shape default).
|
||||
let line = match (&s.geom, line.join) {
|
||||
(Geom::Preset { name, .. }, None) if preset::mitred_by_default(name) => {
|
||||
Line { join: Some(deckcraft_model::style::LineJoin::Miter), ..line }
|
||||
}
|
||||
_ => line,
|
||||
};
|
||||
// Outline.
|
||||
if line.fill.as_ref().is_some_and(|f| !f.is_none()) {
|
||||
let lw = line.width.unwrap_or(0.75).max(0.0);
|
||||
@@ -779,7 +932,7 @@ impl Renderer {
|
||||
stroke(ctx, rctx, &line, line_ph, &path, Some(&whole), base, lw, fade(i).stroke);
|
||||
}
|
||||
if ends {
|
||||
arrowheads(ctx, rctx, &line, line_ph, &geo, rel, base, lw);
|
||||
arrowheads(ctx, rctx, &line, line_ph, geo, rel, base, lw);
|
||||
}
|
||||
if clipped {
|
||||
ctx.pop_layer();
|
||||
@@ -788,33 +941,46 @@ impl Renderer {
|
||||
ctx.pop_layer();
|
||||
}
|
||||
}
|
||||
if let Some(r) = soft {
|
||||
// Feather: the shape (fill and line) fades to nothing at its outline. Measured on
|
||||
// PowerPoint's export: the mask is the outline shrunk by 0.85 r, blurred by a third of
|
||||
// r, so it's half opaque 0.85 r inside the edge and transparent at the edge itself.
|
||||
ctx.set_transform(m);
|
||||
ctx.push_layer(
|
||||
None,
|
||||
Some(peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::DestIn)),
|
||||
None,
|
||||
None,
|
||||
Some(blur(r / 3.0 * scale_of(m))),
|
||||
);
|
||||
ctx.set_paint(peniko::Color::from_rgba8(0, 0, 0, 255));
|
||||
ctx.fill_path(&outline);
|
||||
ctx.push_blend_layer(peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::DestOut));
|
||||
ctx.set_stroke(kurbo::Stroke::new(r * 1.7).with_join(kurbo::Join::Round));
|
||||
ctx.stroke_path(&outline);
|
||||
ctx.pop_layer();
|
||||
ctx.pop_layer();
|
||||
ctx.pop_layer();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_feather(&mut self, ctx: &mut RenderContext, d: &ShapeDraw, r: f64) {
|
||||
let m = d.m;
|
||||
let outline = &d.outline;
|
||||
// Feather: the shape (fill and line) fades to nothing at its outline. Measured on
|
||||
// PowerPoint's export: the mask is the outline shrunk by 0.85 r, blurred by a third of
|
||||
// r, so it's half opaque 0.85 r inside the edge and transparent at the edge itself.
|
||||
ctx.set_transform(m);
|
||||
ctx.push_layer(
|
||||
None,
|
||||
Some(peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::DestIn)),
|
||||
None,
|
||||
None,
|
||||
Some(blur(r / 3.0 * scale_of(m))),
|
||||
);
|
||||
ctx.set_paint(peniko::Color::from_rgba8(0, 0, 0, 255));
|
||||
ctx.fill_path(outline);
|
||||
ctx.push_blend_layer(peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::DestOut));
|
||||
ctx.set_stroke(kurbo::Stroke::new(r * 1.7).with_join(kurbo::Join::Round));
|
||||
ctx.stroke_path(outline);
|
||||
ctx.pop_layer();
|
||||
ctx.pop_layer();
|
||||
ctx.pop_layer();
|
||||
}
|
||||
|
||||
fn draw_inner_shadow(&mut self, ctx: &mut RenderContext, d: &ShapeDraw) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let fx_ph = d.fx_ph;
|
||||
let rctx = d.rctx;
|
||||
let effects = &d.effects;
|
||||
let outline = &d.outline;
|
||||
// Inner shadow.
|
||||
if let Some(e) = &effects
|
||||
&& let Some(sh) = &e.inner_shadow
|
||||
{
|
||||
ctx.set_transform(m);
|
||||
ctx.push_clip_layer(&outline);
|
||||
ctx.push_clip_layer(outline);
|
||||
let c = rctx.color(&sh.color, fx_ph);
|
||||
let d = Vec2::new(sh.dir.to_radians().cos(), sh.dir.to_radians().sin()) * sh.dist;
|
||||
// As PowerPoint draws it (measured on its export): the shadow falls inside along the
|
||||
@@ -832,6 +998,19 @@ impl Renderer {
|
||||
ctx.pop_layer();
|
||||
ctx.pop_layer();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_text_body(&mut self, ctx: &mut RenderContext, d: &ShapeDraw) {
|
||||
let m = d.m;
|
||||
let w = d.w;
|
||||
let h = d.h;
|
||||
let depth = d.depth;
|
||||
let empty_ph = d.empty_ph;
|
||||
let s = d.s;
|
||||
let f = d.f;
|
||||
let rctx = d.rctx;
|
||||
let st = d.st;
|
||||
let geo = &d.geo;
|
||||
// Text (or the placeholder prompt in the editor).
|
||||
let skip_text = f.opts.skip_text == Some(s.id);
|
||||
if let Some(body) = &s.text
|
||||
@@ -842,7 +1021,7 @@ impl Renderer {
|
||||
let (tw, th, m) = self.text_space(m, depth, w, h);
|
||||
let text_geo;
|
||||
let geo = if (tw, th) == (w, h) {
|
||||
&geo
|
||||
geo
|
||||
} else {
|
||||
text_geo = shape_geometry(s, tw, th);
|
||||
&text_geo
|
||||
@@ -878,7 +1057,7 @@ impl Renderer {
|
||||
let prompt_body = prompt_body(rctx, s, kind);
|
||||
if !prompt_body.is_empty() {
|
||||
let grey = Rgba::rgb(0x59, 0x59, 0x59);
|
||||
draw_text(ctx, rctx, s, &prompt_body, tr, m, &f.fields, Some(grey));
|
||||
draw_text(ctx, rctx, s, &prompt_body, m, &Opts { rect: tr, fields: &f.fields, prompt_color: Some(grey), no_shrink: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -895,21 +1074,8 @@ impl Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
fn effects_below(
|
||||
&mut self,
|
||||
ctx: &mut RenderContext,
|
||||
rctx: &Ctx,
|
||||
e: &Effects,
|
||||
ph: Option<Rgba>,
|
||||
geo: &Geometry,
|
||||
fill: &Option<Fill>,
|
||||
fill_alpha: Option<f64>,
|
||||
outline: Option<Caster>,
|
||||
content: Option<&deckcraft_model::style::PictureFill>,
|
||||
m: Affine,
|
||||
w: f64,
|
||||
h: f64,
|
||||
) {
|
||||
fn effects_below(&mut self, ctx: &mut RenderContext, rctx: &Ctx, e: &Effects, ph: Option<Rgba>, src: FxSource) {
|
||||
let FxSource { geo, fill, fill_alpha, outline, content, m, w, h } = src;
|
||||
let silhouette = {
|
||||
let mut p = BezPath::new();
|
||||
for sp in &geo.paths {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::opts::{RenderOpts, SlideFields};
|
||||
use super::raster::{Image, MAX_SIDE};
|
||||
use super::renderer::{Frame, RENDERER, Renderer};
|
||||
use super::*;
|
||||
use super::{Affine, Ctx, PathMorph, Presentation, Rect, RenderContext, Shape, ShapeId, Slide, TextMorph, resolve};
|
||||
|
||||
static PANICS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
@@ -125,8 +125,7 @@ pub fn render_slide_part(pres: &Presentation, index: usize, part: SlidePart, opt
|
||||
/// to `shapes` only.
|
||||
pub fn render_blend(
|
||||
pres: &Presentation,
|
||||
old: usize,
|
||||
new: usize,
|
||||
(old, new): (usize, usize),
|
||||
mix: f64,
|
||||
shapes: &[(Shape, bool)],
|
||||
text: &[TextMorph],
|
||||
|
||||
@@ -616,14 +616,14 @@ fn blend_ends_match_the_slides_and_mixes_backdrops() {
|
||||
let o = RenderOpts { scale: 0.5, ..Default::default() };
|
||||
let shapes = vec![(sh, false)];
|
||||
// At the ends the frame is the slide itself; the shape stays opaque throughout.
|
||||
assert_eq!(render_blend(&p, 0, 1, 1.0, &shapes, &[], &[], &o).pixels, render_slide(&p, 1, &o).pixels);
|
||||
let mid = render_blend(&p, 0, 1, 0.5, &shapes, &[], &[], &o);
|
||||
assert_eq!(render_blend(&p, (0, 1), 1.0, &shapes, &[], &[], &o).pixels, render_slide(&p, 1, &o).pixels);
|
||||
let mid = render_blend(&p, (0, 1), 0.5, &shapes, &[], &[], &o);
|
||||
let px = mid.pixel(5, 5);
|
||||
assert!((px[0] as i32 - 128).abs() <= 3, "{px:?}");
|
||||
assert_eq!(mid.pixel(100, 75), render_slide(&p, 1, &o).pixel(100, 75));
|
||||
// Hostile input: bad indices and NaN never panic.
|
||||
assert_eq!(render_blend(&p, 0, 9, 0.5, &shapes, &[], &[], &o).width, 0);
|
||||
let _ = render_blend(&p, 0, 1, f64::NAN, &shapes, &[], &[], &o);
|
||||
assert_eq!(render_blend(&p, (0, 9), 0.5, &shapes, &[], &[], &o).width, 0);
|
||||
let _ = render_blend(&p, (0, 1), f64::NAN, &shapes, &[], &[], &o);
|
||||
}
|
||||
|
||||
fn text_shape(id: u32, text: &str, x: f64, y: f64) -> Shape {
|
||||
@@ -668,7 +668,7 @@ fn morph_text_ends_match_the_slides() {
|
||||
sb.xfrm = Some(x);
|
||||
sa.xfrm = Some(x);
|
||||
let tm = TextMorph { old: ShapeId(11), new: ShapeId(10), from, to, chars: false, t };
|
||||
render_blend(&p, 0, 1, t, &[(sb, false), (sa, true)], &[tm], &[], &o)
|
||||
render_blend(&p, (0, 1), t, &[(sb, false), (sa, true)], &[tm], &[], &o)
|
||||
};
|
||||
// Warm up: faces load lazily (a background scan runs), so the first render may still fall back.
|
||||
let _ = (render_slide(&p, 0, &o), render_slide(&p, 1, &o), frame(0.5));
|
||||
@@ -781,7 +781,7 @@ fn morph_path_ends_match_the_slides() {
|
||||
let lerp = |a: f64, b: f64| a + (b - a) * t;
|
||||
let x = Xfrm::new(lerp(from.x, to.x), lerp(from.y, to.y), lerp(from.w, to.w), lerp(from.h, to.h));
|
||||
let pm = PathMorph { id: ShapeId(10), from: Geom::preset("ellipse"), from_box: from, to_box: to, t };
|
||||
render_blend(&p, 0, 1, t, &[(form(10, "star5", x), false)], &[], &[pm], &o)
|
||||
render_blend(&p, (0, 1), t, &[(form(10, "star5", x), false)], &[], &[pm], &o)
|
||||
};
|
||||
// The morph splits the outlines into more curves, which flatten slightly differently: edge pixels
|
||||
// may differ a little, a different shape differs a lot.
|
||||
@@ -792,7 +792,7 @@ fn morph_path_ends_match_the_slides() {
|
||||
let d1 = differ(&frame(1.0), &render_slide(&p, 1, &o));
|
||||
assert!(d1 < 40, "end differs {d1}");
|
||||
// Without the morph the start would show the star.
|
||||
let star = render_blend(&p, 0, 1, 0.0, &[(form(10, "star5", from), false)], &[], &[], &o);
|
||||
let star = render_blend(&p, (0, 1), 0.0, &[(form(10, "star5", from), false)], &[], &[], &o);
|
||||
assert!(differ(&star, &render_slide(&p, 0, &o)) > 400);
|
||||
// Halfway the shape is filled around the middle of its box.
|
||||
let mid = frame(0.5);
|
||||
@@ -818,7 +818,7 @@ fn morph_path_fades_a_fill_only_one_side_has() {
|
||||
let at = |t: f64| {
|
||||
let pm = PathMorph { id: ShapeId(10), from: Geom::preset("rect"), from_box: bx, to_box: bx, t };
|
||||
// A point near the line but not on it: inside the shape all the way.
|
||||
render_blend(&p, 0, 1, t, &[(form(10, "line"), false)], &[], &[pm], &o).pixel(110, 90)
|
||||
render_blend(&p, (0, 1), t, &[(form(10, "line"), false)], &[], &[pm], &o).pixel(110, 90)
|
||||
};
|
||||
let (full, half, none) = (at(0.0), at(0.5), at(1.0));
|
||||
assert_eq!(none, [255, 255, 255, 255]);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use super::geometry::{blur, color, pre_translate, scale_of};
|
||||
use super::opts::ParaState;
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Arc, BezPath, Ctx, FontDb, HashMap, Opts, PhType, Rect, RenderContext, Rgba, Shape, TextBody, Vec2, colr, paint, peniko, resolve,
|
||||
};
|
||||
|
||||
/// The prompt shown in an empty placeholder, formatted like its content would be.
|
||||
pub(crate) fn prompt_body(rctx: &Ctx, s: &Shape, kind: PhType) -> TextBody {
|
||||
@@ -53,10 +55,10 @@ pub(crate) fn glyph(face: &deckcraft_fonts::FontFace, gid: u32) -> Arc<BezPath>
|
||||
p
|
||||
}
|
||||
|
||||
/// Draw a text body into shape space `m` within the text rect.
|
||||
pub fn draw_text(ctx: &mut RenderContext, rctx: &Ctx, s: &Shape, body: &TextBody, tr: Rect, m: Affine, fields: &dyn Fields, prompt: Option<Rgba>) {
|
||||
let l = deckcraft_text::layout(rctx, s, body, &Opts { rect: tr, fields, prompt_color: prompt, no_shrink: false });
|
||||
draw_layout(ctx, &l, tr, m);
|
||||
/// Draw a text body into shape space `m` within the text rect `opts.rect`.
|
||||
pub fn draw_text(ctx: &mut RenderContext, rctx: &Ctx, s: &Shape, body: &TextBody, m: Affine, opts: &Opts) {
|
||||
let l = deckcraft_text::layout(rctx, s, body, opts);
|
||||
draw_layout(ctx, &l, opts.rect, m);
|
||||
}
|
||||
|
||||
/// Add transient list markers to an editor layout, using the document's own bullet glyphs.
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn render_transition(pres: &Presentation, index: usize, t: f64, opts: &Rende
|
||||
.iter()
|
||||
.map(|x| crate::PathMorph { id: x.id, from: x.from.clone(), from_box: x.from_box, to_box: x.to_box, t: m.mix })
|
||||
.collect();
|
||||
return render_blend(pres, from, index, m.mix, &m.shapes, &text, &paths, &o);
|
||||
return render_blend(pres, (from, index), m.mix, &m.shapes, &text, &paths, &o);
|
||||
}
|
||||
}
|
||||
let new = render_slide(pres, index, opts);
|
||||
|
||||
Reference in New Issue
Block a user