吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7882|回复: 154
上一主题 下一主题
收起左侧

[其他原创] 25.11.6更新 试卷分割神器 把老师发的不同尺寸试卷都能用家用A4打印机打出!

    [复制链接]
跳转到指定楼层
楼主
smartblack 发表于 2025-4-18 16:10 回帖奖励
本帖最后由 smartblack 于 2025-11-6 10:53 编辑

因为要给孩子打印试卷,市面上的试卷电子版通常是A3尺寸的两列,用家里的A4打印机打印出来的字太小,容易看不清楚。
制作了一个方便各位家长或者有需要这类功能的来使用。专门针对以下情况

2025.11.06更新
新增了一个比较大的功能
老版本软件只能进行竖向分割,如果遇到三列的试卷(如下图),打印出来就会是细长条,效果不好。本次更新增加了模拟A4大小进行框选,这样可以通过框选多个A4尺寸,来更方便提取页面(不知道有没有形容清楚,可以去试试)
有用户反映不会使用HTML,所以直接给挂到了一个网站上,可以直接打开就用(所有数据都在浏览器本地,不联网,可以放心用)
在线使用的网站(网站单纯只是为了方便在线使用,无任何引流和广告,当然你也可以直接下载源码自己本地用,完全一样):http://i7q.cn/6oCzyj  



2025.07.14更新
有空又重新优化了一下界面,这次效果更好。
对PDF和图片支持效果进行调优


shijuan.zip (7.01 KB, 下载次数: 804)
旧版:
操作非常简单:新建一个TXT文档,复制下面的代码粘贴进去,然后把文件后缀修改为html,双击打开即可。

免费评分

参与人数 27吾爱币 +33 热心值 +25 收起 理由
阴凉 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
端午后第四天 + 1 + 1 我很赞同!
lucklao + 1 + 1 我很赞同!
lmwldj + 1 + 1 谢谢@Thanks!
淡忘° + 1 + 1 谢谢@Thanks!
虚拟人生 + 1 + 1 谢谢@Thanks!
sdwhsea + 1 1106的版本的代码没看到,给的链接已经打不开了!
就玩一会吧 + 1 + 1 非常实用得工具
netbirds + 1 + 1 我很赞同!
傻傻的鱼缸 + 1 + 1 谢谢@Thanks!
eloved + 1 + 1 这样操作太方便了
cj52 + 1 + 1 谢谢@Thanks!
makzv + 1 + 1 谢谢@Thanks!
lgh1217 + 1 + 1 好用
1qaz + 1 + 1 谢谢@Thanks!
seanyang + 1 + 1 我很赞同!
com2mon + 1 + 1 谢谢@Thanks!
jiangcen + 1 + 1 谢谢@Thanks!
0jiao0 + 1 谢谢@Thanks!
2255 + 2 + 1 谢谢@Thanks!
grrr_zhao + 1 + 1 谢谢@Thanks!
ds360 + 1 + 1 牛掰,试了下 图片也可以。。
xy6538 + 1 谢谢@Thanks!
gztf + 1 + 1 能不能添加批量功能
天涯89 + 1 + 1 谢谢@Thanks!
caoyisheng + 1 + 1 用心讨论,共获提升!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
qingchao 发表于 2026-1-10 09:31
我看网址也打不开了,我给大家挂一个在线的,由于服务器用的是免费的,不知道速度怎么样?勉强用吧!https://7kk.us.ci
推荐
lilongjiang 发表于 2025-7-1 10:51
本帖最后由 lilongjiang 于 2025-7-1 10:52 编辑

