fix(worksheets): dynamically size grid based on actual problem digits
Problem: All problems rendered with max-digits columns (e.g., 5 columns for 2-digit problems), showing scaffolding for unused place values. Root cause: Grid column count was fixed at page-level max-digits, and all rendering loops used max-digits instead of per-problem actual-digits. Solution: - Calculate actual-digits per problem (max of addends + sum, accounting for overflow like 99+1=100) - Extract max-digits+1 positions to capture sum overflow - Generate column-list dynamically in Typst based on actual-digits - Update all loops to use actual-digits instead of max-digits - Hide leading zeros by checking i <= highest position Now a 2-digit problem gets a 3-column grid (allowing sum overflow), and a 5-digit problem gets a 6-column grid. Each problem renders exactly the scaffolding it needs. Includes: - Leading zero detection (a-highest, b-highest, sum-highest) - Dynamic column list generation in Typst - Ten-frames support for all place values (removed digit restrictions) - Proper overflow handling for sums 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,24 @@ function chunkProblems(problems: AdditionProblem[], pageSize: number): AdditionP
|
||||
return pages
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate maximum number of digits in any problem on this page
|
||||
* Returns max digits across all addends (handles 1-6 digit sums)
|
||||
*/
|
||||
function calculateMaxDigits(problems: AdditionProblem[]): number {
|
||||
let maxDigits = 1
|
||||
for (const problem of problems) {
|
||||
const digitsA = problem.a.toString().length
|
||||
const digitsB = problem.b.toString().length
|
||||
const maxProblemDigits = Math.max(digitsA, digitsB)
|
||||
maxDigits = Math.max(maxDigits, maxProblemDigits)
|
||||
}
|
||||
// Sum might have one more digit than the largest addend
|
||||
// e.g., 99999 + 99999 = 199998 (5 digits + 5 digits = 6 digits)
|
||||
// But we render based on addend size, not sum size
|
||||
return maxDigits
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Typst source code for a single page
|
||||
*/
|
||||
@@ -31,6 +49,10 @@ function generatePageTypst(
|
||||
showTenFrames: config.mode === 'manual' ? config.showTenFrames : 'N/A (smart mode)',
|
||||
})
|
||||
|
||||
// Calculate maximum digits for proper column layout
|
||||
const maxDigits = calculateMaxDigits(pageProblems)
|
||||
console.log('[typstGenerator] Max digits on this page:', maxDigits)
|
||||
|
||||
// Enrich problems with display options based on mode
|
||||
const enrichedProblems = pageProblems.map((p, index) => {
|
||||
if (config.mode === 'smart') {
|
||||
@@ -103,10 +125,19 @@ function generatePageTypst(
|
||||
const anyProblemMayShowTenFrames = enrichedProblems.some((p) => p.showTenFrames)
|
||||
|
||||
// Calculate cell size to fill the entire problem box
|
||||
// Without ten-frames: 5 rows (carry, first number, second number, line, answer)
|
||||
// With ten-frames: 5 rows + ten-frames row (0.8 * cellSize for square cells)
|
||||
// Total with ten-frames: 5.8 rows, reduced breathing room to maximize size
|
||||
const cellSize = anyProblemMayShowTenFrames ? problemBoxHeight / 6.0 : problemBoxHeight / 4.5
|
||||
// Base vertical stack: carry row + addend1 + addend2 + line + answer = 5 rows
|
||||
// With ten-frames: add 0.8 * cellSize row
|
||||
// Total with ten-frames: ~5.8 rows
|
||||
//
|
||||
// Horizontal constraint: maxDigits columns + 1 for + sign
|
||||
// Cell size must fit: (maxDigits + 1) * cellSize <= problemBoxWidth
|
||||
const maxCellSizeForWidth = problemBoxWidth / (maxDigits + 1)
|
||||
const maxCellSizeForHeight = anyProblemMayShowTenFrames
|
||||
? problemBoxHeight / 6.0
|
||||
: problemBoxHeight / 4.5
|
||||
|
||||
// Use the smaller of width/height constraints
|
||||
const cellSize = Math.min(maxCellSizeForWidth, maxCellSizeForHeight)
|
||||
|
||||
return String.raw`
|
||||
// addition-worksheet-page.typ (auto-generated)
|
||||
@@ -135,15 +166,11 @@ function generatePageTypst(
|
||||
|
||||
${generateTypstHelpers(cellSize)}
|
||||
|
||||
${generateProblemStackFunction(cellSize)}
|
||||
${generateProblemStackFunction(cellSize, maxDigits)}
|
||||
|
||||
#let problem-box(problem, index) = {
|
||||
let a = problem.a
|
||||
let b = problem.b
|
||||
let aT = calc.floor(calc.rem(a, 100) / 10)
|
||||
let aO = calc.rem(a, 10)
|
||||
let bT = calc.floor(calc.rem(b, 100) / 10)
|
||||
let bO = calc.rem(b, 10)
|
||||
|
||||
// Extract per-problem display flags
|
||||
let grid-stroke = if problem.showCellBorder { (thickness: 1pt, dash: "dashed", paint: gray.darken(20%)) } else { none }
|
||||
@@ -156,7 +183,7 @@ ${generateProblemStackFunction(cellSize)}
|
||||
)[
|
||||
#align(center + horizon)[
|
||||
#problem-stack(
|
||||
a, b, aT, aO, bT, bO, index,
|
||||
a, b, index,
|
||||
problem.showCarryBoxes,
|
||||
problem.showAnswerBoxes,
|
||||
problem.showPlaceValueColors,
|
||||
|
||||
Reference in New Issue
Block a user