PDFium 是 Chromium 的 PDF 渲染引擎,许可协议为 BSD 3-Clause。不同于 Mozilla 基于 HTML5 的 PDF.js,PDFium 是基于 Foxit Software (福昕软件)的渲染代码,Google 与其合作开源出的。

此外,Qt PDF 模块也选用了 PDFium ,可见 QtWebEngine / QtPdf

本文将介绍如何用 PDFium 实现一个简单的 PDF 阅读器,代码见:https://github.com/ikuokuo/pdfium-reader

编译 PDFium

使用预编译库:https://github.com/bblanchon/pdfium-binaries

不然,参考 PDFium / README 自己编译,实践步骤如下:

# get depot_tools, contains: gclient, ninja, gn, ...
git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH="$PATH:$HOME/Codes/Star/depot_tools" # get pdfium
cd pdfium-reader/
mkdir -p third_party/chromium
cd third_party/chromium
gclient config --unmanaged https://pdfium.googlesource.com/pdfium.git
gclient sync
cd pdfium # get deps
# on linux, install additional build dependencies
./build/install-build-deps.sh # gn config
# args see the following `out/Release/args.gn`
gn args out/Release # ninja build
# pdfium
ninja -C out/Release pdfium
# pdfium_test
ninja -C out/Release pdfium_test # run sample: pdf > ppm
./out/Release/pdfium_test --ppm path/to/myfile.pdf

期间 out/Release/args.gn 内容如下:

use_goma = false  # Googlers only. Make sure goma is installed and running first.
is_debug = false # Enable debugging features. # Set true to enable experimental Skia backend.
pdf_use_skia = false
# Set true to enable experimental Skia backend (paths only).
pdf_use_skia_paths = false pdf_enable_xfa = false # Set false to remove XFA support (implies JS support).
pdf_enable_v8 = false # Set false to remove Javascript support.
pdf_is_standalone = true # Set for a non-embedded build.
pdf_is_complete_lib = true # Set for a static library build.
is_component_build = false # Disable component build (Though it should work)

使用 PDFium

阅读 PDFium / Getting Started,了解如何初始化 PDFium 及载入文档。步骤如下,或见 pdfium_start.c:

#include <fpdfview.h>
#include <stdio.h> int main(int argc, char const *argv[]) {
FPDF_STRING test_doc = "test_doc.pdf";
if (argc >= 2) {
test_doc = argv[1];
}
printf("test_doc: %s\n", test_doc); FPDF_InitLibrary(); FPDF_DOCUMENT doc = FPDF_LoadDocument(test_doc, NULL);
if (!doc) {
unsigned long err = FPDF_GetLastError();
// Load pdf docs unsuccessful: ...
goto EXIT;
} FPDF_CloseDocument(doc);
EXIT:
FPDF_DestroyLibrary();
return 0;
}

获取信息

样例见 pdf_info.cc,可打印 PDF 元数据、页面信息等。

FPDF_GetMetaText 获取元数据(UTF-16LE 编码):

void PrintPdfMetaData(FPDF_DOCUMENT doc) {
static constexpr const char *kMetaTags[] = {
"Title", "Author", "Subject", "Keywords",
"Creator", "Producer", "CreationDate", "ModDate"};
for (const char *meta_tag : kMetaTags) {
const unsigned long len = FPDF_GetMetaText(doc, meta_tag, nullptr, 0);
if (!len)
continue; std::vector<char16_t> buf(len);
FPDF_GetMetaText(doc, meta_tag, buf.data(), buf.size());
auto text = strings::FromUtf16(std::u16string(buf.data()));
if (strcmp(meta_tag, "CreationDate") == 0 ||
strcmp(meta_tag, "ModDate") == 0) {
text = fpdf::DateToRFC3399(text);
}
std::cout << " " << meta_tag << ": " << text << std::endl;
}
}

渲染页面

样例见 pdf_render.cc,可渲染 PDF 页面并保存为 PNG。

FPDF_RenderPageBitmap 渲染某一页:

