mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-28 04:06:54 -06:00
refactor: split complex functions in swatch_refs, xlsx, render group drawing (Sonar S3776) (#1733)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
5478eb655e
commit
8e77e8db2d
@@ -17,6 +17,53 @@ struct Names<'a> {
|
||||
}
|
||||
|
||||
impl Names<'_> {
|
||||
/// Rename the swatch references in every paragraph, character, object, cell and table style.
|
||||
fn styles(&self, styles: &mut crate::styles::Styles) {
|
||||
let n = self;
|
||||
for p in &mut styles.paragraph {
|
||||
n.para(&mut p.para);
|
||||
n.chars(&mut p.chars);
|
||||
}
|
||||
for c in &mut styles.character {
|
||||
n.chars(&mut c.chars);
|
||||
}
|
||||
for o in &mut styles.object {
|
||||
if let Some(f) = &mut o.fill {
|
||||
n.fill(f);
|
||||
}
|
||||
if let Some(s) = &mut o.stroke {
|
||||
n.stroke(s);
|
||||
}
|
||||
if let Some(tf) = &mut o.text_frame {
|
||||
n.frame(tf);
|
||||
}
|
||||
}
|
||||
for c in &mut styles.cell {
|
||||
n.o(&mut c.fill);
|
||||
if let Some(s) = &mut c.stroke {
|
||||
n.cell_stroke(s);
|
||||
}
|
||||
for s in &mut c.strokes {
|
||||
n.stroke_attrs(s);
|
||||
}
|
||||
}
|
||||
for t in &mut styles.table {
|
||||
if let Some(b) = &mut t.border {
|
||||
n.cell_stroke(b);
|
||||
}
|
||||
for b in &mut t.borders {
|
||||
n.stroke_attrs(b);
|
||||
}
|
||||
if let Some(f) = &mut t.alt_rows {
|
||||
n.alt_fills(f);
|
||||
}
|
||||
n.alt_fill_attrs(&mut t.row_fills);
|
||||
n.alt_fill_attrs(&mut t.column_fills);
|
||||
n.alt_stroke_attrs(&mut t.row_strokes);
|
||||
n.alt_stroke_attrs(&mut t.column_strokes);
|
||||
}
|
||||
}
|
||||
|
||||
fn s(&self, v: &mut String) {
|
||||
if v == self.from {
|
||||
*v = self.to.to_string();
|
||||
@@ -170,49 +217,7 @@ impl Document {
|
||||
n.s(b);
|
||||
}
|
||||
n.s(&mut self.footnote_options.rule.color);
|
||||
let styles = self.styles_mut();
|
||||
for p in &mut styles.paragraph {
|
||||
n.para(&mut p.para);
|
||||
n.chars(&mut p.chars);
|
||||
}
|
||||
for c in &mut styles.character {
|
||||
n.chars(&mut c.chars);
|
||||
}
|
||||
for o in &mut styles.object {
|
||||
if let Some(f) = &mut o.fill {
|
||||
n.fill(f);
|
||||
}
|
||||
if let Some(s) = &mut o.stroke {
|
||||
n.stroke(s);
|
||||
}
|
||||
if let Some(tf) = &mut o.text_frame {
|
||||
n.frame(tf);
|
||||
}
|
||||
}
|
||||
for c in &mut styles.cell {
|
||||
n.o(&mut c.fill);
|
||||
if let Some(s) = &mut c.stroke {
|
||||
n.cell_stroke(s);
|
||||
}
|
||||
for s in &mut c.strokes {
|
||||
n.stroke_attrs(s);
|
||||
}
|
||||
}
|
||||
for t in &mut styles.table {
|
||||
if let Some(b) = &mut t.border {
|
||||
n.cell_stroke(b);
|
||||
}
|
||||
for b in &mut t.borders {
|
||||
n.stroke_attrs(b);
|
||||
}
|
||||
if let Some(f) = &mut t.alt_rows {
|
||||
n.alt_fills(f);
|
||||
}
|
||||
n.alt_fill_attrs(&mut t.row_fills);
|
||||
n.alt_fill_attrs(&mut t.column_fills);
|
||||
n.alt_stroke_attrs(&mut t.row_strokes);
|
||||
n.alt_stroke_attrs(&mut t.column_strokes);
|
||||
}
|
||||
n.styles(self.styles_mut());
|
||||
for sp in self.spreads.iter_mut().chain(self.parents.iter_mut()) {
|
||||
for it in &mut Arc::make_mut(sp).items {
|
||||
n.item(Arc::make_mut(it), 0);
|
||||
|
||||
+43
-39
@@ -555,6 +555,48 @@ impl Renderer {
|
||||
self.drawing_items.pop();
|
||||
}
|
||||
|
||||
/// A group: children carry their own transforms relative to the group.
|
||||
fn draw_group(&mut self, ctx: &mut RenderContext, f: &Frame, it: &Item, xf: Affine, page: ShownOn) {
|
||||
// Isolate Blending / Knockout: the group is its own layer (blend modes stop at it).
|
||||
let layered = it.opacity < 0.999 || it.blend != DcBlend::Normal || it.isolate || it.knockout;
|
||||
if layered {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, Some(blend_mode(it.blend)), Some(it.opacity), None, None);
|
||||
}
|
||||
// Gradient feather: the group's objects drawn together, then faded as one.
|
||||
let feather = it.effects.gradient_feather.on && f.opts.quality != DisplayQuality::Fast;
|
||||
if feather {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, None, None, None, None);
|
||||
}
|
||||
if it.knockout {
|
||||
// Knockout: each object first clears its area of those behind it, so it composites
|
||||
// with the group's backdrop rather than with its siblings.
|
||||
for c in it.shown_children() {
|
||||
if !c.hidden {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, Some(BlendMode::new(Mix::Normal, Compose::DestOut)), None, None, None);
|
||||
ctx.set_transform(f.view * xf * c.xf);
|
||||
ctx.set_paint(peniko::Color::BLACK);
|
||||
ctx.fill_path(&c.path.to_bezpath());
|
||||
ctx.pop_layer();
|
||||
}
|
||||
self.draw_item(ctx, f, c, xf, page);
|
||||
}
|
||||
} else {
|
||||
for c in it.shown_children() {
|
||||
self.draw_item(ctx, f, c, xf, page);
|
||||
}
|
||||
}
|
||||
if feather {
|
||||
fx::gradient_feather_mask(ctx, f, it, xf);
|
||||
ctx.pop_layer();
|
||||
}
|
||||
if layered {
|
||||
ctx.pop_layer();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_item_inner(&mut self, ctx: &mut RenderContext, f: &Frame, it: &Item, parent: Affine, page: ShownOn) {
|
||||
if it.hidden || f.opts.hidden.contains(&it.id) || f.opts.printing_only && it.nonprinting {
|
||||
return;
|
||||
@@ -564,45 +606,7 @@ impl Renderer {
|
||||
// A frame with items pasted into it draws like any other object (draw_body: fill, the items
|
||||
// clipped to the frame, stroke), so its effects apply to all of it.
|
||||
if !it.children().is_empty() && !it.has_nested_items() {
|
||||
// Groups: children carry their own transforms relative to the group.
|
||||
// Isolate Blending / Knockout: the group is its own layer (blend modes stop at it).
|
||||
let layered = it.opacity < 0.999 || it.blend != DcBlend::Normal || it.isolate || it.knockout;
|
||||
if layered {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, Some(blend_mode(it.blend)), Some(it.opacity), None, None);
|
||||
}
|
||||
// Gradient feather: the group's objects drawn together, then faded as one.
|
||||
let feather = it.effects.gradient_feather.on && f.opts.quality != DisplayQuality::Fast;
|
||||
if feather {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, None, None, None, None);
|
||||
}
|
||||
if it.knockout {
|
||||
// Knockout: each object first clears its area of those behind it, so it composites
|
||||
// with the group's backdrop rather than with its siblings.
|
||||
for c in it.shown_children() {
|
||||
if !c.hidden {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, Some(BlendMode::new(Mix::Normal, Compose::DestOut)), None, None, None);
|
||||
ctx.set_transform(f.view * xf * c.xf);
|
||||
ctx.set_paint(peniko::Color::BLACK);
|
||||
ctx.fill_path(&c.path.to_bezpath());
|
||||
ctx.pop_layer();
|
||||
}
|
||||
self.draw_item(ctx, f, c, xf, page);
|
||||
}
|
||||
} else {
|
||||
for c in it.shown_children() {
|
||||
self.draw_item(ctx, f, c, xf, page);
|
||||
}
|
||||
}
|
||||
if feather {
|
||||
fx::gradient_feather_mask(ctx, f, it, xf);
|
||||
ctx.pop_layer();
|
||||
}
|
||||
if layered {
|
||||
ctx.pop_layer();
|
||||
}
|
||||
self.draw_group(ctx, f, it, xf, page);
|
||||
return;
|
||||
}
|
||||
// Composed text stays inside its frame apart from glyph overhang (italics, swashes, big
|
||||
|
||||
@@ -91,6 +91,34 @@ fn general(f: f64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// The first and last column used on the header row, if that row has any cells.
|
||||
fn header_span(cells: &[(usize, usize, String)], header_row: usize) -> Option<(usize, usize)> {
|
||||
cells.iter().filter(|(r, _, _)| *r == header_row).fold(None, |span, (_, c, _)| match span {
|
||||
None => Some((*c, *c)),
|
||||
Some((lo, hi)) => Some((lo.min(*c), hi.max(*c))),
|
||||
})
|
||||
}
|
||||
|
||||
/// One row's values laid into `width` columns starting at column `min_c`; values outside that
|
||||
/// span come back separately (only the non-empty ones), so the caller can warn.
|
||||
fn place_row(cols: &[(usize, String)], min_c: usize, width: usize) -> (Vec<String>, Vec<String>) {
|
||||
let mut row = vec![String::new(); width];
|
||||
let mut extra: Vec<String> = Vec::new();
|
||||
for (c, v) in cols {
|
||||
let at = c.checked_sub(min_c).filter(|at| *at < width);
|
||||
match at {
|
||||
Some(at) => {
|
||||
if let Some(cell) = row.get_mut(at) {
|
||||
*cell = v.clone();
|
||||
}
|
||||
}
|
||||
None if !v.is_empty() => extra.push(v.clone()),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
(row, extra)
|
||||
}
|
||||
|
||||
/// One worksheet as a grid of cached cell text, empty cells kept, for data merge.
|
||||
/// The first used row is the header. Later rows that are entirely empty are left out.
|
||||
/// Cells past the header are kept so the caller can warn. Over 200 columns or 100,000
|
||||
@@ -117,19 +145,9 @@ pub fn records(bytes: &[u8], sheet: Option<&str>) -> Result<Vec<Vec<String>>, Im
|
||||
return Err(ImportError::Corrupt("the worksheet is empty".into()));
|
||||
}
|
||||
let header_row = cells.iter().map(|c| c.0).min().unwrap_or(0);
|
||||
let mut min_c = usize::MAX;
|
||||
let mut max_c = 0usize;
|
||||
let mut header_used = false;
|
||||
for (r, c, _) in &cells {
|
||||
if *r == header_row {
|
||||
header_used = true;
|
||||
min_c = min_c.min(*c);
|
||||
max_c = max_c.max(*c);
|
||||
}
|
||||
}
|
||||
if !header_used || min_c == usize::MAX {
|
||||
let Some((min_c, max_c)) = header_span(&cells, header_row) else {
|
||||
return Err(ImportError::Corrupt("the worksheet has no header".into()));
|
||||
}
|
||||
};
|
||||
let width = max_c.saturating_sub(min_c).saturating_add(1);
|
||||
if width > crate::DATA_MERGE_MAX_COLS {
|
||||
return Err(ImportError::Corrupt(format!("the sheet has {width} columns; the limit is {}", crate::DATA_MERGE_MAX_COLS)));
|
||||
@@ -154,25 +172,7 @@ pub fn records(bytes: &[u8], sheet: Option<&str>) -> Result<Vec<Vec<String>>, Im
|
||||
}
|
||||
let mut out = Vec::with_capacity(data_rows.saturating_add(1));
|
||||
for (r, cols) in &by_row {
|
||||
let mut row = vec![String::new(); width];
|
||||
let mut extra: Vec<String> = Vec::new();
|
||||
for (c, v) in cols {
|
||||
if *c < min_c {
|
||||
// Outside the header span. Keep one non-empty value so the caller can warn.
|
||||
if !v.is_empty() {
|
||||
extra.push(v.clone());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let at = c - min_c;
|
||||
if at < width {
|
||||
if let Some(cell) = row.get_mut(at) {
|
||||
*cell = v.clone();
|
||||
}
|
||||
} else if !v.is_empty() {
|
||||
extra.push(v.clone());
|
||||
}
|
||||
}
|
||||
let (mut row, extra) = place_row(cols, min_c, width);
|
||||
if *r != header_row && row.iter().all(String::is_empty) && extra.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user