在日常写文档、生成报告或导出说明书时,我们经常会遇到一个看似简单但实际并不稳定的需求:在 PDF 文件的左上角添加 Logo、项目标识或印章

一开始,我尝试使用 Typora 的 PDF 导出功能解决这个问题。Typora 支持在导出 PDF 时向生成的 HTML 中追加自定义内容,也就是在 PDF 偏好设置面板中的 “添加额外内容 / Append Extra Content” 功能。理论上,我们可以在这里插入 HTML、CSS 或 JavaScript,从而在导出的 PDF 中添加自定义封面、页脚、Logo 等元素。

但是在实际使用过程中,我发现通过 Typora 的 HTML 附加内容来添加 Logo 并不稳定。最终,我改用 Python 对已经导出的 PDF 进行二次处理,直接在 PDF 页面上添加 Logo。这个方案更加清晰、稳定,也不会受到 Typora 主题样式、页眉页脚或正文排版的影响。

本文记录这个问题的解决过程,并给出最终可用的完整代码。

一、问题背景

我的需求很简单:在使用 Typora 导出 PDF 后,希望在 PDF 的左上角添加一个 Logo。

理想效果是:

  • Logo 位于 PDF 页面左上角;
  • 可以控制 Logo 的位置和大小;
  • 可以选择只在第一页添加,也可以添加到所有页面;
  • 不受 Typora 主题样式影响;
  • 不出现图片阴影、白底、圆角等额外样式;
  • 导出的 PDF 仍然保持原有正文内容不变。

最开始,我尝试使用 Typora 的 Append Extra Content 功能,通过 HTML 插入图片。例如使用 <img> 标签或者将图片转为 Base64 后嵌入页面中。

但是很快就遇到了几个问题。

二、Typora 自定义 HTML 方案的问题

Typora 的 PDF 导出本质上是先将 Markdown 渲染为 HTML,再将 HTML 输出为 PDF。因此,它提供的“添加额外内容”功能,本质上是将自定义 HTML 内容追加到页面的 <body> 中。

这意味着插入的 Logo 并不是真正添加到了 PDF 页眉或页面画布中,而是作为 HTML 正文的一部分参与渲染。

实际测试中遇到的问题主要有三个。

首先,Logo 会出现在正文最后面,而不是页面左上角。这是因为追加的 <img> 标签默认会被当作普通正文图片处理。

其次,Logo 会继承 Typora 当前主题中对图片的默认样式。例如某些主题会给图片添加阴影、白色背景、圆角、边框或默认外边距。这样一来,Logo 看起来就像普通插图,而不是一个干净的页面标识。

再次,即使使用 position: fixedposition: absolute 强制定位,Logo 也可能只能出现在正文渲染区域内,无法稳定进入 PDF 真正的页眉或页边距区域。尤其当 PDF 中设置了页眉、页脚或较大的页边距时,Logo 往上移动还可能被裁切或覆盖。

后来我尝试改用 div + background-image 的方式,避免 <img> 被主题样式影响。这个方法可以解决图片阴影的问题,但仍然无法完全解决 PDF 顶部区域被 Typora 渲染规则限制的问题。

因此,对于“给 PDF 添加 Logo / 印章”这种需求,更稳定的做法不是在 Typora 导出时修改 HTML,而是:先正常导出 PDF,再使用 Python 对 PDF 文件进行后处理

三、为什么选择 Python 处理 PDF

使用 Python 后处理 PDF 有几个明显优势。

第一,Logo 是直接添加到 PDF 页面上的,不再受 Typora 主题 CSS 影响。

第二,可以精确控制 Logo 的位置、宽度和高度。

第三,可以灵活选择添加范围:只添加第一页,或者添加到全部页面。

第四,处理逻辑清晰,后续也方便扩展。例如可以进一步添加水印、页眉、页脚、二维码、签名图片等。

这里使用的库是 PyMuPDF,在代码中通过 import fitz 引入。它可以读取 PDF 页面,并向指定坐标区域插入图片。

安装依赖:

pip install pymupdf

四、最终实现代码

下面是最终整理好的完整代码。它支持两种模式:

  • mode = "first":只在第一页添加 Logo;
  • mode = "all":在所有页面添加 Logo。

代码中 Logo 的位置和大小使用毫米作为单位,更符合日常排版习惯。

import fitz
from pathlib import Path


# =========================
# 基础配置
# =========================

input_pdf = "input.pdf"                 # 原始 PDF
logo_path = "logo.png"                  # Logo 图片
output_pdf = "output_with_logo.pdf"     # 输出 PDF

# 添加模式:
# "first" = 只添加到第一页
# "all"   = 添加到全部页面
mode = "all"

# Logo 位置和大小,单位:mm
left_mm = 8
top_mm = 8
width_mm = 28
height_mm = 12

# 是否覆盖在正文上方
# True  = Logo 显示在最上层
# False = Logo 显示在底层
overlay = True


# =========================
# 工具函数
# =========================

def mm_to_pt(mm: float) -> float:
    """
    PDF 坐标单位是 pt。
    1 mm ≈ 2.83465 pt
    """
    return mm * 2.83465


