PDF(Portable Document Format)是由Adobe公司于1993年创建的文件格式,现为ISO 32000国际标准。
PDF文件在任何操作系统和设备上都能保持一致的显示效果,确保文档的可靠性和专业性。
支持密码保护、数字签名和权限控制,保护敏感文档不被未授权访问或修改。
提供精确的排版控制,包括字体、颜色、图像位置和页面布局,适合专业出版。
PDF 2.0(ISO 32000-2:2017)引入了重大改进,最新的PDF技术继续演进,包括:
%PDF-2.0
对象集合
对象位置索引
%%EOF
10 0 obj
<<
/Type /Catalog
/Pages 20 0 obj
/ViewerPreferences <<
/DisplayDocTitle true
>>
/OpenAction [30 0 R /Fit]
>>
endobj
PDF文件采用线性结构存储,包含四个主要部分:
位于文件开头,用于标识PDF文件版本。标准格式:
%PDF-2.0
%âãÏÓ
其中:
包含所有PDF对象,每个对象都有唯一的对象编号和生成号:
1 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 3 0 R
/Resources 4 0 R
>>
endobj
记录所有对象的字节偏移量,实现快速随机访问:
xref
0 5
0000000000 65535 f
0000000009 00000 n
0000000074 00000 n
0000000179 00000 n
0000000306 00000 n
trailer
包含定位交叉引用表和根对象的信息:
<<
/Size 5
/Root 1 0 R
/Info 2 0 R
/ID [<8f8a4d...> <8f8a4d...>]
>>
startxref
383
%%EOF
PDF使用间接引用实现对象间的链接:
/Parent 2 0 R % 引用对象编号2
/Pages [3 0 R 4 0 R] % 引用对象数组
PDF支持增量更新,修改文档时只需追加内容:
通过分析一个真实的PDF文件,深入理解PDF格式的工作原理
以下是一个包含基本内容的最小化PDF文件,包含1页文本内容:
%PDF-1.4
%âãÏÓ
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 12 Tf
100 700 Td
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000214 00000 n
0000000309 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
0000000382
%%EOF
%PDF-1.4
%âãÏÓ
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
/Resources <<
/Font <<
/F1 5 0 R
>>
>>
>>
endobj
4 0 obj
<<
/Length 44
>>
stream
BT
/F1 12 Tf
100 700 Td
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000214 00000 n
0000000309 00000 n
trailer
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
0000000382
%%EOF
这个最小的PDF文件展示了PDF格式的核心结构:
实际生产环境的PDF文件会包含更多对象:嵌入字体、图像、元数据、书签、注释等,但基本结构完全相同。
要查看真实PDF文件的内部结构,可以使用以下方法:
基于机器学习的智能压缩算法,可自动识别文档内容类型并应用最优压缩策略,平均文件大小减少40%。
原生支持多人实时协作编辑,内置版本控制和冲突解决机制,类似于Google Docs的协作体验。
增强的3D模型支持,包括WebGL渲染、光照效果、纹理映射和交互式动画。
支持语音录制作为注释,自动转换为文本索引,方便搜索和定位。
将数字签名哈希值存储在区块链上,提供不可篡改的文档验证服务。
基于CSS Grid和Flexbox的现代布局引擎,实现响应式PDF显示,完美适配各种屏幕尺寸。
PDF/A-4(2024)及更新包含:
支持AES-256、ChaCha20-Poly1305等现代加密算法,保护文档内容不被未授权访问。
基于X.509证书的数字签名,验证文档来源和完整性,支持时间戳服务。
细粒度的权限管理,控制打印、复制、编辑、注释等操作。
记录文档访问和修改日志,支持安全事件追踪和合规性审计。
利用大语言模型(LLM)和计算机视觉技术,自动提取、分类和理解PDF内容。包括:
AI驱动的自动化文档处理流程:
AI赋能的PDF数据处理能力:
最新的PDF处理采用云原生设计:
import pypdf
from transformers import pipeline
import pdf2image
import pytesseract
# 使用AI提取PDF内容
def extract_pdf_with_ai(pdf_path):
# 1. 提取文本
reader = pypdf.PdfReader(pdf_path)
text = ""
for page in reader.pages:
text += page.extract_text()
# 2. 使用LLM进行摘要
summarizer = pipeline("summarization")
summary = summarizer(text, max_length=150)
# 3. OCR处理扫描页面
images = pdf2image.convert_from_path(pdf_path)
ocr_text = pytesseract.image_to_string(images[0])
return {
"text": text,
"summary": summary[0]['summary_text'],
"ocr_text": ocr_text
}
# 使用示例
result = extract_pdf_with_ai("document.pdf")
print(result["summary"])
使用不同编程语言解析和操作PDF文件
from pypdf import PdfReader
def extract_text_from_pdf(pdf_path):
"""
从PDF中提取所有文本内容
"""
reader = PdfReader(pdf_path)
full_text = ""
for page in reader.pages:
page_text = page.extract_text()
full_text += f"--- Page {page.page_number + 1} ---\n"
full_text += page_text + "\n\n"
return full_text
# 使用示例
text = extract_text_from_pdf("document.pdf")
print(text)
from pypdf import PdfReader
def get_pdf_metadata(pdf_path):
"""
获取PDF文档的元数据信息
"""
reader = PdfReader(pdf_path)
metadata = reader.metadata
print(f"标题: {metadata.get('/Title', 'N/A')}")
print(f"作者: {metadata.get('/Author', 'N/A')}")
print(f"创建者: {metadata.get('/Creator', 'N/A')}")
print(f"创建日期: {metadata.get('/CreationDate', 'N/A')}")
print(f"修改日期: {metadata.get('/ModDate', 'N/A')}")
print(f"主题: {metadata.get('/Subject', 'N/A')}")
print(f"关键词: {metadata.get('/Keywords', 'N/A')}")
return metadata
# 使用示例
metadata = get_pdf_metadata("document.pdf")
from pypdf import PdfReader, PdfWriter
def split_pdf_by_pages(pdf_path, output_prefix):
"""
将PDF的每一页保存为单独的文件
"""
reader = PdfReader(pdf_path)
for page_num in range(len(reader.pages)):
writer = PdfWriter()
writer.add_page(reader.pages[page_num])
output_filename = f"{output_prefix}_page_{page_num + 1}.pdf"
with open(output_filename, "wb") as output_file:
writer.write(output_file)
print(f"已保存: {output_filename}")
# 使用示例
split_pdf_by_pages("document.pdf", "output")
from pypdf import PdfMerger
def merge_pdfs(input_files, output_file):
"""
合并多个PDF文件为一个
"""
merger = PdfMerger()
for pdf_file in input_files:
merger.append(pdf_file)
print(f"已添加: {pdf_file}")
merger.write(output_file)
merger.close()
print(f"合并完成,输出文件: {output_file}")
# 使用示例
input_pdfs = ["file1.pdf", "file2.pdf", "file3.pdf"]
merge_pdfs(input_pdfs, "merged_output.pdf")
from pypdf import PdfReader
from PIL import Image
import io
def extract_images_from_pdf(pdf_path, output_dir="extracted_images"):
"""
从PDF中提取所有图像
"""
import os
os.makedirs(output_dir, exist_ok=True)
reader = PdfReader(pdf_path)
image_count = 0
for page_num, page in enumerate(reader.pages):
if '/Resources' in page and '/XObject' in page['/Resources']:
xObject = page['/Resources']['/XObject'].get_object()
for obj in xObject:
if xObject[obj]['/Subtype'] == '/Image':
image = xObject[obj]._data
try:
# 尝试使用PIL打开图像
pil_image = Image.open(io.BytesIO(image))
image_format = pil_image.format or 'png'
output_path = f"{output_dir}/page_{page_num + 1}_image_{image_count}.{image_format.lower()}"
pil_image.save(output_path)
print(f"已保存图像: {output_path}")
image_count += 1
except Exception as e:
print(f"无法保存图像: {e}")
print(f"总共提取了 {image_count} 张图像")
return image_count
# 使用示例
extract_images_from_pdf("document.pdf", "images")
from pypdf import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import io
def add_text_to_pdf(input_pdf, output_pdf, text, x, y, font_size=12):
"""
在PDF指定位置添加文本
"""
# 读取原始PDF
reader = PdfReader(input_pdf)
writer = PdfWriter()
# 复制所有页面
for page in reader.pages:
writer.add_page(page)
# 创建新的PDF页面用于添加文本
packet = io.BytesIO()
c = canvas.Canvas(packet, pagesize=letter)
c.setFont("Helvetica", font_size)
c.drawString(x, y, text)
c.save()
# 将新页面合并到原始PDF
overlay = PdfReader(packet).pages[0]
writer.pages[0].merge_page(overlay)
# 保存结果
with open(output_pdf, "wb") as output_file:
writer.write(output_file)
print(f"已添加文本,输出文件: {output_pdf}")
# 使用示例
add_text_to_pdf("document.pdf", "annotated.pdf", "已审核", 450, 750, 14)
# 基础PDF解析库
go get github.com/unidoc/unipdf/v3
# 文本提取
go get github.com/ledongthuc/pdf
# PDF生成
go get github.com/jung-kurt/gofpdf
package main
import (
"fmt"
"log"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/extractor"
"github.com/unidoc/unipdf/v3/model"
)
func init() {
// 设置UniDoc许可证(社区版免费但有限制)
// 获取免费许可证: https://unidoc.io
err := license.SetMeteredKey("your-license-key")
if err != nil {
log.Fatal("Failed to set license: ", err)
}
}
func extractText(pdfPath string) error {
// 打开PDF文件
f, err := model.NewPdfReaderFromFile(pdfPath, nil)
if err != nil {
return fmt.Errorf("无法打开PDF: %v", err)
}
// 获取页数
numPages, err := f.GetNumPages()
if err != nil {
return fmt.Errorf("获取页数失败: %v", err)
}
fmt.Printf("PDF共 %d 页\n", numPages)
// 逐页提取文本
for i := 1; i <= numPages; i++ {
page, err := f.GetPage(i)
if err != nil {
return fmt.Errorf("获取第%d页失败: %v", i, err)
}
ex, err := extractor.New(page)
if err != nil {
return fmt.Errorf("创建提取器失败: %v", err)
}
text, err := ex.ExtractText()
if err != nil {
return fmt.Errorf("提取文本失败: %v", err)
}
fmt.Printf("\n--- 第 %d 页 ---\n", i)
fmt.Println(text)
}
return nil
}
func main() {
err := extractText("document.pdf")
if err != nil {
log.Fatal(err)
}
}
package main
import (
"fmt"
"log"
"time"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/model"
)
func getMetadata(pdfPath string) error {
// 打开PDF文件
f, err := model.NewPdfReaderFromFile(pdfPath, nil)
if err != nil {
return fmt.Errorf("无法打开PDF: %v", err)
}
// 获取PDF信息
pdfInfo, err := f.GetPdfInfo()
if err != nil {
return fmt.Errorf("获取PDF信息失败: %v", err)
}
// 获取元数据
metadata := pdfInfo.PdfInfo
fmt.Println("=== PDF元数据 ===")
if metadata.Title != "" {
fmt.Printf("标题: %s\n", metadata.Title)
}
if metadata.Author != "" {
fmt.Printf("作者: %s\n", metadata.Author)
}
if metadata.Subject != "" {
fmt.Printf("主题: %s\n", metadata.Subject)
}
if metadata.Keywords != "" {
fmt.Printf("关键词: %s\n", metadata.Keywords)
}
if metadata.Creator != "" {
fmt.Printf("创建者: %s\n", metadata.Creator)
}
if metadata.Producer != "" {
fmt.Printf("生产者: %s\n", metadata.Producer)
}
// 解析日期
if metadata.CreationDate != nil {
creationTime := metadata.CreationDate.Time
fmt.Printf("创建日期: %s\n", creationTime.Format(time.RFC3339))
}
if metadata.ModDate != nil {
modTime := metadata.ModDate.Time
fmt.Printf("修改日期: %s\n", modTime.Format(time.RFC3339))
}
return nil
}
func main() {
err := getMetadata("document.pdf")
if err != nil {
log.Fatal(err)
}
}
package main
import (
"fmt"
"log"
"os"
"strconv"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/creator"
"github.com/unidoc/unipdf/v3/model"
)
func splitPDF(inputPath, outputPrefix string) error {
// 打开PDF文件
f, err := model.NewPdfReaderFromFile(inputPath, nil)
if err != nil {
return fmt.Errorf("无法打开PDF: %v", err)
}
numPages, err := f.GetNumPages()
if err != nil {
return fmt.Errorf("获取页数失败: %v", err)
}
// 逐页分割
for i := 1; i <= numPages; i++ {
// 创建新的PDF
c := creator.New()
// 获取页面
page, err := f.GetPage(i)
if err != nil {
return fmt.Errorf("获取第%d页失败: %v", i, err)
}
// 添加页面到新PDF
err = c.AddPage(page)
if err != nil {
return fmt.Errorf("添加页面失败: %v", err)
}
// 生成输出文件名
outputPath := outputPrefix + "_page_" + strconv.Itoa(i) + ".pdf"
// 保存文件
err = c.WriteToFile(outputPath)
if err != nil {
return fmt.Errorf("保存文件失败: %v", err)
}
fmt.Printf("已保存: %s\n", outputPath)
}
return nil
}
func main() {
err := splitPDF("document.pdf", "output")
if err != nil {
log.Fatal(err)
}
}
package main
import (
"fmt"
"log"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/model"
)
func mergePDFs(inputPaths []string, outputPath string) error {
// 创建新的PDF写入器
writer := model.NewPdfWriter()
// 遍历所有输入PDF
for _, inputPath := range inputPaths {
// 打开PDF文件
reader, err := model.NewPdfReaderFromFile(inputPath, nil)
if err != nil {
return fmt.Errorf("无法打开PDF %s: %v", inputPath, err)
}
// 获取页数
numPages, err := reader.GetNumPages()
if err != nil {
return fmt.Errorf("获取页数失败: %v", err)
}
// 将所有页面添加到写入器
for i := 1; i <= numPages; i++ {
page, err := reader.GetPage(i)
if err != nil {
return fmt.Errorf("获取第%d页失败: %v", i, err)
}
err = writer.AddPage(page)
if err != nil {
return fmt.Errorf("添加页面失败: %v", err)
}
}
fmt.Printf("已添加: %s (%d页)\n", inputPath, numPages)
}
// 写入合并后的PDF
f, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("创建输出文件失败: %v", err)
}
defer f.Close()
err = writer.Write(f)
if err != nil {
return fmt.Errorf("写入PDF失败: %v", err)
}
fmt.Printf("合并完成,输出文件: %s\n", outputPath)
return nil
}
func main() {
inputPDFs := []string{"file1.pdf", "file2.pdf", "file3.pdf"}
err := mergePDFs(inputPDFs, "merged_output.pdf")
if err != nil {
log.Fatal(err)
}
}
package main
import (
"log"
"github.com/jung-kurt/gofpdf"
)
func createPDF(outputPath string) error {
// 创建新的PDF
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
// 设置字体
pdf.SetFont("Arial", "B", 16)
// 添加标题
pdf.Cell(40, 10, "Hello, PDF!")
pdf.Ln(12)
// 添加普通文本
pdf.SetFont("Arial", "", 12)
pdf.MultiCell(0, 10, "这是一个使用Golang创建的PDF文件。\n"+
"你可以添加文本、图像、表格等各种内容。",
"", "", false)
pdf.Ln(10)
// 添加列表
pdf.SetFont("Arial", "B", 12)
pdf.Cell(0, 10, "功能列表:")
pdf.Ln(6)
pdf.SetFont("Arial", "", 12)
items := []string{
"文本绘制",
"图像插入",
"表格创建",
"多页支持",
"字体自定义",
}
for _, item := range items {
pdf.Cell(10, 6, "")
pdf.Cell(0, 6, item)
pdf.Ln(6)
}
// 保存文件
err := pdf.OutputFileAndClose(outputPath)
if err != nil {
return err
}
log.Printf("PDF创建成功: %s\n", outputPath)
return nil
}
func main() {
err := createPDF("created.pdf")
if err != nil {
log.Fatal(err)
}
}
package main
import (
"fmt"
"log"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/extractor"
"github.com/unidoc/unipdf/v3/model"
)
type TableData struct {
Rows [][]string
}
func extractTable(pdfPath, pageNum int) (*TableData, error) {
// 打开PDF文件
f, err := model.NewPdfReaderFromFile(pdfPath, nil)
if err != nil {
return nil, fmt.Errorf("无法打开PDF: %v", err)
}
// 获取指定页面
page, err := f.GetPage(pageNum)
if err != nil {
return nil, fmt.Errorf("获取第%d页失败: %v", pageNum, err)
}
// 创建文本提取器
ex, err := extractor.New(page)
if err != nil {
return nil, fmt.Errorf("创建提取器失败: %v", err)
}
// 提取页面布局
pageLayout, err := ex.ExtractPageLayout()
if err != nil {
return nil, fmt.Errorf("提取页面布局失败: %v", err)
}
// 分析文本位置,识别表格
table := &TableData{
Rows: make([][]string, 0),
}
// 遍历文本块,按行分组
currentRow := make([]string, 0)
lastY := 0.0
for _, block := range pageLayout.Text {
// 简单的行分组逻辑(实际应用中需要更复杂的算法)
if lastY != 0 && block.Position.Y != lastY {
table.Rows = append(table.Rows, currentRow)
currentRow = make([]string, 0)
}
currentRow = append(currentRow, block.Text)
lastY = block.Position.Y
}
if len(currentRow) > 0 {
table.Rows = append(table.Rows, currentRow)
}
return table, nil
}
func main() {
table, err := extractTable("document.pdf", 1)
if err != nil {
log.Fatal(err)
}
fmt.Println("提取的表格数据:")
for i, row := range table.Rows {
fmt.Printf("第%d行: %v\n", i+1, row)
}
}
注意: UniDoc商业版需要付费,但提供了社区版免费许可证(有限制)。
| 需求 | Python推荐 | Golang推荐 |
|---|---|---|
| 简单文本提取 | pypdf | ledongthuc/pdf |
| 完整读写操作 | pypdf, PyMuPDF | unipdf |
| 表格提取 | pdfplumber, camelot | unipdf (需自定义) |
| 从零创建PDF | reportlab | gofpdf, unipdf |
| PDF渲染到图像 | pdf2image, PyMuPDF | unipdf, skia-pdf |