javascript: convert HTML files to PDF

 

convert HTML files to PDF
https://ekoopmans.github.io/html2pdf.js/
https://github.com/eKoopmans/html2pdf.js
https://www.nutrient.io/blog/top-ten-ways-to-convert-html-to-pdf/
Nutrient Document Engine — Enterprise-grade HTML-to-PDF with modern CSS support, form conversion, and workflow automation.
wkhtmltopdf — CLI powered by WebKit; handles complex HTML/CSS and JavaScript.
Puppeteer — Node.js automation for Chrome/Chromium; great for JavaScript‑heavy pages.
Playwright — Cross‑browser automation (Chromium, Firefox, WebKit).
jsPDF — Lightweight client‑side PDF creation for simple layouts.
html2pdf.js — Combines html2canvas + jsPDF for browser‑side export.
WeasyPrint — Python engine that renders HTML/CSS precisely.
pdfmake — Declarative JSON schema for PDFs in Node and browser.
PDFKit — Programmatic Node.js PDF generation with fine layout control.
Dompdf — PHP library for HTML/CSS to PDF.

 

https://github.com/niklasvh/html2canvas
https://html2canvas.hertzen.com/
https://github.com/MrRio/jsPDF

 

<script src="jspdf.min.js"></script>
<script src="html2canvas.min.js"></script>
<script src="html2pdf.min.js"></script>

  

