纯前端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>
相关推荐
zh_xuan23 分钟前
个人主页左侧菜单支持分组,以及菜单显示和隐藏
前端·javascript·css
Ming_studying33 分钟前
HTML + CSS + JavaScript实现可视化JSON工具:格式化、折叠、搜索与错误定位
javascript·css·html·json·数据可视化·web工具
Emily156853598704 小时前
Emerson 1C31129G03 Analog Input Module
java·开发语言·前端·plc·emerson·1c31129g03·input module
To_OC8 小时前
面试被问了三回三栏布局,这次我终于把 BFC 那层窗户纸捅破了
前端·css·面试
To_OC9 小时前
啃完 TS 工具类型我发现:Pick 和 Omit 原来就是一层窗户纸
前端·面试·typescript
风月说与山鬼10 小时前
三、大括号语法
前端·react.js
kyriewen11 小时前
我把最常踩的8个CORS跨域报错整理了一遍——第8个去年还不存在
前端·javascript
Csvn11 小时前
🕰️ 闭包 + setTimeout 的 5 个经典陷阱:为什么定时器看到的永远不是最新的值?
前端
lilian23312 小时前
Harmony os 技术实战|拼豆制图27:用单字符编码承载 50 张 70×70 图纸
前端·数据库·华为·harmonyos