纸张生成器(html开源)

纸张生成器(html开源)

功能说明

这个网页版纸张生成器具有以下功能:

核心功能

  1. 多种纸张模板:提供22种不同的纸张模板,包括标准笔记本、数学草稿纸、点阵纸、书法练习纸、乐谱纸、康奈尔笔记等

  2. 实时预览:所有调整即时反映在画布预览中

  3. 完全自定义:可调整线条间距、粗细、颜色、纸张颜色、尺寸和方向

  4. 导出功能:支持导出为PDF和PNG格式

  5. 打印功能:一键打印生成的纸张

特色功能

  1. 模板保存:可将当前设置保存到浏览器,下次访问时自动加载

  2. 响应式设计:适配各种屏幕尺寸

  3. 直观界面:清晰的控制面板和实时预览

  4. 高精度绘制:基于Canvas的精确绘制,支持高质量打印

使用说明

  1. 从左侧模板列表中选择一个纸张模板

  2. 根据需要调整线条设置和页面设置

  3. 实时预览区域会立即显示更改效果

  4. 使用底部按钮导出、打印或保存模板

这个工具完全在浏览器中运行,不需要任何服务器支持,可以直接保存为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>纸张生成器 - PaperMe</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }
        
        body {
            background-color: #f5f7fa;
            color: #333;
            line-height: 1.6;
            padding: 20px;
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
            display: grid;
            grid-template-columns: 320px 1fr;
            gap: 24px;
        }
        
        header {
            grid-column: 1 / -1;
            text-align: center;
            margin-bottom: 20px;
            padding-bottom: 20px;
            border-bottom: 1px solid #e0e6ed;
        }
        
        h1 {
            color: #2c3e50;
            margin-bottom: 8px;
            font-size: 2.5rem;
        }
        
        .tagline {
            color: #7f8c8d;
            font-size: 1.1rem;
        }
        
        /* 侧边栏样式 */
        .sidebar {
            background-color: white;
            border-radius: 12px;
            padding: 24px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
            height: fit-content;
        }
        
        .sidebar h2 {
            color: #2c3e50;
            margin-bottom: 20px;
            font-size: 1.5rem;
            padding-bottom: 12px;
            border-bottom: 1px solid #eaeaea;
        }
        
        .control-group {
            margin-bottom: 24px;
        }
        
        .control-group h3 {
            font-size: 1.1rem;
            margin-bottom: 12px;
            color: #34495e;
        }
        
        label {
            display: block;
            margin-bottom: 8px;
            color: #555;
            font-weight: 500;
        }
        
        select, input[type="range"], input[type="color"] {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 6px;
            margin-bottom: 16px;
            font-size: 1rem;
            background-color: white;
        }
        
        input[type="range"] {
            padding: 0;
            height: 30px;
        }
        
        .value-display {
            display: flex;
            justify-content: space-between;
            margin-top: -10px;
            font-size: 0.9rem;
            color: #777;
        }
        
        .template-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 12px;
            margin-top: 16px;
            max-height: 400px;
            overflow-y: auto;
            padding-right: 8px;
        }
        
        .template-item {
            background-color: #f8f9fa;
            border: 2px solid #e9ecef;
            border-radius: 8px;
            padding: 12px;
            cursor: pointer;
            transition: all 0.2s;
            text-align: center;
        }
        
        .template-item:hover {
            border-color: #4dabf7;
            background-color: #e7f5ff;
        }
        
        .template-item.active {
            border-color: #339af0;
            background-color: #d0ebff;
        }
        
        .template-name {
            font-weight: 600;
            margin-bottom: 4px;
        }
        
        .template-desc {
            font-size: 0.85rem;
            color: #666;
        }
        
        /* 主内容区样式 */
        .main-content {
            display: flex;
            flex-direction: column;
            gap: 24px;
        }
        
        .preview-container {
            background-color: white;
            border-radius: 12px;
            padding: 24px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
            flex-grow: 1;
        }
        
        .preview-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }
        
        .preview-header h2 {
            color: #2c3e50;
            font-size: 1.5rem;
        }
        
        .paper-canvas-container {
            background-color: white;
            border: 1px solid #e0e6ed;
            border-radius: 8px;
            overflow: auto;
            min-height: 600px;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
        }
        
        #paperCanvas {
            background-color: white;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
        }
        
        .actions {
            background-color: white;
            border-radius: 12px;
            padding: 24px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
        }
        
        .action-buttons {
            display: flex;
            gap: 16px;
            flex-wrap: wrap;
        }
        
        button {
            padding: 14px 24px;
            border: none;
            border-radius: 8px;
            font-weight: 600;
            font-size: 1rem;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        
        .btn-primary {
            background-color: #339af0;
            color: white;
        }
        
        .btn-primary:hover {
            background-color: #228be6;
        }
        
        .btn-secondary {
            background-color: #f8f9fa;
            color: #495057;
            border: 1px solid #dee2e6;
        }
        
        .btn-secondary:hover {
            background-color: #e9ecef;
        }
        
        .btn-success {
            background-color: #40c057;
            color: white;
        }
        
        .btn-success:hover {
            background-color: #37b24d;
        }
        
        .btn-warning {
            background-color: #fab005;
            color: white;
        }
        
        .btn-warning:hover {
            background-color: #f59f00;
        }
        
        .tips {
            margin-top: 32px;
            padding: 20px;
            background-color: #fff9db;
            border-radius: 8px;
            border-left: 4px solid #fab005;
        }
        
        .tips h3 {
            color: #e67700;
            margin-bottom: 10px;
        }
        
        footer {
            grid-column: 1 / -1;
            text-align: center;
            margin-top: 40px;
            padding-top: 20px;
            border-top: 1px solid #e0e6ed;
            color: #7f8c8d;
            font-size: 0.9rem;
        }
        
        /* 响应式设计 */
        @media (max-width: 1100px) {
            .container {
                grid-template-columns: 1fr;
            }
            
            .template-grid {
                grid-template-columns: repeat(3, 1fr);
            }
        }
        
        @media (max-width: 768px) {
            .template-grid {
                grid-template-columns: repeat(2, 1fr);
            }
            
            .action-buttons {
                flex-direction: column;
            }
        }
        
        @media (max-width: 480px) {
            .template-grid {
                grid-template-columns: 1fr;
            }
            
            body {
                padding: 10px;
            }
        }
    </style>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>纸张生成器 - PaperMe</h1>
            <p class="tagline">在线生成个性化纸张模板,满足学习、工作与创作需求</p>
        </header>
        
        <div class="sidebar">
            <h2>纸张设置</h2>
            
            <div class="control-group">
                <h3>模板选择</h3>
                <div class="template-grid" id="templateGrid">
                    <!-- 模板将通过JavaScript动态生成 -->
                </div>
            </div>
            
            <div class="control-group">
                <h3>线条设置</h3>
                <label for="lineSpacing">线条间距 (mm)</label>
                <input type="range" id="lineSpacing" min="1" max="30" value="8" step="1">
                <div class="value-display">
                    <span>1mm</span>
                    <span id="lineSpacingValue">8mm</span>
                    <span>30mm</span>
                </div>
                
                <label for="lineThickness">线条粗细 (px)</label>
                <input type="range" id="lineThickness" min="0.1" max="3" value="0.5" step="0.1">
                <div class="value-display">
                    <span>0.1px</span>
                    <span id="lineThicknessValue">0.5px</span>
                    <span>3px</span>
                </div>
                
                <label for="lineColor">线条颜色</label>
                <input type="color" id="lineColor" value="#888888">
                
                <label for="paperColor">纸张颜色</label>
                <input type="color" id="paperColor" value="#ffffff">
            </div>
            
            <div class="control-group">
                <h3>页面设置</h3>
                <label for="paperSize">纸张尺寸</label>
                <select id="paperSize">
                    <option value="A4">A4 (210×297mm)</option>
                    <option value="A5">A5 (148×210mm)</option>
                    <option value="Letter">Letter (216×279mm)</option>
                    <option value="Legal">Legal (216×356mm)</option>
                </select>
                
                <label for="orientation">纸张方向</label>
                <select id="orientation">
                    <option value="portrait">纵向</option>
                    <option value="landscape">横向</option>
                </select>
                
                <label for="margin">页边距 (mm)</label>
                <input type="range" id="margin" min="5" max="50" value="20" step="5">
                <div class="value-display">
                    <span>5mm</span>
                    <span id="marginValue">20mm</span>
                    <span>50mm</span>
                </div>
            </div>
        </div>
        
        <div class="main-content">
            <div class="preview-container">
                <div class="preview-header">
                    <h2>纸张预览</h2>
                    <div>
                        <span id="currentTemplate">标准笔记本</span>
                        <span id="paperDimensions">(210×297mm)</span>
                    </div>
                </div>
                
                <div class="paper-canvas-container">
                    <canvas id="paperCanvas" width="794" height="1123"></canvas>
                </div>
            </div>
            
            <div class="actions">
                <div class="action-buttons">
                    <button id="exportPDF" class="btn-primary">
                        <i class="fas fa-file-pdf"></i> 导出为PDF
                    </button>
                    <button id="exportPNG" class="btn-secondary">
                        <i class="fas fa-image"></i> 导出为PNG
                    </button>
                    <button id="printBtn" class="btn-secondary">
                        <i class="fas fa-print"></i> 打印纸张
                    </button>
                    <button id="resetBtn" class="btn-warning">
                        <i class="fas fa-redo"></i> 重置设置
                    </button>
                    <button id="saveTemplate" class="btn-success">
                        <i class="fas fa-save"></i> 保存模板
                    </button>
                </div>
                
                <div class="tips">
                    <h3>使用提示</h3>
                    <p>1. 点击左侧模板可直接应用预设样式,然后可进一步自定义调整</p>
                    <p>2. 调整线条间距、粗细、颜色等参数后,纸张预览会实时更新</p>
                    <p>3. 导出PDF后,建议使用高质量纸张打印以获得最佳效果</p>
                    <p>4. 使用"保存模板"功能可以将当前设置保存到浏览器中,下次访问时自动加载</p>
                </div>
            </div>
        </div>
        
        <footer>
            <p>© 2026 纸张生成器 PaperMe | 丙午马年新春快乐!</p>
            <p>本工具适用于笔记、学习、书法、绘画、规划等多种用途</p>
        </footer>
    </div>

    <!-- 引入jsPDF库用于导出PDF -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
    <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
    
    <script>
        // 纸张模板数据
        const templates = [
            { id: 'standard', name: '标准笔记本', desc: '适合日常笔记使用的横线纸', spacing: 8, thickness: 0.5, color: '#888888', type: 'lines' },
            { id: 'math', name: '数学草稿纸', desc: '适合数学计算的方格纸', spacing: 5, thickness: 0.3, color: '#aaaaaa', type: 'grid' },
            { id: 'dot', name: '点阵纸', desc: '适合绘制图表和规划的点阵纸', spacing: 5, thickness: 0.3, color: '#cccccc', type: 'dots' },
            { id: 'blank', name: '空白纸', desc: '纯白纸张,适合自由创作', spacing: 8, thickness: 0.5, color: '#ffffff', type: 'blank' },
            { id: 'music', name: '乐谱纸', desc: '适合音乐创作和记谱', spacing: 7, thickness: 0.5, color: '#666666', type: 'music' },
            { id: 'calligraphy', name: '书法练习纸', desc: '适合书法练习和毛笔字创作', spacing: 10, thickness: 0.4, color: '#888888', type: 'lines' },
            { id: 'cornell', name: '康奈尔笔记', desc: '高效学习笔记方法,分区记录与总结', spacing: 7, thickness: 0.5, color: '#777777', type: 'cornell' },
            { id: 'isometric', name: '等距网格纸', desc: '适合3D设计和透视图绘制', spacing: 10, thickness: 0.3, color: '#999999', type: 'isometric' },
            { id: 'hexagon', name: '六边形网格纸', desc: '适合游戏设计和有机形状绘制', spacing: 15, thickness: 0.3, color: '#aaaaaa', type: 'hexagon' },
            { id: 'french', name: '法式田字格', desc: '传统法国学校使用的笔记纸格式', spacing: 2, thickness: 0.3, color: '#777777', type: 'grid' },
            { id: 'storyboard', name: '分镜纸', desc: '电影和动画故事板设计', spacing: 8, thickness: 0.5, color: '#666666', type: 'storyboard' },
            { id: 'vintage', name: '复古笔记纸', desc: '怀旧风格的笔记纸,适合文学创作与日记', spacing: 8, thickness: 0.6, color: '#8B7355', type: 'lines' },
            { id: 'children', name: '儿童绘画纸', desc: '色彩丰富的绘画纸,激发儿童创造力', spacing: 10, thickness: 0.8, color: '#FF6B6B', type: 'blank' },
            { id: 'minimalist', name: '简约手账纸', desc: '极简设计的手账模板,专注于核心内容', spacing: 7, thickness: 0.4, color: '#555555', type: 'lines' },
            { id: 'project', name: '项目规划纸', desc: '帮助规划和跟踪项目进度的专业模板', spacing: 7, thickness: 0.5, color: '#777777', type: 'project' },
            { id: 'sketch', name: '草图纸', desc: '适合速写和设计构思的模板', spacing: 10, thickness: 0.3, color: '#aaaaaa', type: 'grid' },
            { id: 'creative', name: '创意记录纸', desc: '多功能创意记录与灵感收集模板', spacing: 8, thickness: 0.5, color: '#888888', type: 'creative' },
            { id: 'rice', name: '米字格', desc: '传统书法练习用米字格,适合练习楷书和行书', spacing: 20, thickness: 0.6, color: '#888888', type: 'rice' },
            { id: 'palace', name: '回宫格', desc: '九宫格内分小格的书法练习纸,适合练习笔画细节', spacing: 25, thickness: 0.6, color: '#888888', type: 'palace' },
            { id: 'ninegrid', name: '九宫格', desc: '传统书法九宫格,适合练习结构布局', spacing: 10, thickness: 0.6, color: '#888888', type: 'ninegrid' },
            { id: 'chinesebox', name: '中国田字格', desc: '传统书法田字格,适合练习汉字基本结构', spacing: 20, thickness: 0.6, color: '#888888', type: 'chinesebox' },
            { id: 'crossgrid', name: '交叉格', desc: '交叉点突出的网格纸,适合精确绘图和设计', spacing: 10, thickness: 0.4, color: '#aaaaaa', type: 'crossgrid' }
        ];

        // 纸张尺寸数据 (mm)
        const paperSizes = {
            'A4': { width: 210, height: 297 },
            'A5': { width: 148, height: 210 },
            'Letter': { width: 216, height: 279 },
            'Legal': { width: 216, height: 356 }
        };

        // 页面元素
        const canvas = document.getElementById('paperCanvas');
        const ctx = canvas.getContext('2d');
        const templateGrid = document.getElementById('templateGrid');
        const lineSpacing = document.getElementById('lineSpacing');
        const lineSpacingValue = document.getElementById('lineSpacingValue');
        const lineThickness = document.getElementById('lineThickness');
        const lineThicknessValue = document.getElementById('lineThicknessValue');
        const lineColor = document.getElementById('lineColor');
        const paperColor = document.getElementById('paperColor');
        const paperSize = document.getElementById('paperSize');
        const orientation = document.getElementById('orientation');
        const margin = document.getElementById('margin');
        const marginValue = document.getElementById('marginValue');
        const currentTemplate = document.getElementById('currentTemplate');
        const paperDimensions = document.getElementById('paperDimensions');
        const exportPDF = document.getElementById('exportPDF');
        const exportPNG = document.getElementById('exportPNG');
        const printBtn = document.getElementById('printBtn');
        const resetBtn = document.getElementById('resetBtn');
        const saveTemplate = document.getElementById('saveTemplate');

        // 当前设置
        let currentSettings = {
            template: 'standard',
            lineSpacing: 8,
            lineThickness: 0.5,
            lineColor: '#888888',
            paperColor: '#ffffff',
            paperSize: 'A4',
            orientation: 'portrait',
            margin: 20
        };

        // 初始化模板选择器
        function initTemplates() {
            templates.forEach(template => {
                const templateElement = document.createElement('div');
                templateElement.className = 'template-item';
                if (template.id === currentSettings.template) {
                    templateElement.classList.add('active');
                }
                
                templateElement.innerHTML = `
                    <div class="template-name">${template.name}</div>
                    <div class="template-desc">${template.desc}</div>
                `;
                
                templateElement.addEventListener('click', () => {
                    // 移除所有活动状态
                    document.querySelectorAll('.template-item').forEach(item => {
                        item.classList.remove('active');
                    });
                    
                    // 设置当前活动模板
                    templateElement.classList.add('active');
                    
                    // 更新设置
                    currentSettings.template = template.id;
                    currentSettings.lineSpacing = template.spacing;
                    currentSettings.lineThickness = template.thickness;
                    currentSettings.lineColor = template.color;
                    
                    // 更新UI
                    lineSpacing.value = template.spacing;
                    lineSpacingValue.textContent = template.spacing + 'mm';
                    lineThickness.value = template.thickness;
                    lineThicknessValue.textContent = template.thickness.toFixed(1) + 'px';
                    lineColor.value = template.color;
                    
                    currentTemplate.textContent = template.name;
                    
                    // 重新绘制纸张
                    drawPaper();
                });
                
                templateGrid.appendChild(templateElement);
            });
        }

        // 更新纸张尺寸显示
        function updatePaperDimensions() {
            const size = paperSizes[currentSettings.paperSize];
            const width = currentSettings.orientation === 'portrait' ? size.width : size.height;
            const height = currentSettings.orientation === 'portrait' ? size.height : size.width;
            paperDimensions.textContent = `(${width}×${height}mm)`;
        }

        // 将毫米转换为像素 (1mm = 3.78px,基于300DPI)
        function mmToPx(mm) {
            return mm * 3.78;
        }

        // 绘制纸张
        function drawPaper() {
            // 清除画布
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // 设置画布尺寸
            const size = paperSizes[currentSettings.paperSize];
            const widthMM = currentSettings.orientation === 'portrait' ? size.width : size.height;
            const heightMM = currentSettings.orientation === 'portrait' ? size.height : size.width;
            
            // 设置画布尺寸为像素
            canvas.width = mmToPx(widthMM);
            canvas.height = mmToPx(heightMM);
            
            // 填充纸张背景色
            ctx.fillStyle = currentSettings.paperColor;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // 计算实际绘图区域(考虑边距)
            const marginPx = mmToPx(currentSettings.margin);
            const drawingWidth = canvas.width - 2 * marginPx;
            const drawingHeight = canvas.height - 2 * marginPx;
            
            // 设置线条样式
            ctx.strokeStyle = currentSettings.lineColor;
            ctx.lineWidth = currentSettings.lineThickness;
            
            // 获取当前模板
            const template = templates.find(t => t.id === currentSettings.template);
            
            // 根据模板类型绘制
            switch(template.type) {
                case 'lines':
                    drawLines(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'grid':
                    drawGrid(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'dots':
                    drawDots(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'music':
                    drawMusicStaff(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'cornell':
                    drawCornell(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'isometric':
                    drawIsometric(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'hexagon':
                    drawHexagon(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'rice':
                    drawRiceGrid(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'palace':
                    drawPalaceGrid(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'ninegrid':
                    drawNineGrid(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'chinesebox':
                    drawChineseBox(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'crossgrid':
                    drawCrossGrid(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'storyboard':
                    drawStoryboard(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'project':
                    drawProjectGrid(marginPx, drawingWidth, drawingHeight);
                    break;
                case 'creative':
                    drawCreativeGrid(marginPx, drawingWidth, drawingHeight);
                    break;
                default:
                    // 空白纸,不绘制任何内容
                    break;
            }
            
            // 绘制纸张边框
            ctx.strokeStyle = '#cccccc';
            ctx.lineWidth = 1;
            ctx.strokeRect(marginPx, marginPx, drawingWidth, drawingHeight);
        }

        // 绘制横线
        function drawLines(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            const startY = marginPx;
            
            ctx.beginPath();
            for (let y = startY; y <= marginPx + height; y += spacingPx) {
                ctx.moveTo(marginPx, y);
                ctx.lineTo(marginPx + width, y);
            }
            ctx.stroke();
        }

        // 绘制方格
        function drawGrid(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            
            ctx.beginPath();
            // 绘制水平线
            for (let y = marginPx; y <= marginPx + height; y += spacingPx) {
                ctx.moveTo(marginPx, y);
                ctx.lineTo(marginPx + width, y);
            }
            
            // 绘制垂直线
            for (let x = marginPx; x <= marginPx + width; x += spacingPx) {
                ctx.moveTo(x, marginPx);
                ctx.lineTo(x, marginPx + height);
            }
            ctx.stroke();
        }

        // 绘制点阵
        function drawDots(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            
            ctx.fillStyle = currentSettings.lineColor;
            for (let y = marginPx; y <= marginPx + height; y += spacingPx) {
                for (let x = marginPx; x <= marginPx + width; x += spacingPx) {
                    ctx.beginPath();
                    ctx.arc(x, y, currentSettings.lineThickness/2, 0, Math.PI * 2);
                    ctx.fill();
                }
            }
        }

        // 绘制五线谱
        function drawMusicStaff(marginPx, width, height) {
            const staffHeight = mmToPx(7); // 五线谱高度
            const staffSpacing = mmToPx(15); // 五线谱之间的间距
            
            let y = marginPx;
            
            while (y < marginPx + height) {
                // 绘制一个五线谱
                ctx.beginPath();
                for (let i = 0; i < 5; i++) {
                    const lineY = y + i * (staffHeight / 4);
                    ctx.moveTo(marginPx, lineY);
                    ctx.lineTo(marginPx + width, lineY);
                }
                ctx.stroke();
                
                // 绘制谱号位置
                ctx.font = "bold 24px Arial";
                ctx.fillStyle = currentSettings.lineColor;
                ctx.fillText("𝄞", marginPx + 10, y + staffHeight/2 + 8);
                
                // 绘制小节线
                ctx.beginPath();
                ctx.moveTo(marginPx, y);
                ctx.lineTo(marginPx, y + staffHeight);
                ctx.stroke();
                
                ctx.beginPath();
                ctx.moveTo(marginPx + width, y);
                ctx.lineTo(marginPx + width, y + staffHeight);
                ctx.stroke();
                
                y += staffHeight + staffSpacing;
            }
        }

        // 绘制康奈尔笔记模板
        function drawCornell(marginPx, width, height) {
            // 绘制横线
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            
            ctx.beginPath();
            for (let y = marginPx; y <= marginPx + height; y += spacingPx) {
                ctx.moveTo(marginPx, y);
                ctx.lineTo(marginPx + width, y);
            }
            
            // 绘制左侧关键词栏(占宽度20%)
            const keywordWidth = width * 0.2;
            ctx.moveTo(marginPx + keywordWidth, marginPx);
            ctx.lineTo(marginPx + keywordWidth, marginPx + height);
            
            // 绘制底部摘要栏(占高度15%)
            const summaryHeight = height * 0.15;
            ctx.moveTo(marginPx, marginPx + height - summaryHeight);
            ctx.lineTo(marginPx + width, marginPx + height - summaryHeight);
            
            ctx.stroke();
            
            // 添加标签
            ctx.font = "bold 16px Arial";
            ctx.fillStyle = currentSettings.lineColor;
            ctx.fillText("关键词", marginPx + 10, marginPx + 20);
            ctx.fillText("笔记", marginPx + keywordWidth + 10, marginPx + 20);
            ctx.fillText("摘要", marginPx + 10, marginPx + height - summaryHeight + 20);
        }

        // 绘制米字格
        function drawRiceGrid(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            const cellSize = spacingPx * 2; // 每个格子大小为2倍间距
            
            // 计算可以容纳的格子数量
            const cols = Math.floor(width / cellSize);
            const rows = Math.floor(height / cellSize);
            
            // 计算起始位置,使格子居中
            const startX = marginPx + (width - cols * cellSize) / 2;
            const startY = marginPx + (height - rows * cellSize) / 2;
            
            ctx.beginPath();
            
            // 绘制外框和十字
            for (let row = 0; row <= rows; row++) {
                for (let col = 0; col <= cols; col++) {
                    const x = startX + col * cellSize;
                    const y = startY + row * cellSize;
                    
                    // 绘制格子外框
                    if (row < rows && col < cols) {
                        ctx.rect(x, y, cellSize, cellSize);
                    }
                    
                    // 绘制十字线
                    if (row < rows && col < cols) {
                        // 水平中线
                        ctx.moveTo(x, y + cellSize/2);
                        ctx.lineTo(x + cellSize, y + cellSize/2);
                        // 垂直中线
                        ctx.moveTo(x + cellSize/2, y);
                        ctx.lineTo(x + cellSize/2, y + cellSize);
                        // 左上到右下对角线
                        ctx.moveTo(x, y);
                        ctx.lineTo(x + cellSize, y + cellSize);
                        // 右上到左下对角线
                        ctx.moveTo(x + cellSize, y);
                        ctx.lineTo(x, y + cellSize);
                    }
                }
            }
            ctx.stroke();
        }

        // 绘制回宫格
        function drawPalaceGrid(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            const cellSize = spacingPx * 2.5; // 每个大格子大小
            
            // 计算可以容纳的格子数量
            const cols = Math.floor(width / cellSize);
            const rows = Math.floor(height / cellSize);
            
            // 计算起始位置,使格子居中
            const startX = marginPx + (width - cols * cellSize) / 2;
            const startY = marginPx + (height - rows * cellSize) / 2;
            
            ctx.beginPath();
            
            // 绘制大格子
            for (let row = 0; row <= rows; row++) {
                for (let col = 0; col <= cols; col++) {
                    const x = startX + col * cellSize;
                    const y = startY + row * cellSize;
                    
                    // 绘制大格子外框
                    if (row < rows && col < cols) {
                        ctx.rect(x, y, cellSize, cellSize);
                        
                        // 绘制内部九宫格
                        const smallCell = cellSize / 3;
                        for (let i = 1; i < 3; i++) {
                            // 垂直线
                            ctx.moveTo(x + i * smallCell, y);
                            ctx.lineTo(x + i * smallCell, y + cellSize);
                            // 水平线
                            ctx.moveTo(x, y + i * smallCell);
                            ctx.lineTo(x + cellSize, y + i * smallCell);
                        }
                    }
                }
            }
            ctx.stroke();
        }

        // 绘制其他纸张类型的方法(简略实现)
        function drawIsometric(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            
            ctx.beginPath();
            // 绘制等距网格需要绘制两组平行线
            for (let i = 0; i < width / spacingPx; i++) {
                // 第一组平行线
                ctx.moveTo(marginPx + i * spacingPx, marginPx);
                ctx.lineTo(marginPx, marginPx + i * spacingPx * 0.5);
                
                // 第二组平行线
                ctx.moveTo(marginPx + i * spacingPx, marginPx + height);
                ctx.lineTo(marginPx + width, marginPx + height - i * spacingPx * 0.5);
            }
            ctx.stroke();
        }

        function drawHexagon(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            const hexSize = spacingPx;
            
            for (let y = marginPx; y < marginPx + height; y += hexSize * Math.sqrt(3)) {
                for (let x = marginPx; x < marginPx + width; x += hexSize * 1.5) {
                    drawHexagonAt(x, y, hexSize);
                }
            }
            
            for (let y = marginPx + hexSize * Math.sqrt(3) / 2; y < marginPx + height; y += hexSize * Math.sqrt(3)) {
                for (let x = marginPx + hexSize * 0.75; x < marginPx + width; x += hexSize * 1.5) {
                    drawHexagonAt(x, y, hexSize);
                }
            }
        }

        function drawHexagonAt(x, y, size) {
            ctx.beginPath();
            for (let i = 0; i < 6; i++) {
                const angle = Math.PI / 3 * i;
                const px = x + size * Math.cos(angle);
                const py = y + size * Math.sin(angle);
                
                if (i === 0) {
                    ctx.moveTo(px, py);
                } else {
                    ctx.lineTo(px, py);
                }
            }
            ctx.closePath();
            ctx.stroke();
        }

        function drawNineGrid(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            const cellSize = spacingPx * 3;
            
            // 计算可以容纳的格子数量
            const cols = Math.floor(width / cellSize);
            const rows = Math.floor(height / cellSize);
            
            // 计算起始位置,使格子居中
            const startX = marginPx + (width - cols * cellSize) / 2;
            const startY = marginPx + (height - rows * cellSize) / 2;
            
            ctx.beginPath();
            
            // 绘制九宫格
            for (let row = 0; row <= rows; row++) {
                for (let col = 0; col <= cols; col++) {
                    const x = startX + col * cellSize;
                    const y = startY + row * cellSize;
                    
                    // 绘制大格子外框
                    if (row < rows && col < cols) {
                        ctx.rect(x, y, cellSize, cellSize);
                        
                        // 绘制内部九宫格线
                        const smallCell = cellSize / 3;
                        for (let i = 1; i < 3; i++) {
                            // 垂直线
                            ctx.moveTo(x + i * smallCell, y);
                            ctx.lineTo(x + i * smallCell, y + cellSize);
                            // 水平线
                            ctx.moveTo(x, y + i * smallCell);
                            ctx.lineTo(x + cellSize, y + i * smallCell);
                        }
                    }
                }
            }
            ctx.stroke();
        }

        function drawChineseBox(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            const cellSize = spacingPx * 2;
            
            // 计算可以容纳的格子数量
            const cols = Math.floor(width / cellSize);
            const rows = Math.floor(height / cellSize);
            
            // 计算起始位置,使格子居中
            const startX = marginPx + (width - cols * cellSize) / 2;
            const startY = marginPx + (height - rows * cellSize) / 2;
            
            ctx.beginPath();
            
            // 绘制田字格
            for (let row = 0; row <= rows; row++) {
                for (let col = 0; col <= cols; col++) {
                    const x = startX + col * cellSize;
                    const y = startY + row * cellSize;
                    
                    // 绘制大格子外框
                    if (row < rows && col < cols) {
                        ctx.rect(x, y, cellSize, cellSize);
                        
                        // 绘制十字线
                        // 水平中线
                        ctx.moveTo(x, y + cellSize/2);
                        ctx.lineTo(x + cellSize, y + cellSize/2);
                        // 垂直中线
                        ctx.moveTo(x + cellSize/2, y);
                        ctx.lineTo(x + cellSize/2, y + cellSize);
                    }
                }
            }
            ctx.stroke();
        }

        function drawCrossGrid(marginPx, width, height) {
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            
            ctx.beginPath();
            // 绘制网格线
            for (let y = marginPx; y <= marginPx + height; y += spacingPx) {
                for (let x = marginPx; x <= marginPx + width; x += spacingPx) {
                    // 在每个交叉点绘制一个小十字
                    ctx.moveTo(x - 2, y);
                    ctx.lineTo(x + 2, y);
                    ctx.moveTo(x, y - 2);
                    ctx.lineTo(x, y + 2);
                }
            }
            ctx.stroke();
        }

        function drawStoryboard(marginPx, width, height) {
            const frameWidth = width / 3 - 20;
            const frameHeight = frameWidth * 0.75;
            const spacing = 20;
            
            ctx.beginPath();
            
            // 绘制分镜框
            for (let row = 0; row < 3; row++) {
                for (let col = 0; col < 3; col++) {
                    const x = marginPx + col * (frameWidth + spacing) + spacing/2;
                    const y = marginPx + row * (frameHeight + spacing + 40) + spacing/2;
                    
                    // 绘制分镜框
                    ctx.rect(x, y, frameWidth, frameHeight);
                    
                    // 绘制编号
                    ctx.font = "16px Arial";
                    ctx.fillStyle = currentSettings.lineColor;
                    const frameNum = row * 3 + col + 1;
                    ctx.fillText(`分镜 ${frameNum}`, x + 10, y + 20);
                    
                    // 绘制描述区域
                    ctx.moveTo(x, y + frameHeight);
                    ctx.lineTo(x + frameWidth, y + frameHeight);
                }
            }
            ctx.stroke();
        }

        function drawProjectGrid(marginPx, width, height) {
            const colWidth = width / 7;
            const rowHeight = 40;
            
            ctx.beginPath();
            
            // 绘制表格标题行
            ctx.fillStyle = currentSettings.lineColor;
            ctx.font = "bold 16px Arial";
            const headers = ["任务", "负责人", "周一", "周二", "周三", "周四", "周五", "状态"];
            
            for (let i = 0; i < headers.length; i++) {
                const x = marginPx + i * (i === 0 ? colWidth * 2 : colWidth);
                const y = marginPx + 30;
                
                if (i < headers.length - 1) {
                    ctx.fillText(headers[i], x + 10, y);
                }
                
                // 绘制垂直线
                if (i > 0 && i < headers.length) {
                    ctx.moveTo(x, marginPx);
                    ctx.lineTo(x, marginPx + Math.min(height, 300));
                }
            }
            
            // 绘制水平线
            for (let i = 0; i <= 6; i++) {
                const y = marginPx + 50 + i * rowHeight;
                ctx.moveTo(marginPx, y);
                ctx.lineTo(marginPx + width, y);
            }
            
            ctx.stroke();
        }

        function drawCreativeGrid(marginPx, width, height) {
            // 创意记录纸 - 混合样式
            const spacingPx = mmToPx(currentSettings.lineSpacing);
            
            // 绘制点阵背景
            ctx.fillStyle = currentSettings.lineColor;
            for (let y = marginPx; y <= marginPx + height; y += spacingPx * 2) {
                for (let x = marginPx; x <= marginPx + width; x += spacingPx * 2) {
                    ctx.beginPath();
                    ctx.arc(x, y, 1, 0, Math.PI * 2);
                    ctx.fill();
                }
            }
            
            // 绘制标题区域
            ctx.strokeStyle = currentSettings.lineColor;
            ctx.lineWidth = currentSettings.lineThickness * 2;
            ctx.strokeRect(marginPx + 10, marginPx + 10, width - 20, 60);
            
            // 绘制灵感区域
            ctx.lineWidth = currentSettings.lineThickness;
            ctx.strokeRect(marginPx + 10, marginPx + 90, width/2 - 15, height - 140);
            ctx.strokeRect(marginPx + width/2 + 5, marginPx + 90, width/2 - 15, height - 140);
            
            // 添加标签
            ctx.font = "bold 18px Arial";
            ctx.fillStyle = currentSettings.lineColor;
            ctx.fillText("标题", marginPx + 20, marginPx + 40);
            ctx.fillText("灵感记录", marginPx + 20, marginPx + 120);
            ctx.fillText("草图/图表", marginPx + width/2 + 15, marginPx + 120);
        }

        // 初始化事件监听器
        function initEventListeners() {
            // 线条间距
            lineSpacing.addEventListener('input', function() {
                currentSettings.lineSpacing = parseFloat(this.value);
                lineSpacingValue.textContent = this.value + 'mm';
                drawPaper();
            });
            
            // 线条粗细
            lineThickness.addEventListener('input', function() {
                currentSettings.lineThickness = parseFloat(this.value);
                lineThicknessValue.textContent = this.value + 'px';
                drawPaper();
            });
            
            // 线条颜色
            lineColor.addEventListener('input', function() {
                currentSettings.lineColor = this.value;
                drawPaper();
            });
            
            // 纸张颜色
            paperColor.addEventListener('input', function() {
                currentSettings.paperColor = this.value;
                drawPaper();
            });
            
            // 纸张尺寸
            paperSize.addEventListener('change', function() {
                currentSettings.paperSize = this.value;
                updatePaperDimensions();
                drawPaper();
            });
            
            // 纸张方向
            orientation.addEventListener('change', function() {
                currentSettings.orientation = this.value;
                updatePaperDimensions();
                drawPaper();
            });
            
            // 页边距
            margin.addEventListener('input', function() {
                currentSettings.margin = parseFloat(this.value);
                marginValue.textContent = this.value + 'mm';
                drawPaper();
            });
            
            // 导出为PDF
            exportPDF.addEventListener('click', function() {
                const { jsPDF } = window.jspdf;
                const doc = new jsPDF({
                    orientation: currentSettings.orientation,
                    unit: 'mm',
                    format: currentSettings.paperSize.toLowerCase()
                });
                
                // 将画布转换为图像
                const imgData = canvas.toDataURL('image/png');
                const size = paperSizes[currentSettings.paperSize];
                const width = currentSettings.orientation === 'portrait' ? size.width : size.height;
                const height = currentSettings.orientation === 'portrait' ? size.height : size.width;
                
                doc.addImage(imgData, 'PNG', 0, 0, width, height);
                doc.save(`纸张模板_${templates.find(t => t.id === currentSettings.template).name}_${new Date().getTime()}.pdf`);
            });
            
            // 导出为PNG
            exportPNG.addEventListener('click', function() {
                const link = document.createElement('a');
                link.download = `纸张模板_${templates.find(t => t.id === currentSettings.template).name}_${new Date().getTime()}.png`;
                link.href = canvas.toDataURL('image/png');
                link.click();
            });
            
            // 打印
            printBtn.addEventListener('click', function() {
                const printWindow = window.open('', '_blank');
                const imgData = canvas.toDataURL('image/png');
                
                printWindow.document.write(`
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <title>打印纸张模板</title>
                        <style>
                            body { 
                                margin: 0; 
                                display: flex; 
                                justify-content: center; 
                                align-items: center; 
                                min-height: 100vh;
                            }
                            img { 
                                max-width: 100%; 
                                height: auto; 
                            }
                        </style>
                    </head>
                    <body>
                        <img src="${imgData}" alt="纸张模板">
                        <script>
                            window.onload = function() {
                                window.print();
                                setTimeout(function() {
                                    window.close();
                                }, 500);
                            };
                        <\/script>
                    </body>
                    </html>
                `);
                printWindow.document.close();
            });
            
            // 重置设置
            resetBtn.addEventListener('click', function() {
                // 重置到默认模板
                const defaultTemplate = templates.find(t => t.id === 'standard');
                
                // 更新当前设置
                currentSettings = {
                    template: 'standard',
                    lineSpacing: defaultTemplate.spacing,
                    lineThickness: defaultTemplate.thickness,
                    lineColor: defaultTemplate.color,
                    paperColor: '#ffffff',
                    paperSize: 'A4',
                    orientation: 'portrait',
                    margin: 20
                };
                
                // 更新UI
                lineSpacing.value = defaultTemplate.spacing;
                lineSpacingValue.textContent = defaultTemplate.spacing + 'mm';
                lineThickness.value = defaultTemplate.thickness;
                lineThicknessValue.textContent = defaultTemplate.thickness.toFixed(1) + 'px';
                lineColor.value = defaultTemplate.color;
                paperColor.value = '#ffffff';
                paperSize.value = 'A4';
                orientation.value = 'portrait';
                margin.value = 20;
                marginValue.textContent = '20mm';
                
                // 更新模板选择状态
                document.querySelectorAll('.template-item').forEach(item => {
                    item.classList.remove('active');
                });
                document.querySelectorAll('.template-item')[0].classList.add('active');
                
                currentTemplate.textContent = defaultTemplate.name;
                updatePaperDimensions();
                drawPaper();
                
                // 显示成功消息
                alert('设置已重置为默认值!');
            });
            
            // 保存模板
            saveTemplate.addEventListener('click', function() {
                try {
                    localStorage.setItem('paperMeSettings', JSON.stringify(currentSettings));
                    alert('模板已保存到浏览器!下次访问时会自动加载。');
                } catch (e) {
                    alert('保存失败:' + e.message);
                }
            });
        }

        // 加载保存的模板
        function loadSavedTemplate() {
            try {
                const saved = localStorage.getItem('paperMeSettings');
                if (saved) {
                    const savedSettings = JSON.parse(saved);
                    
                    // 更新当前设置
                    Object.assign(currentSettings, savedSettings);
                    
                    // 更新UI
                    lineSpacing.value = currentSettings.lineSpacing;
                    lineSpacingValue.textContent = currentSettings.lineSpacing + 'mm';
                    lineThickness.value = currentSettings.lineThickness;
                    lineThicknessValue.textContent = currentSettings.lineThickness.toFixed(1) + 'px';
                    lineColor.value = currentSettings.lineColor;
                    paperColor.value = currentSettings.paperColor;
                    paperSize.value = currentSettings.paperSize;
                    orientation.value = currentSettings.orientation;
                    margin.value = currentSettings.margin;
                    marginValue.textContent = currentSettings.margin + 'mm';
                    
                    // 更新模板选择状态
                    document.querySelectorAll('.template-item').forEach(item => {
                        item.classList.remove('active');
                    });
                    
                    const templateIndex = templates.findIndex(t => t.id === currentSettings.template);
                    if (templateIndex >= 0) {
                        document.querySelectorAll('.template-item')[templateIndex].classList.add('active');
                        currentTemplate.textContent = templates[templateIndex].name;
                    }
                    
                    updatePaperDimensions();
                    drawPaper();
                    
                    console.log('已加载保存的模板设置');
                }
            } catch (e) {
                console.log('无法加载保存的模板:', e);
            }
        }

        // 初始化应用
        function initApp() {
            initTemplates();
            initEventListeners();
            updatePaperDimensions();
            loadSavedTemplate();
            drawPaper();
        }

        // 当页面加载完成后初始化应用
        document.addEventListener('DOMContentLoaded', initApp);
    </script>
</body>
</html>
相关推荐
心.c2 小时前
虚拟滚动列表
前端·javascript·vue.js·js
猫头虎2 小时前
OpenClaw 常用操作命令完整速查手册:终端 CLI 操作指令详解|聊天斜杠指令详情
运维·git·容器·开源·github·aigc·ai编程
祯民2 小时前
《复合型 AI Agent 开发:从理论到实践》实体书上架
前端
NEXT062 小时前
深拷贝与浅拷贝的区别
前端·javascript·面试
a1117762 小时前
网页魔方(html threeJS)
开源·html
不写八个2 小时前
PixiJS教程(一):快速搭建环境启动项目
前端·pixijs
PieroPc2 小时前
用html+css+js 写一个Docker 教程
javascript·css·docker·html
菜鸟小芯3 小时前
【GLM-5 陪练式前端新手入门】第二篇:CSS 让网页从 “能用” 变 “好看”
前端·css
We་ct3 小时前
LeetCode 112. 路径总和:两种解法详解
前端·算法·leetcode·typescript