PDFium 渲染

GoCoding
• 阅读 1509

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 渲染

编译 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 个人实践的经验分享,可关注公众号!

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
GoCoding
GoCoding
Lv1
Go coding in my way :)
文章
32
粉丝
5
获赞
10