void PdfRenderPage(const std::string &pdf_name, FPDF_DOCUMENT doc, int index) {
Timer t; FPDF_PAGE page = FPDF_LoadPage(doc, index); double scale = 1.0;
// double scale = 2.0;
int width = static_cast<int>(FPDF_GetPageWidth(page) * scale);
int height = static_cast<int>(FPDF_GetPageHeight(page) * scale);
int alpha = FPDFPage_HasTransparency(page) ? 1 : 0;
ScopedFPDFBitmap bitmap(FPDFBitmap_Create(width, height, alpha)); // BGRx if (bitmap) {
FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF;
FPDFBitmap_FillRect(bitmap.get(), 0, 0, width, height, fill_color); int rotation = 0;
int flags = FPDF_ANNOT;
FPDF_RenderPageBitmap(bitmap.get(), page, 0, 0, width, height,
rotation, flags);
auto t_render = t.Elapsed(); int stride = FPDFBitmap_GetStride(bitmap.get());
void *buffer = FPDFBitmap_GetBuffer(bitmap.get()); char img_name[256];
int chars_formatted = snprintf(
img_name, sizeof(img_name), "%s.%d.png", pdf_name.c_str(), index);
if (chars_formatted < 0 ||
static_cast<size_t>(chars_formatted) >= sizeof(img_name)) {
fprintf(stderr, "Filename is too long: %s\n", img_name);
exit(EXIT_FAILURE);
} auto ok = PdfWritePng(img_name, buffer, width, height, stride);
if (!ok) {
fprintf(stderr, "Write png failed: %s\n", img_name);
exit(EXIT_FAILURE);
}
auto t_write = t.Elapsed(); fprintf(stdout, "%s\n", img_name);
fprintf(stdout, " %02d: %dx%d, render=%lldms, write=%lldms\n",
index, width, height, t_render, t_write);
} else {
fprintf(stderr, "Page was too large to be rendered.\n");
exit(EXIT_FAILURE);
} FPDF_ClosePage(page);
}

stb_image_write.h 存为 PNG:

bool PdfWritePng(const std::string &img_name, void *buffer,
int width, int height, int stride) {
// BGRA > RGBA
auto buf = reinterpret_cast<uint8_t *>(buffer);
for (int r = 0; r < height; ++r) {
for (int c = 0; c < width; ++c) {
auto pixel = buf + (r*stride) + (c*4);
auto b = pixel[0];
pixel[0] = pixel[2]; // b = r
pixel[2] = b; // r = b
}
}
return stbi_write_png(img_name.c_str(), width, height, 4, buf, stride) != 0;
}

实现 UI

本文给出的 PDFium Reader 代码,用的 ImGui+GLFW+OpenGL3 实现的 UI,可跨三大桌面系统。

想进一步了解的,可以直接看代码,编译运行依照 README。

GoCoding 个人实践的经验分享,可关注公众号!