def add_logo_to_page(page: fitz.Page, logo_file: str):
    """
    向单页 PDF 添加 Logo。
    """
    left = mm_to_pt(left_mm)
    top = mm_to_pt(top_mm)
    width = mm_to_pt(width_mm)
    height = mm_to_pt(height_mm)

    rect = fitz.Rect(
        left,
        top,
        left + width,
        top + height
    )

    page.insert_image(
        rect,
        filename=logo_file,
        keep_proportion=True,
        overlay=overlay
    )


# =========================
# 主程序
# =========================

def main():
    input_path = Path(input_pdf)
    logo_file = Path(logo_path)

    if not input_path.exists():
        raise FileNotFoundError(f"找不到输入 PDF:{input_path}")

    if not logo_file.exists():
        raise FileNotFoundError(f"找不到 Logo 图片:{logo_file}")

    if mode not in ["first", "all"]:
        raise ValueError('mode 只能设置为 "first" 或 "all"')

    doc = fitz.open(input_path)

    if len(doc) == 0:
        raise ValueError("PDF 页数为 0,无法处理")

    if mode == "first":
        # 只添加第一页
        add_logo_to_page(doc[0], str(logo_file))

    elif mode == "all":
        # 添加到全部页面
        for page in doc:
            add_logo_to_page(page, str(logo_file))

    doc.save(
        output_pdf,
        garbage=4,
        deflate=True
    )

    doc.close()

    print(f"处理完成:{output_pdf}")


if __name__ == "__main__":
    main()

五、代码使用说明

使用前,需要准备三个文件或配置:

input_pdf = "input.pdf"
logo_path = "logo.png"
output_pdf = "output_with_logo.pdf"

其中:

  • input.pdf 是原始 PDF 文件;
  • logo.png 是需要添加的 Logo 图片;
  • output_with_logo.pdf 是处理后的输出文件。

建议将 Python 脚本、原始 PDF 和 Logo 图片放在同一个文件夹下,这样可以直接使用相对路径,减少路径错误。

运行脚本:

python add_logo_to_pdf.py

运行完成后,会生成一个新的 PDF 文件:output_with_logo.pdf

六、添加到第一页或全部页面

代码中通过 mode 控制 Logo 的添加范围。

只添加到第一页:

mode = "first"

添加到全部页面:

mode = "all"

这个设计比写两个脚本更方便。日常使用时,只需要改一个参数即可。

例如,封面页需要公司 Logo,但正文页不需要,可以使用first

如果是正式报告、合同、说明文档,希望每一页都带有机构标识,可以使用all

七、调整 Logo 的位置和大小

Logo 的位置由下面两个参数控制:

left_mm = 8
top_mm = 8

它们表示 Logo 距离页面左边和上边的距离,单位是毫米。

Logo 的尺寸由下面两个参数控制:

width_mm = 28
height_mm = 12

其中:

  • width_mm 控制 Logo 显示宽度;
  • height_mm 控制 Logo 显示高度。

代码中使用了:

keep_proportion=True

因此图片会尽量保持原始比例,不会被强制拉伸变形。

例如,想让 Logo 更靠近左上角,可以改为:

left_mm = 5
top_mm = 5

想让 Logo 更小,可以改为:

width_mm = 22
height_mm = 10

八、关于 overlay 参数

代码中还有一个参数:

overlay = True

它表示 Logo 是否覆盖在原 PDF 内容上方。

当设置为:

overlay = True

Logo 会显示在最上层,适合添加印章、Logo、水印等。

当设置为:

overlay = False

Logo 会被添加到底层。如果 PDF 原本的页面内容覆盖了这个区域,Logo 可能会被遮住。

一般情况下,添加 Logo 推荐使用:

overlay = True

九、这个方案适合哪些场景

这个脚本适合以下场景:

  • 给 Typora 导出的 PDF 添加 Logo;
  • 给报告、说明书、合同、论文草稿添加机构标识;
  • 给 PDF 首页添加项目封面标识;
  • 给所有页面统一添加公司 Logo;
  • 批量处理 PDF 前的基础脚本改造;
  • 替代不稳定的 HTML / CSS PDF 导出方案。

相比在 Typora 里强行插入 HTML,这种方式更接近“PDF 后期盖章”。它不依赖 Markdown 编辑器,也不依赖主题样式。只要 PDF 已经生成,就可以直接处理。

十、总结

这次问题的解决过程也说明了一个很实用的思路:当 Markdown 编辑器或导出工具无法稳定控制 PDF 细节时,不一定要继续在导出配置里反复调试。对于 Logo、印章、水印这类固定页面元素,直接对 PDF 文件进行后处理,往往更加稳定。

Typora 的自定义 HTML 功能适合做一些简单的导出增强,比如添加封面、自定义说明文字或调整部分样式。但对于“精确控制 PDF 页面坐标”的需求,Python 的 PDF 处理库会更合适。

最终,这个脚本实现了三个核心目标:

  1. 可以精确控制 Logo 的位置和大小;
  2. 可以选择只添加第一页或添加全部页面;
  3. 不受 Typora 主题、页眉页脚和正文样式影响。

对于日常写作、文档发布和报告生成来说,这个方案足够轻量,也足够稳定。

原文链接:https://www.lunalbl.com/post/python-wei-pdf-wen-jian-tian-jia-yin-zhang-logo

更多推荐