纯前端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>
相关推荐
给月亮点灯|6 小时前
Vue基础知识-Vue中v-cloak、v-text、v-html、v-once、v-pre指令详解
前端·javascript·vue.js
LHX sir6 小时前
低代码革命遇瓶颈?这个“套娃神技“才是破局关键!
前端·ui·前端框架·交互·团队开发·软件需求·极限编程
aiden:)6 小时前
Selenium WebUI 自动化“避坑”指南——从常用 API 到 10 大高频问题
开发语言·前端·javascript·python·selenium
掘金一周6 小时前
还在用html2canvas?介绍一个比它快100倍的截图神器!| 掘金一周 9.4
前端·人工智能
南北是北北6 小时前
为什么会出现有声无画/黑屏,以及如何避免与优化
前端·面试
小桥风满袖6 小时前
极简三分钟ES6 - let声明
前端·javascript
南北是北北6 小时前
VSync 是什么、ExoPlayer 怎么对齐 VSync 与音画同步、常见问题与调参要点
前端·面试
前端fighter6 小时前
前端开发中的模块化:从 CommonJS 到 ES6 模块
前端·javascript·面试
wordbaby6 小时前
Reducer 模式(Reducer Pattern)是什么?
前端