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

如何用Python移除PDF边距并提取线条图为SVG文件

提取PDF线条图并裁剪SVG多余边距的实现方案

你可以通过两种方式解决这个问题:要么在导出SVG前先裁剪PDF页面到图形实际区域,要么修改已生成的SVG内容来去除边距。以下是具体实现:

方法一:导出前裁剪PDF页面(推荐)

这种方法直接利用PyMuPDF的页面裁剪功能,先定位页面中线条图的实际边界,再导出仅包含该区域的SVG,步骤更简洁。

import fitz  # PyMuPDF

def extract_single_cropped_svg(pdf_path, output_svg_path, page_idx=0, scale=2):
    doc = fitz.open(pdf_path)
    page = doc[page_idx]
    
    # 收集页面中所有绘图元素的边界框
    all_bboxes = []
    # 获取页面中的矢量绘图(线条图通常属于这类)
    for drawing in page.get_drawings():
        all_bboxes.append(drawing["rect"])
    # 如果有嵌入图像也一并收集(可选)
    for img_info in page.get_images(full=True):
        img_rect = page.get_image_rects(img_info[0])[0]
        all_bboxes.append(img_rect)
    
    if not all_bboxes:
        print(f"第{page_idx+1}页未检测到图形元素")
        doc.close()
        return
    
    # 计算所有图形的最小包围盒
    min_x = min(bbox.x0 for bbox in all_bboxes)
    min_y = min(bbox.y0 for bbox in all_bboxes)
    max_x = max(bbox.x1 for bbox in all_bboxes)
    max_y = max(bbox.y1 for bbox in all_bboxes)
    crop_rect = fitz.Rect(min_x, min_y, max_x, max_y)
    
    # 设置页面裁剪区域
    page.set_cropbox(crop_rect)
    
    # 导出裁剪后的SVG,scale参数控制清晰度
    svg_content = page.get_svg_image(matrix=fitz.Matrix(scale, scale))
    
    # 保存到指定路径
    with open(output_svg_path, "w", encoding="utf-8") as f:
        f.write(svg_content)
    
    doc.close()

# 使用示例
extract_single_cropped_svg("input.pdf", "output_cropped.svg", page_idx=0, scale=2)

代码说明

  1. 先收集页面中所有矢量绘图和图像的边界框,确保覆盖所有线条图区域
  2. 合并这些边界框得到最小的包围矩形,这就是线条图的实际范围
  3. 设置页面的cropbox为这个矩形,导出SVG时就只会包含该区域
  4. scale参数可以调整SVG的分辨率,数值越大,图形越清晰

如果需要批量提取所有页面的线条图,只需在外层加一个循环遍历doc的所有页面即可。

方法二:修改已生成的SVG内容

如果已经导出了整页SVG,也可以通过解析SVG的XML结构,调整viewBox和尺寸属性来裁剪多余边距:

import fitz
import xml.etree.ElementTree as ET

def crop_existing_svg(svg_content):
    root = ET.fromstring(svg_content)
    # 获取原始视图框参数
    orig_viewbox = root.get("viewBox").split()
    _, _, orig_width, orig_height = map(float, orig_viewbox)
    
    # 遍历所有SVG元素,计算实际内容的边界
    min_x, min_y = float('inf'), float('inf')
    max_x, max_y = -float('inf'), -float('inf')
    
    for elem in root.iter():
        # 处理路径元素
        if elem.tag.endswith("path"):
            d_attr = elem.get("d", "")
            tokens = d_attr.split()
            i = 0
            while i < len(tokens):
                if tokens[i] not in ['Z']:
                    try:
                        x = float(tokens[i]) if tokens[i][0] not in ['M','L','C','Q'] else float(tokens[i+1])
                        y = float(tokens[i+1]) if tokens[i][0] not in ['M','L','C','Q'] else float(tokens[i+2])
                        min_x = min(min_x, x)
                        min_y = min(min_y, y)
                        max_x = max(max_x, x)
                        max_y = max(max_y, y)
                        i += 2 if tokens[i][0] in ['M','L','C','Q'] else 2
                    except:
                        i += 1
                else:
                    i += 1
        # 处理矩形、圆形、直线等基础元素
        elif elem.tag.endswith("rect"):
            x = float(elem.get("x", 0))
            y = float(elem.get("y", 0))
            w = float(elem.get("width", 0))
            h = float(elem.get("height", 0))
            min_x = min(min_x, x)
            min_y = min(min_y, y)
            max_x = max(max_x, x+w)
            max_y = max(max_y, y+h)
        elif elem.tag.endswith("circle"):
            cx = float(elem.get("cx", 0))
            cy = float(elem.get("cy", 0))
            r = float(elem.get("r", 0))
            min_x = min(min_x, cx-r)
            min_y = min(min_y, cy-r)
            max_x = max(max_x, cx+r)
            max_y = max(max_y, cy+r)
        elif elem.tag.endswith("line"):
            x1 = float(elem.get("x1", 0))
            y1 = float(elem.get("y1", 0))
            x2 = float(elem.get("x2", 0))
            y2 = float(elem.get("y2", 0))
            min_x = min(min_x, x1, x2)
            min_y = min(min_y, y1, y2)
            max_x = max(max_x, x1, x2)
            max_y = max(max_y, y1, y2)
    
    # 更新SVG的视图框和尺寸
    new_width = max_x - min_x
    new_height = max_y - min_y
    root.set("viewBox", f"{min_x} {min_y} {new_width} {new_height}")
    root.set("width", f"{new_width}")
    root.set("height", f"{new_height}")
    
    return ET.tostring(root, encoding="utf-8").decode("utf-8")

# 使用示例
doc = fitz.open("input.pdf")
page = doc[0]
full_svg = page.get_svg_image(matrix=fitz.Matrix(2,2))
cropped_svg = crop_existing_svg(full_svg)
with open("output_cropped.svg", "w", encoding="utf-8") as f:
    f.write(cropped_svg)
doc.close()

注意事项

  • 如果页面包含多个独立线条图,方法一需要额外处理图形分组(比如根据bbox的距离判断是否为同一图形),才能分别导出单独的SVG
  • 确保使用最新版本的PyMuPDF,避免API兼容性问题
  • 调整scale参数时,过大的数值会导致SVG文件体积增大,按需选择即可

内容的提问来源于stack exchange,提问作者ankit maltare

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 22:07:10