修改了下,支持全部截取,支持图片
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>A3 试卷拆分为 A4 工具</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      margin: 20px;
      background: #f9f9f9;
      color: #333;
    }
    h1 {
      font-size: 24px;
      margin-bottom: 10px;
    }
    #instructions {
      background: #eef5ff;
      padding: 12px 16px;
      border-left: 4px solid #3399ff;
      margin-bottom: 20px;
      border-radius: 4px;
    }
    #canvasContainer {
      display: flex;
      flex-direction: column;
      align-items: center;
      max-width: 100%;
      overflow-x: auto;
      border: 1px solid #ccc;
      background: white;
      padding: 10px;
    }
    .canvas-page {
      position: relative;
      margin-bottom: 10px;
      border: 1px solid #aaa;
      overflow: hidden;
      width: 100%;
      max-width: 1000px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    canvas {
      width: 100%;
      height: auto;
      display: block;
      border: 1px solid #666;
      background: #fff;
    }
    .cut-line {
      position: absolute;
      top: 0;
      width: 2px;
      height: 100%;
      background: red;
      cursor: ew-resize;
      z-index: 10;
    }
    .canvas-page.selected {
      outline: 3px solid #3399ff;
    }
    #controls {
      position: fixed;
      bottom: 20px;
      right: 20px;
      display: flex;
      flex-direction: column;
      gap: 10px;
    }
    #controls button {
      background-color: #3399ff;
      color: white;
      border: none;
      padding: 10px 16px;
      border-radius: 6px;
      font-size: 14px;
      cursor: pointer;
      box-shadow: 0 2px 5px rgba(0,0,0,0.15);
      transition: background-color 0.2s ease;
    }
    #controls button:hover:not(:disabled) {
      background-color: #237ddb;
    }
    #controls button:disabled {
      background-color: #a0cfff;
      cursor: not-allowed;
    }
    input[type="file"] {
      margin: 10px 0 20px;
    }
    #loadingOverlay {
      position: fixed;
      inset: 0;
      background-color: rgba(255, 255, 255, 0.7);
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 1.2em;
      color: #333;
      z-index: 1000;
      visibility: hidden;
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    #loadingOverlay.visible {
      visibility: visible;
      opacity: 1;
    }
  </style>
