You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apps Script如何遍历Google Docs所有页面并为每页添加独立标题?

实现核心前提

Google Docs的Apps Script原生没有提供直接按页遍历的API,因为分页属于前端渲染层动态计算的结果,和文档存储的结构化元素没有固定映射关系,没法直接调用类似getPages()的方法拿到分页位置,需要通过排版参数模拟计算分页点,同时适配跨页元素不拆分的需求。

具体实现思路
  • 第一步先获取文档基础排版参数
    调用Document服务拿到Body的页面高度、上下边距数值,计算出单页可容纳的有效内容高度,所有计算统一用磅(pt)作为单位,和Docs原生排版单位对齐,同时预留2pt左右的容错值抵消行高、空行带来的渲染误差。
  • 第二步逐元素遍历累计高度判断分页点
    从文档Body的第一个子元素开始逐个遍历(覆盖段落、表格、图片、列表所有块级元素),根据元素类型计算单个元素的渲染高度:普通段落按字体大小、行高、段前段后间距算总高度,表格、图片直接取内嵌的高度属性即可。遍历过程中维护一个累计高度变量,每处理一个元素就累加对应高度:
    • 累加后高度未超过单页有效高度:元素属于当前页,继续遍历
    • 遇到手动分页符元素:直接在分页符后插入对应序号的标题,重置累计高度为0,优先尊重手动分页逻辑
    • 累加前高度未超限、累加后超限:说明当前元素是跨页元素,不要拆分元素,直接在该元素的起始位置插入下一页标题,将该元素整体归到下一页,避免同一段落被标题截断
  • 第三步标题插入的防重复处理
    你预存的titles数组按页码顺序取值即可,每插入一个标题就将数组索引+1;可以给插入的标题设置专属样式或者隐藏标记,避免后续重复运行脚本时重复插入标题。
  • 精度优化选项
    如果需要100%匹配前端渲染的分页位置,可以临时将文档导出为PDF,解析PDF的分页偏移量反向映射回Docs的元素位置,不过实现成本较高,普通办公场景用高度累计的方案误差不会超过1行,完全够用。
核心逻辑参考代码
function addPageTopTitles() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();
  // 按页码顺序存储的每页标题数组
  const titles = ["第一页标题", "第二页标题", "第三页标题", "第四页标题"];
  
  // 计算单页有效内容高度 单位pt
  const pageHeight = body.getPageHeight();
  const marginTop = body.getMarginTop();
  const marginBottom = body.getMarginBottom();
  const effectivePageHeight = pageHeight - marginTop - marginBottom;
  const errorTolerance = 2; // 渲染误差容错值

  let currentAccumHeight = 0;
  let titleIndex = 0;
  let totalChildCount = body.getNumChildren();

  // 第一页顶部插入第一个标题
  if (titles.length > 0) {
    const firstTitle = body.insertParagraph(0, titles[titleIndex]);
    firstTitle.setHeading(DocumentApp.ParagraphHeading.HEADING1);
    // 累加标题本身的高度
    currentAccumHeight += firstTitle.getFontSize() * 1.5;
    titleIndex += 1;
    totalChildCount = body.getNumChildren();
  }

  // 遍历所有正文元素
  for (let i = 1; i < totalChildCount; i++) {
    const currentChild = body.getChild(i);
    let childHeight = 0;

    // 按元素类型计算高度
    switch(currentChild.getType()) {
      case DocumentApp.ElementType.PARAGRAPH:
        const para = currentChild.asParagraph();
        // 检测到手动分页符,直接在后面插标题
        if (para.findElement(DocumentApp.ElementType.PAGE_BREAK) && titleIndex < titles.length) {
          const insertPos = i + 1;
          const newTitle = body.insertParagraph(insertPos, titles[titleIndex]);
          newTitle.setHeading(DocumentApp.ParagraphHeading.HEADING1);
          titleIndex += 1;
          currentAccumHeight = 0;
          totalChildCount = body.getNumChildren();
          i += 1; // 跳过刚插入的标题元素
          continue;
        }
        // 计算段落高度
        const fontSize = para.getFontSize() || 12;
        const lineSpacing = para.getLineSpacing() ? para.getLineSpacing()/100 : 1.15;
        const spaceBefore = para.getSpacingBefore() || 0;
        const spaceAfter = para.getSpacingAfter() || 0;
        childHeight = fontSize * lineSpacing + spaceBefore + spaceAfter;
        break;
      case DocumentApp.ElementType.TABLE:
        const table = currentChild.asTable();
        childHeight = table.getRowHeight() * table.getNumRows();
        break;
      case DocumentApp.ElementType.INLINE_IMAGE:
        childHeight = currentChild.asInlineImage().getHeight();
        break;
      case DocumentApp.ElementType.LIST_ITEM:
        const listItem = currentChild.asListItem();
        const listFontSize = listItem.getFontSize() || 12;
        childHeight = listFontSize * 1.2 + (listItem.getSpacingBefore()||0) + (listItem.getSpacingAfter()||0);
        break;
      default:
        childHeight = 12;
    }

    // 判断是否到达分页点
    if (currentAccumHeight + childHeight > effectivePageHeight - errorTolerance && titleIndex < titles.length) {
      // 跨页元素不拆分,在元素前插入下一页标题
      const newTitle = body.insertParagraph(i, titles[titleIndex]);
      newTitle.setHeading(DocumentApp.ParagraphHeading.HEADING1);
      titleIndex += 1;
      // 重置累计高度为当前跨页元素+新标题的高度
      currentAccumHeight = childHeight + newTitle.getFontSize()*1.5;
      totalChildCount = body.getNumChildren();
      i += 1; // 跳过刚插入的标题
    } else {
      currentAccumHeight += childHeight;
    }
  }
}

注意:脚本运行前请先复制原文档做备份,上述代码中的元素高度计算是通用简化逻辑,如果你的文档有复杂混排、特殊字体、自定义段间距,可以对应调整高度计算规则提升精度。

内容的提问来源于stack exchange,提问作者Marco Maggioni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 23:09:31