143 lines
5.7 KiB
JavaScript
143 lines
5.7 KiB
JavaScript
/**
|
|
* Writing/Tracing Worksheet Generator
|
|
* Generates consistent dotted letters and numbers for tracing practice
|
|
*/
|
|
|
|
// Letter path definitions (school-style uppercase)
|
|
const letterPaths = {
|
|
'A': 'M 0 80 L 25 0 L 50 80 M 12 50 L 38 50',
|
|
'B': 'M 0 0 L 0 80 L 35 80 Q 50 80 50 60 Q 50 40 35 40 L 0 40 M 0 0 L 35 0 Q 50 0 50 20 Q 50 40 35 40',
|
|
'C': 'M 50 15 Q 50 0 25 0 Q 0 0 0 40 Q 0 80 25 80 Q 50 80 50 65',
|
|
'D': 'M 0 0 L 0 80 L 25 80 Q 50 80 50 40 Q 50 0 25 0 L 0 0',
|
|
'E': 'M 50 0 L 0 0 L 0 80 L 50 80 M 0 40 L 35 40',
|
|
'F': 'M 50 0 L 0 0 L 0 80 M 0 40 L 35 40',
|
|
'G': 'M 50 15 Q 50 0 25 0 Q 0 0 0 40 Q 0 80 25 80 Q 50 80 50 50 L 30 50',
|
|
'H': 'M 0 0 L 0 80 M 50 0 L 50 80 M 0 40 L 50 40',
|
|
'I': 'M 10 0 L 40 0 M 25 0 L 25 80 M 10 80 L 40 80',
|
|
'J': 'M 10 0 L 40 0 M 30 0 L 30 60 Q 30 80 15 80 Q 0 80 0 65',
|
|
'K': 'M 0 0 L 0 80 M 50 0 L 0 45 L 50 80',
|
|
'L': 'M 0 0 L 0 80 L 50 80',
|
|
'M': 'M 0 80 L 0 0 L 25 40 L 50 0 L 50 80',
|
|
'N': 'M 0 80 L 0 0 L 50 80 L 50 0',
|
|
'O': 'M 25 0 Q 0 0 0 40 Q 0 80 25 80 Q 50 80 50 40 Q 50 0 25 0',
|
|
'P': 'M 0 80 L 0 0 L 35 0 Q 50 0 50 20 Q 50 40 35 40 L 0 40',
|
|
'Q': 'M 25 0 Q 0 0 0 40 Q 0 80 25 80 Q 50 80 50 40 Q 50 0 25 0 M 35 60 L 55 85',
|
|
'R': 'M 0 80 L 0 0 L 35 0 Q 50 0 50 20 Q 50 40 35 40 L 0 40 M 30 40 L 50 80',
|
|
'S': 'M 50 15 Q 50 0 25 0 Q 0 0 0 20 Q 0 40 25 40 Q 50 40 50 60 Q 50 80 25 80 Q 0 80 0 65',
|
|
'T': 'M 0 0 L 50 0 M 25 0 L 25 80',
|
|
'U': 'M 0 0 L 0 60 Q 0 80 25 80 Q 50 80 50 60 L 50 0',
|
|
'V': 'M 0 0 L 25 80 L 50 0',
|
|
'W': 'M 0 0 L 12 80 L 25 30 L 38 80 L 50 0',
|
|
'X': 'M 0 0 L 50 80 M 50 0 L 0 80',
|
|
'Y': 'M 0 0 L 25 40 L 50 0 M 25 40 L 25 80',
|
|
'Z': 'M 0 0 L 50 0 L 0 80 L 50 80'
|
|
};
|
|
|
|
// Number path definitions
|
|
const numberPaths = {
|
|
'1': 'M 15 15 L 25 0 L 25 80 M 10 80 L 40 80',
|
|
'2': 'M 0 20 Q 0 0 25 0 Q 50 0 50 20 Q 50 40 0 80 L 50 80',
|
|
'3': 'M 0 10 Q 0 0 25 0 Q 50 0 50 20 Q 50 40 25 40 Q 50 40 50 60 Q 50 80 25 80 Q 0 80 0 70',
|
|
'4': 'M 40 80 L 40 0 L 0 55 L 50 55',
|
|
'5': 'M 50 0 L 0 0 L 0 35 Q 0 35 25 35 Q 50 35 50 57 Q 50 80 25 80 Q 0 80 0 65',
|
|
'6': 'M 45 10 Q 45 0 25 0 Q 0 0 0 40 L 0 55 Q 0 80 25 80 Q 50 80 50 57 Q 50 35 25 35 Q 0 35 0 55',
|
|
'7': 'M 0 0 L 50 0 L 20 80',
|
|
'8': 'M 25 40 Q 0 40 0 20 Q 0 0 25 0 Q 50 0 50 20 Q 50 40 25 40 Q 0 40 0 60 Q 0 80 25 80 Q 50 80 50 60 Q 50 40 25 40',
|
|
'9': 'M 5 70 Q 5 80 25 80 Q 50 80 50 40 L 50 25 Q 50 0 25 0 Q 0 0 0 23 Q 0 45 25 45 Q 50 45 50 25'
|
|
};
|
|
|
|
/**
|
|
* Generate a dotted path for a single character
|
|
*/
|
|
function generateDottedChar(char, x, y, scale = 1, color = '#000000') {
|
|
const path = letterPaths[char.toUpperCase()] || numberPaths[char];
|
|
if (!path) return '';
|
|
|
|
// Transform the path to the correct position and scale
|
|
const transformedPath = path.replace(/(\d+)/g, (match) => {
|
|
return match; // Keep original values, we'll use transform
|
|
});
|
|
|
|
return `<path d='${path}' transform='translate(${x}, ${y}) scale(${scale})' fill='none' stroke='${color}' stroke-width='4' stroke-dasharray='8,8' stroke-linecap='round'/>`;
|
|
}
|
|
|
|
/**
|
|
* Generate a row of dotted characters centered on the page with dynamic scaling
|
|
*/
|
|
function generateCharacterRow(text, y, baseScale = 1, color = '#000000') {
|
|
const baseCharWidth = 50;
|
|
const baseSpacing = 20;
|
|
const maxWidth = 460; // Leave 20px margin on each side
|
|
|
|
// Calculate required width with base scale
|
|
const requiredWidth = text.length * (baseCharWidth + baseSpacing) * baseScale - baseSpacing * baseScale;
|
|
|
|
// Scale down if content is too wide
|
|
let finalScale = baseScale;
|
|
if (requiredWidth > maxWidth) {
|
|
finalScale = maxWidth / (text.length * (baseCharWidth + baseSpacing) - baseSpacing);
|
|
}
|
|
|
|
const charWidth = baseCharWidth * finalScale;
|
|
const spacing = baseSpacing * finalScale;
|
|
const totalWidth = text.length * charWidth + (text.length - 1) * spacing;
|
|
let startX = (500 - totalWidth) / 2;
|
|
|
|
let svg = '';
|
|
for (let i = 0; i < text.length; i++) {
|
|
const char = text[i];
|
|
svg += generateDottedChar(char, startX + i * (charWidth + spacing), y, finalScale, color);
|
|
}
|
|
return svg;
|
|
}
|
|
|
|
/**
|
|
* Generate baseline guide
|
|
*/
|
|
function generateBaseline(y, width = 400) {
|
|
const startX = (500 - width) / 2;
|
|
return `<line x1='${startX}' y1='${y}' x2='${startX + width}' y2='${y}' stroke='#AAAAAA' stroke-width='1'/>`;
|
|
}
|
|
|
|
/**
|
|
* Generate complete writing worksheet
|
|
* @param {Object} params
|
|
* @param {string} params.word - Word to trace (if tracing letters)
|
|
* @param {number[]} params.numbers - Numbers to trace (if tracing numbers)
|
|
* @param {boolean} params.isNumbers - Whether to trace numbers
|
|
* @param {number} params.age - Child's age (affects complexity)
|
|
*/
|
|
export function generateWritingWorksheet({ word, numbers, isNumbers, age }) {
|
|
const content = isNumbers ? numbers.join('') : word.toUpperCase();
|
|
const title = isNumbers ? 'Trace the Numbers' : `Trace: ${word.toUpperCase()}`;
|
|
|
|
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 500'>
|
|
<!-- Row 1: Full dotted characters -->
|
|
${generateBaseline(130)}
|
|
${generateCharacterRow(content, 50, 1, '#000000')}
|
|
|
|
<!-- Row 2: Lighter dotted characters -->
|
|
${generateBaseline(280)}
|
|
${generateCharacterRow(content, 200, 1, '#888888')}
|
|
|
|
<!-- Row 3: Very light guide -->
|
|
${generateBaseline(430)}
|
|
${generateCharacterRow(content, 350, 1, '#CCCCCC')}
|
|
|
|
<!-- Row 4: Empty practice line -->
|
|
${generateBaseline(495)}
|
|
</svg>`;
|
|
|
|
return {
|
|
title: title,
|
|
subtitle: isNumbers ? 'Practice writing your numbers!' : 'Practice writing letters!',
|
|
sections: [{
|
|
id: 'writing-main',
|
|
type: 'writing',
|
|
content_svg: svg
|
|
}]
|
|
};
|
|
}
|
|
|
|
export default { generateWritingWorksheet };
|