如何用PDFMM(C++)提取PDF中的几何对象(线条等)信息?
PDF线条信息提取方案(基于PDFMM/PoDoFo)
一、核心思路
PDF的绘图指令(直线、线宽、颜色等)都存储在页面的**内容流(Content Stream)**中,解析时需要:
- 跟踪图形状态(如当前线宽、描边颜色),这些属性由指令设置并继承给后续绘图操作
- 使用内容流解析器而非底层Tokenizer,降低实现复杂度
二、修正代码:获取InputStreamDevice并解析内容流
你之前的错误在于新建页面而非加载现有页面,以下是修正后的Tokenizer用法示例:
#include <pdfmm/pdfmm.h> #include <cstring> int main() { try { PdfMemDocument document; document.Load("test.pdf"); // 遍历所有现有页面 for (auto& page : document.GetPages()) { auto contentStream = page.GetContents(); if (!contentStream) continue; // 获取内容流的InputStreamDevice PdfInputStreamDevice stream; contentStream->GetInputStream(stream); PdfTokenizer token(true); char* stoken = nullptr; PdfVariant var; PdfContentType type; // 初始化图形状态变量 double lineWidth = 1.0; PdfColor strokeColor(0.0, 0.0, 0.0); PdfPoint currentPos; bool hasStartPos = false; // 收集操作数,再处理指令关键字 std::vector<PdfVariant> operands; while (token.TryReadNextToken(stream, stoken, type)) { if (type == PdfContentType::Variant) { operands.push_back(var); } else if (type == PdfContentType::Keyword) { std::string cmd = stoken; // 处理线宽指令 'w' if (cmd == "w" && !operands.empty()) { lineWidth = operands.back().GetReal(); } // 处理描边RGB颜色指令 'RG' else if (cmd == "RG" && operands.size() >=3) { double r = operands[operands.size()-3].GetReal(); double g = operands[operands.size()-2].GetReal(); double b = operands[operands.size()-1].GetReal(); strokeColor = PdfColor(r, g, b); } // 处理移动到起点指令 'm' else if (cmd == "m" && operands.size() >=2) { double x = operands[operands.size()-2].GetReal(); double y = operands[operands.size()-1].GetReal(); currentPos = PdfPoint(x, y); hasStartPos = true; } // 处理直线到终点指令 'l' else if (cmd == "l" && hasStartPos && operands.size() >=2) { double endX = operands[operands.size()-2].GetReal(); double endY = operands[operands.size()-1].GetReal(); // 输出直线信息 printf("Line: start(%.2f, %.2f), end(%.2f, %.2f), width: %.2f, color(R:%.2f, G:%.2f, B:%.2f)\n", currentPos.GetX(), currentPos.GetY(), endX, endY, lineWidth, strokeColor.GetRed(), strokeColor.GetGreen(), strokeColor.GetBlue()); // 更新当前位置为终点 currentPos = PdfPoint(endX, endY); } // 清空操作数,准备下一组指令 operands.clear(); } } } } catch (PdfError& err) { err.PrintErrorMsg(); return (int)err.GetError(); } return 0; }
三、更高效的解析方式:使用PdfContentsParser
直接用Tokenizer太底层,推荐使用PoDoFo提供的PdfContentsParser,它会自动拆分操作数和指令:
#include <pdfmm/pdfmm.h> #include <iostream> class LineExtractHandler : public PdfContentsParser::IHandler { private: double m_lineWidth = 1.0; PdfColor m_strokeColor = PdfColor(0.0, 0.0, 0.0); PdfPoint m_currentPos; bool m_hasStart = false; public: void OnCommand(const char* pszToken, const PdfVariantList& rOperands) override { std::string cmd = pszToken; if (cmd == "w" && !rOperands.empty()) { m_lineWidth = rOperands.front().GetReal(); } else if (cmd == "RG" && rOperands.size() >=3) { double r = rOperands[0].GetReal(); double g = rOperands[1].GetReal(); double b = rOperands[2].GetReal(); m_strokeColor = PdfColor(r, g, b); } else if (cmd == "m" && rOperands.size() >=2) { double x = rOperands[0].GetReal(); double y = rOperands[1].GetReal(); m_currentPos = PdfPoint(x, y); m_hasStart = true; } else if (cmd == "l" && m_hasStart && rOperands.size() >=2) { double endX = rOperands[0].GetReal(); double endY = rOperands[1].GetReal(); std::cout << "Line Info:\n"; std::cout << " Start: (" << m_currentPos.GetX() << ", " << m_currentPos.GetY() << ")\n"; std::cout << " End: (" << endX << ", " << endY << ")\n"; std::cout << " Width: " << m_lineWidth << "\n"; std::cout << " Color: R=" << m_strokeColor.GetRed() << ", G=" << m_strokeColor.GetGreen() << ", B=" << m_strokeColor.GetBlue() << "\n\n"; m_currentPos = PdfPoint(endX, endY); } } }; int main() { try { PdfMemDocument document; document.Load("test.pdf"); for (auto& page : document.GetPages()) { auto contentStream = page.GetContents(); if (!contentStream) continue; LineExtractHandler handler; PdfContentsParser parser; parser.Parse(contentStream, &handler); } } catch (PdfError& err) { err.PrintErrorMsg(); return (int)err.GetError(); } return 0; }
四、优质学习资源
- PDF 1.7规范:Adobe官方发布的PDF标准文档,重点研读"Graphics"章节,掌握绘图指令和图形状态的定义。
- PoDoFo官方示例:PoDoFo源码包中的
podofopdfinfo、podofocountpages等工具,展示了PDF页面操作、内容流解析的最佳实践。 - PoDoFo API文档:PDFMM基于PoDoFo开发,直接参考PoDoFo的API文档,可快速了解各类PDF对象的用法。
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

