gui: Save PDF on every report; short and narrow windows hold up

Reports get a Save PDF button beside Copy for a spreadsheet. It typesets
the report as it's set up on screen (range, months/quarters/years,
comparison) through ledger-import's Typst statement template, the same
one the CPA package uses, in the background, writes it to the Downloads
folder (never the books: the viewer stays read-only), and opens it.

- ledger-import: Statement.percent_from marks percentage columns
  (comparison %, % of income), typeset as 12.5% with no dollar sign;
  wide statements turn the page, and past seven columns set smaller so
  a year of months fits; statement() is public for other callers.
- Compared columns that read the same (Jan 1 - Oct 12 vs a year
  earlier) now say which year, on screen and on paper.
- Short windows: the sidebar's and rail's links scroll above the status
  box instead of under it. The search palette fits the window.
- A test typesets every table-backed report from the demo books,
  plain, by month, and compared (PDF_DIR keeps the files).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-12 20:14:35 -06:00
1 parent ea996f6e7e
commit 12308fe3e9
9 files changed
+444 -101

No files matched your search

+5 -1
View File
@@ -102,6 +102,9 @@ pub struct Statement {
pub columns: Vec<String>,
/// The rows, top to bottom.
pub rows: Vec<Row>,
/// Columns from this index on hold percentages ("12.5%"), not money:
/// a comparison's "%" column, a P&L's "% of income".
pub percent_from: Option<usize>,
}
fn row(kind: RowKind, label: impl Into<String>, amounts: Vec<Option<Decimal>>) -> Row {
@@ -131,7 +134,7 @@ fn spacer() -> Row {
}
/// Finish a statement: currency signs on sums, rules above them.
fn statement(title: &str, period: String, columns: &[&str], mut rows: Vec<Row>) -> Statement {
pub fn statement(title: &str, period: String, columns: &[&str], mut rows: Vec<Row>) -> Statement {
let mut above: Option<RowKind> = None;
for r in &mut rows {
r.currency = matches!(r.kind, RowKind::Subtotal | RowKind::Total);
@@ -149,6 +152,7 @@ fn statement(title: &str, period: String, columns: &[&str], mut rows: Vec<Row>)
period,
columns: columns.iter().map(|c| c.to_string()).collect(),
rows,
percent_from: None,
}
}