如何在 C++ 中加载文档
Aspose.PDF FOSS for C++ 通过 Document 类打开已有的 PDF —— 可以使用普通文件路径,或在源文件被加密时提供密码 —— 并通过 DocumentInfo 暴露文件的 /Info 元数据。本文档介绍了加载文档、检测并打开受密码保护的文件、读取和更新标题/作者/生成器元数据,以及保存结果的过程。该库通过 CMake 添加到项目中;没有包管理器的安装步骤。
分步指南
步骤 1:安装包
将库添加为 CMake 子目录,并链接到 aspose_pdf_foss 目标:
add_subdirectory(aspose.pdf-foss-for-cpp)
target_link_libraries(your_app PRIVATE aspose_pdf_foss)通过编译最小程序验证工具链和头文件是否解析成功:
#include <aspose/pdf/document.hpp>
#include <iostream>
int main() {
Aspose::Pdf::Document doc;
std::cout << "Linked OK — pages: " << doc.Pages().Count() << "\n";
}成功构建并打印 pages: 0 表明库已正确链接。
第2步:导入所需的类
#include <aspose/pdf/document.hpp>
#include <aspose/pdf/document_info.hpp>
using namespace Aspose::Pdf;Document 和 DocumentInfo 都直接声明在 Aspose::Pdf 命名空间下。
第3步:从文件路径打开 PDF 文档
Document(filename) 构造函数读取文件,解析其交叉引用表和页面树,并构建内存模型。当文件不存在或无法读取时,它会抛出 std::system_error,当文件不是有效的 PDF 时,则抛出 std::runtime_error:
#include <iostream>
#include <stdexcept>
try {
Document doc("input.pdf");
std::cout << "Pages: " << doc.Pages().Count() << "\n";
} catch (const std::runtime_error& e) {
std::cerr << "Not a valid PDF: " << e.what() << "\n";
} catch (const std::system_error& e) {
std::cerr << "Could not read file: " << e.what() << "\n";
}第4步:打开受密码保护的文档
第3步中的单参数构造函数从不验证密码,也不检查 trailer 的 /Encrypt 条目,因此通过这种方式打开的文档上的 IsEncrypted() 总是报告 false —— 即使源文件实际上已加密。要检测并打开加密的 PDF,请使用双参数构造函数,它会读取 /Encrypt 并验证您提供的密码:
try {
Document doc("input.pdf", "user-password");
if (doc.IsEncrypted()) {
std::cout << "Opened an encrypted document.\n";
}
} catch (const std::runtime_error& e) {
// Thrown when /Encrypt is present and the password matches
// neither the user nor the owner entry in the trailer.
std::cerr << "Wrong password or unreadable /Encrypt dictionary: "
<< e.what() << "\n";
}如果源 PDF 未加密,密码参数将被简单地忽略,文档会正常打开 —— 因此即使您不确定文件是否受保护,也可以安全地使用双参数构造函数。
第5步:使用 DocumentInfo 读取文档元数据
Document.Info() 返回一个绑定到文档的 /Info 字典的 DocumentInfo&。每个预定义字段都有自己的读取访问器,当键在源文件中不存在时返回空字符串:
Document doc("input.pdf");
DocumentInfo& info = doc.Info();
std::cout << "Title: " << info.Title() << "\n";
std::cout << "Author: " << info.Author() << "\n";
std::cout << "Subject: " << info.Subject() << "\n";
std::cout << "Producer: " << info.Producer() << "\n";
std::cout << "Creator: " << info.Creator() << "\n";
std::cout << "Keywords: " << info.Keywords() << "\n";DocumentInfo::IsPredefinedKey 报告给定的 /Info 键名是否是这些内置字段之一,而不是通过 Add 添加的自定义键:
bool builtin = DocumentInfo::IsPredefinedKey("Title"); // true
bool custom = DocumentInfo::IsPredefinedKey("MyKey"); // false
步骤 6:更新元数据并保存文档
通过相应的 setter 重载或通过 Document 上的 SetTitle 便利方法设置元数据,然后使用 Save 持久化更改:
Document doc("input.pdf");
doc.Info().Author("Report Generator");
doc.SetTitle("Quarterly Report"); // equivalent to doc.Info().Title(title)
doc.Save("output.pdf");元数据编辑在内存中暂存,仅在下次调用 Save 时写出。调用 Save(outputFileName) 将写入新路径,或使用无参的 Save() 覆盖文档最初打开时的文件:
Document doc("input.pdf");
doc.SetTitle("Updated In Place");
doc.Save(); // writes back to "input.pdf"
常见问题及解决方案
IsEncrypted() returns false 对于我知道受密码保护的 PDF
单参数 Document(path) 构造函数从不针对文件的 /Encrypt 字典进行身份验证,因此使用这种方式打开的文档,IsEncrypted() 总是报告 false,无论源文件是否真的已加密。请改用双参数 Document(path, password) 构造函数打开文件——它是唯一会填充加密状态的重载。
两个参数的构造函数会抛出 std::runtime_error
这意味着源 PDF 在其 trailer 中有一个 /Encrypt 条目,而提供的密码既不匹配记录的用户密码,也不匹配所有者密码。捕获 std::runtime_error 并提示输入正确的密码,或在重新尝试前通过其他渠道确认密码。
Save() 抛出 “no source filename to save to”
无参数 Save() 会写回文档构建时的路径。使用无参数构造函数创建的 Document —— 一个没有源文件、仅在内存中的新文档 —— 没有可写回的路径,因此会抛出 Save()。请改为调用 Save(outputFileName)。
Decrypt() 似乎没有生成未受保护的文件
Decrypt() 会清除内存中的 IsEncrypted() 标志,使后续读取将文档视为明文,但在当前版本中它并不会重写 trailer 的 /Encrypt 字典或重新编码加密的字符串和流。在 Decrypt() 之后调用 Save() 仍会写回原始字节,包括原始的加密——此方法尚未在磁盘上生成明文副本。
Title/Author/etc. 显示为空字符串
DocumentInfo 访问器在源 PDF 中缺少相应的 /Info 键时返回空字符串,而不是错误。空结果表示该字段从未在该文档上设置过,而不是读取失败。
常见问题
Document(path) 能在加密的 PDF 上工作吗?
它通常会解析页面树而不会抛出异常,但从不对密码进行认证,并且总是将 IsEncrypted() 保持在 false。当源文件可能被加密时,请使用带两个参数的构造函数。
如果文件实际上未加密,向 Document(path, password) 传递密码安全吗?
是的。当 trailer 中没有 /Encrypt 条目时,密码参数会被忽略,文档会以单参数构造函数相同的方式打开。
如何辨别自定义的 /Info 键与内置键?
调用静态的 DocumentInfo::IsPredefinedKey(key)。它对 Title、Author、Subject、Keywords、Creator、Producer 和 Trapped 返回 true,对其他所有情况返回 false。
我能覆盖刚刚加载的文件,而不是写入新路径吗?
可以——在使用文件路径构造的 Document 上调用无参的 Save();它会写回同一源路径。对于使用无参构造函数创建且没有源路径的文档,会抛出异常。
在执行 Decrypt() 后保存,是否会移除 PDF 的密码保护?
不,在当前版本中没有——请参阅上面的常见问题。Decrypt() 只影响内容读取器使用的内存状态;由 Save() 写入的文件仍然保留原始的 /Encrypt 字典。