<%@ Page Language="C#" AutoEventWireup="true" ValidateRequest="false" CodeBehind="WebForm11.aspx.cs" Inherits="ModalPopup.WebForm11" %>
 <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>TinyMCE 图片上传示例</title>
    <meta name="author" content="geovindu,Geovin Du,塗聚文,涂聚文" />
    <!-- TinyMCE 7.0 CDN -->
    <script src="tinymce/7.6.1/tinymce.min.js" referrerpolicy="origin"></script>
 <!-- PDF导出库 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
    <!-- mammoth.js 用于Word文档解析 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js"></script>
    <!-- html-docx-js 用于Word导出 -->
    <script src="https://cdn.jsdelivr.net/npm/html-docx-js@0.3.1/dist/html-docx.min.js"></script>
    <style>
        .upload-progress {
            display: none;
            margin-top: 10px;
        }
        .upload-success {
            color: green;
            margin-top: 10px;
        }
        .upload-error {
            color: red;
            margin-top: 10px;
        }
        .progress-container {
            width: 300px;
            height: 20px;
            background-color: #e0e0e0;
            border-radius: 10px;
            overflow: hidden;
            margin-top: 5px;
        }
        .progress-bar {
            height: 100%;
            background-color: #4CAF50;
            text-align: center;
            line-height: 20px;
            color: white;
            transition: width 0.3s ease;
            width: 0%;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        .editor-section {
            margin-bottom: 30px;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 8px;
            background-color: #f9f9f9;
        }
        .editor-section h3 {
            margin-top: 0;
            color: #333;
            margin-bottom: 15px;
        }
        .button-group {
            margin-top: 20px;
            text-align: center;
        }
        .button-group input[type="button"] {
            margin: 0 5px;
            padding: 8px 16px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        .button-group input[type="button"]:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <div class="container">
            <h2>TinyMCE 7.0 多编辑器演示</h2>
            
            <!-- 编辑器1 -->
            <div class="editor-section">
                <h3>编辑器 1</h3>
                <asp:TextBox ID="txtContent1" runat="server" TextMode="MultiLine" Rows="8" Columns="60"></asp:TextBox>
                
                <div class="upload-progress" id="uploadProgress1">
                    <asp:Label ID="lblProgress1" runat="server" Text="上传中..."></asp:Label>
                    <div class="progress-container">
                        <div class="progress-bar" id="progressBar1">0%</div>
                    </div>
                </div>
                
                <div id="uploadResult1" class="upload-success" style="display: none;">
                    <asp:Label ID="lblSuccess1" runat="server" Text=""></asp:Label>
                </div>
                
                <div id="uploadError1" class="upload-error" style="display: none;">
                    <asp:Label ID="lblError1" runat="server" Text=""></asp:Label>
                </div>
            </div>
            
            <!-- 编辑器2 -->
            <div class="editor-section">
                <h3>编辑器 2</h3>
                <asp:TextBox ID="txtContent2" runat="server" TextMode="MultiLine" Rows="8" Columns="60"></asp:TextBox>
                
                <div class="upload-progress" id="uploadProgress2">
                    <asp:Label ID="lblProgress2" runat="server" Text="上传中..."></asp:Label>
                    <div class="progress-container">
                        <div class="progress-bar" id="progressBar2">0%</div>
                    </div>
                </div>
                
                <div id="uploadResult2" class="upload-success" style="display: none;">
                    <asp:Label ID="lblSuccess2" runat="server" Text=""></asp:Label>
                </div>
                
                <div id="uploadError2" class="upload-error" style="display: none;">
                    <asp:Label ID="lblError2" runat="server" Text=""></asp:Label>
                </div>
            </div>
            
            <!-- 编辑器3 -->
            <div class="editor-section">
                <h3>编辑器 3</h3>
                <asp:TextBox ID="txtContent3" runat="server" TextMode="MultiLine" Rows="8" Columns="60"></asp:TextBox>
                
                <div class="upload-progress" id="uploadProgress3">
                    <asp:Label ID="lblProgress3" runat="server" Text="上传中..."></asp:Label>
                    <div class="progress-container">
                        <div class="progress-bar" id="progressBar3">0%</div>
                    </div>
                </div>
                
                <div id="uploadResult3" class="upload-success" style="display: none;">
                    <asp:Label ID="lblSuccess3" runat="server" Text=""></asp:Label>
                </div>
                
                <div id="uploadError3" class="upload-error" style="display: none;">
                    <asp:Label ID="lblError3" runat="server" Text=""></asp:Label>
                </div>
            </div>
            
            <div class="button-group">
                <asp:Button ID="btnSaveAll" runat="server" Text="保存所有内容" OnClick="btnSaveAll_Click" />
                <asp:Button ID="btnSave1" runat="server" Text="保存编辑器1" OnClick="btnSave1_Click" />
                <asp:Button ID="btnSave2" runat="server" Text="保存编辑器2" OnClick="btnSave2_Click" />
                <asp:Button ID="btnSave3" runat="server" Text="保存编辑器3" OnClick="btnSave3_Click" />
            </div>
        </div>
    </form>

    <script>
        // 全局变量用于存储每个编辑器的上传回调
        var editorCallbacks = {};
        
        // 初始化编辑器的函数
        function initEditor(editorId, progressId, resultId, errorId, successLabelId, errorLabelId) {
            console.log('初始化编辑器:', editorId);
            
            tinymce.init({
                selector: '#' + editorId,
                plugins: 'image media link preview code',
                toolbar: 'undo redo | bold italic | alignleft aligncenter alignright | link image media | preview code | export_pdf export_word import_word',
                height: 300,
                language: 'zh_CN',
                language_url: 'https://cdn.tiny.cloud/1/no-api-key/tinymce/7/langs/zh_CN.js',
                
                // 支持图片和视频上传
                file_picker_types: 'image media',
                
                // 自定义按钮配置
                setup: function (editor) {
                    // 导出为PDF按钮
                    editor.ui.registry.addButton('export_pdf', {
                        text: '导出PDF',
                        icon: 'document-properties',
                        tooltip: '导出为PDF文件',
                        onAction: function () {
                            exportToPDF(editor);
                        }
                    });
                    
                    // 导出为Word按钮
                    editor.ui.registry.addButton('export_word', {
                        text: '导出Word',
                        icon: 'newdocument',
                        tooltip: '导出为Word文件',
                        onAction: function () {
                            exportToWord(editor);
                        }
                    });
                    
                    // 导入Word按钮
                    editor.ui.registry.addButton('import_word', {
                        text: '导入Word',
                        icon: 'upload',
                        tooltip: '从Word文件导入',
                        onAction: function () {
                            importFromWord(editor);
                        }
                    });
                },
                file_picker_callback: function (callback, value, meta) {
                    console.log('编辑器', editorId, '的文件选择器被调用');
                    
                    // 存储该编辑器的回调函数
                    editorCallbacks[editorId] = callback;
                    
                    // 根据meta.type判断是图片还是视频
                    var isImage = meta.filetype === 'image';
                    var isMedia = meta.filetype === 'media';
                    
                    console.log('文件类型:', meta.filetype, 'isImage:', isImage, 'isMedia:', isMedia);
                    
                    // 创建文件输入框
                    var input = document.createElement('input');
                    input.setAttribute('type', 'file');
                    
                    // 设置文件类型过滤
                    if (isImage) {
                        input.setAttribute('accept', 'image/*');
                    } else if (isMedia) {
                        input.setAttribute('accept', 'video/*');
                    } else {
                        input.setAttribute('accept', 'image/*,video/*');
                    }
                    
                    input.onchange = function () {
                        var file = this.files[0];
                        if (!file) {
                            console.log('未选择文件');
                            return;
                        }
                        
                        console.log('选择的文件:', file.name, file.size);
                        
                        // 显示该编辑器的上传进度
                        document.getElementById(progressId).style.display = 'block';
                        document.getElementById(resultId).style.display = 'none';
                        document.getElementById(errorId).style.display = 'none';
                        
                        // 创建表单数据
                        var formData = new FormData();
                        formData.append('file', file);
                        
                        // 创建XMLHttpRequest
                        var xhr = new XMLHttpRequest();
                        xhr.open('POST', '/TinyMCEWebForm/TinyMCEUploadHandler.ashx');
                        
                        // 监听上传进度
                        xhr.upload.addEventListener('progress', function (e) {
                            if (e.lengthComputable) {
                                var percentComplete = Math.round((e.loaded / e.total) * 100);
                                var progressBar = document.getElementById(progressId.replace('Progress', 'Bar'));
                                if (progressBar) {
                                    progressBar.style.width = percentComplete + '%';
                                    progressBar.textContent = percentComplete + '%';
                                }
                            }
                        });
                        
                        // 处理响应
                        xhr.onload = function () {
                            document.getElementById(progressId).style.display = 'none';
                            
                            if (xhr.status === 200) {
                                try {
                                    var response = JSON.parse(xhr.responseText);
                                    console.log('上传响应:', response);
                                    
                                    if (response && response.success) {
                                        // 上传成功
                                        document.getElementById(resultId).style.display = 'block';
                                        document.getElementById(successLabelId).textContent = isMedia ? '视频上传成功!' : '图片上传成功!';
                                        
                                        // 调用该编辑器的回调函数插入文件
                                        if (typeof editorCallbacks[editorId] === 'function') {
                                            console.log('调用回调函数插入文件:', response.location);
                                            // 根据文件类型设置不同的参数
                                            if (isMedia) {
                                                // 视频文件
                                                editorCallbacks[editorId](response.location, { 
                                                    source2: response.location,
                                                    poster: '',
                                                    type: 'video/mp4'
                                                });
                                            } else {
                                                // 图片文件
                                                editorCallbacks[editorId](response.location, { title: file.name });
                                            }
                                            delete editorCallbacks[editorId]; // 清空回调
                                        } else {
                                            console.error('回调函数不存在');
                                        }
                                    } else {
                                        // 上传失败
                                        document.getElementById(errorId).style.display = 'block';
                                        document.getElementById(errorLabelId).textContent = response ? (response.error || '上传失败') : '无效响应';
                                        delete editorCallbacks[editorId];
                                    }
                                } catch (parseError) {
                                    console.error('JSON解析错误:', parseError);
                                    document.getElementById(errorId).style.display = 'block';
                                    document.getElementById(errorLabelId).textContent = '响应格式错误: ' + parseError.message;
                                    delete editorCallbacks[editorId];
                                }
                            } else {
                                console.error('HTTP错误,状态码:', xhr.status);
                                document.getElementById(errorId).style.display = 'block';
                                document.getElementById(errorLabelId).textContent = '上传失败,状态码: ' + xhr.status;
                                delete editorCallbacks[editorId];
                            }
                        };
                        
                        // 处理错误
                        xhr.onerror = function () {
                            console.error('网络错误');
                            document.getElementById(progressId).style.display = 'none';
                            document.getElementById(errorId).style.display = 'block';
                            document.getElementById(errorLabelId).textContent = '网络错误,请重试';
                            delete editorCallbacks[editorId];
                        };
                        
                        // 发送请求
                        console.log('开始上传文件...');
                        xhr.send(formData);
                    };
                    
                    // 触发文件选择
                    input.click();
                }
            });
        }
        
        // 页面加载完成后初始化所有编辑器
        document.addEventListener('DOMContentLoaded', function() {
            // 初始化编辑器1
            initEditor(
                '<%= txtContent1.ClientID %>',
                'uploadProgress1',
                'uploadResult1',
                'uploadError1',
                '<%= lblSuccess1.ClientID %>',
                '<%= lblError1.ClientID %>'
            );
            
            // 初始化编辑器2
            initEditor(
                '<%= txtContent2.ClientID %>',
                'uploadProgress2',
                'uploadResult2',
                'uploadError2',
                '<%= lblSuccess2.ClientID %>',
                '<%= lblError2.ClientID %>'
            );
            
            // 初始化编辑器3
            initEditor(
                '<%= txtContent3.ClientID %>',
                'uploadProgress3',
                'uploadResult3',
                'uploadError3',
                '<%= lblSuccess3.ClientID %>',
                '<%= lblError3.ClientID %>'
            );
            
            console.log('所有编辑器初始化完成');
        });
        
        // PDF导出功能
        function exportToPDF(editor) {
            console.log('开始导出PDF...');
            
            try {
                // 获取编辑器内容
                var content = editor.getContent();
                var editorName = editor.id;
                
                // 创建临时容器
                var tempContainer = document.createElement('div');
                tempContainer.innerHTML = content;
                tempContainer.style.width = '794px'; // A4宽度
                tempContainer.style.padding = '40px';
                tempContainer.style.position = 'absolute';
                tempContainer.style.left = '-9999px';
                document.body.appendChild(tempContainer);
                
                // 使用html2canvas将内容转换为图片
                html2canvas(tempContainer, {
                    scale: 2,
                    useCORS: true,
                    allowTaint: true
                }).then(function(canvas) {
                    // 创建PDF
                    const { jsPDF } = window.jspdf;
                    var pdf = new jsPDF('p', 'px', 'a4');
                    
                    var imgData = canvas.toDataURL('image/png');
                    var imgWidth = 794;
                    var pageHeight = 1123;
                    var imgHeight = canvas.height * imgWidth / canvas.width;
                    var heightLeft = imgHeight;
                    var position = 0;
                    
                    // 添加第一页
                    pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
                    heightLeft -= pageHeight;
                    
                    // 添加更多页面(如果内容超过一页)
                    while (heightLeft >= 0) {
                        position = heightLeft - imgHeight;
                        pdf.addPage();
                        pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
                        heightLeft -= pageHeight;
                    }
                    
                    // 保存PDF
                    var fileName = '文档_' + editorName + '_' + new Date().getTime() + '.pdf';
                    pdf.save(fileName);
                    
                    // 清理临时容器
                    document.body.removeChild(tempContainer);
                    
                    console.log('PDF导出成功:', fileName);
                    alert('PDF导出成功!');
                }).catch(function(error) {
                    console.error('PDF导出失败:', error);
                    alert('PDF导出失败:' + error.message);
                    document.body.removeChild(tempContainer);
                });
            } catch (error) {
                console.error('PDF导出过程中发生错误:', error);
                alert('PDF导出失败:' + error.message);
            }
        }
        
        // 使用html-docx-js实现Word导出功能
        function exportToWord(editor) {
            console.log('开始使用html-docx-js导出Word文档...');
            
            try {
                // 检查html-docx-js是否加载成功
                if (typeof htmlDocx === 'undefined') {
                    console.error('html-docx-js库未正确加载');
                    alert('Word导出失败:html-docx-js库未正确加载,请检查网络连接!');
                    return;
                }
                
                // 获取编辑器内容
                var content = editor.getContent();
                var editorName = editor.id;
                
                // 构建完整的HTML文档(包含样式)
                var htmlContent = `
                    <!DOCTYPE html>
                    <html>
                    <head>
                        <meta charset="UTF-8">
                        <style>
                            body { font-family: "Microsoft YaHei", SimHei, Arial, sans-serif; font-size: 12pt; line-height: 1.5; }
                            h1 { font-size: 24pt; font-weight: bold; margin: 20px 0; }
                            h2 { font-size: 20pt; font-weight: bold; margin: 18px 0; }
                            h3 { font-size: 16pt; font-weight: bold; margin: 16px 0; }
                            p { margin: 10px 0; }
                            img { max-width: 100%; height: auto; }
                            table { border-collapse: collapse; width: 100%; margin: 10px 0; }
                            table td, table th { border: 1px solid #000; padding: 5px; }
                            ul, ol { margin: 10px 0; padding-left: 20px; }
                            .align-left { text-align: left; }
                            .align-center { text-align: center; }
                            .align-right { text-align: right; }
                            .bold { font-weight: bold; }
                            .italic { font-style: italic; }
                            .underline { text-decoration: underline; }
                        </style>
                    </head>
                    <body>
                        ${content}
                    </body>
                    </html>
                `;
                
                console.log('生成HTML内容,开始转换为DOCX...');
                
                // 使用html-docx-js转换为docx
                var converted = htmlDocx.asBlob(htmlContent);
                
                // 生成文件名
                var fileName = '文档_' + editorName + '_' + new Date().getTime() + '.docx';
                
                // 创建下载链接并触发下载
                var link = document.createElement('a');
                link.href = URL.createObjectURL(converted);
                link.download = fileName;
                document.body.appendChild(link);
                link.click();
                
                // 清理
                document.body.removeChild(link);
                URL.revokeObjectURL(link.href);
                
                console.log('Word导出成功:', fileName);
                alert('Word文档导出成功!\n文件名:' + fileName);
                
            } catch (error) {
                console.error('Word导出过程中发生错误:', error);
                alert('导出Word失败:' + error.message + '\n\n请检查浏览器控制台获取更多详细信息。');
            }
        }
        
        // Word导入功能 - 使用mammoth.js实现
        function importFromWord(editor) {
            console.log('开始导入Word...');
            
            try {
                // 检查mammoth库是否正确加载
                if (typeof window.mammoth === 'undefined') {
                    console.error('mammoth.js库未正确加载');
                    alert('Word导入失败:mammoth.js库未正确加载');
                    return;
                }
                
                // 创建文件输入框
                var input = document.createElement('input');
                input.setAttribute('type', 'file');
                input.setAttribute('accept', '.docx');
                
                input.onchange = function() {
                    var file = this.files[0];
                    if (!file) {
                        console.log('未选择文件');
                        return;
                    }
                    
                    console.log('选择的Word文件:', file.name, file.size);
                    
                    // 创建文件读取器
                    var reader = new FileReader();
                    
                    reader.onload = function(e) {
                        try {
                            console.log('文件读取成功,开始解析...');
                            
                            // 使用mammoth.js解析Word文档
                            var arrayBuffer = e.target.result;
                            
                            mammoth.convertToHtml({ arrayBuffer: arrayBuffer })
                                .then(function(result) {
                                    console.log('Word文档解析成功');
                                    
                                    // 获取解析后的HTML内容
                                    var html = result.value;
                                    var messages = result.messages;
                                    
                                    // 显示解析消息(如果有)
                                    if (messages && messages.length > 0) {
                                        console.log('解析消息:', messages);
                                    }
                                    
                                    // 将内容插入编辑器
                                    editor.setContent(html);
                                    
                                    console.log('Word内容已成功导入到编辑器');
                                    alert('Word文件导入成功!');
                                    
                                })
                                .catch(function(error) {
                                    console.error('Word文档解析失败:', error);
                                    alert('Word文档解析失败:' + error.message);
                                });
                                
                        } catch (error) {
                            console.error('文件处理失败:', error);
                            alert('文件处理失败:' + error.message);
                        }
                    };
                    
                    reader.onerror = function() {
                        console.error('文件读取失败');
                        alert('文件读取失败,请重试');
                    };
                    
                    // 读取文件
                    reader.readAsArrayBuffer(file);
                };
                
                // 触发文件选择
                input.click();
            } catch (error) {
                console.error('Word导入过程中发生错误:', error);
                alert('Word导入失败:' + error.message);
            }
        }
    </script>
</body>
</html>

  

posted @ 2025-12-17 21:37  ®Geovin Du Dream Park™  阅读(9)  评论(0)    收藏  举报