纯前端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>
相关推荐
Cache技术分享6 分钟前
233. Java 集合 - 遍历 Collection 中的元素
前端·后端
嚴寒32 分钟前
Mac 安装 Dart & Flutter 完整开发环境指南
前端·macos
用户66006766853936 分钟前
从变量提升到调用栈:V8 引擎如何 “读懂” JS 代码
前端·javascript
白兰地空瓶36 分钟前
【深度揭秘】JS 那些看似简单方法的底层黑魔法
前端·javascript
进阶的小叮当40 分钟前
Vue代码打包成apk?Cordova帮你解决!
android·前端·javascript
天天进步201543 分钟前
从零开始构建现代化React应用:最佳实践与性能优化
前端·react.js·性能优化
程序媛_MISS_zhang_01101 小时前
浏览器开发者工具(尤其是 Vue Devtools 扩展)和 Vuex 的的订阅模式冲突
前端·javascript·vue.js
fruge1 小时前
Vue3.4 Effect 作用域 API 与 React Server Components 实战解析
前端·vue.js·react.js
神秘的猪头1 小时前
🌐 CSS 选择器详解:从基础到实战
前端·javascript
远山枫谷1 小时前
CSS选择器优先级计算你真的会吗?
前端·css