</head>
<body>
  <h1>&#128196; A3试卷拆分为A4工具</h1>
  <div id="instructions">
    <strong>操作步骤:</strong>
    <ol>
      <li>上传PDF或图片格式的A3试卷(支持 PNG、JPEG、GIF、BMP,建议为横向A3)</li>
      <li>点击页面以选中目标页,或点击“全部选中并添加裁切线”为所有页面添加裁切线</li>
      <li>点击“添加裁切线”按钮可在当前选中页面中间添加一条可拖动的垂直裁切线,可重复添加多条</li>
      <li>拖动红线调整精确位置</li>
      <li>如需修改,可点击“删除本页裁切线”重新设置(将删除当前选中页面所有裁切线)</li>
      <li>点击“导出为PDF”按钮,生成裁切后按A4分布的新文件(竖向A4,按原页面顺序、从左到右顺序排列,片段将完整显示并居中)</li>
    </ol>
    &#9888;&#65039; 上传较大的文件或图片时,渲染画面可能需要几秒,请耐心等待加载完成。
  </div>
  <input type="file" id="fileInput" accept=".pdf,image/png,image/jpeg,image/jpg,image/gif,image/bmp" />
  <div id="canvasContainer"></div>
  <div id="loadingOverlay">正在处理,请稍候...</div>
  <div id="controls">
    <button id="selectAllAddCutLines">&#127760; 全部选中并添加裁切线</button>
    <button id="addCutLine">&#10133; 添加裁切线</button>
    <button id="removeCutLines">&#128465;&#65039; 删除本页裁切线</button>
    <button id="exportPDF">&#128196; 导出为PDF (A4)</button>
  </div>
  <script type="text/javascript">
        var gk_isXlsx = false;
        var gk_xlsxFileLookup = {};
        var gk_fileData = {};
        function filledCell(cell) {
          return cell !== '' && cell != null;
        }
        function loadFileData(filename) {
        if (gk_isXlsx && gk_xlsxFileLookup[filename]) {
            try {
                var workbook = XLSX.read(gk_fileData[filename], { type: 'base64' });
                var firstSheetName = workbook.SheetNames[0];
                var worksheet = workbook.Sheets[firstSheetName];

                // Convert sheet to JSON to filter blank rows
                var jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, blankrows: false, defval: '' });
                // Filter out blank rows (rows where all cells are empty, null, or undefined)
                var filteredData = jsonData.filter(row => row.some(filledCell));

                // Heuristic to find the header row by ignoring rows with fewer filled cells than the next row
                var headerRowIndex = filteredData.findIndex((row, index) =>
                  row.filter(filledCell).length >= filteredData[index + 1]?.filter(filledCell).length
                );
                // Fallback
                if (headerRowIndex === -1 || headerRowIndex > 25) {
                  headerRowIndex = 0;
                }

                // Convert filtered JSON back to CSV
                var csv = XLSX.utils.aoa_to_sheet(filteredData.slice(headerRowIndex)); // Create a new sheet from filtered array of arrays
                csv = XLSX.utils.sheet_to_csv(csv, { header: 1 });
                return csv;
            } catch (e) {
                console.error(e);
                return "";
            }
        }
        return gk_fileData[filename] || "";
        }
        </script>
  <script src="https://unpkg.com/pdfjs-dist@2.10.377/build/pdf.min.js"></script>
  <script src="https://unpkg.com/pdfjs-dist@2.10.377/build/pdf.worker.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
  
  <script>
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.10.377/build/pdf.worker.min.js';

    const fileInput = document.getElementById('fileInput');
    const container = document.getElementById('canvasContainer');
    const selectAllAddCutLinesBtn = document.getElementById('selectAllAddCutLines');
    const addCutLineBtn = document.getElementById('addCutLine');
    const removeCutLinesBtn = document.getElementById('removeCutLines');
    const exportBtn = document.getElementById('exportPDF');
    const loadingOverlay = document.getElementById('loadingOverlay');
    const supportedImageTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp'];

    let pages = [];
    let currentPageIndex = null;
    let isDragging = false;
    let activeLine = null;

    function showLoading(message = "正在处理,请稍候...") {
      loadingOverlay.textContent = message;
      loadingOverlay.classList.add('visible');
      addCutLineBtn.disabled = true;
      removeCutLinesBtn.disabled = true;
      exportBtn.disabled = true;
      selectAllAddCutLinesBtn.disabled = true;
      fileInput.disabled = true;
    }

    function hideLoading() {
      loadingOverlay.classList.remove('visible');
      addCutLineBtn.disabled = false;
      removeCutLinesBtn.disabled = Boilerplate artifact_id="0164551c-8ef0-436a-a1e8-913ada14b377" title="index.html" contentType="text/html">
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>A3 试卷拆分为 A4 工具</title>
  <style>
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      margin: 20px;
      background: #f9f9f9;
      color: #333;
    }
    h1 {
      font-size: 24px;
      margin-bottom: 10px;
    }
    #instructions {
      background: #eef5ff;
      padding: 12px 16px;
      border-left: 4px solid #3399ff;
      margin-bottom: 20px;
      border-radius: 4px;
    }
    #canvasContainer {
      display: flex;
      flex-direction: column;
      align-items: center;
      max-width: 100%;
      overflow-x: auto;
      border: 1px solid #ccc;
      background: white;
      padding: 10px;
    }
    .canvas-page {
      position: relative;
      margin-bottom: 10px;
      border: 1px solid #aaa;
      overflow: hidden;
      width: 100%;
      max-width: 1000px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    canvas {
      width: 100%;
      height: auto;
      display: block;
      border: 1px solid #666;
      background: #fff;
    }
    .cut-line {
      position: absolute;
      top: 0;
      width: 2px;
      height: 100%;
      background: red;
      cursor: ew-resize;
      z-index: 10;
    }
    .canvas-page.selected {
      outline: 3px solid #3399ff;
    }
    #controls {
      position: fixed;
      bottom: 20px;
      right: 20px;
      display: flex;
      flex-direction: column;
      gap: 10px;
    }
    #controls button {
      background-color: #3399ff;
      color: white;
      border: none;
      padding: 10px 16px;
      border-radius: 6px;
      font-size: 14px;
      cursor: pointer;
      box-shadow: 0 2px 5px rgba(0,0,0,0.15);
      transition: background-color 0.2s ease;
    }
    #controls button:hover:not(:disabled) {
      background-color: #237ddb;
    }
    #controls button:disabled {
      background-color: #a0cfff;
      cursor: not-allowed;
    }
    input[type="file"] {
      margin: 10px 0 20px;
    }
    #loadingOverlay {
      position: fixed;
      inset: 0;
      background-color: rgba(255, 255, 255, 0.7);
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 1.2em;
      color: #333;
      z-index: 1000;
      visibility: hidden;
      opacity: 0;
      transition: opacity 0.3s ease;
    }
    #loadingOverlay.visible {
      visibility: visible;
      opacity: 1;
    }
  </style>
