refactor: begin modularizing typstHelpers.ts - extract shared components
Phase 1 of typstHelpers.ts refactoring (793 lines → modular structure) Created new directory structure: - typstHelpers/shared/ - Colors, helpers, types - typstHelpers/addition/ - Addition-specific components (future) - typstHelpers/subtraction/ - Subtraction-specific components Extracted components: - shared/colors.ts - Place value color definitions - shared/types.ts - TypeScript types and constants - shared/helpers.ts - Reusable Typst helpers (ten-frames, diagonal-split-box) - subtraction/borrowBoxes.ts - Borrow boxes row rendering with hints/arrows Benefits: - Smaller, focused files (50-100 lines each vs 350+ line functions) - Centralized constants (arrow positioning, stroke widths) - Better separation of concerns - Easier to locate and edit specific features Next steps: - Extract remaining subtraction rows (minuend, subtrahend, answer) - Refactor main function to compose extracted components - Maintain backward compatibility via facade pattern - Validate output matches current worksheets byte-for-byte See TYPST_HELPERS_REFACTOR_PLAN.md for complete plan. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
# typstHelpers.ts Refactoring Plan
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The `typstHelpers.ts` file has grown to **793 lines** and contains massive template literal strings that make it difficult to:
|
||||
|
||||
1. **Edit accurately** - String matching for edits is error-prone due to nested template expressions
|
||||
2. **Navigate** - Hard to find specific sections within large Typst functions
|
||||
3. **Test** - Cannot easily unit test individual Typst rendering logic
|
||||
4. **Maintain** - Changes require careful attention to bracket matching and string escaping
|
||||
|
||||
## Current Structure
|
||||
|
||||
```typescript
|
||||
typstHelpers.ts (793 lines)
|
||||
├── generateTypstHelpers(cellSize) ~75 lines
|
||||
├── generateProblemStackFunction(cellSize) ~235 lines ⚠️ LARGE
|
||||
├── generateSubtractionProblemStackFunction() ~360 lines ⚠️ VERY LARGE
|
||||
└── generateProblemTypst() [DEPRECATED] ~90 lines
|
||||
```
|
||||
|
||||
### Pain Points
|
||||
|
||||
1. **`generateSubtractionProblemStackFunction` is 360 lines** of dense Typst template
|
||||
- Contains nested conditionals for: borrowing hints, place value colors, ten-frames, etc.
|
||||
- Difficult to locate specific features (e.g., arrow rendering at line ~467)
|
||||
- Hard to edit without breaking bracket matching
|
||||
|
||||
2. **Template literal hell** - Mixing TypeScript and Typst syntax:
|
||||
```typescript
|
||||
`#if show-colors {
|
||||
box[#diagonal-split-box(${cellSize}, ...)] // TypeScript interpolation
|
||||
} else {
|
||||
box[...]
|
||||
}`
|
||||
```
|
||||
|
||||
3. **Duplicate code** between addition and subtraction functions
|
||||
- Both have similar grid structures, cell rendering, answer boxes
|
||||
- Borrow boxes (subtraction) vs. Carry boxes (addition) have parallel logic
|
||||
|
||||
## Refactoring Strategy
|
||||
|
||||
### Phase 1: Extract Typst Components to Separate Files (High Priority)
|
||||
|
||||
**Goal**: Break large functions into smaller, composable Typst templates.
|
||||
|
||||
Create new files:
|
||||
|
||||
```
|
||||
typstHelpers/
|
||||
├── index.ts # Re-exports everything
|
||||
├── shared/
|
||||
│ ├── colors.ts # Color constants
|
||||
│ ├── helpers.ts # ten-frames, diagonal-split-box
|
||||
│ └── types.ts # TypeScript types
|
||||
├── addition/
|
||||
│ ├── carryBoxes.ts # Carry box row rendering
|
||||
│ ├── addendRows.ts # Addend row rendering
|
||||
│ ├── answerRow.ts # Answer box row rendering
|
||||
│ └── problemStack.ts # Main problem-stack function
|
||||
└── subtraction/
|
||||
├── borrowBoxes.ts # Borrow box row rendering (with hints/arrows)
|
||||
├── minuendRow.ts # Minuend row rendering
|
||||
├── subtrahendRow.ts # Subtrahend row rendering
|
||||
├── answerRow.ts # Answer box row rendering (shared?)
|
||||
└── problemStack.ts # Main subtraction-problem-stack function
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Each file is 50-150 lines instead of 350+
|
||||
- Easier to locate and edit specific features
|
||||
- Can share common components (answer rows, cell rendering)
|
||||
- Better separation of concerns
|
||||
|
||||
### Phase 2: Extract Reusable Typst Snippets (Medium Priority)
|
||||
|
||||
**Goal**: Create helper functions for frequently duplicated Typst patterns.
|
||||
|
||||
```typescript
|
||||
// Example: Render a cell with optional color and stroke
|
||||
export function generateCellBox(
|
||||
cellSize: number,
|
||||
options: { showColor: boolean; showStroke: boolean }
|
||||
): string {
|
||||
const stroke = options.showStroke ? ', stroke: 0.5pt' : ''
|
||||
return `box(width: ${cellSize}in, height: ${cellSize}in${stroke})`
|
||||
}
|
||||
|
||||
// Example: Render place() helper
|
||||
export function generatePlaceBlock(
|
||||
position: string,
|
||||
dx: number,
|
||||
dy: number,
|
||||
content: string
|
||||
): string {
|
||||
return `#place(
|
||||
${position},
|
||||
dx: ${dx}in,
|
||||
dy: ${dy}in,
|
||||
${content}
|
||||
)`
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Reduces duplication
|
||||
- Consistent formatting across all Typst generation
|
||||
- Easier to change common patterns (e.g., cell styling)
|
||||
|
||||
### Phase 3: Add Unit Tests (Medium Priority)
|
||||
|
||||
**Goal**: Test individual Typst generators in isolation.
|
||||
|
||||
```typescript
|
||||
// tests/typstHelpers/borrowBoxes.test.ts
|
||||
describe('generateBorrowBoxes', () => {
|
||||
it('renders arrow when showBorrowingHints is true', () => {
|
||||
const typst = generateBorrowBoxes({ showBorrowingHints: true })
|
||||
expect(typst).toContain('path(')
|
||||
expect(typst).toContain('[▼]')
|
||||
})
|
||||
|
||||
it('does not use place value colors (fixed per design)', () => {
|
||||
const typst = generateBorrowBoxes({ showPlaceValueColors: true })
|
||||
expect(typst).not.toContain('diagonal-split-box')
|
||||
expect(typst).toContain('stroke: 0.5pt')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Catch regressions when editing Typst templates
|
||||
- Document expected behavior
|
||||
- Faster feedback than manual worksheet generation
|
||||
|
||||
### Phase 4: Centralize Magic Numbers (Low Priority)
|
||||
|
||||
**Goal**: Extract hardcoded values to named constants.
|
||||
|
||||
```typescript
|
||||
// typstHelpers/constants.ts
|
||||
export const TYPST_CONSTANTS = {
|
||||
CELL_STROKE_WIDTH: 0.5,
|
||||
TEN_FRAME_STROKE_WIDTH: 0.8,
|
||||
TEN_FRAME_CELL_STROKE_WIDTH: 0.4,
|
||||
ARROW_STROKE_WIDTH: 1.5,
|
||||
|
||||
// Positioning offsets
|
||||
ARROW_START_DX: 0.9,
|
||||
ARROW_START_DY: 0.15,
|
||||
ARROWHEAD_DX: 0.96,
|
||||
ARROWHEAD_DY: 0.62,
|
||||
|
||||
// Sizing
|
||||
HINT_TEXT_SIZE_FACTOR: 0.25,
|
||||
ARROWHEAD_SIZE_FACTOR: 0.35,
|
||||
} as const
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Easier to adjust visual parameters
|
||||
- Self-documenting code
|
||||
- Reduces magic number proliferation
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Step 1: Create Directory Structure
|
||||
```bash
|
||||
mkdir -p src/app/create/worksheets/addition/typstHelpers/{shared,addition,subtraction}
|
||||
touch src/app/create/worksheets/addition/typstHelpers/index.ts
|
||||
```
|
||||
|
||||
### Step 2: Extract Shared Components (Week 1)
|
||||
1. Move `color-*` definitions to `shared/colors.ts`
|
||||
2. Move `ten-frames-stacked`, `diagonal-split-box` to `shared/helpers.ts`
|
||||
3. Create `shared/types.ts` for TypeScript interfaces
|
||||
|
||||
### Step 3: Split Subtraction Function (Week 1-2)
|
||||
1. Extract borrow box rendering to `subtraction/borrowBoxes.ts`
|
||||
- Keep arrow rendering self-contained
|
||||
- Document the "no place value colors" decision
|
||||
2. Extract minuend row to `subtraction/minuendRow.ts`
|
||||
3. Extract subtrahend row to `subtraction/subtrahendRow.ts`
|
||||
4. Extract answer row to `subtraction/answerRow.ts` (or share with addition?)
|
||||
5. Refactor `generateSubtractionProblemStackFunction` to compose these pieces
|
||||
|
||||
### Step 4: Split Addition Function (Week 2)
|
||||
1. Extract carry box rendering to `addition/carryBoxes.ts`
|
||||
2. Extract addend rows to `addition/addendRows.ts`
|
||||
3. Extract answer row to `addition/answerRow.ts`
|
||||
4. Refactor `generateProblemStackFunction` to compose these pieces
|
||||
|
||||
### Step 5: Update Imports (Week 2)
|
||||
1. Update `typstGenerator.ts` to import from new structure
|
||||
2. Update `example/route.ts` to import from new structure
|
||||
3. Ensure all existing consumers work unchanged (backward compatibility)
|
||||
|
||||
### Step 6: Add Tests (Week 3)
|
||||
1. Add unit tests for each extracted component
|
||||
2. Add integration tests for full problem stack generation
|
||||
3. Verify output matches current worksheets byte-for-byte
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Backward Compatibility
|
||||
|
||||
**Option A: Maintain Old Exports**
|
||||
- Keep `typstHelpers.ts` as a facade that re-exports from new structure
|
||||
- Consumers don't need to change imports immediately
|
||||
- Can gradually migrate to new imports
|
||||
|
||||
**Option B: Update All Consumers**
|
||||
- Change all imports to use new structure immediately
|
||||
- More disruptive but cleaner
|
||||
|
||||
**Recommendation**: Option A - maintain backward compatibility during migration.
|
||||
|
||||
```typescript
|
||||
// typstHelpers.ts (old file becomes facade)
|
||||
export * from './typstHelpers/index'
|
||||
|
||||
// typstHelpers/index.ts (new entry point)
|
||||
export { generateTypstHelpers } from './shared/helpers'
|
||||
export { generateProblemStackFunction } from './addition/problemStack'
|
||||
export { generateSubtractionProblemStackFunction } from './subtraction/problemStack'
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
After each refactoring step:
|
||||
|
||||
1. ✅ Run `npm run type-check` - no new errors
|
||||
2. ✅ Run `npm run lint` - no new warnings
|
||||
3. ✅ Generate test worksheet - byte-for-byte match with current output
|
||||
4. ✅ Visual inspection - worksheets look identical
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] No file exceeds 200 lines
|
||||
- [ ] Each component has a single, clear responsibility
|
||||
- [ ] Adding new features (e.g., new display option) only requires editing 1-2 files
|
||||
- [ ] All tests pass
|
||||
- [ ] Generated worksheets are byte-for-byte identical to current output
|
||||
|
||||
## Timeline
|
||||
|
||||
- **Week 1**: Extract shared components + split subtraction function
|
||||
- **Week 2**: Split addition function + update imports
|
||||
- **Week 3**: Add tests + documentation
|
||||
|
||||
Total estimated effort: **3 weeks** (can be done incrementally)
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
1. **Breaking changes**: Maintain backward compatibility via facade pattern
|
||||
2. **Output differences**: Validate byte-for-byte match after each step
|
||||
3. **Merge conflicts**: Work in feature branch, frequent small PRs
|
||||
4. **Lost context**: Document decisions (e.g., "no place value colors in borrow boxes")
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
After initial refactoring:
|
||||
|
||||
1. **Template system**: Consider using a proper Typst template engine
|
||||
2. **Visual regression testing**: Screenshot comparison for worksheets
|
||||
3. **Configuration builder**: Type-safe builders for worksheet generation
|
||||
4. **Shared answer row**: Unify addition/subtraction answer box rendering
|
||||
@@ -0,0 +1,32 @@
|
||||
// Place value color definitions for Typst
|
||||
// Light pastels - unique color per place value
|
||||
|
||||
/**
|
||||
* Generate Typst color variable definitions
|
||||
* These are used throughout the worksheet for place value visualization
|
||||
*/
|
||||
export function generatePlaceValueColors(): string {
|
||||
return `// Place value colors (light pastels) - unique color per place value
|
||||
#let color-ones = rgb(227, 242, 253) // Light blue (ones)
|
||||
#let color-tens = rgb(232, 245, 233) // Light green (tens)
|
||||
#let color-hundreds = rgb(255, 249, 196) // Light yellow (hundreds)
|
||||
#let color-thousands = rgb(255, 228, 225) // Light pink/rose (thousands)
|
||||
#let color-ten-thousands = rgb(243, 229, 245) // Light purple/lavender (ten-thousands)
|
||||
#let color-hundred-thousands = rgb(255, 239, 213) // Light peach/orange (hundred-thousands)
|
||||
#let color-none = white // No color
|
||||
`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ordered array of place value color names for use in Typst arrays
|
||||
*/
|
||||
export function getPlaceValueColorNames(): string[] {
|
||||
return [
|
||||
'color-ones',
|
||||
'color-tens',
|
||||
'color-hundreds',
|
||||
'color-thousands',
|
||||
'color-ten-thousands',
|
||||
'color-hundred-thousands',
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Shared Typst helper functions and components
|
||||
// Reusable across addition and subtraction worksheets
|
||||
|
||||
import { TYPST_CONSTANTS } from './types'
|
||||
|
||||
/**
|
||||
* Generate Typst helper functions (ten-frames, diagonal boxes, etc.)
|
||||
* These are shared between addition and subtraction problems
|
||||
*/
|
||||
export function generateTypstHelpers(cellSize: number): string {
|
||||
return String.raw`
|
||||
// Ten-frame helper - stacked 2 frames vertically, sized to fit cell width
|
||||
#let ten-frame-spacing = 0pt
|
||||
#let ten-frame-cell-stroke = ${TYPST_CONSTANTS.TEN_FRAME_CELL_STROKE_WIDTH}pt
|
||||
#let ten-frame-cell-color = rgb(0, 0, 0, 30%)
|
||||
#let ten-frame-outer-stroke = ${TYPST_CONSTANTS.TEN_FRAME_STROKE_WIDTH}pt
|
||||
#let ten-frames-stacked(cell-width, top-color, bottom-color) = {
|
||||
let cell-w = cell-width / 5
|
||||
let cell-h = cell-w // Square cells
|
||||
stack(
|
||||
dir: ttb,
|
||||
spacing: ten-frame-spacing,
|
||||
// Top ten-frame (carry to next place value)
|
||||
box(stroke: ten-frame-outer-stroke + black, inset: 0pt)[
|
||||
#grid(
|
||||
columns: 5, rows: 2, gutter: 0pt, stroke: none,
|
||||
..for i in range(0, 10) {
|
||||
(box(width: cell-w, height: cell-h, fill: top-color, stroke: ten-frame-cell-stroke + ten-frame-cell-color)[],)
|
||||
}
|
||||
)
|
||||
],
|
||||
// Bottom ten-frame (current place value overflow)
|
||||
box(stroke: ten-frame-outer-stroke + black, inset: 0pt)[
|
||||
#grid(
|
||||
columns: 5, rows: 2, gutter: 0pt, stroke: none,
|
||||
..for i in range(0, 10) {
|
||||
(box(width: cell-w, height: cell-h, fill: bottom-color, stroke: ten-frame-cell-stroke + ten-frame-cell-color)[],)
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// Diagonal-split box for carry cells
|
||||
// Shows the transition from one place value to another
|
||||
// source-color: color of the place value where the carry comes FROM (right side)
|
||||
// dest-color: color of the place value where the carry goes TO (left side)
|
||||
#let diagonal-split-box(cell-size, source-color, dest-color) = {
|
||||
box(width: cell-size, height: cell-size, stroke: ${TYPST_CONSTANTS.CELL_STROKE_WIDTH}pt)[
|
||||
// Bottom-right triangle (source place value)
|
||||
#place(
|
||||
bottom + right,
|
||||
polygon(
|
||||
fill: source-color,
|
||||
stroke: none,
|
||||
(0pt, 0pt), // bottom-left corner of triangle
|
||||
(cell-size, 0pt), // bottom-right corner
|
||||
(cell-size, cell-size) // top-right corner
|
||||
)
|
||||
)
|
||||
// Top-left triangle (destination place value)
|
||||
#place(
|
||||
top + left,
|
||||
polygon(
|
||||
fill: dest-color,
|
||||
stroke: none,
|
||||
(0pt, 0pt), // top-left corner
|
||||
(cell-size, cell-size), // bottom-right corner of triangle
|
||||
(0pt, cell-size) // bottom-left corner
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Shared TypeScript types for Typst generation
|
||||
|
||||
export interface DisplayOptions {
|
||||
showCarryBoxes: boolean
|
||||
showAnswerBoxes: boolean
|
||||
showPlaceValueColors: boolean
|
||||
showProblemNumbers: boolean
|
||||
showCellBorder: boolean
|
||||
showTenFrames: boolean
|
||||
showTenFramesForAll: boolean
|
||||
fontSize: number
|
||||
}
|
||||
|
||||
export interface CellDimensions {
|
||||
cellSize: number // in inches
|
||||
cellSizeIn: string // formatted for Typst (e.g., "0.55in")
|
||||
cellSizePt: number // in points (for font sizing)
|
||||
}
|
||||
|
||||
export interface TypstConstants {
|
||||
CELL_STROKE_WIDTH: number
|
||||
TEN_FRAME_STROKE_WIDTH: number
|
||||
TEN_FRAME_CELL_STROKE_WIDTH: number
|
||||
ARROW_STROKE_WIDTH: number
|
||||
|
||||
// Positioning offsets for borrowing hint arrows
|
||||
ARROW_START_DX: number
|
||||
ARROW_START_DY: number
|
||||
ARROWHEAD_DX: number
|
||||
ARROWHEAD_DY: number
|
||||
|
||||
// Sizing factors
|
||||
HINT_TEXT_SIZE_FACTOR: number
|
||||
ARROWHEAD_SIZE_FACTOR: number
|
||||
}
|
||||
|
||||
export const TYPST_CONSTANTS: TypstConstants = {
|
||||
CELL_STROKE_WIDTH: 0.5,
|
||||
TEN_FRAME_STROKE_WIDTH: 0.8,
|
||||
TEN_FRAME_CELL_STROKE_WIDTH: 0.4,
|
||||
ARROW_STROKE_WIDTH: 1.5,
|
||||
|
||||
ARROW_START_DX: 0.9,
|
||||
ARROW_START_DY: 0.15,
|
||||
ARROWHEAD_DX: 0.96,
|
||||
ARROWHEAD_DY: 0.62,
|
||||
|
||||
HINT_TEXT_SIZE_FACTOR: 0.25,
|
||||
ARROWHEAD_SIZE_FACTOR: 0.35,
|
||||
} as const
|
||||
@@ -0,0 +1,105 @@
|
||||
// Borrow boxes row rendering for subtraction problems
|
||||
// This row shows where borrows occur (FROM higher place TO lower place)
|
||||
|
||||
import type { CellDimensions } from '../shared/types'
|
||||
import { TYPST_CONSTANTS } from '../shared/types'
|
||||
|
||||
/**
|
||||
* Generate Typst code for the borrow boxes row
|
||||
*
|
||||
* Borrow boxes indicate where a borrow operation occurs:
|
||||
* - Source: The place value we're borrowing FROM (giving)
|
||||
* - Destination: The place value we're borrowing TO (receiving)
|
||||
*
|
||||
* Design decision: Borrow boxes NEVER use place value colors (always stroke-only)
|
||||
* to avoid arrow layering issues where arrows get covered by adjacent cell backgrounds.
|
||||
*
|
||||
* @param cellDimensions - Cell sizing information
|
||||
* @returns Typst code for borrow boxes row
|
||||
*/
|
||||
export function generateBorrowBoxesRow(cellDimensions: CellDimensions): string {
|
||||
const { cellSize, cellSizeIn, cellSizePt } = cellDimensions
|
||||
|
||||
const hintTextSize = (cellSizePt * TYPST_CONSTANTS.HINT_TEXT_SIZE_FACTOR).toFixed(1)
|
||||
const arrowheadSize = (cellSizePt * TYPST_CONSTANTS.ARROWHEAD_SIZE_FACTOR).toFixed(1)
|
||||
|
||||
const arrowStartDx = (cellSize * TYPST_CONSTANTS.ARROW_START_DX).toFixed(2)
|
||||
const arrowStartDy = (cellSize * TYPST_CONSTANTS.ARROW_START_DY).toFixed(2)
|
||||
const arrowEndX = (cellSize * 0.24).toFixed(2)
|
||||
const arrowEndY = (cellSize * 0.7).toFixed(2)
|
||||
const arrowControlX = (cellSize * 0.11).toFixed(2)
|
||||
const arrowControlY = (cellSize * -0.5).toFixed(2)
|
||||
const arrowheadDx = (cellSize * TYPST_CONSTANTS.ARROWHEAD_DX).toFixed(2)
|
||||
const arrowheadDy = (cellSize * TYPST_CONSTANTS.ARROWHEAD_DY).toFixed(2)
|
||||
|
||||
return String.raw`
|
||||
// Borrow boxes row (shows borrows FROM higher place TO lower place)
|
||||
[], // Empty cell for operator column
|
||||
..for i in range(0, grid-digits).rev() {
|
||||
// Check if we need to borrow FROM this place (to give to i-1)
|
||||
// We borrow when m-digit < s-digit at position i-1
|
||||
let shows-borrow = show-borrows and i > 0 and (m-digits.at(i - 1) < s-digits.at(i - 1))
|
||||
|
||||
if shows-borrow {
|
||||
// This place borrowed FROM to give to lower place
|
||||
let source-color = place-colors.at(i) // This place (giving)
|
||||
let dest-color = place-colors.at(i - 1) // Lower place (receiving)
|
||||
|
||||
// When showing hints, determine what to display based on cascading
|
||||
if show-borrowing-hints and i <= m-highest {
|
||||
// Determine the actual value to show in the hint
|
||||
// For cascading: if this digit is 0, it received 10 from left and gives 1 to right
|
||||
// So it shows "10 - 1". Otherwise it shows "original - 1"
|
||||
let original-digit = m-digits.at(i)
|
||||
|
||||
// Check if this is part of a cascade (is it 0 and needs to borrow?)
|
||||
let is-cascade = original-digit == 0
|
||||
|
||||
// The display value is either the original digit or 10 (if cascading)
|
||||
let display-value = if is-cascade { 10 } else { original-digit }
|
||||
|
||||
// Borrow boxes never use place value colors (always stroke-only)
|
||||
// to avoid arrow layering issues
|
||||
(box(width: ${cellSizeIn}, height: ${cellSizeIn}, stroke: ${TYPST_CONSTANTS.CELL_STROKE_WIDTH}pt)[
|
||||
#place(
|
||||
top + center,
|
||||
dy: 2pt,
|
||||
box[
|
||||
#text(size: ${hintTextSize}pt, fill: gray.darken(30%), weight: "bold")[#str(display-value) − ]
|
||||
#text(size: ${hintTextSize}pt, fill: gray.darken(30%), weight: "bold")[1]
|
||||
]
|
||||
)
|
||||
// Draw curved line using Typst bezier with control point
|
||||
#place(
|
||||
top + left,
|
||||
dx: ${arrowStartDx}in,
|
||||
dy: ${arrowStartDy}in,
|
||||
path(
|
||||
stroke: (paint: gray.darken(30%), thickness: ${TYPST_CONSTANTS.ARROW_STROKE_WIDTH}pt),
|
||||
// Start vertex (near the "1" in borrow box)
|
||||
(0pt, 0pt),
|
||||
// End vertex adjusted up and left to align with arrowhead (vertex, relative-control-point)
|
||||
((${arrowEndX}in, ${arrowEndY}in), (${arrowControlX}in, ${arrowControlY}in)),
|
||||
)
|
||||
)
|
||||
// Arrowhead pointing down at the top edge of borrowed 10s box
|
||||
#place(
|
||||
top + left,
|
||||
dx: ${arrowheadDx}in,
|
||||
dy: ${arrowheadDy}in,
|
||||
text(size: ${arrowheadSize}pt, fill: gray.darken(30%))[▼]
|
||||
)
|
||||
],)
|
||||
} else {
|
||||
// No hints - just show stroke box
|
||||
(box(width: ${cellSizeIn}, height: ${cellSizeIn}, stroke: ${TYPST_CONSTANTS.CELL_STROKE_WIDTH}pt)[],)
|
||||
}
|
||||
} else {
|
||||
// No borrow from this position
|
||||
(box(width: ${cellSizeIn}, height: ${cellSizeIn})[
|
||||
#v(${cellSizeIn})
|
||||
],)
|
||||
}
|
||||
},
|
||||
`
|
||||
}
|
||||
Reference in New Issue
Block a user