纯前端html英文字帖图片生成器自动段落和换行

纯前端html英文字帖图片生成器自动段落和换行,页边距定义等

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图立得英文字帖生成器 tulide.cn</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 300px 1fr;
gap: 10px;
}
.form-panel {
background: white;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
height: fit-content;
}
.preview-panel {
background: white;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 10px;
font-size: 24px;
}
.form-group {
margin-bottom: 8px;
}
.form-group label {
display: block;
margin-bottom: 2px;
font-weight: bold;
color: #555;
font-size: 12px;
}
.form-group input, .form-group textarea, .form-group select {
width: 100%;
padding: 5px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
}
.form-group textarea {
resize: vertical;
min-height: 100px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.form-row-triple {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 6px;
}
.color-input {
width: 50px !important;
height: 25px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.submit-btn {
width: 100%;
padding: 12px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
margin-top: 20px;
}
.submit-btn:hover {
background: #0056b3;
}
.canvas-container {
width: 100%;
height: 1123px; /* A4 height in pixels at 72 DPI */
border: 1px solid #ddd;
background: white;
overflow: hidden;
position: relative;
}
#previewCanvas {
width: 100%;
height: 100%;
display: block;
}
.section-title {
font-size: 14px;
font-weight: bold;
color: #333;
margin: 20px 0 10px 0;
padding-bottom: 5px;
border-bottom: 2px solid #007bff;
}
.footer-text {
margin-top: 20px;
text-align: center;
font-size: 10px;
color: #666;
}
@media print {
body {
background: white;
padding: 0;
}
.container {
grid-template-columns: 1fr;
max-width: none;
}
.form-panel {
display: none;
}
.preview-panel {
box-shadow: none;
padding: 0;
}
}
</style>
</head>
<body>
<div class="container">
<div class="form-panel">
<h1>英文字帖生成器</h1>
<form id="copyBookForm">
<div class="section-title">页面设置</div>
<div class="form-row">
<div class="form-group">
<label>左边距 (mm)</label>
<input type="number" id="marginLeft" value="20" min="10" max="50">
</div>
<div class="form-group">
<label>右边距 (mm)</label>
<input type="number" id="marginRight" value="15" min="10" max="50">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>上边距 (mm)</label>
<input type="number" id="marginTop" value="15" min="10" max="50">
</div>
<div class="form-group">
<label>下边距 (mm)</label>
<input type="number" id="marginBottom" value="15" min="10" max="50">
</div>
</div>
<div class="section-title">标题设置</div>
<div class="form-group">
<label>标题内容</label>
<input type="text" id="titleText" value="English Handwriting Practice" placeholder="输入标题内容">
</div>
<div class="form-row-triple">
<div class="form-group">
<label>字体</label>
<select id="titleFont">
<option value="Arial">Arial</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Georgia">Georgia</option>
<option value="Verdana">Verdana</option>
</select>
</div>
<div class="form-group">
<label>字号 (px)</label>
<input type="number" id="titleSize" value="24" min="12" max="48">
</div>
<div class="form-group">
<label>颜色</label>
<input type="color" id="titleColor" value="#000000" class="color-input">
</div>
</div>
<div class="section-title">姓名栏设置</div>
<div class="form-row-triple">
<div class="form-group">
<label>字体</label>
<select id="nameFont">
<option value="Arial">Arial</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Georgia">Georgia</option>
<option value="Verdana">Verdana</option>
</select>
</div>
<div class="form-group">
<label>字号 (px)</label>
<input type="number" id="nameSize" value="14" min="10" max="24">
</div>
<div class="form-group">
<label>颜色</label>
<input type="color" id="nameColor" value="#000000" class="color-input">
</div>
</div>
<div class="section-title">正文设置</div>
<div class="form-group">
<label>练习内容</label>
<textarea id="contentText" placeholder="输入要练习的英文内容...">The quick brown fox jumps over the lazy dog. This sentence contains every letter of the alphabet and is commonly used for handwriting practice. Practice makes perfect, so keep writing to improve your handwriting skills.</textarea>
</div>
<div class="form-row-triple">
<div class="form-group">
<label>字体</label>
<select id="contentFont">
<option value="Arial">Arial</option>
<option value="Times New Roman" selected>Times New Roman</option>
<option value="Georgia">Georgia</option>
<option value="Verdana">Verdana</option>
</select>
</div>
<div class="form-group">
<label>字号 (px)</label>
<input type="number" id="contentSize" value="12" min="10" max="24">
</div>
<div class="form-group">
<label>颜色</label>
<input type="color" id="contentColor" value="#000000" class="color-input">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>行距 (px)</label>
<input type="number" id="lineHeight" value="60" min="40" max="100">
</div>
<div class="form-group">
<label>段首缩进</label>
<select id="indentFirst">
<option value="true">是</option>
<option value="false">否</option>
</select>
</div>
</div>
<!--button type="submit" class="submit-btn">生成PDF字帖</button-->
</form>
<div class="footer-text">
实时预览 • 支持A4打印 • 自动排版
</div>
</div>
<div class="preview-panel">
<div class="canvas-container">
<canvas id="previewCanvas" width="794" height="1123"></canvas>
</div>
</div>
</div>
<script>
class CopyBookGenerator {
constructor() {
this.canvas = document.getElementById('previewCanvas');
this.ctx = this.canvas.getContext('2d');
this.setupEventListeners();
this.render();
}
setupEventListeners() {
const inputs = ['marginLeft', 'marginRight', 'marginTop', 'marginBottom',
'titleText', 'titleFont', 'titleSize', 'titleColor',
'nameFont', 'nameSize', 'nameColor',
'contentText', 'contentFont', 'contentSize', 'contentColor',
'lineHeight', 'indentFirst'];
inputs.forEach(id => {
const element = document.getElementById(id);
if (element) {
element.addEventListener('input', () => this.render());
element.addEventListener('change', () => this.render());
}
});
document.getElementById('copyBookForm').addEventListener('submit', (e) => {
e.preventDefault();
this.submitForm();
});
}
getFormData() {
return {
marginLeft: parseInt(document.getElementById('marginLeft').value),
marginRight: parseInt(document.getElementById('marginRight').value),
marginTop: parseInt(document.getElementById('marginTop').value),
marginBottom: parseInt(document.getElementById('marginBottom').value),
titleText: document.getElementById('titleText').value,
titleFont: document.getElementById('titleFont').value,
titleSize: parseInt(document.getElementById('titleSize').value),
titleColor: document.getElementById('titleColor').value,
nameFont: document.getElementById('nameFont').value,
nameSize: parseInt(document.getElementById('nameSize').value),
nameColor: document.getElementById('nameColor').value,
contentText: document.getElementById('contentText').value,
contentFont: document.getElementById('contentFont').value,
contentSize: parseInt(document.getElementById('contentSize').value),
contentColor: document.getElementById('contentColor').value,
lineHeight: parseInt(document.getElementById('lineHeight').value),
indentFirst: document.getElementById('indentFirst').value === 'true'
};
}
mmToPx(mm) {
return Math.round(mm * 2.834645669); // 72 DPI conversion
}
render() {
const data = this.getFormData();
// Clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.fillStyle = '#ffffff';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
const leftMargin = this.mmToPx(data.marginLeft);
const rightMargin = this.mmToPx(data.marginRight);
const topMargin = this.mmToPx(data.marginTop);
const bottomMargin = this.mmToPx(data.marginBottom);
const contentWidth = this.canvas.width - leftMargin - rightMargin;
let currentY = topMargin;
// Draw title
if (data.titleText.trim()) {
this.ctx.font = `${data.titleSize}px ${data.titleFont}`;
this.ctx.fillStyle = data.titleColor;
this.ctx.textAlign = 'center';
this.ctx.fillText(data.titleText, this.canvas.width / 2, currentY + data.titleSize);
currentY += data.titleSize + 30;
}
// Draw name line
this.ctx.font = `${data.nameSize}px ${data.nameFont}`;
this.ctx.fillStyle = data.nameColor;
this.ctx.textAlign = 'center';
const nameLineText = "姓名________班级________学号________日期________评分________";
this.ctx.fillText(nameLineText, this.canvas.width / 2, currentY + data.nameSize);
currentY += data.nameSize + 40;
// Process content text
this.renderContent(data, leftMargin, currentY, contentWidth, this.canvas.height - bottomMargin);
}
renderContent(data, startX, startY, maxWidth, maxY) {
this.ctx.font = `${data.contentSize * 2.5}px ${data.contentFont}`;
this.ctx.fillStyle = data.contentColor;
this.ctx.textAlign = 'left';
const words = data.contentText.split(/\s+/).filter(word => word.length > 0);
let currentY = startY;
let currentLine = '';
let isFirstParagraph = true;
for (let i = 0; i < words.length; i++) {
const word = words[i];
const testLine = currentLine + (currentLine ? ' ' : '') + word;
const lineWidth = this.ctx.measureText(testLine).width;
const indentWidth = (data.indentFirst && isFirstParagraph) ? data.contentSize * 2 : 0;
if (lineWidth + indentWidth > maxWidth && currentLine) {
// Draw current line with four-line background
this.drawFourLineBackground(startX, currentY, maxWidth, data.lineHeight);
this.ctx.fillStyle = data.contentColor;
this.ctx.fillText(currentLine, startX + indentWidth, currentY + data.lineHeight * 0.6);
currentY += data.lineHeight;
currentLine = word;
isFirstParagraph = false;
if (currentY + data.lineHeight > maxY) {
break;
}
} else {
currentLine = testLine;
}
}
// Draw last line
if (currentLine && currentY + data.lineHeight <= maxY) {
const indentWidth = (data.indentFirst && isFirstParagraph) ? data.contentSize * 2 : 0;
this.drawFourLineBackground(startX, currentY, maxWidth, data.lineHeight);
this.ctx.fillStyle = data.contentColor;
this.ctx.fillText(currentLine, startX + indentWidth, currentY + data.lineHeight * 0.6);
currentY += data.lineHeight;
}
// Fill remaining space with empty lines
while (currentY + data.lineHeight <= maxY) {
this.drawFourLineBackground(startX, currentY, maxWidth, data.lineHeight);
currentY += data.lineHeight;
}
}
drawFourLineBackground(x, y, width, height) {
this.ctx.strokeStyle = '#cccccc';
this.ctx.lineWidth = 1;
// Top line (solid, bold)
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + width, y);
this.ctx.stroke();
// Middle lines (dashed)
this.ctx.lineWidth = 1;
this.ctx.setLineDash([5, 5]);
// First middle line
this.ctx.beginPath();
this.ctx.moveTo(x, y + height * 0.33);
this.ctx.lineTo(x + width, y + height * 0.33);
this.ctx.stroke();
// Second middle line
this.ctx.beginPath();
this.ctx.moveTo(x, y + height * 0.67);
this.ctx.lineTo(x + width, y + height * 0.67);
this.ctx.stroke();
// Bottom line (solid, bold)
this.ctx.setLineDash([]);
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(x, y + height);
this.ctx.lineTo(x + width, y + height);
this.ctx.stroke();
}
submitForm() {
}
}
// Initialize when page loads
document.addEventListener('DOMContentLoaded', () => {
new CopyBookGenerator();
});
</script>
</body>
</html>
相关推荐
天蓝色的鱼鱼18 分钟前
都2026年了还不会Vite插件开发?手写一个版本管理插件,5分钟包会!
前端·vite
苏武难飞29 分钟前
分享一个33号远征队的效果!
前端
鹏程十八少1 小时前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
亿元程序员1 小时前
这款值68亿的游戏,你不实战一下吗?安排!
前端
摸鱼的春哥2 小时前
Agent教程15:认识LangChain(中),状态机思维
前端·javascript·后端
明月_清风2 小时前
告别遮挡:用 scroll-padding 实现优雅的锚点跳转
前端·javascript
明月_清风2 小时前
原生 JS 侧边栏缩放:从 DOM 监听到底层优化
前端·javascript
万少11 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站13 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
炫饭第一名15 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员