</head>
<body>
  <h1>&#128196; A3试卷拆分为A4工具</h1>
  <div id="instructions">
    <strong>操作步骤:</strong>
    <ol>
      <li>上传PDF或图片格式的A3试卷(支持 PNG、JPEG、GIF、BMP,建议为横向A3)</li>
      <li>点击页面以选中目标页,或点击“全部选中并添加裁切线”为所有页面添加裁切线</li>
      <li>点击“添加裁切线”按钮可在当前选中页面中间添加一条可拖动的垂直裁切线,可重复添加多条</li>
      <li>拖动红线调整精确位置</li>
      <li>如需修改,可点击“删除本页裁切线”重新设置(将删除当前选中页面所有裁切线)</li>
      <li>点击“导出为PDF”按钮,生成裁切后按A4分布的新文件(竖向A4,按原页面顺序、从左到右顺序排列,片段将完整显示并居中)</li>
    </ol>
    &#9888;&#65039; 上传较大的文件或图片时,渲染画面可能需要几秒,请耐心等待加载完成。
  </div>
  <input type="file" id="fileInput" accept=".pdf,image/png,image/jpeg,image/jpg,image/gif,image/bmp" />
  <div id="canvasContainer"></div>
  <div id="loadingOverlay">正在处理,请稍候...</div>
  <div id="controls">
    <button id="selectAllAddCutLines">&#127760; 全部选中并添加裁切线</button>
    <button id="addCutLine">&#10133; 添加裁切线</button>
    <button id="removeCutLines">&#128465;&#65039; 删除本页裁切线</button>
    <button id="exportPDF">&#128196; 导出为PDF (A4)</button>
  </div>
  <script src="https://unpkg.com/pdfjs-dist@2.10.377/build/pdf.min.js"></script>
  <script src="https://unpkg.com/pdfjs-dist@2.10.377/build/pdf.worker.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
  <script>
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@2.10.377/build/pdf.worker.min.js';

    const fileInput = document.getElementById('fileInput');
    const container = document.getElementById('canvasContainer');
    const selectAllAddCutLinesBtn = document.getElementById('selectAllAddCutLines');
    const addCutLineBtn = document.getElementById('addCutLine');
    const removeCutLinesBtn = document.getElementById('removeCutLines');
    const exportBtn = document.getElementById('exportPDF');
    const loadingOverlay = document.getElementById('loadingOverlay');
    const supportedImageTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp'];

    let pages = [];
    let currentPageIndex = null;
    let isDragging = false;
    let activeLine = null;

    function showLoading(message = "正在处理,请稍候...") {
      loadingOverlay.textContent = message;
      loadingOverlay.classList.add('visible');
      addCutLineBtn.disabled = true;
      removeCutLinesBtn.disabled = true;
      exportBtn.disabled = true;
      selectAllAddCutLinesBtn.disabled = true;
      fileInput.disabled = true;
    }

    function hideLoading() {
      loadingOverlay.classList.remove('visible');
      addCutLineBtn.disabled = false;
      removeCutLinesBtn.disabled = false;
      exportBtn.disabled = false;
      selectAllAddCutLinesBtn.disabled = false;
      fileInput.disabled = false;
    }

    function createCanvasPage(width, height) {
      const wrapper = document.createElement('div');
      wrapper.className = 'canvas-page';
      const canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;
      wrapper.appendChild(canvas);
      container.appendChild(wrapper);

      const pageData = {
        wrapper,
        canvas,
        ctx: canvas.getContext('2d'),
        originalImage: null,
        cutLines: []
      };

      wrapper.addEventListener('click', (e) => {
        if (e.target.classList.contains('cut-line')) return;
        pages.forEach((p, idx) => {
          p.wrapper.classList.remove('selected');
          if (p === pageData) currentPageIndex = idx;
        });
        wrapper.classList.add('selected');
      });

      return pageData;
    }

    function addCutLineToPage(page, initialRatio = 0.5) {
      const line = document.createElement('div');
      line.className = 'cut-line';
      const cutData = { el: line, ratio: initialRatio };

      const updateLinePosition = () => {
        if (!line.parentNode) {
          window.removeEventListener('resize', updateLinePosition);
          return;
        }
        const wrapperRect = page.wrapper.getBoundingClientRect();
        if (cutData && typeof cutData.ratio === 'number' && wrapperRect.width > 0) {
          line.style.left = `${cutData.ratio * wrapperRect.width}px`;
        }
      };

      line.addEventListener('mousedown', (e) => {
        isDragging = true;
        activeLine = { line, page, cutData };
        e.stopPropagation();
      });

      page.wrapper.appendChild(line);
      page.cutLines.push(cutData);
      cutData.resizeListener = updateLinePosition;

      requestAnimationFrame(updateLinePosition);
      window.addEventListener('resize', updateLinePosition);
    }

    function removeCutLinesFromPage(page) {
      page.cutLines.forEach(cut => {
        cut.el.remove();
        if (cut.resizeListener) {
          window.removeEventListener('resize', cut.resizeListener);
        }
      });
      page.cutLines = [];
    }

    window.addEventListener('mousemove', (e) => {
      if (!isDragging || !activeLine) return;
      const { line, page, cutData } = activeLine;
      const wrapperRect = page.wrapper.getBoundingClientRect();
      if (wrapperRect.width <= 0) return;

      let newX_display = e.clientX - wrapperRect.left;
      newX_display = Math.max(0, Math.min(wrapperRect.width, newX_display));
      const newRatio = newX_display / wrapperRect.width;

      cutData.ratio = newRatio;
      line.style.left = `${newX_display}px`;
    });

    window.addEventListener('mouseup', () => {
      if (isDragging) {
        isDragging = false;
        activeLine = null;
      }
    });

    fileInput.addEventListener('change', async (e) => {
      const file = e.target.files[0];
      container.innerHTML = '';
      pages = [];
      currentPageIndex = null;

      if (!file) return;

      showLoading('正在加载文件,请稍候...');

      try {
        if (file.type === 'application/pdf') {
          const fileReader = new FileReader();
          await new Promise((resolve, reject) => {
            fileReader.onload = async () => {
              try {
                const loadingTask = pdfjsLib.getDocument({ data: fileReader.result });
                const pdf = await loadingTask.promise;
                const scale = 1.5;

                container.innerHTML = '';

                for (let i = 0; i < pdf.numPages; i++) {
                  showLoading(`正在加载 PDF 第 ${i + 1} / ${pdf.numPages} 页...`);
                  const page = await pdf.getPage(i + 1);
                  const viewport = page.getViewport({ scale });
                  const pageData = createCanvasPage(viewport.width, viewport.height);

                  const renderContext = { canvasContext: pageData.ctx, viewport: viewport };
                  await page.render(renderContext).promise;

                  const highResScale = 2.0;
                  const highResViewport = page.getViewport({ scale: highResScale });
                  const offscreenCanvas = document.createElement('canvas');
                  offscreenCanvas.width = highResViewport.width;
                  offscreenCanvas.height = highResViewport.height;
                  const offscreenCtx = offscreenCanvas.getContext('2d');
                  const highResRenderContext = { canvasContext: offscreenCtx, viewport: highResViewport };
                  await page.render(highResRenderContext).promise;
                  pageData.originalImage = offscreenCanvas.toDataURL('image/jpeg', 0.95);

                  pages.push(pageData);
                }
                resolve();
              } catch (pdfError) {
                reject(pdfError);
              }
            };
            fileReader.onerror = reject;
            fileReader.readAsArrayBuffer(file);
          });
        } else if (supportedImageTypes.includes(file.type)) {
          await new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => {
              container.innerHTML = '';
              const maxDimension = 5000;
              let width = img.naturalWidth;
              let height = img.naturalHeight;

              if (width > maxDimension || height > maxDimension) {
                alert('图片尺寸过大,将自动缩放以提高性能。');
                const scale = Math.min(maxDimension / width, maxDimension / height);
                width = width * scale;
                height = height * scale;
              }

              const pageData = createCanvasPage(width, height);
              pageData.ctx.drawImage(img, 0, 0, width, height);
              pageData.originalImage = pageData.canvas.toDataURL('image/jpeg', 0.95);
              pages.push(pageData);
              URL.revokeObjectURL(img.src);
              resolve();
            };
            img.onerror = () => {
              URL.revokeObjectURL(img.src);
              reject(new Error('无法加载图片文件,请确保图片格式为 PNG、JPEG、GIF 或 BMP。'));
            };
            img.src = URL.createObjectURL(file);
          });
        } else {
          throw new Error('不支持的文件类型。请上传 PDF 或支持的图片格式(PNG、JPEG、GIF、BMP)。');
        }
      } catch (error) {
        console.error('文件加载或处理出错:', error);
        container.innerHTML = `<p style="color: red;">文件加载或处理出错: ${error.message}</p>`;
        alert(`文件加载或处理出错: ${error.message}`);
      } finally {
        hideLoading();
      }
    });

    selectAllAddCutLinesBtn.addEventListener('click', () => {
      if (pages.length === 0) {
        alert('请先上传文件。');
        return;
      }
      pages.forEach((page, index) => {
        pages.forEach(p => p.wrapper.classList.remove('selected'));
        page.wrapper.classList.add('selected');
        currentPageIndex = index;
        removeCutLinesFromPage(page); // Clear existing cut lines to avoid duplicates
        addCutLineToPage(page, 0.5);
      });
      alert('已为所有页面添加裁切线!');
    });

    addCutLineBtn.addEventListener('click', () => {
      if (currentPageIndex === null || !pages[currentPageIndex]) {
        alert('请点击选中一页后再添加裁切线');
        return;
      }
      const page = pages[currentPageIndex];
      addCutLineToPage(page, 0.5);
    });

    removeCutLinesBtn.addEventListener('click', () => {
      if (currentPageIndex === null || !pages[currentPageIndex]) {
        alert('请先选中页面');
        return;
      }
      removeCutLinesFromPage(pages[currentPageIndex]);
    });

    exportBtn.addEventListener('click', async () => {
      if (pages.length === 0) {
        alert('请先上传文件。');
        return;
      }

      const pagesWithoutCuts = pages.filter(p => p.cutLines.length === 0);
      if (pagesWithoutCuts.length > 0) {
        const confirmProceed = confirm(`警告:有 ${pagesWithoutCuts.length} 页没有添加裁切线,这些页面将尝试缩放到单张A4页面上。是否继续?`);
        if (!confirmProceed) return;
      }

      showLoading('&#128640; 正在导出PDF,请稍候...');
      exportBtn.disabled = true;

      try {
        const a4WidthPt = 8.27 * 72;
        const a4HeightPt = 11.69 * 72;
        const { jsPDF } = jspdf;
        const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' });
        let pdfPageCount = 0;

        for (const [index, page] of pages.entries()) {
          showLoading(`正在处理第 ${index + 1} / ${pages.length} 页...`);

          await new Promise((resolve, reject) => {
            if (!page.originalImage) {
              console.warn(`Page ${index + 1} missing original image data. Skipping.`);
              alert(`页面 ${index + 1} 缺少图像数据,将跳过此页面。`);
              resolve();
              return;
            }

            const img = new Image();
            img.onload = () => {
              const sourceCanvas = document.createElement('canvas');
              sourceCanvas.width = img.naturalWidth;
              sourceCanvas.height = img.naturalHeight;
              const sourceCtx = sourceCanvas.getContext('2d');
              sourceCtx.drawImage(img, 0, 0);

              const hasCutLines = page.cutLines.length > 0;

              if (!hasCutLines) {
                if (sourceCanvas.width > 0 && sourceCanvas.height > 0) {
                  if (pdfPageCount > 0) pdf.addPage('a4', 'p');
                  pdfPageCount++;
                  pdf.setPage(pdfPageCount);

                  const imgWidth = sourceCanvas.width;
                  const imgHeight = sourceCanvas.height;
                  const scale = Math.min(a4WidthPt / imgWidth, a4HeightPt / imgHeight);
                  const finalWidth = imgWidth * scale;
                  const finalHeight = imgHeight * scale;
                  const offsetX = (a4WidthPt - finalWidth) / 2;
                  const offsetY = (a4HeightPt - finalHeight) / 2;

                  pdf.addImage(sourceCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', offsetX, offsetY, finalWidth, finalHeight);
                } else {
                  console.warn(`Skipping page ${index + 1} (no cuts) due to zero dimensions.`);
                }
              } else {
                const sortedRatios = page.cutLines.map(line => line.ratio).sort((a, b) => a - b);
                const uniqueSortedRatios = [...new Set(sortedRatios)];
                const positions = [0, ...uniqueSortedRatios, 1];

                for (let i = 0; i < positions.length - 1; i++) {
                  const startRatio = positions[i];
                  const endRatio = positions[i + 1];

                  if (typeof startRatio !== 'number' || typeof endRatio !== 'number' || startRatio < 0 || startRatio >= endRatio || endRatio > 1) {
                    console.warn(`Skipping invalid segment on page ${index + 1}: start=${startRatio}, end=${endRatio}`);
                    continue;
                  }

                  const startX = Math.round(startRatio * sourceCanvas.width);
                  const segmentWidth = Math.max(0, Math.round((endRatio - startRatio) * sourceCanvas.width));
                  const segmentHeight = sourceCanvas.height;

                  if (segmentWidth <= 0 || segmentHeight <= 0) {
                    console.warn(`Skipping zero-dimension segment on page ${index + 1} [${startRatio.toFixed(3)}-${endRatio.toFixed(3)}]`);
                    continue;
                  }

                  const tempCanvas = document.createElement('canvas');
                  tempCanvas.width = segmentWidth;
                  tempCanvas.height = segmentHeight;
                  const tempCtx = tempCanvas.getContext('2d');
                  tempCtx.drawImage(sourceCanvas, startX, 0, segmentWidth, segmentHeight, 0, 0, segmentWidth, segmentHeight);

                  if (pdfPageCount > 0) pdf.addPage('a4', 'p');
                  pdfPageCount++;
                  pdf.setPage(pdfPageCount);

                  const imgWidth = segmentWidth;
                  const imgHeight = segmentHeight;

                  if (imgWidth > 0 && imgHeight > 0) {
                    const scaleWidth = a4WidthPt / imgWidth;
                    const scaleHeight = a4HeightPt / imgHeight;
                    const scale = Math.min(scaleWidth, scaleHeight);
                    const finalWidth = imgWidth * scale;
                    const finalHeight = imgHeight * scale;
                    const offsetX = (a4WidthPt - finalWidth) / 2;
                    const offsetY = (a4HeightPt - finalHeight) / 2;

                    pdf.addImage(tempCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', offsetX, offsetY, finalWidth, finalHeight);
                  } else {
                    console.warn(`Skipping segment on page ${index + 1} due to zero dimensions after calculation.`);
                  }
                }
              }
              resolve();
            };

            img.onerror = () => {
              console.error(`Error loading image data for page ${index + 1} for export.`);
              alert(`页面 ${index + 1} 的图像数据加载失败,将跳过此页面。`);
              resolve();
            };

            img.src = page.originalImage;
          });
        }

        if (pdfPageCount > 0) {
          pdf.save('拆分后试卷_A4.pdf');
        } else {
          alert('未能生成任何PDF页面。请检查源文件和裁切线设置。');
        }
      } catch (error) {
        console.error("导出PDF时发生错误:", error);
        alert(`导出PDF失败: ${error.message}`);
      } finally {
        hideLoading();
        exportBtn.disabled = false;
      }
    });
  </script>
</body>
</html>

免费评分

参与人数 4吾爱币 +5 热心值 +4 收起 理由
samniboy + 1 + 1 热心回复!
rays2004 + 1 + 1 谢谢@Thanks!
atpjcom + 1 + 1 谢谢@Thanks!
0jiao0 + 2 + 1 谢谢@Thanks!

查看全部评分

沙发
seapipi 发表于 2025-4-19 21:26
弄过去了,不过是乱码。只有保存这里的图片看了
3#
happyorange 发表于 2025-4-19 22:28
谢谢楼主,非常好用
4#
robertclarke 发表于 2025-4-20 08:42
学生的福音
5#
cai2532 发表于 2025-4-21 08:59
这个不错,真的很实用。
6#
smggg888 发表于 2025-4-21 14:05
xiao26871411 发表于 2025-4-19 20:08
用楼主方法剪切好了后,可以把A3纸对折塞到A4打印机里 一面一面打 就行。

牛 还能对折了打印,之前没试过
7#
lingqixzw 发表于 2025-4-21 14:59
虽然还没有用,但是点赞楼主的分享精神!
8#
caoyisheng 发表于 2025-4-21 16:31
能不能多选几张,批量处理?
9#
 楼主| smartblack 发表于 2025-4-21 18:05 |楼主
caoyisheng 发表于 2025-4-21 16:31
能不能多选几张,批量处理?

pdf文件可以一次处理所有的
10#
ZZ1993 发表于 2025-4-22 14:37
谢谢楼主,非常好用
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-3-25 21:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表