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. centOs7.6安装 mysql-8.0.27

    1.下载mysql 2.连接服务器 3.通过 rpm -qa | grep mariadb 命令查看 mariadb 的安装包 4.通过 rpm -e mariadb-libs-5.5.68-1.el ...

  2. 2021 CCPC女生赛

    newbie,A了五题铜牌收工 比赛时和队友悠哉游哉做题,想着干饭,最后幸好没滚出铜尾. 贴一下比赛过的代码 A题 签到 队友A的,判断正反方向序列是否符合要求 /*** * @Author: _Kr ...

  3. 【转】PLI是什么以及怎么用

    programmable language interface 这里就说给verilog用的一些系统函数,还是无双大大的帖子 首先介绍了怎么让你自己写的pli系统函数在ncverilog里面可以成功调 ...

  4. 设计模式(1-3)-动态代理(WeakCache的运用)

    阅读本篇文章前,请事先阅读 理解Java的强引用.软引用.弱引用和虚引用. 看看什么是强引用.什么是弱引用及它们的用途,很必要!!! 上一节讲到,获取对应的代理类时,首先会从缓存中去拿,若拿不到才会去 ...

  5. Jenkins MultiJob

    前提:项目有十几个服务每次发版/更新服务需要一个个去编译 目的:希望能够建立一个任务一次构建可以批量编译很多服务,并且需要输入一个参数指定编译的分支 需要插件: MultiJob 安装插件 1.在Je ...

  6. Go语言实现APPID登录

    package thirdparty import ( "crypto/rsa" "fmt" "github.com/dgrijalva/jwt-go ...

  7. “TCP:三次握手”分析——以一个简单的“服务器”和“客户端”为例

    linux&C这两天学到了网络编程这一章,自己写了一个小的"服务器"和"客户端"程序,目的在于简单理解tcp/ip模型,以及要搭建一台简单服务器,服务器 ...

  8. ELK集群之elasticsearch(3)

    Elasticsearch-基础介绍及索引原理分析 介绍 Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引 ...

  9. 【接口】HttpClient 处理get和post请求(二)(2019-07-14 18:41)

    一.环境准备 1.导入httpClient依赖包 <dependency> <groupId>org.apache.httpcomponents</groupId> ...

  10. 攻防世界 WEB 高手进阶区 TokyoWesterns CTF shrine Writeup

    攻防世界 WEB 高手进阶区 TokyoWesterns CTF shrine Writeup 题目介绍 题目考点 模板注入 Writeup 进入题目 import flask import os a ...