Foxit PDF SDK 简介
您是否曾经想要构建一个可以对PDF文档进行任何操作的应用程序?如果您的答案是”Yes”, 那么恭喜您!您找到了业界中可以构建稳定、安全、高效且功能齐全的PDF应用的优选解决方案。
Foxit PDF SDK提供高性能的开发库,帮助软件开发人员使用最流行的开发语言和环境在不同平台 (包括Windows、Mac、Linux、Web、Android、iOS 和 UWP) 的企业版、移动版和云应用程序中添加强大的PDF功能。
为什么选择Foxit PDF SDK
Foxit 是领先的PDF软件解决方案供应商,专注于PDF显示、编辑、创建、管理以及安全方面。Foxit PDF SDK 开发库已在当今许多知名的应用程序中使用,并且经过长期的测试证明Foxit PDF SDK的质量、性能和功能正是业界大部分应用程序所需要的。选择Foxit PDF SDK 产品的几大理由:
- 易于集成
开发人员可以将SDK无缝集成到他们自己的应用程序中。
- 轻量级
部署简单快速,占用系统资源少。
- 支持跨平台
支持当前主流的平台,比如Windows、Mac、Linux、Web、Android、iOS和UWP。
- 基于福昕高保真的PDF渲染引擎
Foxit PDF SDK的核心技术是基于世界众多知名企业所信赖的福昕PDF引擎。福昕强大的PDF引擎可快速解析和渲染文档,不受设备环境的约束。
- 优秀的技术支持
福昕对自己的开发产品提供了优秀的技术支持,当您在开发关键重要的产品时,可以提供高效的帮助和支持。福昕拥有一支PDF行业优秀的技术支持工程师团队,同时将定期地进行版本更新发布,通过添加新的功能和增强已有的功能来提升用户体验。
Foxit PDF SDK for Mac Objective-C API
使用Foxit PDF SDK的应用程序开发人员可以利用Foxit强大、符合标准的PDF技术来安全地显示、创建、编辑、注释、格式化、组织、打印、共享、保护、搜索文档,以及填写PDF表单。此外,Foxit PDF SDK (C++和.NET) 包含一个内置可嵌入的PDF Viewer,使开发过程更容易和更快捷。有关更多详细信息,请访问网站https://developers.foxitsoftware.cn/pdf-sdk/。
在本手册中,我们专注于介绍Mac平台的Foxit PDF SDK for Objective-C API。
Foxit PDF SDK for Mac (Objective-C API) 提供简单易用的API,帮助Objective-C开发人员将强大的PDF技术无缝集成到他们自己的Mac平台项目中。并且提供了PDF文档相关的丰富功能,比如PDF浏览、书签导航、文本选择/复制/搜索、PDF签名、PDF表单、权限管理、PDF注释以及全文搜索等。
评估
用户可申请下载Foxit PDF SDK的试用版本进行试用评估。试用版除了有试用期10天时间的限制以及生成的PDF页面上会有试用水印以外,其他都和标准认证版一样。当试用期到期后,用户需联系福昕销售团队并购买licenses以便继续使用Foxit PDF SDK.
授权
程序开发人员需购买licenses授权才能在其解决方案中使用Foxit PDF SDK。Licenses授予用户发布基于Foxit PDF SDK开发的应用程序的权限。然而,在未经福昕软件公司授权下,用户不能将Foxit PDF SDK包中的任何文档、示例代码以及源代码分发给任何第三方机构。
关于此文档
此文档适用于需要使用Objective-C开发语言将Foxit PDF SDK集成到Mac平台应用程序中的开发人员。它旨在介绍SDK 包结构和SDK的用法。
入门指南
安装并集成Foxit PDF SDK非常简单。本手册将提供SDK包的简要介绍。本章的主要内容是介绍系统要求、SDK包结构、以及如何运行demo和创建自己的项目。
系统要求
Mac OS X 10.6 及以上
包结构说明
下载Foxit PDF SDK for Mac包,解压到一个新的目录如 “foxitpdfsdk_7_4_mac_oc”,如Figure 2-1所示。
备注:图片上面高亮的矩形区域指的是SDK的版本号,当前SDK的版本是7.4,则其代表7_4。
其中解压包中包括如下的内容:
doc: API 手册,开发者指南
examples: 示例工程和demos
include: Foxit PDF SDK API的头文件
lib: SDK库和授权文件
res: 输出预览 (output preview) demo使用的默认icc profile文件
Figure 2-1
运行demo
Foxit PDF SDK for Mac (Objective-C) 在 “\examples\simple_demo” 目录下提供了一些简单示例demo。除了security, signature, compliance, html2pdf 和output preview demo以外,其他demo都可以直接运行 “\examples\simple_demo” 下的”.sh” 文件。
在命令行终端运行demo,请按照如下的步骤操作:
1)打开一个命令行终端,导航到 “foxitpdfsdk_7_4_mac_oc\examples\simple_demo”;
2)运行 “.sh” 文件。选择以下方法中的一种:
- 运行 “./RunAllDemo.sh” 以逐个运行所有的demo。
- 如果您只需要运行某个特定的demo,请定位到该demo的目录,例如,定位到”\examples\simple_demo\annotation”,运行 “./RunDemo.sh”。
“\examples\simple_demo\input_files” 文件夹下包含了所有demo使用的输入文件。对于会生成输出文件 (pdf, 文本或者图片文件) 的demo,会在 “\examples\simple_demo\output_files\” 文件夹下生成以该demo名称命名的文件夹,并且输出文件将会在该文件夹下生成。
Security 和Signature demo
在运行security 和 signature demo之前,您需要确保已经安装了OpenSSL。从OpenSSL官网下载OpenSSL 源码包,或者您可以直接与我们客服联系。获取到源码包后,解压并进行如下操作:
- 将OpenSSL文件夹拷贝到 “include”文件夹下,以确保demo中引用的OpenSSL头文件可以被识别到。
- 将 “libssl.a” 和 “libcrypto.a” 库拷贝到 “lib” 文件夹下。
- 参考其他demo的运行步骤运行该demo。
备注:OpenSSL 1.0.2m 版本在security和 signature中已经验证是可用的。您可以替换为其他所需的版本,但可能需要做一些相应的更改。
Compliance demo
对于compliance demo,您需要首先构建一个资源目录,请联系Foxit支持团队或者销售团队获取相应的资源包。关于如何运行该demo的更详细的信息,请参考3.33小节 “Compliance“。
HTML to PDF demo
对于html2pdf demo,您需要首先联系Foxit支持团队或者销售团队获取HTML 转PDF的引擎包。关于如何运行该demo的更详细的信息,请参考3.35小节 “HTML转PDF“。
Output Preview demo
对于output preview demo,您需要设置包含默认icc profile文件的文件夹路径。关于如何运行该demo的更详细信息,请参考3.36小节 “输出预览 (Output Preview)“。
创建一个简单的工程
本节主要介绍如何使用Foxit PDF SDK for Mac (Objective-C) 创建一个简单的工程,该工程将PDF文档的首页渲染成bitmap,然后将其另存为JPG图片。请按照如下的步骤操作:
1)创建一个名为 “test_oc” 的文件夹。
2)将 “foxitpdfsdk_7_4_mac_oc” 文件夹下的 “include” 和 “lib” 文件夹拷贝到 “test_oc” 工程目录下。
3)在 “test_oc” 文件夹下创建一个名为 “test_oc.mm” 的类。
4)打开 “test_oc.mm” 文件,在test_oc.mm的开头添加 “include” 头文件声明。
#include "FSPDFObjC.h"
5)初始化Foxit PDF SDK 库。在调用任何APIs之前,应用程序必须使用license授权码初始化Foxit PDF SDK 库。试用license文件在 “lib” 文件夹下。
int main(int argc, const char * argv[]) { // The value of "sn" can be got from "gsdk_sn.txt"(the string after "SN="). // The value of "key" can be got from "gsdk_key.txt"(the string after "Sign="). NSString* sn = @" "; NSString* key = @" "; // Initialize library. FSErrorCode code = [FSLibrary initialize:sn key:key]; if (code != FSErrSuccess) return -1; }
备注:参数 “sn”的值在 “gsdk_sn.txt” 中 (“SN=”后面的字符串),“key” 的值在 “gsdk_key.txt“中 (“Sign=”后面的字符串)。
6)加载一个PDF文档,然后解析该文档的首页。假设您已经在 “test_oc” 文件夹下放入了一个 “Sample.pdf” 文件。
// Load a PDF document, and parse the first page of the document. NSString* pdfpath = [[NSBundle mainBundle] pathForResource:@"Sample" ofType:@"pdf"]; FSPDFDoc* doc = [[FSPDFDoc alloc] initWithPath:pdfpath]; FSErrorCode errorCode = [doc load:nil]; if (errorCode != FSErrSuccess) { return -1; } FSPDFPage* page = [doc getPage:0]; [page startParse:FSPDFPageParsePageNormal pause:nil is_reparse:NO];
7)将页面渲染成bitmap,然后将其另存为JPG图片。
int width = (int)[page getWidth]; int height = (int)[page getHeight]; FSMatrix2D* matrix = [page getDisplayMatrix:0 top:0 width:width height:height rotate:page.rotation]; // Prepare a bitmap for rendering. FSBitmap* bitmap = [[FSBitmap alloc] initWithWidth:width height:height format:FSBitmapDIBArgb buffer:nil pitch:0]; [bitmap fillRect:0xFFFFFFFF rect:nil]; // Render page. FSRenderer* render = [[FSRenderer alloc] initWithBitmap:bitmap is_rgb_order:NO]; [render startRender:page matrix:matrix pause:nil]; // Add the bitmap to image and save the image. FSImage* image = [FSImage new]; [image addFrame:bitmap]; [image saveAs:@"testpage.jpg"];
8)创建一个名为 “RunTest.sh” 的shell文件,包含libfsdk_oc_mac64.dylib。Shell文件示例如下所示:
#!/bin/bash export TEST_NAME=test_oc clang -framework Foundation -I include -L lib -lfsdk_oc_mac64 -Xlinker -rpath -Xlinker lib ${TEST_NAME}.mm -o ${TEST_NAME} ./${TEST_NAME}
9)运行 “RunTest.sh”。打开一个命令行终端,导航到 “test_oc” 目录,运行 “./RunTest.sh”,然后在 “test_oc” 文件夹下会生成 “testpage.jpg”,如下图所示。
“test_oc.mm” 的完整内容如下:
#include "FSPDFObjC.h" int main(int argc, const char * argv[]) { // The value of "sn" can be got from "gsdk_sn.txt"(the string after "SN="). // The value of "key" can be got from "gsdk_key.txt"(the string after "Sign="). NSString* sn = @" "; NSString* key = @" "; // Initialize library. FSErrorCode code = [FSLibrary initialize:sn key:key]; if (code != FSErrSuccess) { return -1; } // Load a PDF document, and parse the first page of the document. NSString* pdfpath = [[NSBundle mainBundle] pathForResource:@"Sample" ofType:@"pdf"]; FSPDFDoc* doc = [[FSPDFDoc alloc] initWithPath:pdfpath]; FSErrorCode errorCode = [doc load:nil]; if (errorCode != FSErrSuccess) { return -1; } FSPDFPage* page = [doc getPage:0]; [page startParse:FSPDFPageParsePageNormal pause:nil is_reparse:NO]; int width = (int)[page getWidth]; int height = (int)[page getHeight]; FSMatrix2D* matrix = [page getDisplayMatrix:0 top:0 width:width height:height rotate:page.rotation]; // Prepare a bitmap for rendering. FSBitmap* bitmap = [[FSBitmap alloc] initWithWidth:width height:height format:FSBitmapDIBArgb buffer:nil pitch:0]; [bitmap fillRect:0xFFFFFFFF rect:nil]; // Render page. FSRenderer* render = [[FSRenderer alloc] initWithBitmap:bitmap is_rgb_order:NO]; [render startRender:page matrix:matrix pause:nil]; // Add the bitmap to image and save the image. FSImage* image = [FSImage new]; [image addFrame:bitmap]; [image saveAs:@"testpage.jpg"]; }
使用 SDK API
在本节中,我们将介绍Foxit PDF SDK的主要功能,并列举相关示例来展示如何使用Foxit PDF SDK Objective-C API将强大的PDF功能集成到您的应用程序中。您可以参阅API reference [2] 来获取示例中APIs更详细的使用说明。
初始化库
在调用任何API之前,都需要首先初始化Foxit PDF SDK。FSLibrary::initialize用来初始化Foxit PDF SDK,您需要购买正式的license来获取license key和序列号。当不再需要使用Foxit PDF SDK时,请调用FSLibrary::destroy将其释放。
备注:参数 “sn” 的值在 “gsdk_sn.txt” 中 (“SN=”后面的字符串),“key” 的值在 “gsdk_key.txt” 中 (“Sign=” 后面的字符串)。
Example:
如何初始化Foxit PDF SDK
#include "FSPDFObjC.h" ... NSString* sn = @" "; NSString* key = @" "; // Initialize library. FSErrorCode code = [FSLibrary initialize:sn key:key]; if (code != FSErrSuccess) { return -1; }
文档 (Document)
一个PDF document对象可以由一个已有的PDF文件从文件路径、内存缓冲区、自定义实现的FSFileReaderCallback对象、输入文件流中构建。然后调用FSPDFDoc::load或者FSPDFDoc::startLoad加载文档内容。PDF document对象用于文档级操作,比如打开和关闭PDF文档,获取页面、metadata等。
Example:
如何从0开始创建一个PDF文档
#include "FSPDFObjC.h" ... FSPDFDoc* doc = [[FSPDFDoc alloc] init]; Note: It creates a new PDF document without any pages.
如何通过文件路径加载一个现有的PDF文档
#include "FSPDFObjC.h" ... NSString* pdfpath = [[NSBundle mainBundle] pathForResource:@"Sample" ofType:@"pdf"]; FSPDFDoc* doc = [[FSPDFDoc alloc] initWithPath:pdfpath]; FSErrorCode errorCode = [doc load:nil]; if (errorCode != FSErrSuccess) { return -1; }
如何通过内存缓冲区加载一个现有的PDF文档
#include "FSPDFObjC.h" ... NSData* file_data = [NSData dataWithContentsOfFile:pdf_path]; FSPDFDoc* doc = [[FSPDFDoc alloc] initWithBuffer:file_data]; FSErrorCode errorCode = [doc load:nil]; if (errorCode != FSErrSuccess) { return -1; }
如何通过自定义实现的FSFileReaderCallback对象加载一个现有的PDF文档
#include "FSPDFObjC.h" ... @interface FSFileRead : NSObject<FSFileReaderCallback> - (id)initWithSourceFilePath:(NSString *)path offset:(long long)offset; @end @implementation FSFileRead { FileReader *imp; } - (id)initWithSourceFilePath:(NSString *)path offset:(long long)offset { if (self = [super init]) { imp = new FileReader(offset); if (!imp->LoadFile([path UTF8String])) { return nil; } } return self; } - (void)dealloc { delete imp; } - (unsigned long long)getSize { return imp->GetSize(); } - (NSData *)readBlock:(unsigned long long)offset size:(unsigned long long)size { void *buffer = malloc(size); if (!buffer) { NSLog(@"failed to malloc buffer of size %llu", size); return nil; } if (imp->ReadBlock(buffer, offset, (size_t)size)) { return [NSData dataWithBytesNoCopy:buffer length:size]; } else { free(buffer); return nil; } } @end FSFileRead *file_reader = [[FSFileRead alloc] initWithSourceFilePath:file_name offset:offset]; FSPDFDoc *doc_real = [[FSPDFDoc alloc] initWithFile_read:file_reader is_async:NO]; FSErrorCode code = [doc_real load:nil]; if (code != FSErrSuccess) { return -1; }
如何加载PDF文档以及获取文档的首页
#include "FSPDFObjC.h" ... NSString* pdfpath = [[NSBundle mainBundle] pathForResource:@"Sample" ofType:@"pdf"]; FSPDFDoc* doc = [[FSPDFDoc alloc] initWithPath:pdfpath]; FSErrorCode errorCode = [doc load:nil]; if (errorCode != FSErrSuccess) { return -1; } // Get the first page of the document. FSPDFPage* page = [doc getPage:0]; // Parse page. [page startParse:FSPDFPageParsePageNormal pause:nil is_reparse:NO];
如何将PDF文档另存为一个新的文档
#include "FSPDFObjC.h" ... NSString* pdfpath = [[NSBundle mainBundle] pathForResource:@"Sample" ofType:@"pdf"]; FSPDFDoc* doc = [[FSPDFDoc alloc] initWithPath:pdfpath]; FSErrorCode errorCode = [doc load:nil]; if (errorCode != FSErrSuccess) { return -1; } [doc saveAs:output_file save_flags:FSPDFDocSaveFlagNoOriginal];
如何通过FileWriterCallback将PDF文档保存到内存缓冲区
#include "FSPDFObjC.h" ... @interface FSFileWriterCallbackImpl : NSObject<FSFileWriterCallback> @property (nonatomic) NSMutableData* mutableData; @end @implementation FSFileWriterCallbackImpl - (instancetype)init { self = [super init]; if (self) { _mutableData = [[NSMutableData alloc] init]; } return self; } -(unsigned long long)getSize { if (!self.mutableData) return NO; return self.mutableData.length; } -(BOOL)writeBlock:(NSData*)data offset:(unsigned long long)offset { if (!self.mutableData) return NO; [self.mutableData appendData:data]; return YES; } -(BOOL)flush { return YES; } @end ... FSFileWriterCallbackImpl* filewriter = [[FSFileWriterCallbackImpl alloc] init]; // Assuming FSPDFDoc doc has been loaded. ... [doc startSaveAsWithWriterCallback:filewriter save_flags:FSPDFDocSaveFlagNoOriginal pause:nil]; ...
页面 (Page)
PDF页面是PDF Document基础和重要的组成部分。使用函数FSPDFDoc::getPage从文档中获取FSPDFPage对象。页面级API提供了解析/渲染/编辑 (包括创建、删除、扁平化等) 页面、获取PDF注释、获取和设置页面属性等功能。对于大多数情况,在渲染和处理页面之前,需要先对页面进行解析。
Example:
如何获取页面的大小
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. ... float width = [page getWidth]; float height = [page getHeight]; ...
如何计算页面内容的边界框
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. ... FSRectF* content_box = [page calcContentBBox:FSPDFPageCalcContentsBox];
如何创建一个PDF页面以及设置其页面大小
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... float w = 612.0; float h = 792.0; FSPDFPage* page = [doc insertPage:index width:w height:h];
如何删除一个PDF页面
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. // Remove a PDF page by page index. [doc removePage:index]; // Remove a specified PDF page. [doc removePageWithPDFPage:page]; ...
如何扁平化一个PDF页面
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. // Flatten all contents of a PDF page. [page flatten:YES options:FSPDFPageFlattenAll]; // Flatten a PDF page without annotations. [page flatten:YES options:FSPDFPageFlattenNoAnnot]; // Flatten a PDF page without form controls. [page flatten:YES options:FSPDFPageFlattenNoFormControl]; // Flatten a PDF page without annotations and form controls (Equals to nothing to be flattened). [page flatten:YES options:FSPDFPageFlattenNoAnnot|FSPDFPageFlattenNoFormControl]; ...
如何获取和设置PDF文档中的页面缩略图
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. FSBitmap* thumbnail_bmp = [page loadThumbnail];
渲染 (Render)
PDF渲染是通过Foxit渲染引擎实现的,Foxit渲染引擎是一个图形引擎,用于将页面渲染到位图或平台设备上下文。 Foxit PDF SDK提供了APIs用来设置渲染选项/flags,例如设置 flag来决定是否渲染表单域和签名,是否绘制图像反锯齿 (anti-aliasing) 和路径反锯齿。可以使用以下APIs进行渲染:
- 渲染页面和注释时,首先使用FSRenderer::setRenderContentFlags接口来决定是否同时渲染页面和注释,然后使用FSRenderer::startRender接口进行渲染。FSRenderer::startQuickRender接口也可以用来渲染页面,但仅用于缩略图。
- 渲染单个annotation注释,使用FSRenderer::renderAnnot接口。
- 在位图上渲染,使用FSRenderer::startRenderBitmap接口。
- 渲染一个重排的页面,使用 FSRenderer::startRenderReflowPage接口。
在Foxit PDF SDK中,Widget注释常与表单域和表单控件相关联。渲染widget注释,推荐使用如下的流程:
- 加载PDF页面后,首先渲染页面以及该页面上所有的注释 (包括widget注释)。
- 然后,如果使用FSFiller对象来填表,则应使用FSFiller::render接口来渲染当前获取到焦点的表单控件,而不是使用FSRenderer::renderAnnot接口。
Example:
如何将PDF页面渲染到bitmap
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. int width = (int)[page getWidth]; int height = (int)[page getHeight]; FSMatrix2D* matrix = [page getDisplayMatrix:0 top:0 width:width height:height rotate:page.rotation]; // Prepare a bitmap for rendering. FSBitmap* bitmap = [[FSBitmap alloc] initWithWidth:width height:height format:FSBitmapDIBArgb buffer:nil pitch:0]; [bitmap fillRect:0xFFFFFFFF rect:nil]; // Render page. FSRenderer* render = [[FSRenderer alloc] initWithBitmap:bitmap is_rgb_order:NO]; [render startRender:page matrix:matrix pause:nil]; ...
如何渲染页面和注释
#include "FSPDFObjC.h" ... // Assuming PDFPage page has been loaded and parsed. int width = (int)[page getWidth]; int height = (int)[page getHeight]; FSMatrix2D* matrix = [page getDisplayMatrix:0 top:0 width:width height:height rotate:page.rotation]; // Prepare a bitmap for rendering. FSBitmap* bitmap = [[FSBitmap alloc] initWithWidth:width height:height format:FSBitmapDIBArgb buffer:nil pitch:0]; [bitmap fillRect:0xFFFFFFFF rect:nil]; // Render page. FSRenderer* render = [[FSRenderer alloc] initWithBitmap:bitmap is_rgb_order:NO]; unsigned int render_flag = FSRendererRenderPage | FSRendererRenderAnnot; [render setRenderContentFlags:render_flag]; [render startRender:page matrix:matrix pause:nil]; ...
附件 (Attachment)
在Foxit PDF SDK中,attachments指的是文档附件而不是文件附件注释。它允许将整个文件封装在文档中,就像电子邮件附件一样。Foxit PDF SDK提供应用程序APIs来访问附件,例如加载附件,获取附件,插入/删除附件,以及访问附件的属性。
Example:
如何从PDF文档中导出嵌入的附件文件,并将其另存为单个文件
#include "FSPDFObjC.h" ... FSAttachments *attachments = [[FSAttachments alloc] initWithDoc:doc nametree:[[FSPDFNameTree alloc] init]]; int count = [attachments getCount]; for (int i = 0; i < count; i++) { NSString *key = [attachments getKey:i]; FSFileSpec *file_spec = [attachments getEmbeddedFile:key]; if (![file_spec isEmpty]) { NSString *name = [file_spec getFileName]; if ([file_spec isEmbedded]) { NSString *exFilePath = [[NSString alloc] initWithFormat:@"%@%@", output_directory, name]; bool bExportStatus = [file_spec exportToFile:exFilePath]; } } }
如何删除PDF文档中所有的附件文件
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. FSAttachments *attachments = [[FSAttachments alloc] initWithDoc:doc nametree:[[FSPDFNameTree alloc] init]]; [attachments removeAllEmbeddedFiles]; ...
文本页面 (Text Page)
Foxit PDF SDK提供APIs来提取,选择,搜索和检索PDF文档中的文本。 PDF文本内容存储在与特定页面相关的FSTextPage对象中。FSTextPage类可用于获取PDF页面中文本的信息,例如单个字符,单个单词,指定字符范围或矩形内的文本内容等。它还可用于构造其他文本相关类的对象,用来对文本内容执行更多操作或从文本内容访问指定信息:
- 在PDF页面的文本内容中搜索文本,使用FSTextPage对象来构建FSTextSearch对象。
- 访问类似超文本链接的文本,使用FSTextPage对象来构建FSPageTextLinks对象。
Example:
如何从PDF页面中提取文本
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. // Get the text page object. FSTextPage *textPage = [[FSTextPage alloc] initWithPage:page flags:FSTextPageParseTextNormal]; int charCount = [textPage getCharCount]; if (charCount > 0) { NSString *currentText = [textPage getChars:0 count:-1]; } ...
如何在PDF文档中获取矩形区域中的文本
#include "FSPDFObjC.h" ... FSTextPage *textPage = [[FSTextPage alloc] initWithPage:page flags:FSTextPageParseTextNormal]; FSRectF* rect = [[FSRectF alloc] initWithLeft1:90 bottom1:580 right1:450 top1:595]; NSString* text = [textPage getTextInRect:rect];
文本搜索 (Text Search)
Foxit PDF SDK 提供APIs来搜索PDF文档、XFA文档、文本页面或者PDF注释中的文本。它提供了文本搜索和获取搜索结果的函数:
- 指定搜索模式和选项,使用FSTextSearch::setPattern、FSTextSearch::setStartPage (仅对PDF文档中的文本搜索有用)、FSTextSearch::setEndPage (仅对PDF文档中的文本搜索有用)、和FSTextSearch::setSearchFlags接口。
- 进行搜索,使用FSTextSearch::findNext和FSTextSearch::findPrev接口。
- 获取搜索结果,使用FSTextSearch::getMatchXXX() 接口。
Example:
如何在PDF文档中搜索指定的文本
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSTextSearch *search = [[FSTextSearch alloc] initWithDocument:doc cancel:nil]; int startIndex = 0; int endIndex = [doc getPageCount] - 1; [search setStartPage:startIndex]; [search setEndPage:endIndex]; NSString *pattern = @"Foxit"; [search setPattern:pattern]; NSInteger flags = FSTextSearchSearchNormal; [search setSearchFlags:(unsigned int)flags]; int match_count = 0; while ([search findNext]) { FSRectFArray *rects = [search getMatchRects]; match_count ++; } ...
文本链接 (Text Link)
在PDF页面中,指向网站、网络资源以及电子邮件地址的超链接文本和普通文本一样。在处理文本链接之前,用户应首先调用FSPageTextLinks::getTextLink接口来获取一个textlink对象。
Example:
如何检索PDF页面中的超链接
#include "FSPDFObjC.h" ... FSTextPage *textPage = [[FSTextPage alloc] initWithPage:page flags:FSTextPageParseTextNormal]; FSPageTextLinks* page_text_links = [[FSPageTextLinks alloc] initWithPage:textPage]; if (NO == [page_text_links isEmpty]) { int index = 0; FSTextLink* text_link = [page_text_links getTextLink:index]; if (NO == [text_link isEmpty]) NSString* url = [text_link getURI]; } ...
书签 (Bookmark)
Foxit PDF SDK提供了名为书签的导航工具,允许用户在PDF文档中快速定位和链接他们感兴趣的部分。PDF书签也称为大纲 (outline),每个书签包含一个目标位置或动作来描述它链接到的位置。它是一个树形的层次结构,因此在访问bookmark 树之前,必须首先调用接口FSPDFDoc::getRootBookmark以获取整个bookmark树的根节点。这里,”书签根节点” 是一个抽象对象,它只有一些子节点,没有兄弟节点, 也没有任何数据 (包括bookmark数据,目标位置数据和动作数据)。因为它没有任何数据,因此无法在应用程序界面上显示,能够调用的接口只有FSBookmark::getFirstChild。
在获取书签根节点后,就可以调用以下的接口去访问其他的书签:
- 访问parent bookmark,使用FSBookmark::getParent接口。
- 访问第一个child bookmark,使用FSBookmark::getFirstChild接口。
- 访问next sibling bookmark,使用FSBookmark::getNextSibling接口。
- 插入一个新的bookmark,使用FSBookmark::insert接口。
- 移动一个bookmark,使用FSBookmark::moveTo接口。
Example:
如何遍历PDF文档中所有的书签
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSBookmark *root = [doc getRootBookmark]; if (![root isEmpty]) { ShowBookmarkInfo(root, info, 0); } void ShowBookmarkInfo(FSBookmark *bookmark, int depth) { if (depth > 32) return; if ([bookmark isEmpty]) { return; } ShowBookmarkInfo([bookmark getFirstChild], depth + 1); ShowBookmarkInfo([bookmark getNextSibling], depth); } ...
如何向PDF文档中添加一个新的书签
#include "FSPDFObjC.h" // Assuming PDFDoc doc has been loaded. FSBookmark *root = [doc getRootBookmark]; if ([root isEmpty]) { root = [doc createRootBookmark]; } FSDestination *dest = [FSDestination createFitPage:doc page_index:0]; NSString *title = [NSString stringWithFormat:@"A bookmark to a page (index: %d)", 0]; FSBookmark *child = [root insert:title position:FSBookmarkPosLastChild]; [child setDestination:dest]; [child setColor:0xF68C21];
表单 (AcroForm)
PDF目前支持两种类型的form,用于以交互方式收集用户信息:AcroForms 和 XFA forms。Acroforms是基于PDF框架的原始的可填写表单。Foxit PDF SDK提供了以编程方式查看和编辑表单域的APIs。在PDF文档中,表单域通常用于收集数据。FSForm类提供了APIs用来获取表单域或表单控件,导入/导出表单数据,以及其他功能,例如:
- 获取表单域,使用FSForm::getFieldCount和FSForm::getField接口。
- 获取PDF页面中的表单控件,使用FSForm::getControlCount和FSForm::getControl接口。
- 从XML文件导入表单数据,使用FSForm::importFromXML接口;导出表单数据到XML文件,使用FSForm::exportToXML接口。
- 获取form filler对象,使用FSForm::getFormFiller接口。
从FDF/XFDF文件中导入表单数据,或者导出数据到FDF/XFDF文件,请参考FSPDFDoc::importFromFDF 和 FSPDFDoc::exportToFDF接口。
Example:
如何加载PDF中的表单
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. BOOL hasform = [doc hasForm]; if(hasform) FSForm *form = [[FSForm alloc] initWithDocument:doc]; ...
如何获取表单域个数以及其属性
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. BOOL hasform = [doc hasForm]; if(hasform) { FSForm *form = [[FSForm alloc] initWithDocument:doc]; int count = [form getFieldCount:@""]; for (int i = 0; i < count; i++) { FSField* field = [form getField:i filter:@""]; FSFieldType field_type = [field getType]; NSString* name = [field getName]; } }
如何将PDF中的表单数据导出到XML文件
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSForm *form = [[FSForm alloc] initWithDocument:doc]; BOOL is_success = [field exportToXML:@"test.xml"]; ...
如何通过XML文件导入表单数据到PDF
#include "FSPDFObjC.h" ... FSForm *form = [[FSForm alloc] initWithDocument:doc]; BOOL is_success = [field importFromXML:@"test.xml"]; ...
XFA 表单
XFA (XML Forms Architecture) forms 是基于XML的表单,封装在PDF内。XFA提供了基于模板的语法和一系列处理规则,允许用户构建交互式表单。最简单的来说,基于模板的语法定义了用户数据的字段。
Foxit PDF SDK提供了APIs用来渲染XFA表单、填表、导出和导入表单数据。
备注:
- Foxit PDF SDK 提供了两个回调类FSAppProviderCallback和FSDocProviderCallback,分别将回调对象通过FSLibrary::registerXFAAppProviderCallback以及FSXFADoc的构造函数注册到SDK中。这两个类中的所有函数都是纯虚函数,需要用户自己实现。
- 使用XFA form功能,请确保授权key文件中包含 ‘XFA’的权限。
Example:
如何加载XFADoc并且显示XFA交互式表单
#include "FSPDFObjC.h" ... // implement from FSAppProviderCallback. CFS_XFAAppHandler* pXFAAppHandler = [CFS_XFAAppHandler alloc]; [FSLibrary registerXFAAppProviderCallback:pXFAAppHandler]; FSPDFDoc *doc = [[FSPDFDoc alloc] initWithPath:input_file]; FSErrorCode errorCode = [doc load:@""]; if (errorCode != FSErrSuccess) { return -1; } // implement from FSDocProviderCallback. CFS_XFADocHandler* pXFADocHandler = [CFS_XFADocHandler alloc]; FSXFADoc* xfa_doc = [[FSXFADoc alloc] initWithDocument:doc xfa_doc_provider_handler:pXFADocHandler]; [xfa_doc startLoad:nil]; ...
如何导出和导入XFA表单数据
#include "FSPDFObjC.h" ... // Assuming FSXFADoc xfa_doc has been loaded. [xfa_doc exportData:@"xfa_form.xml" export_type:FSXFADocExportDataTypeXML]; [xfa_doc resetForm]; [doc saveAs:@"xfa_dynamic_resetform.pdf" save_flags:FSPDFDocSaveFlagNormal]; [xfa_doc importData:@"xfa_form.xml"]; [doc saveAs:@"xfa_dynamic_importdata.pdf" save_flags:FSPDFDocSaveFlagNormal]; ...
表单设计 (Form Design)
可填写的PDF表单 (AcroForm) 特别适用于各种应用程序表单设计,比如税收和其他政府部门表单。表单设计提供了APIs用来向PDF文件中添加表单域或者从PDF文档中移除表单域。从零开始设计一个表单允许开发人员创建他们需要的内容和布局的表单。
Example:
如何向PDF添加一个文本表单域
#include "FSPDFObjC.h" ... // Add test field FSControl *control = [form addControl:page field_name:@"Text Field0" field_type:FSFieldTypeTextField rect:[[FSRectF alloc] initWithLeft1:50.0 bottom1:600 right1:90 top1:640]]; [control getField].value = @"3"; // Update text field's appearance. [[control getWidget] resetAppearanceStream]; ...
如何从PDF中移除一个文本表单域
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSForm *form = [[FSForm alloc] initWithDocument:doc]; int count = [form getFieldCount:@""]; for (int i = 0; i < count; i++) { FSField* field = [form getField:i filter:@""]; FSFieldType field_type = [field getType]; if (FSFieldTypeTextField == field_type) [field removeField:field]; } ...
注释 (Annotations)
常规注释
一个annotation注释将对象(如注释,线条和高亮)与PDF文档页面上的位置相关联。其提供了一种通过鼠标和键盘与用户进行交互的方式。PDF包括如Table 3-1中列出的各种标准注释类型。在这些注释类型中,许多被定义为标记注释,因为它们主要用于标记PDF文档。标记注释中作为其自身一部分的文本,可以在其他符合标准的阅读器中以其他方式显示,例如在Comments面板。Table 3-1中的 “Markup” 列用来说明是否为标记注释。
Foxit PDF SDK支持PDF Reference [1] 中定义的大多数注释类型。Foxit PDF SDK提供了注释创建,属性访问和修改,外观设置和绘制的APIs。
Table 3-1
注释类型 | 描述 | Markup | SDK是否支持 |
Text(Note) | Text annotation | Yes | Yes |
Link | Link Annotation | No | Yes |
FreeText (TypeWriter/TextBox/Callout) |
Free text annotation | Yes | Yes |
Line | Line annotation | Yes | Yes |
Square | Square annotation | Yes | Yes |
Circle | Circle annotation | Yes | Yes |
Polygon | Polygon annotation | Yes | Yes |
PolyLine | PolyLine annotation | Yes | Yes |
Highlight | Highlight annotation | Yes | Yes |
Underline | Underline annotation | Yes | Yes |
Squiggly | Squiggly annotation | Yes | Yes |
StrikeOut | StrikeOut annotation | Yes | Yes |
Stamp | Stamp annotation | Yes | Yes |
Caret | Caret annotation | Yes | Yes |
Ink(pencil) | Ink annotation | Yes | Yes |
Popup | Popup annotation | No | Yes |
File Attachment | FileAttachment annotation | Yes | Yes |
Sound | Sound annotation | Yes | No |
Movie | Movie annotation | No | No |
Widget* | Widget annotation | No | Yes |
Screen | Screen annotation | No | Yes |
PrinterMark | PrinterMark annotation | No | No |
TrapNet | Trap network annotation | No | No |
Watermark* | Watermark annotation | No | Yes |
3D | 3D annotation | No | No |
Redact | Redact annotation | Yes | Yes |
备注:
- Widget和watermark注释类型是比较特殊的。’Annotation’ 模块不支持它们。Widget类型仅在 ‘form filler’ 模块中使用,watermark类型仅在 ‘watermark’ 模块中使用。
- Foxit SDK支持名为PSI (pressure sensitive ink,压感笔迹) 的自定义注释类型。在PDF Reference [1]中没有对该注释进行描述。通常,PSI用于手写签名功能,Foxit SDK将其视为PSI注释,以便其他PDF产品可以对其进行相关处理。
Example:
如何向PDF页面中添加link注释
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. // Add link annotation. FSRectF* annot_rect = [[FSRectF alloc] initWithLeft1:350 bottom1:350 right1:380 top1:400]; FSLink* link = [[FSLink alloc] initWithAnnot:[page addAnnot:FSAnnotLink rect:annot_rect]]; [link setHighlightingMode:FSAnnotHighlightingToggle]; [link resetAppearanceStream]; ...
如何向PDF页面中添加highlight注释,并且设置相关属性
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. // Add highlight annotation. FSRectF* annot_rect = [[FSRectF alloc] initWithLeft1:10 bottom1:450 right1:100 top1:550]; FSHighlight* highlight = [[FSHighlight alloc] initWithAnnot:[page addAnnot:FSAnnotHighlight rect:annot_rect]]; [highlight setContent:@"Highlight"]; FSQuadPoints* quad_points = [FSQuadPoints new]; FSPointF* point = [FSPointF new]; [point set:10 y:500]; quad_points.first = point; [point set:90 y:500]; quad_points.second = point; [point set:10 y:480]; quad_points.third = point; [point set:90 y:480]; quad_points.fourth = point; FSQuadPointsArray* quad_points_array = [FSQuadPointsArray new]; [quad_points_array add:quad_points]; [highlight setQuadPoints:quad_points_array]; [highlight setSubject:@"Highlight"]; [highlight setTitle:@"Foxit SDK"]; FSDateTime* local_time = getLocalDateTime(); [highlight setCreationDateTime:local_time]; [highlight setModifiedDateTime:local_time]; [highlight setUniqueID:randomUID()]; // Appearance should be reset. [highlight resetAppearanceStream]; ...
如何在创建markup注释时设置popup信息
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. // Add note annotation FSRectF* annot_rect = [[FSRectF alloc] initWithLeft1:10 bottom1:350 right1:50 top1:400]; FSNote* note = [[FSNote alloc] initWithAnnot:[page addAnnot:FSAnnotNote rect:annot_rect]]; [note setIconName:@"Comment"]; [note setSubject:@"Note"]; [note setTitle:@"Foxit SDK"]; [note setContent:@"Note annotation."]; FSDateTime* local_time = getLocalDateTime(); [note setCreationDateTime:local_time]; [note setModifiedDateTime:local_time]; [note setUniqueID:randomUID()]; // Add popup to note annotation annot_rect = [[FSRectF alloc] initWithLeft1:300 bottom1:450 right1:500 top1:550]; FSPopup* popup = [[FSPopup alloc] initWithAnnot:[page addAnnot:FSAnnotPopup rect:annot_rect]]; [popup setBorderColor:0x00FF00]; [popup setOpenStatus:NO]; local_time = getLocalDateTime(); [popup setModifiedDateTime:local_time]; [note setPopup:popup]; [note resetAppearanceStream]; ...
如何使用设备坐标获取PDF中特定的注释
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. ... int width = (int)[page getWidth]; int height = (int)[page getHeight]; FSMatrix2D* display_matrix = [page getDisplayMatrix:0 top:0 width:width height:height rotate:page.rotation]; int annot_count = [page getAnnotCount]; for (int i=0; i<annot_count; i++) { FSAnnot* annot = [page getAnnot:i]; FSAnnotType annot_type = [annot getType]; if (FSAnnotPopup == annot_type) continue; FSRectI* device_rect = [annot getDeviceRect:NO matrix:display_matrix]; // Get the same annot by using device point. float tolerance = 1.0; FSPointF* point = [FSPointF alloc]; [point set:device_rect.left+tolerance y:(device_rect.top - device_rect.bottom)/2+device_rect.bottom]; FSAnnot* get_annot = [page getAnnotAtDevicePoint:point tolerance:tolerance matrix:display_matrix]; }
如何提取text markup annotation中的文本内容
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSPDFPage *page = [doc getPage:0]; [page startParse:FSPDFPageParsePageNormal pause:nil is_reparse:NO]; // Get a FSTextPage object. FSTextPage *textPage = [[FSTextPage alloc] initWithPage:page flags:FSTextPageParseTextNormal]; int annot_count = [page getAnnotCount]; for (int i = 0; i < annot_count; i++) { FSAnnot *annot = [page getAnnot:i]; FSTextMarkup *text_markup = [[FSTextMarkup alloc] initWithAnnot:annot]; if (![text_markup isEmpty]) { // Get the texts which intersect with a text markup annotation. NSString *text = [textPage getTextUnderAnnot:text_markup]; } }
从FDF文件导入注释或者将注释导出到FDF文件
在Foxit PDF SDK中,可以使用来自应用程序或者FDF文件的数据来创建注释。同时,PDF SDK支持将注释导出到FDF文件。
Example:
如何从FDF文件导入注释,并将其添加到PDF文档的首页
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSFDFDoc* fdf_doc = [[FSFDFDoc alloc] initWithPath: @"AnnotationData.fdf"]; [doc importFromFDF:fdf_doc types:FSPDFDocAnnots page_range:[FSRange new]]; ...
图片转换 (Image Conversion)
Foxit PDF SDK提供了PDF文件和图片之间进行转换的APIs. 应用程序可以轻松地实现图片创建和图片转换等功能,支持如下的图片格式:BMP、TIFF、PNG、JPX、JPEG和 GIF。通过Foxit PDF SDK,PDF文件和支持的图片格式 (除了GIF) 之间可以互相转换。Foxit PDF SDK只支持将GIF图片转换为PDF文件。
Example:
如何将PDF页面转换为位图文件
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSImage* image = [FSImage new]; // Get page count int page_count = [doc getPageCount]; for(int i=0;i<page_count;i++) { FSPDFPage* page = [doc getPage:i]; // Parse page. [page startParse:FSPDFPageParsePageNormal pause:nil is_reparse:NO]; int width = (int)[page getWidth]; int height = (int)[page getHeight]; FSMatrix2D* matrix = [page getDisplayMatrix:0 top:0 width:width height:height rotate:page.rotation]; // Prepare a bitmap for rendering. FSBitmap* bitmap = [[FSBitmap alloc] initWithWidth:width height:height format:FSBitmapDIBArgb buffer:nil pitch:0]; [bitmap fillRect:0xFFFFFFFF rect:nil]; // Render page FSRenderer* render = [[FSRenderer alloc] initWithBitmap:bitmap is_rgb_order:NO]; [render startRender:page matrix:matrix pause:nil]; [image addFrame:bitmap]; }
如何将图片转换为PDF文件
#include "FSPDFObjC.h" ... FSImage *image = [[FSImage alloc] initWithPath:input_file]; int count = [image getFrameCount]; FSPDFDoc *doc = [[FSPDFDoc alloc] init]; for (int i = 0; i < count; i ++) { FSBitmap *bitmap = [image getFrameBitmap:i]; float w = 612.0; float h = 792.0; FSPDFPage *page = [doc insertPage:i width:w height:h]; FSPointF *ptZero = [[FSPointF alloc] init]; ptZero.x = 0; ptZero.y = 0; [page addImage:image frame_index:i position:ptZero width:w height:h auto_generate_content:YES]; } [doc saveAs:output_file save_flags:FSPDFDocSaveFlagNoOriginal]; ...
水印 (Watermark)
水印是一种PDF注释,广泛用于PDF文档。水印是文档上嵌入的可见叠加层,包含文本、logo或版权声明。水印的目的是对作者工作成果的保护,防止其未经授权而被他人使用。Foxit PDF SDK提供了允许应用程序创建、插入和删除水印的APIs。
Example:
如何创建一个文本水印,并将其插入到PDF文档的第一页
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. FSWatermarkSettings* settings = [FSWatermarkSettings new]; settings.flags = FSWatermarkSettingsFlagASPageContents | FSWatermarkSettingsFlagOnTop; settings.offset_x = 0; settings.offset_y = 0; settings.opacity = 90; settings.position = FSPosTopRight; settings.rotation = -45.f; settings.scale_x = 1.f; settings.scale_y = 1.f; FSWatermarkTextProperties* text_properties = [FSWatermarkTextProperties new]; text_properties.alignment = FSAlignmentCenter; text_properties.color = 0xF68C21; text_properties.font_style = FSWatermarkTextPropertiesFontStyleNormal; text_properties.line_space = 1; text_properties.font_size = 12.f; FSFont* new_font = [[FSFont alloc] initWithFont_id:FSFontStdIDTimesB]; text_properties.font = new_font; FSWatermark* watermark = [[FSWatermark alloc] initWithDocument:doc text:@"Foxit PDF SDK\nwww.foxitsoftware.com" properties:text_properties settings:settings]; [watermark insertToPage:page]; // Save document to file ...
如何创建一个图片水印,并将其插入到PDF文档的第一页
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. FSWatermarkSettings* settings = [FSWatermarkSettings new]; settings.flags = FSWatermarkSettingsFlagASPageContents | FSWatermarkSettingsFlagOnTop; settings.offset_x = 0.f; settings.offset_y = 0.f; settings.opacity = 20; settings.position = FSPosCenter; settings.rotation = 0.0f; FSImage* image = [[FSImage alloc] initWithPath:image_file_path]; FSBitmap* bitmap = [image getFrameBitmap:0]; settings.scale_x = [page getWidth] * 0.618f / [bitmap getWidth]; settings.scale_y = settings.scale_x; FSWatermark* watermark = [[FSWatermark alloc] initWithDocument:doc image:image frame_index:0 settings:settings]; [watermark insertToPage:page]; // Save document to file. ...
如何从PDF页面中删除所有的水印
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded and parsed. ... [page removeAllWatermarks]; // Save document to file ...
条形码 (Barcode)
条形码用于表示与某个对象相关的数据,该数据可通过光学机器进行读取。最初的条形码系统是通过平行线间宽度和间距的不同来表示数据,可称为线性条形码或一维条形码(1D)。后来条形码逐渐演变成矩形、点、六边形等2D几何图案。虽然2D系统使用了一系列符号,但是它们通常也被称为条形码。条形码最初由特定的光学扫描器进行扫描,该光学扫面器被称为条形码读取器。后来扫描器和解释软件成功应用于桌面打印机和智能手机等设备。Foxit PDF SDK提供了从给定字符串生成条形码位图的应用程序。Table 3-2列出了Foxit PDF SDK支持的条形码类型。
Table 3-2
条形码类型 | Code39 | Code128 | EAN8 | UPCA | EAN13 | ITF | PDF417 | QR |
维度 | 1D | 1D | 1D | 1D | 1D | 1D | 2D | 2D |
Example:
如何从字符串生成条形码位图
#include "FSPDFObjC.h" ... // Strings used as barcode content. NSString *code_string = @"TEST-SHEET"; // Barcode format types. FSBarcodeFormat code_format = FSBarcodeFormatCode39; // Format error correction level of QR code. FSBarcodeQRErrorCorrectionLevel qr_level = FSBarcodeQRCorrectionLevelLow; // Unit width for barcode in pixels, preferred value is 1-5 pixels. int unitWidth = 2; // Unit height for barcode in pixels, preferred value is >= 20 pixels. int unitHeight = 120; FSBarcode *barcode = [[FSBarcode alloc] init]; FSBitmap *bitmap = [barcode generateBitmap: code_string format:code_format unit_width:unit_width unit_height:unit_height level:qr_level]; ...
安全 (Security)
Foxit PDF SDK提供了一系列加密和解密功能,以满足不同级别的文档安全保护。用户可以使用常规密码加密和证书驱动加密,或使用自己的安全处理机制来自定义安全实现。另外,Foxit PDF SDK还提供了APIs用于集成第三方安全技术 (Microsoft RMS),允许开发人员使用Microsoft RMS SDK加密和解密PDF文档。
备注:有关RMS加密和解密更详细的信息,请参考SDK包中 “\examples\simple_demo” 文件夹下的 “security” demo。
Example:
如何使用证书加密PDF文件
#include "FSPDFObjC.h" ... FSPDFDoc *doc = [[FSPDFDoc alloc] initWithPath:input_file]; FSErrorCode code = [doc load:nil]; if (code != FSErrSuccess) { return -1; } // Do encryption. NSMutableArray<NSData *> *envelopes = @[].mutableCopy; NSMutableData *initial_key = [NSMutableData new]; NSString *cert_file_path = [input_path stringByAppendingPathComponent:@"foxit.cer"]; // GetCertificateInfo is implemented in user side to get information from certificate file. if (!GetCertificateInfo(cert_file_path, envelopes, initial_key, true, 16)) { return -1; } FSCertificateSecurityHandler *handler = [[FSCertificateSecurityHandler alloc] init]; FSCertificateEncryptData *encrypt_data = [[FSCertificateEncryptData alloc] initWithIs_encrypt_metadata:YES cipher:FSSecurityHandlerCipherAES envelopes:envelopes]; [handler initialize:encrypt_data encrypt_key:initial_key]; [doc setSecurityHandler:handler]; NSString *output_file = [output_directory stringByAppendingPathComponent:@"certificate_encrypt.pdf"]; [doc saveAs:output_file save_flags:FSPDFDocSaveFlagNoOriginal]; ...
如何使用Foxit DRM加密PDF文件
#include "FSPDFObjC.h" ... FSPDFDoc *doc = [[FSPDFDoc alloc] initWithPath:input_file]; FSErrorCode code = [doc load:nil]; if (code != FSErrSuccess) { return -1; } // Do encryption. FSDRMSecurityHandler *handler = [[FSDRMSecurityHandler alloc] init]; NSString *file_id = @"Simple-DRM-file-ID"; NSData *initialize_key = [@"Simple-DRM-initialize-key" dataUsingEncoding:NSUTF8StringEncoding]; FSDRMEncryptData *encrypt_data = [[FSDRMEncryptData alloc] initWithIs_encrypt_metadata:TRUE sub_filter:@"Simple-DRM-filter" cipher:FSSecurityHandlerCipherAES key_length:16 is_owner:YES user_permissions:0xfffffffc]; [handler initialize:encrypt_data file_id:file_id initial_key:initialize_key]; [doc setSecurityHandler:handler]; NSString *output_file = [output_directory stringByAppendingPathComponent:@"foxit_drm_encrypt.pdf"]; [doc saveAs:output_file save_flags:FSPDFDocSaveFlagNoOriginal]; ...
页面重排 (Reflow)
页面重排功能是在页面大小发生变化时自动重排页面内容。该功能对那些需要在不同尺寸的输出设备上显示PDF文档的应用程序具有很大的利用价值。页面重排让应用程序无需考虑设备的不同尺寸。Foxit PDF SDK提供了APIs用来创建、渲染、释放Reflow页面,以及访问Reflow页面的属性。
Example:
如何创建一个Reflow页面,并将其渲染为位图文件
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSPDFPage* page = [doc getPage:0]; // Parse PDF page. [page startParse:FSPDFPageParsePageNormal pause:nil is_reparse:NO]; FSReflowPage* reflow_page = [[FSReflowPage alloc] initWithPage:page]; // Set some arguments used for parsing the relfow page. [reflow_page setLineSpace:0]; [reflow_page setScreenMargin:margin.left top:(int)margin.top right:(int)margin.right bottom:(int)margin.bottom]; [reflow_page setScreenSize:size.x height:size.y]; [reflow_page setZoom:100]; [reflow_page setParseFlags:FSReflowPageNormal]; // Parse reflow page. [reflow_page startParse:nil]; // Get actual size of content of reflow page. The content size does not contain the margin. float content_width = [reflow_page getContentWidth]; float content_height = [reflow_page getContentHeight]; // Create a bitmap for rendering the reflow page. The bitmap size contains the margin. FSBitmap* bitmap = [[FSBitmap alloc] initWithWidth:(int)(content_width + margin.left + margin.right) height:(int)(content_height + margin.top + margin.bottom) format:FSBitmapDIBArgb buffer:nil pitch:0]; [bitmap fillRect:0xFFFFFFFF rect:nil]; // Render reflow page. FSRenderer* renderer = [[FSRenderer alloc] initWithBitmap:bitmap is_rgb_order:NO]; FSMatrix2D* matrix = [reflow_page getDisplayMatrix:0 offset_y:0]; [renderer startRenderReflowPage:reflow_page matrix:matrix pause:nil]; ...
异步加载PDF (Asynchronous PDF)
异步加载PDF技术是一种在文档加载需要花费很长时间时,可以不用加载整个文档就可以对PDF页面进行访问的方法。该方法专为访问互联网上的PDF文件而设计。使用异步PDF技术,应用程序无需等待下载整个PDF文件就可以对其进行访问,可以打开任何已经下载加载的PDF页面。该技术为Web阅读类的应用程序提供了一种方便和有效的方式。关于如何使用异步模式打开和解析PDF页面,请参考SDK包中 “\examples\simple_demo” 文件夹下的 “async_load” demo。
压感笔迹 (Pressure Sensitive Ink)
压感笔迹 (PSI) 是一种获取变化电力输出以响应作用于压力感应设备元件上的各种变化压力或受力的技术。在PDF中,PSI通常被用于手写签名,通过捕捉手指或触控笔的压力变化来收集PSI数据。PSI数据包含操作区域的坐标和画布,并用其来绘制PSI的外观。Foxit PDF SDK允许应用程序创建PSI、访问其属性、操作ink笔迹和画布、以及释放PSI。
Example:
如何创建PSI并设置相关属性
#include "FSPDFObjC.h" ... FSPSI *psi = [[FSPSI alloc] initWithWidth:480 height:180 simulate:YES]; // Set ink diameter. [psi setDiameter:9]; // Set ink color. [psi setColor:0x434236]; // Set ink opacity. [psi setOpacity:0.8f]; // Add points to pressure sensitive ink. float x = 121.3043f; float y = 326.6846f; float pressure = 0.0966f; FSPathPointType type = FSPathTypeMoveTo; FSPointF *pt = [[FSPointF alloc] init]; pt.x = 121.3043f; pt.y = 326.6846f; [psi addPoint:pt type:type pressure:pressure]; ...
Wrapper
Wrapper为用户提供了一种保存与PDF文档相关的数据的方法。例如,在打开一个加密未授权的PDF文档,用户会看到错误信息提示其没有权限访问该文档。在这种情况下,使用wrapper,用户即使无法访问PDF中的内容,但仍然可以访问该文档的wrapper数据。Wrapper数据可用来提供信息给用户,比如文档的解密方法。
Example:
如何打开包含wrapper数据的PDF文档
#include "FSPDFObjC.h" ... FSPDFDoc *doc = [[FSPDFDoc alloc] initWithPath:file_name]; FSErrorCode code = [doc load:nil]; if (code != FSErrSuccess) { return -1; } if (![doc isWrapper]) { return -1; } long long offset = [doc getWrapperOffset]; FSFileRead *file_reader = [[FSFileRead alloc] initWithSourceFilePath:file_name offset:offset]; FSPDFDoc *doc_real = [[FSPDFDoc alloc] initWithFile_read:file_reader is_async:NO]; code = [doc_real load:nil]; if (code != FSErrSuccess) { return -1; } ...
PDF对象 (PDF Objects)
PDF中有八种类型的对象:布尔对象、数字对象、字符串对象、名称对象、数组对象、字典对象、流对象和空对象。 PDF对象是文档级文档,与页面对象(见3.23)不同,每个页面对象都与特定的页面相关联。Foxit PDF SDK提供了APIs用来创建、修改、检索和删除文档中的这些对象。
Example:
如何从目录字典中删除指定的属性
#include "FSPDFObjC.h" ... FSPDFDictionary *catalog = [document getCatalog]; if (catalog == NULL) return; NSArray *key_strings = [[NSArray alloc] initWithObjects:@"Type", @"Boolean", @"Name", @"String", @"Array", @"Dict", nil]; for (int i = 0; i < [key_strings count]; i++) { if ([catalog hasKey:key_strings[i]]) { [catalog removeAt:key_strings[i]]; } } ...
页面对象 (Page Object)
页面对象可以帮助对PDF对象 (关于PDF对象更详细的介绍,见3.22) 知识了解有限的开发人员能够处理PDF文档中的文本、路径、图像和画布等对象。Foxit PDF SDK提供APIs用以在页面中添加和删除PDF对象并设置特定属性。使用页面对象,用户可以从对象内容创建PDF页面。页面对象的其他可能用法包括向PDF文档添加页眉和页脚,向每个页面添加图片logo,或者根据需要生成PDF模板。
Example:
如何在PDF页面中创建一个文本对象
#include "FSPDFObjC.h" ... long position = [page getLastGraphicsObjectPosition:FSGraphicsObjectTypeText]; FSTextObject *text_object = [FSTextObject create]; text_object.fillColor = 0xFFFF7F00; // Prepare text state FSTextState *state = [[FSTextState alloc] init]; state.font_size = 80.0f; FSFont *font = [[FSFont alloc] initWithName:@"Simsun" styles:FSFontStylesSmallCap charset:FSFontCharsetGB2312 weight:0]; state.font = font; state.textmode = FSTextStateModeFill; [text_object setTextState:page text_state:state is_italic:false weight:750]; // Set text. text_object.text = @"Foxit Software"; long last_position = [page insertGraphicsObject:position graphics_object:text_object]; ...
如何向PDF页面中插入一个图片logo
#include "FSPDFObjC.h" ... long position = [page getLastGraphicsObjectPosition:FSGraphicsObjectTypeImage]; FSImage *image = [[FSImage alloc] initWithPath:image_file]; FSImageObject *image_object = [FSImageObject create:[page getDocument]]; [image_object setImage:image frame_index:0]; float width = [image getWidth]; float height = [image getHeight]; float page_width = [page getWidth]; float page_height = [page getHeight]; // Please notice the matrix value. image_object.matrix = [[FSMatrix2D alloc] initWithA1:width b1:0 c1:0 d1:height e1:(page_width - width) / 2.0f f1:(page_height - height) / 2.0f]; [page insertGraphicsObject:position graphics_object:image_object]; [page generateContent]; ...
标记内容 (Marked content)
在PDF文档中,可以将一部分内容标记为标记内容元素。标记内容功能有助于管理PDF文档的逻辑结构信息并且可以用于生成加标记的PDF (tagged PDF) 。加标记的PDF具有标准的结构类型和属性,有助于提取和再利用页面内容。有关标记内容的更多详细信息,请参阅PDF reference 1.7 [1] 的第10.5章。
Example:
如何获取页面中的标记内容以及tag名称
#include "FSPDFObjC.h" ... long position = [page getFirstGraphicsObjectPosition:FSGraphicsObjectTypeText]; FSTextObject *text_obj = [[page getGraphicsObject:position] getTextObject]; FSMarkedContent *content = [text_obj getMarkedContent]; int item_count = [content getItemCount]; // Get marked content property for (int i = 0; i < item_count; i++) { NSString *tag_name = [content getItemTagName:i]; int mcid = [content getItemMCID:i]; } ...
PDF 图层 (PDF Layer)
Foxit PDF SDK支持PDF图层,也称为可选内容组 (Optional Content Groups,OCG) 。用户可以选择性地查看或隐藏多图层PDF文档的不同层中的内容。多图层广泛用于许多应用领域,如CAD制图、地图、分层艺术品以及多语言文档等。
在Foxit PDF SDK中,PDF图层与图层节点相关联。要获取图层节点,用户应首先构建PDF FSLayerTree对象,然后调用函数FSLayerTree::getRootNode以获取整个图层树的根图层节点。另外,您可以从根图层节点开始枚举图层树中的所有节点。Foxit PDF SDK提供APIs用来获取/设置图层数据,查看或隐藏不同图层中的内容,设置图层名称,添加或删除图层,以及编辑图层。
Example:
如何创建一个PDF图层
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSLayerTree* layertree = [[FSLayerTree alloc] initWithDocument:doc]; FSLayerNode* root = [layertree getRootNode]; if ([root isEmpty]) { return -1; } ...
如何设置所有图层节点的信息
#include "FSPDFObjC.h" ... // Assuming FSPDFDoc doc has been loaded. ... FSLayerTree* layertree = [[FSLayerTree alloc] initWithDocument:doc]; FSLayerNode* root = [layertree getRootNode]; if ([root isEmpty]) { return -1; } setAllLayerNodesInformation(root); void setAllLayerNodesInformation(FSLayerNode* layer_node) { if ([layer_node hasLayer]) { [layer_node setDefaultVisible:YES]; [layer_node setExportUsage:FSLayerTreeStateUndefined]; [layer_node setViewUsage:FSLayerTreeStateOFF]; FSLayerPrintData* print_data = [[FSLayerPrintData alloc] initWithSubtype:@"subtype_print" print_state:FSLayerTreeStateON]; [layer_node setPrintUsage:print_data]; FSLayerZoomData* zoom_data = [[FSLayerZoomData alloc] initWithMin_factor:1 max_factor:10]; [layer_node setZoomUsage:zoom_data]; NSString* new_name = [NSString stringWithFormat:@"%@%@",@"[View_OFF_Print_ON_Export_Undefined]", [layer_node getName]]; [layer_node setName:new_name]; } int count = [layer_node getChildrenCount]; for (int i = 0; i < count; i++) { FSLayerNode* child = [layer_node getChild:i]; setAllLayerNodesInformation(child); } } ...
如何编辑图层树
#include "FSPDFObjC.h" ... FSLayerTree* layertree = [[FSLayerTree alloc] initWithDocument:doc]; FSLayerNode* root = [layertree getRootNode]; if ([root isEmpty]) { return -1; } int children_count = [root getChildrenCount]; [root removeChild:children_count-1]; FSLayerNode* child = [root getChild:children_count-2]; FSLayerNode* child0 = [root getChild:0]; [child moveTo:child0 index:0]; [child addChild:0 name:@"AddedLayerNode" has_Layer:YES]; [child addChild:0 name:@"AddedNode" has_Layer:NO]; ...
签名 (Signature)
PDF签名可用于创建和签署PDF文档的数字签名,从而保护文档内容的安全性并避免文档被恶意篡改。它可以让接收者确保其收到的文档是由签名者发送的,并且文档内容是完整和未被经篡的。Foxit PDF SDK提供APIs用来创建数字签名,验证签名的有效性,删除现有的数字签名,获取和设置数字签名的属性,显示签名和自定义签名表单域的外观。
备注:Foxit PDF SDK提供了默认签名回调函数,支持如下两种类型的signature filter 和 subfilter:
(1) filter: Adobe.PPKLite subfilter: adbe.pkcs7.detached
(2) filter: Adobe.PPKLite subfilter: adbe.pkcs7.sha1
如果您使用以上任意一种的signature filter 和 subfilter,您可以直接签名PDF文档和验证签名的有效性,而不需要注册自定义回调函数。
Example:
如何对PDF文档进行签名
#include "FSPDFObjC.h" ... NSString *filter = @"Adobe.PPKLite"; NSString *sub_filter = @"adbe.pkcs7.detached"; if (!use_default) { InitializeOpenssl(); sub_filter = @"adbe.pkcs7.sha1"; SignatureCallback *sig_callback = [[SignatureCallback alloc] initWithSubFilter:sub_filter]; [FSLibrary registerSignatureCallback:filter sub_filter:sub_filter signature_callback:sig_callback]; } [filter UTF8String], [sub_filter UTF8String]); FSPDFPage *pdf_page = [pdf_doc getPage:0]; // Add a new signature to first page. FSSignature *new_signature = AddSiganture(pdf_page, sub_filter); // Set filter and subfilter for the new signature. [new_signature setFilter:filter]; [new_signature setSubFilter:sub_filter]; // Sign the new signature. NSString *signed_pdf_path = [output_directory stringByAppendingPathComponent:@"signed_newsignature.pdf"]; if (use_default) signed_pdf_path = [output_directory stringByAppendingPathComponent:@"signed_newsignature_default_handler.pdf"]; NSString *cert_file_path = [input_path stringByAppendingPathComponent:@"foxit_all.pfx"]; NSString *cert_file_password = @"123456"; // Cert file path will be passed back to application through callback function FSSignatureCallback::Sign(). // In this demo, the cert file path will be used for signing in callback function FSSignatureCallback::Sign(). [new_signature startSign:cert_file_path cert_password:cert_file_password digest_algorithm:FSSignatureDigestSHA1 save_path:signed_pdf_path client_data:nil pause:nil]; // Open the signed document and verify the newly added signature (which is the last one). FSPDFDoc *signed_pdf_doc = [[FSPDFDoc alloc] initWithPath:signed_pdf_path]; FSErrorCode error_code = [signed_pdf_doc load:nil]; if (FSErrSuccess != error_code) { return; } // Get the last signature which is just added and signed. int sig_count = [signed_pdf_doc getSignatureCount]; FSSignature *signed_signature = [signed_pdf_doc getSignature:sig_count - 1]; // Verify the signature. [signed_signature startVerify:nil pause:nil]; ...
如何实现签名的回调函数
#include "FSPDFObjC.h" #include "openssl/rsa.h" #include "openssl/evp.h" #include "openssl/objects.h" #include "openssl/x509.h" #include "openssl/err.h" #include "openssl/pem.h" #include "openssl/ssl.h" #include "openssl/pkcs12.h" #include "openssl/rand.h" #include "openssl/pkcs7.h" #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <netdb.h> #include <string> // some base type declarations typedef std::string String; // Used for implementing SignatureCallback. class DigestContext { public: DigestContext(id<FSFileReaderCallback> file_read_callback, NSArray<NSNumber *> *byte_range_array) : file_read_callback_(file_read_callback), byte_range_array_(byte_range_array) {} ~DigestContext() {} id<FSFileReaderCallback> GetFileReadCallback() { return file_read_callback_; } NSUInteger GetByteRangeSize() { return byte_range_array_.count; } unsigned int GetByteRangeElement(NSUInteger index) { if (!byte_range_array_) return 0; return [byte_range_array_[index] unsignedIntValue]; } SHA_CTX sha_ctx_; protected: id<FSFileReaderCallback> file_read_callback_; NSArray<NSNumber *> *byte_range_array_; }; // Implementation of pdf::SignatureCallback class SignatureCallbackImpl { public: SignatureCallbackImpl(std::string subfilter) : sub_filter_(subfilter), digest_context_(NULL) {} ~SignatureCallbackImpl(); void Release() { delete this; } bool StartCalcDigest(id<FSFileReaderCallback> file, NSArray<NSNumber *> *byte_range_array, FSSignature *signature, const void *client_data); FSProgressiveState ContinueCalcDigest(const void *client_data, id<FSPauseCallback> pause); NSData *GetDigest(const void *client_data); NSData *Sign(const void *digest, uint32 digest_length, std::string cert_path, std::string password, FSSignatureDigestAlgorithm digest_algorithm, void *client_data); FSSignatureStates VerifySigState(const void *digest, uint32 digest_length, const void *signed_data, uint32 signed_data_len, void *client_data); bool IsNeedPadData() { return false; } protected: bool GetTextFromFile(unsigned char *plainString); unsigned char *PKCS7Sign(std::string cert_file_path, String cert_file_password, String plain_text, int &signed_data_size); bool PKCS7VerifySignature(String signed_data, String plain_text); bool ParseP12File(std::string cert_file_path, String cert_file_password, EVP_PKEY **pkey, X509 **x509, STACK_OF(X509) * *ca); ASN1_INTEGER *CreateNonce(int bits); private: std::string sub_filter_; DigestContext *digest_context_; std::string cert_file_path_; std::string cert_file_password_; }; #define FREE_CERT_KEY if(pkey)\ EVP_PKEY_free(pkey);\ if(x509)\ X509_free(x509);\ if(ca)\ sk_X509_free(ca); void InitializeOpenssl() { // SSLeay_add_all_algorithms(); } SignatureCallbackImpl::~SignatureCallbackImpl() { if (digest_context_) { delete digest_context_; digest_context_ = NULL; } } bool SignatureCallbackImpl::GetTextFromFile(unsigned char *file_buffer) { if (!digest_context_ || !digest_context_->GetFileReadCallback()) return false; id<FSFileReaderCallback> file_read = digest_context_->GetFileReadCallback(); NSData *data = [file_read readBlock:digest_context_->GetByteRangeElement(0) size:digest_context_->GetByteRangeElement(1)]; [data getBytes:file_buffer length:data.length]; data = [file_read readBlock:digest_context_->GetByteRangeElement(2) size:digest_context_->GetByteRangeElement(3)]; [data getBytes:file_buffer + (digest_context_->GetByteRangeElement(1) - digest_context_->GetByteRangeElement(0)) length:data.length]; return true; } bool SignatureCallbackImpl::StartCalcDigest(id<FSFileReaderCallback> file, NSArray<NSNumber *> *byte_range_array, FSSignature *signature, const void *client_data) { if (digest_context_) { delete digest_context_; digest_context_ = NULL; } digest_context_ = new DigestContext(file, byte_range_array); if (!SHA1_Init(&digest_context_->sha_ctx_)) { delete digest_context_; digest_context_ = NULL; return false; } return true; } FSProgressiveState SignatureCallbackImpl::ContinueCalcDigest(const void *client_data, id<FSPauseCallback> pause) { if (!digest_context_) return FSProgressiveError; uint32 file_length = digest_context_->GetByteRangeElement(1) + digest_context_->GetByteRangeElement(3); unsigned char *file_buffer = (unsigned char *) malloc(file_length); if (!file_buffer || !GetTextFromFile(file_buffer)) return FSProgressiveError; SHA1_Update(&digest_context_->sha_ctx_, file_buffer, file_length); free(file_buffer); return FSProgressiveFinished; } NSData *SignatureCallbackImpl::GetDigest(const void *client_data) { if (!digest_context_) return nil; unsigned char *md = reinterpret_cast<unsigned char *>(OPENSSL_malloc((SHA_DIGEST_LENGTH) * sizeof(unsigned char))); if (1 != SHA1_Final(md, &digest_context_->sha_ctx_)) return nil; NSData *digest = [NSData dataWithBytes:reinterpret_cast<const void *>(md) length:SHA_DIGEST_LENGTH]; OPENSSL_free(md); return digest; } NSData *SignatureCallbackImpl::Sign(const void *digest, uint32 digest_length, std::string cert_path, std::string password, FSSignatureDigestAlgorithm digest_algorithm, void *client_data) { if (!digest_context_) return nil; String plain_text; if ("adbe.pkcs7.sha1" == sub_filter_) { plain_text = String((const char *) digest, digest_length); } int signed_data_length = 0; unsigned char *signed_data_buffer = PKCS7Sign(cert_path, password, plain_text, signed_data_length); if (!signed_data_buffer) return nil; NSData *signed_data = [NSData dataWithBytes:(const void *) signed_data_buffer length:signed_data_length]; free(signed_data_buffer); return signed_data; } FSSignatureStates SignatureCallbackImpl::VerifySigState(const void *digest, uint32 digest_length, const void *signed_data, uint32 signed_data_len, void *client_data) { // Usually, the content of a signature field is contain the certification of signer. // But we can't judge this certification is trusted. // For this example, the signer is ourself. So when using api PKCS7_verify to verify, // we pass NULL to it's parameter <i>certs</i>. // Meanwhile, if application should specify the certificates, we suggest pass flag PKCS7_NOINTERN to // api PKCS7_verify. if (!digest_context_) return FSSignatureStateVerifyErrorData; String plain_text; unsigned char *file_buffer = NULL; if ("adbe.pkcs7.sha1" == sub_filter_) { plain_text = String(reinterpret_cast<const char *>(digest), digest_length); } else { return FSSignatureStateUnknown; } String signed_data_str = String(reinterpret_cast<const char *>(signed_data), signed_data_len); bool ret = PKCS7VerifySignature(signed_data_str, plain_text); if (file_buffer) free(file_buffer); return ret ? FSSignatureStateVerifyNoChange: FSSignatureStateVerifyChange; } ASN1_INTEGER *SignatureCallbackImpl::CreateNonce(int bits) { unsigned char buf[20]; int len = (bits - 1) / 8 + 1; // Generating random byte sequence. if (len > (int) sizeof(buf)) { return NULL; } if (RAND_bytes(buf, len) <= 0) { return NULL; } // Find the first non-zero byte and creating ASN1_INTEGER object. int i = 0; for (i = 0; i < len && !buf[i]; ++i) ; ASN1_INTEGER *nonce = NULL; if (!(nonce = ASN1_INTEGER_new())) { ASN1_INTEGER_free(nonce); return NULL; } OPENSSL_free(nonce->data); // Allocate at least one byte. nonce->length = len - i; if (!(nonce->data = reinterpret_cast<unsigned char *>(OPENSSL_malloc(nonce->length + 1)))) { ASN1_INTEGER_free(nonce); return NULL; } memcpy(nonce->data, buf + i, nonce->length); return nonce; } bool SignatureCallbackImpl::ParseP12File(std::string cert_file_path, String cert_file_password, EVP_PKEY **pkey, X509 **x509, STACK_OF(X509) * *ca) { FILE *file = NULL; #if defined(_WIN32) || defined(_WIN64) _wfopen_s(&file, cert_file_path, @"rb"); #else file = fopen(cert_file_path.c_str(), "rb"); #endif // defined(_WIN32) || defined(_WIN64) if (!file) { return false; } PKCS12 *pkcs12 = d2i_PKCS12_fp(file, NULL); fclose(file); if (!pkcs12) { return false; } if (!PKCS12_parse(pkcs12, cert_file_password.c_str(), pkey, x509, ca)) { return false; } PKCS12_free(pkcs12); if (!pkey) return false; return true; } unsigned char *SignatureCallbackImpl::PKCS7Sign(std::string cert_file_path, String cert_file_password, String plain_text, int &signed_data_size) { PKCS7 *p7 = NULL; EVP_PKEY *pkey = NULL; X509 *x509 = NULL; STACK_OF(X509) *ca = NULL; if (!ParseP12File(cert_file_path, cert_file_password, &pkey, &x509, &ca)) return NULL; p7 = PKCS7_new(); PKCS7_set_type(p7, NID_pkcs7_signed); PKCS7_content_new(p7, NID_pkcs7_data); // Application should not judge the sign algorithm with the content's length. // Here, just for convenient; if (plain_text.size() > 32) PKCS7_ctrl(p7, PKCS7_OP_SET_DETACHED_SIGNATURE, 1, NULL); PKCS7_SIGNER_INFO *signer_info = PKCS7_add_signature(p7, x509, pkey, EVP_sha1()); signer_info = NULL; PKCS7_add_certificate(p7, x509); # define CHECKED_STACK_OF(type, p) \ ((_STACK*) (1 ? p : (STACK_OF(type)*)0)) for (int i = 0; i < sk_num(CHECKED_STACK_OF(X509, ca)); i++) PKCS7_add_certificate(p7, (X509 *) sk_value(CHECKED_STACK_OF(X509, ca), i)); // Set source data to BIO. BIO *p7bio = PKCS7_dataInit(p7, NULL); BIO_write(p7bio, plain_text.c_str(), (int) plain_text.size()); PKCS7_dataFinal(p7, p7bio); FREE_CERT_KEY; BIO_free_all(p7bio); // Get signed data. unsigned long der_length = i2d_PKCS7(p7, NULL); unsigned char *der = reinterpret_cast<unsigned char *>(malloc(der_length)); memset(der, 0, der_length); unsigned char *der_temp = der; i2d_PKCS7(p7, &der_temp); PKCS7_free(p7); signed_data_size = (int) der_length; return (unsigned char *) der; } bool SignatureCallbackImpl::PKCS7VerifySignature(String signed_data, String plain_text) { // Retain PKCS7 object from signed data. BIO *vin = BIO_new_mem_buf((void *) signed_data.c_str(), (int) signed_data.size()); PKCS7 *p7 = d2i_PKCS7_bio(vin, NULL); STACK_OF(PKCS7_SIGNER_INFO) *sk = PKCS7_get_signer_info(p7); int sign_count = sk_PKCS7_SIGNER_INFO_num(sk); // int length = 0; bool bSigAppr = false; // unsigned char *p = NULL; for (int i = 0; i < sign_count; i++) { PKCS7_SIGNER_INFO *sign_info = sk_PKCS7_SIGNER_INFO_value(sk, i); BIO *p7bio = BIO_new_mem_buf((void *) plain_text.c_str(), (int) plain_text.size()); X509 *x509 = PKCS7_cert_from_signer_info(p7, sign_info); x509 = NULL; if (1 == PKCS7_verify(p7, NULL, NULL, p7bio, NULL, PKCS7_NOVERIFY)) bSigAppr = true; BIO_free(p7bio); } PKCS7_free(p7); BIO_free(vin); return bSigAppr; } ...
长期签名验证(LTV,Long term validation)
从7.0版本开始,Foxit PDF SDK提供了API接口进行长期签名验证,主要用于解决已经过期的签名的验证问题。LTV需要DSS(Document Security Store),其包含了签名的验证信息,以及需要文档时间戳签名DTS (Document Timestamp Signature),其是time stamp类型的signature。
为了支持LTV,Foxit PDF SDK提供了:
- 支持添加time stamp类型的signature,以及提供了sub filter “ETSI.RFC3161” 的默认签名回调。
- TimeStampServerMgr 和 TimeStampServer 类,用于time stamp的server设置和管理等。sub filter “ETSI.RFC3161” 的默认签名回调将会使用默认的time stamp server。
- LTVVerifier类,其提供了验证签名和向文档中添加DSS信息的功能。同时,也提供了LTVVerifier所需的一个基本默认的回调函数RevocationCallback。
以下仅以使用SDK默认的sub filter “ETSI.RFC3161” 签名回调及默认的RevocationCallback 为例来说明如何进行长期签名验证。有关更详细的信息,请参阅下载包中 “\examples\simple_demo”目录下的 “ltv” demo。
Example:
如何使用SDK默认的sub filter “ETSI.RFC3161” 签名回调及默认的RevocationCallback进行长期签名验证
#include "FSPDFObjC.h" // Initialize time stamp server manager, add and set a default time stamp server, which will be used by default signature callback for time stamp signature. [FSTimeStampServerMgr initialize]; FSTimeStampServer* timestamp_server = [FSTimeStampServerMgr addServer:server_name server_url:server_url user_name:user_name password:password]; [FSTimeStampServerMgr setDefaultServer:timestamp_server]; // Assume that "signed_pdf_path" represents a signed PDF document which contains signed signature. FSPDFDoc *pdf_doc = [[FSPDFDoc alloc] initWithPath:signed_pdf_path]; [pdf_doc startLoad:nil is_cache_stream:NO pause:nil]; { // Use LTVVerifier to verify and add DSS. FSLTVVerifier* ltv_verifier = [[FSLTVVerifier alloc] initWithDocument:pdf_doc is_verify_signature:YES use_expired_tst:NO ignore_doc_info:NO time_type:FSLTVVerifierSignatureTSTTime]; // Set verifying mode which is necessary. [ltv_verifier setVerifyMode:FSLTVVerifierVerifyModeETSI]; FSSignatureVerifyResultArray* sig_verify_result_array = [ltv_verifier verify]; unsigned long array_size = [sig_verify_result_array getSize]; for (size_t i = 0; i < array_size; i++) { FSSignatureVerifyResult* sig_verify_result = [sig_verify_result_array getAt:i]; // ltv state would be FSSignatureVerifyResultLTVStateNotEnable here. FSSignatureVerifyResultLTVState ltv_state = [sig_verify_result getLTVState]; if (([sig_verify_result getSignatureState] & FSSignatureStateVerifyValid) == FSSignatureStateVerifyValid) [ltv_verifier addDSS:sig_verify_result]; } } // Add a time stamp signature as DTS and sign it. "saved_ltv_pdf_path" represents the newly saved signed PDF file. FSPDFPage *pdf_page = [pdf_doc getPage:0]; // The new time stamp signature will have default filter name "Adobe.PPKLite" and default subfilter name "ETSI.RFC3161". FSRectF* empty_rect = [[FSRectF alloc] init]; FSSignature* timestamp_signature = [pdf_page addSignatureWithSignatureType:empty_rect field_name:@"" signature_type:FSSignatureSignatureTypeTimeStamp]; [timestamp_signature startSign:@"" cert_password:@"" digest_algorithm:FSSignatureDigestSHA1 save_path:saved_ltv_pdf_path client_data:nil pause:nil]; // Then use LTVVeirfier to verify the new signed PDF file. FSPDFDoc *check_pdf_doc = [[FSPDFDoc alloc] initWithPath:saved_ltv_pdf_path]; [check_pdf_doc startLoad:nil is_cache_stream:NO pause:nil]; { // Use LTVVerifier to verify FSLTVVerifier* ltv_verifier = [[FSLTVVerifier alloc] initWithDocument:pdf_doc is_verify_signature:YES use_expired_tst:NO ignore_doc_info:NO time_type:FSLTVVerifierSignatureTSTTime]; // Set verifying mode which is necessary. [ltv_verifier setVerifyMode:FSLTVVerifierVerifyModeETSI]; FSSignatureVerifyResultArray* sig_verify_result_array = [ltv_verifier verify]; unsigned long array_size = [sig_verify_result_array getSize]; for (size_t i = 0; i < array_size; i++) { FSSignatureVerifyResult* sig_verify_result = [sig_verify_result_array getAt:i]; // ltv state would be FSSignatureVerifyResultLTVStateEnable here. FSSignatureVerifyResultLTVState ltv_state = [sig_verify_result getLTVState]; ... // User can get other information from FSSignatureVerifyResult. } } // Destroy time stamp server manager when everything is done. [FSTimeStampServerMgr destroy];
PAdES
从7.0版本开始,Foxit PDF SDK支持PAdES (PDF Advanced Electronic Signature),其是CAdES签名在PDF中的应用。CAdES是高级数字签名的一种新标准,其默认subfilter是”ETSI.CAdES.detached”。PAdES 签名分为四个等级:B-B, B-T, B-LT, 和 B-LTA。
- B-B: 包含基本的必须出现的属性。
- B-T: 在B-B的基础上,包含文档时间戳或者签名时间戳,来为存在的签名提供可信的时间。
- B-LT: 在B-T的基础上,包含DSS/VRI,来提供证书和吊销信息。
- B-LTA: 在B-LT的基础上,为存在的吊销信息提供可信时间DTS。
Foxit PDF SDK提供了subfilter 为 “ETSI.CAdES.detached” 的默认签名回调,可用来签名和验证subfilter 为 “ETSI.CAdES.detached” 的签名。还提供了TimeStampServerMgr 和 TimeStampServer类,用于设置和管理time stamp server。subfilter “ETSI.CAdES.detached” 的默认签名回调将会使用默认的time stamp server。
Foxit PDF SDK 提供了从签名中获取PAdES不同等级的方法,应用层面也可以根据各个等级的要求来判定所属等级。有关如何在PDF文档中添加、签名、和验证PAdES签名的更详细信息,请参阅下载包中 “\examples\simple_demo”目录下的 “pades” demo。
PDF 行为 (PDF Action)
PDF Action代表PDF操作类的基类。Foxit PDF SDK提供了APIs用来创建一系列行为,并获取行为句柄,比如embedded goto action, JavaScript action, named action 和 launch action等。
Example:
如何创建一个URI 行为并将其插入到link注释
#include "FSPDFObjC.h" ... // Add link annotation FSRectF* annot_rect = [[FSRectF alloc] initWithLeft1:350 bottom1:350 right1:380 top1:400]; FSLink* link = [[FSLink alloc] initWithAnnot:[page addAnnot:FSAnnotLink rect:annot_rect]]; [link setHighlightingMode:FSAnnotHighlightingToggle]; // Add action for link annotation FSPDFDoc* doc = [page getDocument]; FSURIAction* action = [[FSURIAction alloc] initWithAction:[FSAction create:doc action_type:FSActionTypeURI]]; [action setTrackPositionFlag:YES]; [action setURI:@"www.foxitsoftware.com"]; [link setAction:action]; // Appearance should be reset. [link resetAppearanceStream]; ...
如何创建一个GoTo 行为并将其插入到link注释
#include "FSPDFObjC.h" ... // Assuming FSPDFPage page has been loaded. ... // Add link annotation FSRectF* annot_rect = [[FSRectF alloc] initWithLeft1:350 bottom1:350 right1:380 top1:400]; FSLink* link = [[FSLink alloc] initWithAnnot:[page addAnnot:FSAnnotLink rect:annot_rect]]; [link setHighlightingMode:FSAnnotHighlightingToggle]; // Add action for link annotation FSPDFDoc* doc = [page getDocument]; FSGotoAction* action = [[FSGotoAction alloc] initWithAction:[FSAction create:doc action_type: FSActionTypeGoto]]; action.destination = [FSDestination createFitPage:doc page_index:0]; // Appearance should be reset. [link resetAppearanceStream]; ...
JavaScript
创建JavaScript是为了将Web页面的相关处理从服务器转移到基于Web的应用程序的客户端上。 Foxit PDF SDK JavaScript以JavaScript语言的形式实现新对象及其附带方法和属性的扩展。其使开发人员能够管理文档安全性,与数据库通信,处理文件附件以及操作PDF文件,因此其表现为交互式、web表单等。
JavaScript action是一种由JavaScript解释器编译和执行脚本的动作。类FSJavaScriptAction派生自FSAction, 并提供接口用来获取/设置JavaScript action数据。
在附录中可以查看Foxit PDF SDK 支持的JavaScript方法和属性列表。
Example:
如何添加文档级的JavaScript 动作
#include "FSPDFObjC.h" ... // Load Document doc. ... FSJavaScriptAction *javascript_action = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:doc action_type:FSActionTypeJavaScript]]; javascipt_action.script = @"app.alert(\"Hello Foxit \");"; FSAdditionalAction *additional_act = [[FSAdditionalAction alloc] initWithDoc:doc pdf_dict:nil]; [additional_act setAction:FSAdditionalActionTriggerDocWillClose action:javascipt_action]; [additional_act doJSAction:FSAdditionalActionTriggerDocWillClose]; ...
如何添加注释级的JavaScript 动作
#include "FSPDFObjC.h" ... // Load Document and get a widget annotation. ... FSJavaScriptAction *javascript_action = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:[form getDocument] action_type:FSActionTypeJavaScript]]; javascipt_action.script = @"app.alert(\"Hello Foxit \");"; FSAdditionalAction *additional_act = [[FSAdditionalAction alloc] initWithAnnot:widget_annot]; [additional_act setAction:FSAdditionalActionTriggerAnnotMouseButtonPressed action:javascipt_action]; ... #include "FSPDFObjC.h" ... // Load Document and get a widget annotation. ... FSJavaScriptAction *javascript_action = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:[page getDocument] action_type:FSActionTypeJavaScript]]; javascipt_action.script = @"app.alert(\"Hello Foxit \");"; FSAdditionalAction *additional_act = [[FSAdditionalAction alloc] initWithAnnot:widget_annot]; [additional_act setAction:FSAdditionalActionTriggerAnnotMouseButtonPressed action:javascipt_action]; [additional_act doJSAction:FSAdditionalActionTriggerAnnotMouseButtonPressed]; ...
如何添加表单级的JavaScript 动作
#include "FSPDFObjC.h" ... // Load Document and get a form field. ... // Add text field. FSControl *control = [form addControl:page field_name:@"Text Field0" field_type:FSFieldTypeTextField rect:[[FSRectF alloc] initWithLeft1:50.0 bottom1:600 right1:90 top1:640]]; [control getField].value = @"3"; // Update text field's appearance. [[control getWidget] resetAppearanceStream]; FSControl *control1 = [form addControl:page field_name:@"Text Field1" field_type:FSFieldTypeTextField rect:[[FSRectF alloc] initWithLeft1:100 bottom1:600 right1:140 top1:640]]; [control1 getField].value = @"23"; // Update text field's appearance. [[control1 getWidget] resetAppearanceStream]; FSControl *control2 = [form addControl:page field_name:@"Text Field2" field_type:FSFieldTypeTextField rect:[[FSRectF alloc] initWithLeft1:150 bottom1:600 right1:190 top1:640]]; FSJavaScriptAction *javascipt_action = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:[form getDocument] action_type:FSActionTypeJavaScript]]; javascipt_action.script = @"AFSimple_Calculate(\"SUM\", new Array (\"Text Field0\", \"Text Field1\"));"; FSField *field2 = [control2 getField]; FSAdditionalAction *additional_act = [[FSAdditionalAction alloc] initWithField:field2]; [additional_act setAction:FSAdditionalActionTriggerFieldRecalculateValue action:javascipt_action]; // Update text field's appearance. [[control2 getWidget] resetAppearanceStream]; ...
如何使用JavaScript向PDF页面添加一个新的注释
#include "FSPDFObjC.h" ... // Load Document and get form field, construct a FSForm object and a FSFiller object. ... FSJavaScriptAction *javascipt_action = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:[form getDocument] action_type:FSActionTypeJavaScript]]; javascipt_action.script = @"var annot = this.addAnnot({ page : 0, type : \"Square\", rect : [ 0, 0, 100, 100 ], name : \"UniqueID\", author : \"A. C. Robat\", contents : \"This section needs revision.\" });"; FSAdditionalAction *additional_act = [[FSAdditionalAction alloc] initWithField:field]; [additional_act setAction:FSAdditionalActionTriggerAnnotCursorEnter action:javascipt_action]; [additional_act doJSAction:FSAdditionalActionTriggerAnnotCursorEnter]; ...
如何使用JavaScript获取/设置注释的属性 (strokeColor, fillColor, readOnly, rect, type等)
#include "FSPDFObjC.h" ... // Load Document and get form field, construct a FSForm object and a FSFiller object. ... // Get properties of annotations. FSJavaScriptAction *javascipt_action = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:[form getDocument] action_type:FSActionTypeJavaScript]]; javascipt_action.script = @"var ann = this.getAnnot(0, \" UniqueID \"); if (ann != null) { console.println(\"Found it! type: \" + ann.type); console.println(\"readOnly: \" + ann.readOnly); console.println(\"strokeColor: \" + ann.strokeColor);console.println(\"fillColor: \" + ann.fillColor); console.println(\"rect: \" + ann.rect);}"; FSAdditionalAction *additional_act = [[FSAdditionalAction alloc] initWithField:field]; [additional_act setAction:FSAdditionalActionTriggerAnnotCursorEnter action:javascipt_action]; [additional_act doJSAction:FSAdditionalActionTriggerAnnotCursorEnter]; // Set properties of annotations (only take strokeColor as an example). FSJavaScriptAction *javascipt_action1 = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:[form getDocument] action_type:FSActionTypeJavaScript]]; javascipt_action1.script = @"var ann = this.getAnnot(0, \"UniqueID\"); if (ann != null) { ann.strokeColor = color.blue; }"; FSAdditionalAction *additional_act1 = [[FSAdditionalAction alloc] initWithField:field1]; [additional_act1 setAction:FSAdditionalActionTriggerAnnotCursorEnter action:javascipt_action1]; [additional_act1 doJSAction:FSAdditionalActionTriggerAnnotCursorEnter]; ...
如何使用JavaScript销毁注释
#include "FSPDFObjC.h" ... // Load Document and get form field, construct a FSForm object and a FSFiller object. ... FSJavaScriptAction *javascipt_action = [[FSJavaScriptAction alloc] initWithAction:[FSAction create:[form getDocument] action_type:FSActionTypeJavaScript]]; javascipt_action.script = @"var ann = this.getAnnot(0, \" UniqueID \"); if (ann != null) { ann.destroy(); }"; FSAdditionalAction *additional_act = [[FSAdditionalAction alloc] initWithField:field]; [additional_act setAction:FSAdditionalActionTriggerAnnotCursorEnter action:javascipt_action]; [additional_act doJSAction:FSAdditionalActionTriggerAnnotCursorEnter]; ...
密文 (Redaction)
密文是一种在保持文档布局的同时删除文档中敏感信息的功能。它可以帮助用户永久删除PDF文档中的可见文本和图片,以保护一些保密信息,如社会安全号码、信用卡信息、产品发布日期等等。
密文是一种标记注释,用于标记PDF文件的某些内容,标记的内容在注释被应用后会被删除。
执行密文,您可以使用如下的APIs:
- 调用FSRedaction 创建一个redaction模块。如果在函数FSLibrary::initialize中使用的license授权信息没有定义”Redaction”,则表示用户没有权限使用redaction相关的函数,并且构造函数会抛出FSErrInvalidLicense异常。
- 然后调用FSRedaction::markRedactAnnot创建一个redaction对象,对需要进行redaction的页面内容 (文本对象、图片对象和路径对象) 进行标记。
- 最后调用FSRedaction.apply在标记区域应用redaction:永久删除标记区域的文本和图形。
备注:要使用redaction功能,请确保授权key文件中包含 ‘Redaction’ 模块。
Example:
如何将PDF文档第一页中的文本 “PDF” 设置为密文
#include "FSPDFObjC.h" ... // Assuming that FSPDFDoc doc has been loaded. ... FSRedaction *redaction = [[FSRedaction alloc] initWithDocument:doc]; // Parse PDF page. FSPDFPage *page = [doc getPage:0]; [page startParse:FSPDFPageParsePageNormal pause:nil is_reparse:NO]; FSTextPage *text_page = [[FSTextPage alloc] initWithPage:page flags:FSTextPageParseTextNormal]; FSTextSearch *text_search = [[FSTextSearch alloc] initWithText_page:text_page]; [text_search setPattern:@"PDF"]; FSRectFArray *rect_array = [[FSRectFArray alloc] init]; while ([text_search findNext]) { FSRectFArray *matchrects = [text_search getMatchRects]; for (int z = 0; z < [matchrects getSize]; z++) { FSRectF *temp_rect = [matchrects getAt:z]; [rect_array add:temp_rect]; } } if ([rect_array getSize] > 0) { FSRedact *redact = [redaction markRedactAnnot:page rects:rect_array]; [redact resetAppearanceStream]; [doc saveAs:[output_directory stringByAppendingString:@"AboutFoxit_redected_default.pdf"] save_flags:FSPDFDocSaveFlagNormal]; // set border color to Green. [redact setBorderColor:0x00FF00]; // set fill color to Blue. [redact setFillColor:0x0000FF]; // set rollover fill color to Red. [redact setApplyFillColor:0xFF0000]; [redact resetAppearanceStream]; [doc saveAs:[output_directory stringByAppendingString:@"AboutFoxit_redected_setColor.pdf"] save_flags:FSPDFDocSaveFlagNormal]; [redact setOpacity:0.5]; [redact resetAppearanceStream]; [doc saveAs:[output_directory stringByAppendingString:@"AboutFoxit_redected_setOpacity.pdf"] save_flags:FSPDFDocSaveFlagNormal]; }
对比 (Comparison)
对比功能可以帮助用户查看两个版本的PDF文档之间的差异。Foxit PDF SDK提供APIs用以逐页比较两个PDF文档,并返回文档间的差异。
差异可以定义为三种类型:删除、插入和替换。您可以将这些差异保存为PDF文件并标记为注释。
备注:要使用对比功能,请确保授权key文件中包含 ‘Comparison’ 模块。
Example:
如何对比两个PDF文档,并将差异保存到一个PDF文件中
#include "FSPDFObjC.h" ... FSPDFDoc *base_doc = [[FSPDFDoc alloc] initWithPath:@"input_base_file"]; errorCode = [base_doc load:@""]; if (errorCode != FSErrSuccess) { return -1; } FSPDFDoc *compared_doc = [[FSPDFDoc alloc] initWithPath:@"input_compared_file"]; errorCode = [compared_doc load:@""]; if (errorCode != FSErrSuccess) { return -1; } FSComparison* comparison = [[FSComparison alloc] initWithBase_doc:base_doc compared_doc:compared_doc]; // Start comparison. FSCompareResults* result = [comparison doCompare:0 compared_page_index:0 compare_flags:FSComparisonCompareTypeText]; int oldInfoSize = [result.results_base_doc getSize]; int newInfoSize = [result.results_compared_doc getSize]; FSPDFPage* page = [compared_doc getPage:0]; for (int i=0; i<newInfoSize; i++) { FSCompareResultInfo* item = [result.results_compared_doc getAt:i]; FSCompareResultInfoCompareResultType type = item.type; if (type == FSCompareResultInfoCompareResultTypeDeleteText) { NSString* res_string = [NSString stringWithFormat:@"\"%@\"", item.diff_contents]; // Add stamp to mark the "delete" type differences between the two documents. createDeleteTextStamp(page, item.rect_array, 0xff0000, res_string, @"Compare : Delete", @"Text"); } else if (type == FSCompareResultInfoCompareResultTypeInsertText) { NSString* res_string = [NSString stringWithFormat:@"\"%@\"", item.diff_contents]; // Highlight the "insert" type differences between the two documents. createHighlightRect(page, item.rect_array, 0x0000ff, res_string, @"Compare : Insert", @"Text"); } else if (type == FSCompareResultInfoCompareResultTypeReplaceText) { NSString* res_string = [NSString stringWithFormat:@"[Old]: \"%@\"\r\n[New]: \"%@\"",[result.results_base_doc getAt:i].diff_contents,item.diff_contents]; // Highlight the "replace" type differences between the two documents. createHighlightRect(page, item.rect_array, 0xe7651a, res_string, @"Compare : Replace", @"Text"); } } // Save the comparison result to a PDF file. [compared_doc saveAs:[output_directory stringByAppendingString:@"result.pdf"] save_flags:FSPDFDocSaveFlagNormal];
备注:对于createDeleteTextStamp 和 createHighlightRect函数,请参考SDK包中“\examples\simple_demo”文件夹下的 “pdfcompare” demo。
Compliance
PDF Compliance
Foxit PDF SDK 支持PDF版本标准化转换,当前支持的版本有PDF 1.3, PDF 1.4, PDF 1.5, PDF 1.6 和 PDF 1.7。当转换到PDF 1.3版本时,如果源文档含有透明度的数据,则其会被转换到PDF 1.4版本而不是PDF 1.3版本 (PDF 1.3版本不支持透明度);如果源文档不含有任何透明度的数据,则会按预期转换到PDF 1.3版本。
PDF/A Compliance
PDF/A是一种ISO标准的PDF文件格式版本,用于电子文档的存档和长期保存。PDF/A与PDF的不同之处在于PDF/A禁用了PDF中不适合长期存档的特性,比如字体链接 (与嵌入字体相对)、加密、JavaScript、音频和视频等。
Foxit PDF SDK提供APIs用以将PDF转换为符合PDF/A标准的文档,或验证PDF是否符合PDF/A标准。支持的PDF/A标准包括PDF/A-1a、PDF/A-1b、PDF/A-2a、PDF/A-2b、PDF/A-2u、PDF/A-3a、PDF/A-3b、PDF/A-3u (ISO 19005- 1, 19005 -2 和 19005-3)。
本节将介绍如何设置相关环境以运行 ‘compliance’ demo。
系统需求
平台: Windows, Linux, Mac
开发语言: C, C++, Java, C#, Objective-C
License Key: license key中包含 ‘Compliance’ 模块的权限
SDK 版本: Foxit PDF SDK 6.4 或更高版本 (对于 PDF Compliance,则需要Foxit PDF SDK 7.1或更高版本)
Compliance 资源文件
请联系Foxit支持团队或者销售团队以获取Compliance资源文件包。
获取到资源文件包后,将其解压到所需目录 (比如,解压到 “compliance/mac” 的目录),然后您将看到Compliance的资源文件如下:
For Mac:
如何运行compliance demo
Foxit PDF SDK提供了一个compliance demo用来展示如何使用Foxit PDF SDK验证PDF文档是否符合PDF/A标准,如何将PDF转换为符合PDF/A标准的文档,以及如何进行PDF版本标准化转换。该demo位于 “\examples\simple_demo\compliance” 文件夹下。
构建一个compliance资源目录
在运行compliance demo之前,您需要首先构建一个compliance资源目录,然后将该目录的路径传给FSComplianceEngine::initialize接口用来初始化compliance引擎。
对于Mac平台,您可以直接使用 “compliance/mac” 资源文件夹作为compliance的资源目录。
将 “compliance” 目录下的 “mac” 文件夹拷贝到 “\examples\simple_demo\compliance” 文件夹下,如下图所示。然后按照以下内容配置demo。
配置demo
在 “\examples\simple_demo\compliance\compliance.m” 文件中配置demo。
指定compliance资源目录
如下所示,添加compliance资源目录,用以初始化compliance引擎。
备注:如果您已经购买了授权的license key (包含 ‘Compliance’ 模块的权限),Foxit销售团队会给您额外发送一个unlock code,用以初始化compliance引擎。
(可选) 为compliance engine设置语言
FSComplianceEngine::setLanguage 函数用来为compliance引擎设置语言。默认的语言是英语,所有支持的语言如下所示:
“Czech”, “Danish”, “Dutch”, “English”, “French”, “Finnish”, “German”, “Italian”, “Norwegian”, “Polish”, “Portuguese”, “Spanish”, “Swedish”, “Chinese-Simplified”, “Chinese-Traditional”, “Japanese”, “Korean”.
例如,取消注释 FSComplianceEngine::setLanguage 函数,并将语言设置为 “Chinese-Simplified”。
(可选) 为compliance 引擎设置临时文件夹
FSComplianceEngine::setTempFolderPath函数用于设置一个临时文件夹以存储处理过程 (如验证或转换) 中生成的文件。如果此函数未设置自定义的临时文件夹,则将使用系统中默认的临时文件夹。
例如,取消注释 FSComplianceEngine::setTempFolderPath 函数,并将路径设置为”D:/compliance_temp” (假设您已经在 “\examples\simple_demo\compliance” 文件夹下创建了一个名为 “compliance_temp” 的文件夹)。
运行demo
该demo
- 验证PDF (“\examples\simple_demo\input_files\AboutFoxit.pdf”) 是否符合PDF/A-1a标准,并将此文档转换为符合PDF/A-1a标准的文档。
- 将PDF (“\examples\simple_demo\input_files\AF_ImageXObject_FormXObject.pdf”) 分别转换为PDF-1.4和PDF-1.7版本。
成功运行demo后,在 “\examples\simple_demo\output_files\compliance” 文件夹下将生成相应的输出文档,如下图所示:
优化 (Optimization)
优化功能可以通过压缩PDF文件中的图片、删除冗余数据,以及丢弃无用的用户数据等方式有效地减少PDF文件的大小,从而节省磁盘空间以及便于PDF文件的传输和存储。从7.0版本开始,优化模块提供了压缩PDF文件中彩色、灰度和黑白图像的方法,用于减少PDF文件的大小。
备注:要使用优化功能,请确保授权key文件中包含 ‘Optimization’模块。
Example:
如何通过压缩PDF文件中的彩色、灰度和黑白图像来减少PDF文件的大小
#include "FSPDFObjC.h" ... NSString *input_file = [input_path stringByAppendingString:@"[Optimize]ImageCompression.pdf"]; FSPDFDoc *doc = [[FSPDFDoc alloc] initWithPath:input_file]; errorCode = [doc load:@""]; if (errorCode != FSErrSuccess) { NSLog(@"The Doc [%@] Error: %ld\n", input_file, errorCode); return -1; } PauseUtil* pause = [[PauseUtil alloc]initWithParam:30]; // Using default settings. FSOptimizerSettings* settings = [[FSOptimizerSettings alloc]init]; NSLog(@"Optimized Start."); FSProgressive *progressive = [FSOptimizer optimize:doc settings:settings pause:pause]; while ([progressive resume] == FSProgressiveToBeContinued) { int rate = [progressive getRateOfProgress]; NSLog(@"Optimize progress percent: %d %%\n", rate); } NSString *output_file = [output_directory stringByAppendingPathComponent:@"ImageCompression_Optimized.pdf"]; [doc saveAs:output_file save_flags:FSPDFDocSaveFlagRemoveRedundantObjects]; NSLog(@"Optimized Finish.");
HTML转PDF
对于一些内容比较多的HTML大文件或者网页,直接进行打印或者存档不太容易。Foxit PDF SDK提供API接口将在线网页或者本地HTML文件转换为PDF文件,比如发票,报告等,使其更容易打印或者存档。在HTML转PDF的过程中,Foxit PDF SDK支持在基于HTML的组织结构的基础上创建和添加PDF Tag。
本节主要介绍如何配置运行 ‘html2pdf’ demo所需的环境。
系统需求
平台: Windows, Mac
开发语言: C, C++, Java, C#, Objective-C
License Key: license key中包含 ‘Conversion’ 模块的权限
SDK版本: Foxit PDF SDK 7.0 或更高版本
HTML转PDF引擎资源
请联系Foxit支持团队或者销售团队以获取HTML转PDF引擎文件包。
当获取到引擎文件包后,将其解压到所需目录 (比如,解压到桌面的 “htmltopdf/mac“目录下)。
如何运行html2pdf demo
Foxit PDF SDK提供了一个html2pdf demo用来展示如何使用Foxit PDF SDK将html文件转换为PDF文件。该demo位于 “\examples\simple_demo\html2pdf” 文件夹下。
配置demo
对于html2pdf demo,您可以在”\examples\simple_demo\html2pdf\html2pdf.mm“文件中配置demo,或者您可以在终端窗口中直接使用参数来对demo进行配置。以下将在 “html2pdf.mm” 文件中配置demo。
指定html2pdf引擎资源目录
在 “html2pdf.mm” 文件中,如下所示,添加 “fxhtml2pdf.exe” 引擎文件的路径,用以将html文件转换为PDF文件。
(可选) 指定cookies文件路径
添加cookies文件路径,该文件是从您需要转换的web页面中导出的。比如,
运行demo
运行demo (不带参数)
打开终端窗口,导航到 “\examples\simple_demo\html2pdf”,运行”./RunDemo.sh“。当demo成功运行后,控制台将打印 “Convert HTML to PDF successfully.”。
运行demo (带参数)
首先,打开终端窗口,导航到 “\examples\simple_demo\html2pdf”,运行 “./RunDemo.sh“。
然后,输入”./html2pdf –help” 命令查看如何使用参数来运行demo。
例如,将 “www.foxitsoftware.com” 网页转换为PDF文件,并且设置转换后的PDF页面的宽度为900 points,高度为300 points,则运行如下的命令:
./html2pdf -html www.foxitsoftware.com -w 900 -h 300
成功运行后,在 “\examples\simple_demo\output_files\html2pdf” 文件夹下将生成PDF输出文档。
参数描述
基本语法:
html2pdf <-html <the url or html path>> <-o <output pdf path>> <-engine <htmltopdf engine path>>
[-w <page width>] [-h <page height>] [-ml <margin left>] [-mr <margin right>]
[-mt <margin top>] [-mb <margin bottom>] [-r <page rotate degree>] [-mode <page mode>]
[-scale <whether scale page>] [-link <whether convert link>] [-tag <whether generate tag>]
[-cookies <cookies file path>] [-timeout <timeout>]
html2pdf –help
备注:
- <> 必选
- [ ] 可选
参数 | 描述 |
–help | 参数用法的帮助信息。 |
-html | URL或者html文件的路径。比如’-html www.foxitsoftware.com’。 |
-o | 输出PDF文件的路径。 |
-engine | “fxhtml2pdf.exe” 引擎文件的路径。 |
-w | 输出PDF文件页面的宽度,单位是points。 |
-h | 输出PDF文件页面的高度,单位是points。 |
-r | 输出PDF文件页面的旋转度。
|
-ml | 输出PDF文件页面的左边距。 |
-mr | 输出PDF文件页面的右边距。 |
-mt | 输出PDF文件页面的上边距。 |
-mb | 输出PDF文件页面的下边距。 |
-mode | 输出PDF文件的页面模式。
|
-scale | 是否缩放页面。
|
-link | 是否转换链接。
|
-tag | 是否生成tag.
|
-cookies | cookies文件路径,该文件是从您需要转换的web页面中导出的。 |
-timeout | 加载web页面的超时时间。 |
输出预览 (Output Preview)
从版本7.4开始,Foxit PDF SDK支持输出阅览功能,可以预览分色和测试不同的颜色配置文件。
系统需求
平台: Windows, Linux, Mac
开发语言: C, C++, Java, C#, Objective-C
License Key: 有效的license key
SDK版本: Foxit PDF SDK 7.4
如何运行output preview demo
在运行 “\examples\simple_demo\output_preview” 文件夹下的output preview demo之前,您需要首先将变量default_icc_folder_path 设置为SDK包下 “\res\icc_profile” 文件夹的路径。例如:
// "default_icc_folder_path" is the path of the folder which contains default icc profile files. Please refer to Developer Guide for more details. NSString* default_icc_folder_path = @"/Users/foxit/Desktop/foxitpdfsdk_X_X_mac_oc/res/icc_profile ";
然后,按照其他demo运行的步骤运行该demo。
如何使用Foxit PDF SDK进行输出预览
#include "include/FSPDFObjC.h" // Make sure that SDK has already been initialized successfully. // Set folder path which contains default icc profile files. [FSLibrary setDefaultICCProfilesPath:default_icc_folder_path]; // Load a PDF document; Get a PDF page and parse it. // Prepare a Renderer object and the matrix for rendering. FSOutputPreview* output_preview = [[FSOutputPreview alloc] initWithPdf_doc:pdf_doc]; NSString* simulation_icc_file_path = @"icc_profile.icc"; [output_preview setSimulationProfile:simulation_icc_file_path]; [output_preview setShowType:FSOutputPreviewShowAll]; NSArray<NSString *>* process_plates = [output_preview getPlates:FSOutputPreviewColorantTypeProcess]; NSArray<NSString *>* spot_plates = [output_preview getPlates:FSOutputPreviewColorantTypeSpot]; // Set check status of process plate to be true, if there's any process plate. for (int i = 0; i < [process_plates count]; i++) { [output_preview setCheckStatus:[process_plates objectAtIndex:i] to_check:true]; } // Set check status of spot plate to be true, if there's any spot plate. for (int i = 0; i < [spot_plates count]; i++) { [output_preview setCheckStatus:[spot_plates objectAtIndex:i] to_check:true]; } FSBitmap* preview_bitmap = [output_preview generatePreviewBitmap:pdf_page matrix:display_matrix renderer:renderer];
FAQ
1. 如何获取PDF文件中指定位置的文本对象,以及更改文本对象的内容?
使用Foxit PDF SDK获取PDF文件中指定位置的文本对象以及修改文本对象的内容,请按照如下的步骤:
1)打开一个PDF文件。
2)加载PDF页面并获取该页面中的页面对象。
3)使用FSPDFPage::getGraphicsObjectAtPoint获取指定位置的文本对象。注意:使用页面对象获取矩形来查看文本对象的位置。
4)更改文本对象的内容并保存PDF文档。
以下是示例代码:
#include "FSPDFObjC.h" ... void ChangeTextObjectContent() { NSString* input_file = [input_path stringByAppendingString:@"AboutFoxit.pdf"]; @try { FSPDFDoc *doc = [[FSPDFDoc alloc] initWithPath:input_file]; FSErrorCode error_code = [doc load:@""]; if (error_code != FSErrSuccess) { NSLog(@"The Doc [%@] Error: %ld\n", input_file, errorCode); return ; } // Get original shading objects from the first PDF page. FSPDFPage *original_page = [doc getPage:0]; [original_page startParse:FSPDFPageParsePageNormal pause:NULL is_reparse:NO]; FSPointF *pointf = [[FSPointF alloc] init]; [point set:92 y:762]; FSGraphicsObjectArray* arr = [original_page getGraphicsObjectsAtPoint:pointf tolerance:10 filter: FSGraphicsObjectTypeText]; int array_size = [arr getSize]; for(int i = 0; i<array_size; i++) { FSGraphicsObject* graphobj = [arr getAt:i]; FSTextObject * textobj = [graphobj getTextObject]; textobj.text = @"Foxit Test"; } [original_page generateContent]; NSString *output_directory = [output_path stringByAppendingString:@"graphics_objects/"]; NSString* output_file = [output_directory stringByAppendingString:@"After_revise.pdf"]; [doc saveAs:output_file save_flags:FSPDFDocSaveFlagNormal]; } @catch (NSException *e) { NSLog(@"Exception occurs, %@", e); } } ...
2. 是否可以改变嵌入TIFF图像的DPI?
无法改变。PDF中图像的DPI是静态的,如果图像已经存在,Foxit PDF SDK没有更改图像DPI的功能。
解决办法是您可以使用第三方库来更改图像的DPI,然后将其添加到PDF中。
备注:Foxit PDF SDK提供了一个函数 “FSImage::setDPIs”,可以用来设置图片对象的DPI属性,但是它仅支持使用Foxit PDF SDK创建或者使用 “FSImage::addFrame“函数创建的图像,不支持JPX, GIF 和 TIF格式。
附录
Foxit PDF SDK支持的JavaScript列表
对象的属性或者方法
对象 | 属性/方法名称 | 支持的最低SDK版本 |
annotation properties | alignment | V7.0 |
author | V7.0 | |
contents | V7.0 | |
creationDate | V7.0 | |
fillColor | V7.0 | |
hidden | V7.0 | |
modDate | V7.0 | |
name | V7.0 | |
opacity | V7.0 | |
page | V7.0 | |
readOnly | V7.0 | |
rect | V7.0 | |
richContents | V7.1 | |
rotate | V7.0 | |
strokeColor | V7.0 | |
textSize | V7.0 | |
type | V7.0 | |
annotation method | destroy | V7.0 |
app properties | activeDocs | V4.0 |
calculate | V4.0 | |
formsVersion | V4.0 | |
fs | V4.0 | |
fullscreen | V4.0 | |
language | V4.2 | |
platform | V4.0 | |
runtimeHighlight | V4.0 | |
viewerType | V4.0 | |
viewerVariation | V4.0 | |
viewerVersion | V4.0 | |
app methods | alert | V4.0 |
beep | V4.0 | |
browseForDoc | V4.0 | |
clearInterval | V4.0 | |
clearTimeOut | V4.0 | |
launchURL | V4.0 | |
mailMsg | V4.0 | |
response | V4.0 | |
setInterval | V4.0 | |
setTimeOut | V4.0 | |
popUpMenu | V4.0 | |
color properties | black | V4.0 |
blue | V4.0 | |
cyan | V4.0 | |
dkGray | V4.0 | |
gray | V4.0 | |
green | V4.0 | |
ltGray | V4.0 | |
magenta | V4.0 | |
red | V4.0 | |
transparent | V4.0 | |
white | V4.0 | |
yellow | V4.0 | |
color methods | convert | V4.0 |
equal | V4.0 | |
document properties | author | V4.0 |
baseURL | V4.0 | |
bookmarkRoot | V7.0 | |
calculate | V4.0 | |
Collab | V4.0 | |
creationDate | V4.0 | |
creator | V4.0 | |
delay | V4.0 | |
dirty | V4.0 | |
documentFileName | V4.0 | |
external | V4.0 | |
filesize | V4.0 | |
icons | V4.0 | |
info | V4.0 | |
keywords | V4.0 | |
modDate | V4.0 | |
numFields | V4.0 | |
numPages | V4.0 | |
pageNum | V4.0 | |
path | V4.0 | |
producer | V4.0 | |
subject | V4.0 | |
title | V4.0 | |
document methods | addAnnot | V7.0 |
addField | V4.0 | |
addIcon | V4.0 | |
calculateNow | V4.0 | |
createDataObject | V6.2 | |
deletePages | V4.0 | |
exportAsFDF | V4.0 | |
flattenPages | V7.1 | |
getAnnot | V7.0 | |
getAnnots | V7.0 | |
getField | V4.0 | |
getIcon | V4.0 | |
getNthFieldName | V4.0 | |
getOCGs | V4.0 | |
getPageBox | V4.0 | |
getPageNthWord | V4.0 | |
getPageNthWordQuads | V4.0 | |
getPageNumWords | V4.0 | |
getPageRotation | V7.0 | |
getPrintParams | V4.0 | |
getURL | V4.0 | |
importAnFDF | V4.0 | |
insertPages | V6.2 | |
mailForm | V4.0 | |
V4.0 | ||
removeField | V4.0 | |
replacePages | V6.2 | |
resetForm | V4.0 | |
submitForm | V4.0 | |
mailDoc | V4.0 | |
event properties | change | V4.0 |
changeEx | V4.0 | |
commitKey | V4.0 | |
fieldFull | V4.0 | |
keyDown | V4.0 | |
modifier | V4.0 | |
name | V4.0 | |
rc | V4.0 | |
selEnd | V4.0 | |
selStart | V4.0 | |
shift | V4.0 | |
source | V4.0 | |
target | V4.0 | |
targetName | V4.0 | |
type | V4.0 | |
value | V4.0 | |
willCommit | V4.0 | |
event methods | – | – |
field properties | alignment | V4.0 |
borderStyle | V4.0 | |
buttonAlignX | V4.0 | |
buttonAlignY | V4.0 | |
buttonFitBounds | V4.0 | |
buttonPosition | V4.0 | |
buttonScaleHow | V4.0 | |
buttonScaleWhen | V4.0 | |
calcOrderIndex | V4.0 | |
charLimit | V4.0 | |
comb | V4.0 | |
commitOnSelChange | V4.0 | |
currentValueIndices | V4.0 | |
defaultValue | V4.0 | |
doNotScroll | V4.0 | |
doNotSpellCheck | V4.0 | |
delay | V4.0 | |
display | V4.0 | |
doc | V4.0 | |
editable | V4.0 | |
exportValues | V4.0 | |
hidden | V4.0 | |
fileSelect | V4.0 | |
fillColor | V4.0 | |
lineWidth | V4.0 | |
highlight | V4.0 | |
multiline | V4.0 | |
multipleSelection | V4.0 | |
name | V4.0 | |
numItems | V4.0 | |
page | V4.0 | |
password | V4.0 | |
V4.0 | ||
radiosInUnison | V4.0 | |
readonly | V4.0 | |
rect | V4.0 | |
required | V4.0 | |
richText | V4.0 | |
rotation | V4.0 | |
strokeColor | V4.0 | |
style | V4.0 | |
textColor | V4.0 | |
textFont | V4.0 | |
textSize | V4.0 | |
type | V4.0 | |
userName | V4.0 | |
value | V4.0 | |
valueAsString | V4.0 | |
field methods | browseForFileToSubmit | V4.0 |
buttonGetCaption | V4.0 | |
buttonGetIcon | V4.0 | |
buttonSetCaption | V4.0 | |
buttonSetIcon | V4.0 | |
checkThisBox | V4.0 | |
clearItems | V4.0 | |
defaultIsChecked | V4.0 | |
deleteItemAt | V4.0 | |
getArray | V4.0 | |
getItemAt | V4.0 | |
insertItemAt | V4.0 | |
isBoxChecked | V4.0 | |
isDefaultChecked | V4.0 | |
setAction | V4.0 | |
setFocus | V4.0 | |
setItems | V4.0 | |
global methods | setPersistent | V4.0 |
Icon properties | name | V4.0 |
util methods | printd | V4.0 |
printf | V4.0 | |
printx | V4.0 | |
scand | V4.0 | |
identity properties | loginName | V4.2 |
Name | V4.2 | |
corporation | V4.2 | |
V4.2 | ||
collab properties | user | V6.2 |
ocg properties | name | V6.2 |
ocg methods | setAction | V6.2 |
全局方法
方法名称 | 支持的最低SDK版本 |
AFNumber_Format | V4.0 |
AFNumber_Keystroke | V4.0 |
AFPercent_Format | V4.0 |
AFPercent_Keystroke | V4.0 |
AFDate_FormatEx | V4.0 |
AFDate_KeystrokeEx | V4.0 |
AFDate_Format | V4.0 |
AFDate_Keystroke | V4.0 |
AFTime_FormatEx | V4.0 |
AFTime_KeystrokeEx | V4.0 |
AFTime_Format | V4.0 |
AFTime_Keystroke | V4.0 |
AFSpecial_Format | V4.0 |
AFSpecial_Keystroke | V4.0 |
AFSpecial_KeystrokeEx | V4.0 |
AFSimple | V4.0 |
AFMakeNumber | V4.0 |
AFSimple_Calculate | V4.0 |
AFRange_Validate | V4.0 |
AFMergeChange | V4.0 |
AFParseDateEx | V4.0 |
AFExtractNums | V4.0 |
引用
[1] PDF reference 1.7
http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=51502
[2] PDF reference 2.0
https://www.iso.org/standard/63534.html
[3] Foxit PDF SDK OC API reference
sdk_folder/doc/Foxit PDF SDK OC API Reference.html
备注: sdk_folder 是SDK包解压后的目录。
技术支持
您可以直接联系Foxit,请使用以下的联系方式:
线上支持:
联系销售:
- 电话: 1-866-680-3668
- 邮箱: sales@foxitsoftware.com
联系技术支持团队:
- 电话: 1-866-MYFOXIT or 1-866-693-6948
- 邮箱: support@foxitsoftware.com