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:
Dylan M. Taylor
2026-10-25 17:33:59 -04:00
committed by GitHub
co-authored by Claude Sonnet 5.5
parent 5478eb655e
commit 8e77e8db2d
3 changed files with 122 additions and 113 deletions
+48 -43
View File
@@ -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
View File
@@ -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
+31 -31
View File
@@ -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;
}