PDFium 渲染的更多相关文章

  1. pdfium 之二

    https://www.foxitsoftware.cn/products/premium-pdfium/feature.php 基于谷歌PDFium开源代码 谷歌采用福昕的PDF技术为其PDF开源项 ...

  2. IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法

    直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...

  3. 【前端性能】高性能滚动 scroll 及页面渲染优化

    最近在研究页面渲染及web动画的性能问题,以及拜读<CSS SECRET>(CSS揭秘)这本大作. 本文主要想谈谈页面优化之滚动优化. 主要内容包括了为何需要优化滚动事件,滚动与页面渲染的 ...

  4. HTML渲染过程详解

    无意中看到寒冬关于前端的九个问题,细细想来我也只是对第一.二.九问有所了解,正好也趁着这个机会梳理一下自己的知识体系.由于本人对http协议以及dns对url的解析问题并不了解,所以这里之探讨url请 ...

  5. 【原】实时渲染中常用的几种Rendering Path

    [原]实时渲染中常用的几种Rendering Path 本文转载请注明出处 —— polobymulberry-博客园 本文为我的图形学大作业的论文部分,介绍了一些Rendering Path,比较简 ...

  6. geotrellis使用(二十八)栅格数据色彩渲染(多波段真彩色)

    目录 前言 实现过程 总结 一.前言        上一篇文章介绍了如何使用Geotrellis渲染单波段的栅格数据,已然很是头疼,这几天不懈努力之后工作又进了一步,整清楚了如何使用Geotrelli ...

  7. CSS 3 学习——transform 3D转换渲染

    以下内容根据官方规范翻译,没有翻译关于SVG变换的内容和关于矩阵计算的内容. 一般情况下,元素在一个无景深无立体感的平面(flat plane)上渲染,这个平面就是其包含块所处的平面.同时,页面上的其 ...

  8. 高级渲染技巧和代码示例 GPU Pro 7

    下载代码示例 移动设备正呈现着像素越来越高,屏幕尺寸越来越小的发展趋势. 由于像素着色的能耗非常大,因此 DPI 的增加以及移动设备固有的功耗受限环境为降低像素着色成本带来了巨大的压力. MSAA 有 ...

  9. 【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理

    承接上一篇:[CSS3进阶]酷炫的3D旋转透视 . 最近入坑 Web 动画,所以把自己的学习过程记录一下分享给大家. CSS3 3D 行星运转 demo 页面请戳:Demo.(建议使用Chrome打开 ...

随机推荐

  1. Noip模拟6 2021.6.10

    T1 辣鸡 首先吐嘈一下,这题的测试点就离谱,不说了,附上我65分代码: 1 #include<bits/stdc++.h> 2 #define int long long 3 using ...

  2. 2021.8.9考试总结[NOIP模拟34]

    T1 Merchant 如果$t=0$时不能达到$s$,那么所拿物品的价值一定关于时间单调递增,答案单调.因此可以特判$0$后二分. 用$sort$复杂度被卡,要用$\textit{nth_eleme ...

  3. 关于把RTL工程代码封装成IP时对define宏定义参数的处理

    在把RTL工程封装成IP的时候,如果工程中的代码中含有global include中定义的参数,则vivado不支持该参数文件的封装.出现IP_FLOW 19-4646的错误代码,解决方法: 1.在用 ...

  4. Swift-技巧(二)模糊脸部功能

    摘要 本文介绍模糊脸部的功能逻辑和实现方式,实现方式会尽可能的使用苹果提供的 API,保证功能高效率和简洁. 逻辑 模糊脸部的逻辑主要有两个流程,就是先找到脸部,然后模糊脸部,那么就引申出这两个实现问 ...

  5. docker 存储驱动(storage driver)知识总结

    http://www.sohu.com/a/101016494_116235 一,先看docker镜像是如何构建和存储. 下面是ubuntu:15.04的镜像分层.一共是4层,每一层都由一些只读并且描 ...

  6. centos安装pm2报错

    报错信息: /usr/lib/node_modules/pm2/node_modules/chalk/source/index.js:103 ...styles, 这个问题其实很简单,就是npm和no ...

  7. 微服务之十四如何在 Ocelot 网关中配置多实例 Swagger 访问

    一.介绍 当我们开发基于微服务的应用程序的时候,有一个环节总是跳不过去的,那就是要创建 WebApi,然后,我们的应用程序基于 WebApi 接口去访问.在没有 Swagger 以前,我们开发好了 W ...

  8. adb 安装与使用(一)

    一.ADB简介 1. 什么是adb? adb(Android Debug Bridage)是Android sdk的一个工具: adb 是用来连接安卓手机和PC端的桥梁,要有adb作为二者之间的维系, ...

  9. Java经典面试题(二)-不古出品

    @ 目录 1. 为什么说 Java 语言"编译与解释并存"? 2.Oracle JDK 和 OpenJDK 的对比? 3.字符型常量和字符串常量的区别? 4.Java 泛型了解么? ...

  10. Mysql - 整数类型的存储字节数和范围

    MySQL 整数类型的存储字节数和范围 type 存储字节数 有符号最小值 无符号最小值 有符号最大值 无符号最大值 TINYINT 1 -128 0 127 255 SMALLINT 2 -3276 ...