202 lines
6.3 KiB
TypeScript
202 lines
6.3 KiB
TypeScript
// Script to generate ten-frame example images for blog post
|
|
// Shows single problems with different ten-frame scaffolding levels
|
|
//
|
|
// REUSABLE PATTERN: This script demonstrates how to generate single-problem
|
|
// examples for blog posts using the SAME code that powers the display options
|
|
// preview in the worksheet generator UI. The generateExampleTypst function
|
|
// in src/app/api/create/worksheets/addition/example/route.ts is the single
|
|
// source of truth for rendering individual problems with display options.
|
|
//
|
|
// To generate examples for other blog posts:
|
|
// 1. Import generateTypstHelpers and generateProblemStackFunction from typstHelpers.ts
|
|
// 2. Use the generateExampleTypst pattern below with your desired options
|
|
// 3. Compile to SVG using typst
|
|
// 4. Save to public/blog/[your-post-name]/
|
|
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { execSync } from 'child_process'
|
|
import {
|
|
generateTypstHelpers,
|
|
generateProblemStackFunction,
|
|
} from '../src/app/create/worksheets/addition/typstHelpers'
|
|
|
|
// Output directory
|
|
const outputDir = path.join(process.cwd(), 'public', 'blog', 'ten-frame-examples')
|
|
|
|
// Ensure output directory exists
|
|
if (!fs.existsSync(outputDir)) {
|
|
fs.mkdirSync(outputDir, { recursive: true })
|
|
}
|
|
|
|
interface ExampleOptions {
|
|
showCarryBoxes?: boolean
|
|
showAnswerBoxes?: boolean
|
|
showPlaceValueColors?: boolean
|
|
showTenFrames?: boolean
|
|
showProblemNumbers?: boolean
|
|
transparentBackground?: boolean
|
|
fontSize?: number
|
|
addend1: number
|
|
addend2: number
|
|
}
|
|
|
|
/**
|
|
* Generate a single compact problem example
|
|
* This is the SAME logic used by the API route for display option previews
|
|
* Extracted here so we can generate static examples for blog posts
|
|
*/
|
|
function generateExampleTypst(config: ExampleOptions): string {
|
|
const a = config.addend1
|
|
const b = config.addend2
|
|
const fontSize = config.fontSize || 16
|
|
const cellSize = 0.45 // Slightly larger for blog examples vs UI previews (0.35)
|
|
|
|
// Boolean flags matching worksheet generator
|
|
const showCarries = config.showCarryBoxes ?? false
|
|
const showAnswers = config.showAnswerBoxes ?? false
|
|
const showColors = config.showPlaceValueColors ?? false
|
|
const showNumbers = config.showProblemNumbers ?? false
|
|
const showTenFrames = config.showTenFrames ?? false
|
|
const showTenFramesForAll = false // Not used for blog examples
|
|
const transparentBg = config.transparentBackground ?? false
|
|
|
|
return String.raw`
|
|
#set page(width: auto, height: auto, margin: 12pt, fill: ${transparentBg ? 'none' : 'white'})
|
|
#set text(size: ${fontSize}pt, font: "New Computer Modern Math")
|
|
|
|
#let heavy-stroke = 0.8pt
|
|
#let show-ten-frames-for-all = ${showTenFramesForAll ? 'true' : 'false'}
|
|
|
|
${generateTypstHelpers(cellSize)}
|
|
|
|
${generateProblemStackFunction(cellSize)}
|
|
|
|
#let a = ${a}
|
|
#let b = ${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)
|
|
|
|
#align(center + horizon)[
|
|
#problem-stack(
|
|
a, b, aT, aO, bT, bO,
|
|
${showNumbers ? '0' : 'none'},
|
|
${showCarries},
|
|
${showAnswers},
|
|
${showColors},
|
|
${showTenFrames},
|
|
${showNumbers}
|
|
)
|
|
]
|
|
`
|
|
}
|
|
|
|
// Generate examples showing ten-frames in action
|
|
// Use problems that WILL have regrouping to show ten-frames
|
|
const examples = [
|
|
{
|
|
name: 'with-ten-frames',
|
|
filename: 'with-ten-frames.svg',
|
|
description: 'With Ten-Frames: Visual scaffolding for regrouping',
|
|
options: {
|
|
addend1: 47,
|
|
addend2: 38, // 7+8=15 requires regrouping, will show ten-frames
|
|
showCarryBoxes: false,
|
|
showAnswerBoxes: false,
|
|
showPlaceValueColors: false,
|
|
showTenFrames: true,
|
|
showProblemNumbers: false,
|
|
transparentBackground: true,
|
|
},
|
|
},
|
|
{
|
|
name: 'without-ten-frames',
|
|
filename: 'without-ten-frames.svg',
|
|
description: 'Without Ten-Frames: Abstract representation',
|
|
options: {
|
|
addend1: 47,
|
|
addend2: 38, // Same problem, no ten-frames
|
|
showCarryBoxes: false,
|
|
showAnswerBoxes: false,
|
|
showPlaceValueColors: false,
|
|
showTenFrames: false, // No ten-frames
|
|
showProblemNumbers: false,
|
|
transparentBackground: true,
|
|
},
|
|
},
|
|
{
|
|
name: 'beginner-with-ten-frames',
|
|
filename: 'beginner-ten-frames.svg',
|
|
description: 'Beginner: Learning regrouping with ten-frames',
|
|
options: {
|
|
addend1: 28,
|
|
addend2: 15, // 8+5=13 requires regrouping
|
|
showCarryBoxes: false,
|
|
showAnswerBoxes: false,
|
|
showPlaceValueColors: false,
|
|
showTenFrames: true,
|
|
showProblemNumbers: false,
|
|
transparentBackground: true,
|
|
},
|
|
},
|
|
{
|
|
name: 'ten-frames-both-columns',
|
|
filename: 'ten-frames-both-columns.svg',
|
|
description: 'Ten-frames in both columns: Double regrouping',
|
|
options: {
|
|
addend1: 57,
|
|
addend2: 68, // Both ones (7+8=15) and tens (5+6+1=12) regroup
|
|
showCarryBoxes: false,
|
|
showAnswerBoxes: false,
|
|
showPlaceValueColors: false,
|
|
showTenFrames: true,
|
|
showProblemNumbers: false,
|
|
transparentBackground: true,
|
|
},
|
|
},
|
|
] as const
|
|
|
|
console.log('Generating ten-frame example images (single problems)...\n')
|
|
|
|
for (const example of examples) {
|
|
console.log(`Generating ${example.description}...`)
|
|
|
|
try {
|
|
const typstSource = generateExampleTypst(example.options)
|
|
|
|
// Compile to SVG
|
|
let svg = execSync('typst compile --format svg - -', {
|
|
input: typstSource,
|
|
encoding: 'utf8',
|
|
maxBuffer: 2 * 1024 * 1024,
|
|
})
|
|
|
|
// Post-process: Make SVG visible on dark background
|
|
// - Digits on white cells should stay BLACK
|
|
// - Operator symbols (+) should be WHITE
|
|
// - Structural elements (borders, bars) should be WHITE
|
|
svg = svg
|
|
.replace(/stroke="#000000"/g, 'stroke="rgba(255, 255, 255, 0.8)"')
|
|
.replace(/stroke="#0000004d"/g, 'stroke="rgba(255, 255, 255, 0.4)"')
|
|
|
|
// Replace operator (+) fill specifically to white
|
|
svg = svg.replace(
|
|
/(<use xlink:href="#gCFEF70472F9D2AA9AC128F96529819DA"[^>]*fill=")#000000/g,
|
|
'$1rgba(255, 255, 255, 0.9)'
|
|
)
|
|
|
|
// Save to file
|
|
const outputPath = path.join(outputDir, example.filename)
|
|
fs.writeFileSync(outputPath, svg, 'utf-8')
|
|
|
|
console.log(` ✓ Saved to ${outputPath}`)
|
|
} catch (error) {
|
|
console.error(` ✗ Error generating ${example.name}:`, error)
|
|
}
|
|
}
|
|
|
|
console.log('\nDone! Ten-frame example images generated.')
|
|
console.log(`\nFiles saved to: ${outputDir}`)
|