在iPad远程监控LaTeX 编译的PDF

经常希望能在iPad或其他平板上远程查看正在编译的LaTeX PDF,使用远程桌面的方案过于卡顿,在gpt的帮助下实现了用HTTP Server转发的形式进行远程监控。

1. 放行防火墙端口

以5500端口为例。

  • Powershell(管理员):
netsh advfirewall firewall add rule name="PDF Live 5500" dir=in action=allow protocol=TCP localport=5500

或通过 Windows 防火墙图形界面添加入站规则。

2. 远程转发网页

新建viewer.html放在pdf文件夹下

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>PDF.js Remote Viewer</title>

<style>
  html, body {
    margin: 0;
    padding: 0;
    background: #2b2b2b;
    color: #ddd;
    font-family: system-ui, -apple-system, BlinkMacSystemFont;
  }

#toolbar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: #1e1e1e;
  padding: 8px 12px;
  font-size: 14px;
  display: none;   /* 默认隐藏 */
}

  #status {
    opacity: 0.8;
  }

  #viewer {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 16px;
  }

  canvas {
    background: white;
    margin-bottom: 16px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.4);
  }
</style>
</head>

<body>

<div id="toolbar">
  <span id="status">Loading…</span>
</div>

<div id="viewer"></div>

<script src="pdfjs/pdf.js"></script>
<script>
/* ================== 基本配置 ================== */

const PDF_URL = 'main.pdf';

// DPR 上限(关键:防止 iPad 3x 过载)
const MAX_DPR = 2;

// 轮询 PDF 是否更新(秒)
const CHECK_INTERVAL = 1500;

/* ================== PDF.js Worker ================== */

pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs/pdf.worker.js';

/* ================== 状态变量 ================== */

const viewer = document.getElementById('viewer');
const statusEl = document.getElementById('status');

let rendering = false;
let lastModified = null;

/* ================== 工具函数 ================== */

function logStatus(msg) {
  statusEl.textContent = msg;
}

/* ================== 核心渲染函数(DPR 修复) ================== */

async function renderPDF() {
  if (rendering) return;
  rendering = true;

  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

  try {
    logStatus('Rendering…');

    const pdf = await pdfjsLib.getDocument(PDF_URL + '?t=' + Date.now()).promise;
    viewer.innerHTML = '';

    const rawDpr = window.devicePixelRatio || 1;
    const dpr = Math.min(rawDpr, MAX_DPR);

    const screenWidth = Math.min(
      document.documentElement.clientWidth,
      window.innerWidth || 0
    ) - 32;

    for (let i = 1; i <= pdf.numPages; i++) {
      const page = await pdf.getPage(i);

      // 未缩放尺寸(PDF points)
      const baseViewport = page.getViewport({ scale: 1 });

      // CSS 级别缩放
      const cssScale = screenWidth / baseViewport.width;

      // 实际渲染 scale(× DPR)
      const renderScale = cssScale * dpr;
      const viewport = page.getViewport({ scale: renderScale });

      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');

      // backing pixels
      canvas.width  = Math.floor(viewport.width);
      canvas.height = Math.floor(viewport.height);

      // CSS 尺寸(逻辑像素)
      canvas.style.width  = Math.floor(viewport.width / dpr) + 'px';
      canvas.style.height = Math.floor(viewport.height / dpr) + 'px';

      viewer.appendChild(canvas);

      await page.render({
        canvasContext: ctx,
        viewport: viewport
      }).promise;
    }

    // 恢复滚动位置
    window.scrollTo(0, scrollTop);

    logStatus(`Rendered ${pdf.numPages} pages · DPR=${dpr.toFixed(2)}`);
  } catch (err) {
    console.error(err);
    logStatus('Render failed');
  } finally {
    rendering = false;
  }
}

/* ================== PDF 更新检测(HEAD / Last-Modified) ================== */

async function checkPdfUpdate() {
  try {
    const res = await fetch(PDF_URL, {
      method: 'HEAD',
      cache: 'no-store'
    });

    if (!res.ok) return;

    const lm = res.headers.get('Last-Modified');
    if (lm && lm !== lastModified) {
      lastModified = lm;
      await renderPDF();
    }
  } catch (e) {
    // 忽略临时网络错误
  }
}

/* ================== 启动 ================== */

renderPDF();
setInterval(checkPdfUpdate, CHECK_INTERVAL);

document.addEventListener('dblclick', () => {
  const bar = document.getElementById('toolbar');
  bar.style.display = (bar.style.display === 'none') ? 'block' : 'none';
});

</script>

</body>
</html>

3. 下载js文件

https://github.com/mozilla/pdf.js
下载 UMD 版,我测试的是 https://github.com/mozilla/pdf.js/releases/tag/v3.10.111
将pdf.js和pdf.worker.js放在pdfjs文件夹中。

gs-zip/
├─ viewer.html         # 完整 HTML
├─ main.pdf          # 目标 PDF
└─ pdfjs/
   ├─ pdf.js           # UMD PDF.js
   └─ pdf.worker.js    # Worker

4. HTTP Server开启

python -m http.server 5500 --bind 0.0.0.0
  • 0.0.0.0 表示局域网可访问
  • iPad 浏览器访问:http://(PC-IP):5500/viewer.html
    • 在命令行运行 ipconfig,取(PC-IP)
posted @ 2026-01-14 12:37  阿奘  阅读(3)  评论(0)    收藏  举报