Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/bugfixes/issue-418/after.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bugfixes/issue-418/before.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bugfixes/issue-418/gt.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 104 additions & 4 deletions crates/office2pdf/src/render/typst_gen_advanced_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@ fn test_data_bar_fixed_row_height_codegen() {
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
// With a 24pt row and 5pt top/bottom insets, the bar fills the cell's
// 14pt content height instead of resolving against the page.
// A 24pt row less Excel's 2pt clearance per side gives a concrete 20pt
// bar instead of a height that resolves against the page.
assert!(
output.source.contains("height: 14pt"),
"DataBar in a fixed-height row should fill the cell content height. Got: {}",
output.source.contains("height: 20pt"),
"DataBar in a fixed-height row should be inset from the row edges. Got: {}",
output.source,
);
assert!(
Expand Down Expand Up @@ -1144,3 +1144,103 @@ fn test_header_without_non_repeating_rows_stays_a_single_block() {
assert!(!output.source.contains("repeat: false"));
assert!(!output.source.contains("level: 2"));
}

/// Excel insets data bars from the row's top and bottom edges instead of
/// filling the cell: native Excel PDFs print a 10 pt bar in a 14 pt row.
#[test]
fn test_data_bar_is_inset_from_the_row_edges() {
use crate::ir::DataBarInfo;

let cell = TableCell {
content: vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: "120".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
}],
})],
data_bar: Some(DataBarInfo {
color: Color::new(0x63, 0x8E, 0xC6),
fill_pct: 24.0,
}),
..TableCell::default()
};
let table = Table {
rows: vec![TableRow {
cells: vec![cell],
height: Some(14.0),
}],
column_widths: vec![100.0],
..Table::default()
};
let page = Page::Sheet(SheetPage {
name: "Sheet1".to_string(),
size: PageSize::default(),
margins: Margins::default(),
table,
header: None,
footer: None,
charts: vec![],
images: Vec::new(),
text_boxes: Vec::new(),
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(
output.source.contains("height: 10pt"),
"a 14pt row must print a 10pt bar. Got: {}",
output.source,
);
}

/// The inset never collapses the bar in very short rows.
#[test]
fn test_data_bar_in_a_short_row_keeps_a_visible_height() {
use crate::ir::DataBarInfo;

let cell = TableCell {
content: vec![Block::Paragraph(Paragraph {
style: ParagraphStyle::default(),
runs: vec![Run {
text: "3".to_string(),
style: TextStyle::default(),
href: None,
footnote: None,
}],
})],
data_bar: Some(DataBarInfo {
color: Color::new(0x63, 0x8E, 0xC6),
fill_pct: 50.0,
}),
..TableCell::default()
};
let table = Table {
rows: vec![TableRow {
cells: vec![cell],
height: Some(3.0),
}],
column_widths: vec![100.0],
..Table::default()
};
let page = Page::Sheet(SheetPage {
name: "Sheet1".to_string(),
size: PageSize::default(),
margins: Margins::default(),
table,
header: None,
footer: None,
charts: vec![],
images: Vec::new(),
text_boxes: Vec::new(),
});
let doc = make_doc(vec![page]);
let output = generate_typst(&doc).unwrap();
assert!(
!output.source.contains("height: 0pt"),
"the bar must stay visible. Got: {}",
output.source,
);
assert!(!output.source.contains("height: -"));
}
18 changes: 13 additions & 5 deletions crates/office2pdf/src/render/typst_gen_tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ fn generate_table_rows(
Ok(())
}

/// Excel does not fill the cell with a data bar: it insets the bar from the
/// row's top and bottom edges. Native Excel PDF exports of the business corpus
/// print a 10 pt bar in every 14 pt row, which is 2 pt of clearance per side.
const DATA_BAR_VERTICAL_INSET_PT: f64 = 2.0;

/// Floor for rows shorter than the inset, so a bar never vanishes or inverts.
const DATA_BAR_MIN_HEIGHT_PT: f64 = 1.0;

fn generate_table_cell(
out: &mut String,
cell: &TableCell,
Expand Down Expand Up @@ -255,15 +263,15 @@ fn generate_table_cell(
// no cell frame to resolve against and blows up to the page height,
// smearing over neighboring rows (issue #362).
let pct = db.fill_pct.clamp(0.0, 100.0);
let padding = cell.padding.unwrap_or(default_cell_padding);
let bar_height: String = match row_height {
Some(height) => {
let content_height = (height - padding.top - padding.bottom).max(1.0);
format!("{}pt", format_f64(content_height))
let inset_height =
(height - 2.0 * DATA_BAR_VERTICAL_INSET_PT).max(DATA_BAR_MIN_HEIGHT_PT);
format!("{}pt", format_f64(inset_height))
}
// Excel sizes default rows to the font's line box; 1.2em tracks
// that for single-line numeric cells.
None => "1.2em".to_string(),
// that for single-line numeric cells, less the same inset.
None => format!("1.2em - {}pt", format_f64(2.0 * DATA_BAR_VERTICAL_INSET_PT)),
};
let _ = write!(
out,
Expand Down
Loading