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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 12:45:46