如何用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)
代码说明
- 先收集页面中所有矢量绘图和图像的边界框,确保覆盖所有线条图区域
- 合并这些边界框得到最小的包围矩形,这就是线条图的实际范围
- 设置页面的
cropbox为这个矩形,导出SVG时就只会包含该区域 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
相关产品推荐
相关产品推荐

