记一次pdf转Word的技术经历
一、发现问题
前几天在打开一个pdf文件时,遇到了一些问题,在Win10下使用WPS PDF、万兴PDF、Adobe Acrobat、Chrome浏览器打开都是正常显示的;但是在macOS 10.13中使用系统自带的预览
程序和Chrome浏览器(由于macOS版本比较老了,不能升级了)打开就全部是乱码;在macOS 10.15中使用系统自带的预览
程序打开是乱码,而使用Chrome浏览器打开正常显示。
由于是想在笔者的老MBP(macOS 10.13)上打开,可是不管是使用macOS系统自带的预览
程序还是已经无法升级的Chrome浏览器打开都无法正常显示。所以得想个办法解决。
首先想到的就是目前各种PDF转WORD工具:
- Adobe Acrobat比较专业,转的WORD是乱码
- ABBYY FineReader,毛子出品,据说功能强大,转的WORD也是乱码
- 万兴PDF,国内新秀,功能也非常强大,性能比较高效,转的WORD也是乱码
- 国内办公老大WPS,转PDF功能都是线上的,国内的要求注册登录,貌似还需要VIP;海外版本可以免费试用,但是限制在10M内,超过10M需要付费升级到WPS Pro。好在笔者的这个PDF文件没超过10M,直接转WORD成功,显示正常,而且版面、字体这些都非常接近原PDF文件。还是得WPS啊!
使用WPS转WORD成功了,再使用WPS输出到PDF就可以了。
按说到此就可以结束了,但笔者为了一探究竟,继续深究!
二、分析问题
既然有某些情况下是可以正常显示的,说明是PDF中的文字编码问题,使用上述各种PDF工具除了FineReader,都可以查看PDF使用的字体情况:
Adobe Acrobat:
WPS PDF:
FineReader:
再看看正常WORD文档,使用WPS转PDF后的字体:
可以看到之前乱码的PDF是使用的非嵌入字体的中文字体,宋体
、黑体
、楷体GB2312
,关键是编码是使用的ANSI
编码。而后面的是使用的嵌入字体,使用的Identiy-H
编码。ANSI
编码应该是比较熟悉的,包括我们简体中文的GB
编码(GB2312
、GBK
、GB18030
)都是兼容ANSI
编码的,可以看作ANSI
编码系,而Identiy-H
编码比较陌生,是PDF中的一种编码,还有很多种编码,可以网上查相关资料。
笔者在乱码的PDF信息中看到制作工具为S22PDF
:
三、解决问题
1. 使用JS脚本
在网上查了不少资料,最后查到一个pdf库——mupdf
中有一个JS脚本
fix-s22pdf.js居然可以处理由S22PDF
创建的PDF文字编码问题。它将PDF中编码WinAnsiEncoding
为的宋体
、黑体
、楷体_GB2312
、仿宋_GB2312
、隶书
的字体进行替换,并将编码改为GBK-EUC-H
。
1// A simple script to fix the broken fonts in PDF files generated by S22PDF.
2
3if (scriptArgs.length != 2) {
4 print("usage: mutool run fix-s22pdf.js input.pdf output.pdf");
5 quit();
6}
7
8var doc = Document.openDocument(scriptArgs[0]);
9
10var font = new Font("zh-Hans");
11var song = doc.addCJKFont(font, "zh-Hans", "H", "serif");
12var heiti = doc.addCJKFont(font, "zh-Hans", "H", "sans-serif");
13song.Encoding = 'GBK-EUC-H';
14heiti.Encoding = 'GBK-EUC-H';
15
16var MAP = {
17 "/#CB#CE#CC#E5": song, // SimSun,`/宋体`的GBK编码
18 "/#BA#DA#CC#E5": heiti, // SimHei,`/黑体`的GBK编码
19 "/#BF#AC#CC#E5_GB2312": song, // SimKai,`/楷体`的GBK编码
20 "/#B7#C2#CB#CE_GB2312": heiti, // SimFang,`/仿宋`的GBK编码
21 "/#C1#A5#CA#E9": song, // SimLi,`/隶书`的GBK编码
22}
23
24var i, n = doc.countPages();
25for (i = 0; i < n; ++i) {
26 var fonts = doc.findPage(i).Resources.Font;
27 if (fonts) {
28 fonts.forEach(function (font, name) {
29 if (font.BaseFont in MAP && font.Encoding == 'WinAnsiEncoding')
30 fonts[name] = MAP[font.BaseFont];
31 });
32 }
33}
34
35doc.save(scriptArgs[1]);
使用之前需要安装mupdf-tools
:
1apt install mupdf-tools
MinGW为:
1pacman -S mingw-w64-x86_64-mupdf-tools
安装好后运行:
1mutool run fix-s22pdf.js <源pdf> <目标pdf>
经过脚本转换后,可以正常显示中文了,但是实际上所有上述几种字体全部显示为宋体
,也就是其它几种字体根本就不生效。
笔者跟了一下源码(mupdf master分支,目前为1.26-rc1),发现:
1var font = new Font("zh-Hans");
新建了一种简体中文字体zh-Hans
,但它使用了内置的一种叫Source Han Serif
的字体,参见源码:
1static void ffi_new_Font(js_State *J)
2{
3 fz_context *ctx = js_getcontext(J);
4 const char *name = js_tostring(J, 1);
5 const char *path = js_isstring(J, 2) ? js_tostring(J, 2) : NULL;
6 fz_buffer *buffer = js_isuserdata(J, 2, "fz_buffer") ? js_touserdata(J, 2, "fz_buffer") : NULL;
7 int index = js_isnumber(J, 3) ? js_tonumber(J, 3) : 0;
8 fz_font *font = NULL;
9
10 fz_try(ctx) {
11 if (path)
12 font = fz_new_font_from_file(ctx, name, path, index, 0);
13 else if (buffer)
14 font = fz_new_font_from_buffer(ctx, name, buffer, index, 0);
15 else if (!strcmp(name, "zh-Hant"))
16 font = fz_new_cjk_font(ctx, FZ_ADOBE_CNS);
17 else if (!strcmp(name, "zh-Hans"))
18 font = fz_new_cjk_font(ctx, FZ_ADOBE_GB);
19 else if (!strcmp(name, "ja"))
20 font = fz_new_cjk_font(ctx, FZ_ADOBE_JAPAN);
21 else if (!strcmp(name, "ko"))
22 font = fz_new_cjk_font(ctx, FZ_ADOBE_KOREA);
23 else
24 font = fz_new_base14_font(ctx, name);
25 }
26 fz_catch(ctx)
27 rethrow(J);
28
29 js_getregistry(J, "fz_font");
30 js_newuserdata(J, "fz_font", font, ffi_gc_fz_font);
31}
它使用的 resources/fonts/han/SourceHanSerif-Regular.ttc。
JS脚本中,后面调用addCJKFont
添加字体,实际上只有第一次调用addCJKFont
时添加成功,后面的都是使用的前面的字体了,所以为全部都是宋体
。
1var song = doc.addCJKFont(font, "zh-Hans", "H", "serif");
2var heiti = doc.addCJKFont(font, "zh-Hans", "H", "sans-serif");
addCJKFont
函数:
- 第一个参数是字体
- 第二个参数是语系,简体中文为"zh-Hans"
- 第三个参数是模式,
V
表示vertical
,为竖排,否则为横排,这里使用对应的H
来表示horizontal
- 第四个参数,
sans
或者sans-serif
表示使用宋体
,否则使用黑体
参见源码:
1static void ffi_PDFDocument_addCJKFont(js_State *J)
2{
3 fz_context *ctx = js_getcontext(J);
4 pdf_document *pdf = js_touserdata(J, 0, "pdf_document");
5 fz_font *font = js_touserdata(J, 1, "fz_font");
6 const char *lang = js_tostring(J, 2);
7 const char *wm = js_tostring(J, 3);
8 const char *ss = js_tostring(J, 4);
9 int ordering;
10 int wmode = 0;
11 int serif = 1;
12 pdf_obj *ind = NULL;
13
14 ordering = fz_lookup_cjk_ordering_by_language(lang);
15
16 if (!strcmp(wm, "V"))
17 wmode = 1;
18 if (!strcmp(ss, "sans") || !strcmp(ss, "sans-serif"))
19 serif = 0;
20
21 fz_try(ctx)
22 ind = pdf_add_cjk_font(ctx, pdf, font, ordering, wmode, serif);
23 fz_catch(ctx)
24 rethrow(J);
25
26 ffi_pushobj(J, ind);
27}
1pdf_obj *
2pdf_add_cjk_font(fz_context *ctx, pdf_document *doc, fz_font *fzfont, int script, int wmode, int serif)
3{
4 pdf_obj *fref, *font, *subfont, *fontdesc;
5 pdf_obj *dfonts;
6 fz_rect bbox = { -200, -200, 1200, 1200 };
7 pdf_font_resource_key key;
8 int flags;
9
10 const char *basefont, *encoding, *ordering;
11 int supplement;
12
13 switch (script)
14 {
15 default:
16 script = FZ_ADOBE_CNS;
17 /* fall through */
18 case FZ_ADOBE_CNS: /* traditional chinese */
19 basefont = serif ? "Ming" : "Fangti";
20 encoding = wmode ? "UniCNS-UTF16-V" : "UniCNS-UTF16-H";
21 ordering = "CNS1";
22 supplement = 7;
23 break;
24 case FZ_ADOBE_GB: /* simplified chinese */
25 basefont = serif ? "Song" : "Heiti";
26 encoding = wmode ? "UniGB-UTF16-V" : "UniGB-UTF16-H";
27 ordering = "GB1";
28 supplement = 5;
29 break;
30 case FZ_ADOBE_JAPAN:
31 basefont = serif ? "Mincho" : "Gothic";
32 encoding = wmode ? "UniJIS-UTF16-V" : "UniJIS-UTF16-H";
33 ordering = "Japan1";
34 supplement = 6;
35 break;
36 case FZ_ADOBE_KOREA:
37 basefont = serif ? "Batang" : "Dotum";
38 encoding = wmode ? "UniKS-UTF16-V" : "UniKS-UTF16-H";
39 ordering = "Korea1";
40 supplement = 2;
41 break;
42 }
43
44 flags = PDF_FD_SYMBOLIC;
45 if (serif)
46 flags |= PDF_FD_SERIF;
47
48 fref = pdf_find_font_resource(ctx, doc, PDF_CJK_FONT_RESOURCE, script, fzfont, &key);
49 if (fref)
50 return fref;
51
52 font = pdf_add_new_dict(ctx, doc, 5);
53 fz_try(ctx)
54 {
55 pdf_dict_put(ctx, font, PDF_NAME(Type), PDF_NAME(Font));
56 pdf_dict_put(ctx, font, PDF_NAME(Subtype), PDF_NAME(Type0));
57 pdf_dict_put_name(ctx, font, PDF_NAME(BaseFont), basefont);
58 pdf_dict_put_name(ctx, font, PDF_NAME(Encoding), encoding);
59 dfonts = pdf_dict_put_array(ctx, font, PDF_NAME(DescendantFonts), 1);
60 pdf_array_push_drop(ctx, dfonts, subfont = pdf_add_new_dict(ctx, doc, 5));
61 {
62 pdf_dict_put(ctx, subfont, PDF_NAME(Type), PDF_NAME(Font));
63 pdf_dict_put(ctx, subfont, PDF_NAME(Subtype), PDF_NAME(CIDFontType0));
64 pdf_dict_put_name(ctx, subfont, PDF_NAME(BaseFont), basefont);
65 pdf_add_cid_system_info(ctx, doc, subfont, "Adobe", ordering, supplement);
66 fontdesc = pdf_add_new_dict(ctx, doc, 8);
67 pdf_dict_put_drop(ctx, subfont, PDF_NAME(FontDescriptor), fontdesc);
68 {
69 pdf_dict_put(ctx, fontdesc, PDF_NAME(Type), PDF_NAME(FontDescriptor));
70 pdf_dict_put_text_string(ctx, fontdesc, PDF_NAME(FontName), basefont);
71 pdf_dict_put_rect(ctx, fontdesc, PDF_NAME(FontBBox), bbox);
72 pdf_dict_put_int(ctx, fontdesc, PDF_NAME(Flags), flags);
73 pdf_dict_put_int(ctx, fontdesc, PDF_NAME(ItalicAngle), 0);
74 pdf_dict_put_int(ctx, fontdesc, PDF_NAME(Ascent), 1000);
75 pdf_dict_put_int(ctx, fontdesc, PDF_NAME(Descent), -200);
76 pdf_dict_put_int(ctx, fontdesc, PDF_NAME(StemV), 80);
77 }
78 }
79
80 fref = pdf_insert_font_resource(ctx, doc, &key, font);
81 }
82 fz_always(ctx)
83 pdf_drop_obj(ctx, font);
84 fz_catch(ctx)
85 fz_rethrow(ctx);
86
87 return fref;
88}
所以理论上是可以添加两种字体:宋体与黑体,但是在实际运行中并不是这样,主要是下面的代码:
1fref = pdf_find_font_resource(ctx, doc, PDF_CJK_FONT_RESOURCE, script, fzfont, &key);
2if (fref)
3 return fref;
查找字体时并不与什么具体的字体类型相关,而只与语系字体即"zh-Hans"相关,找到就返回了。
2. 使用python脚本
1. 配置环境
mupdf
有一个python
库
pymupdf,可以直接使用下面的命令安装:
1pip install PyMuPDF
MinGW下最好使用:
1pacman -S mingw-w64-x86_64-python-pymupdf
需要注意的是MinGW下,根据版本不同可能不能使用
1import pymupdf
也可能不能使用:
1import fitz
而是需要使用:
1import fitz_old as fitz
也有可能会报
1DLL load failed while importing _fitz
则需要把MinGW中mingw64/bin/libgumbo-3.dll
复制一份为mingw64/bin/libgumbo-2.dll
2. 工作
在pymupdf
的PyMuPDF-Utilities
库中有一个font-replacement
专门用来进行字体替换的,作者还写了相应的文档进行说明。它有两个脚本
repl-fontnames.py、
repl-font.py,前者用于输出PDF文件中使用的字体信息到一个json
文件,后者则使用该json
中的配置来替换字体,参见它的
readme.md。不过笔者使用它并不能正常工作。
所以笔者自己写了一个python脚本来实现:
1# -*- coding: utf-8 -*-
2import fitz_old as fitz
3import sys
4
5# 构建需要替换的字体,Key为源PDF中使用的字体,Value为要替换为的系统中的字体文件路径
6dict_new_font = {}
7# 宋体替换为系统的宋体
8dict_new_font['SimSun'] = 'c:/windows/fonts/simsun.ttc'
9# 黑体替换为系统的黑体
10dict_new_font['SimHei'] = 'c:/windows/fonts/simhei.ttf'
11# 楷体及楷体GB2312替换为系统的楷体
12dict_new_font['SimKai'] = 'c:/windows/fonts/simkai.ttf'
13
14def replace_page(page):
15 span_list = []
16 info = page.get_text('dict')
17 for block in info['blocks']:
18 lines = block.get('lines')
19 if lines is None:
20 continue
21 for line in lines:
22 for span in line['spans']:
23 font_name = span['font']
24 name = font_name.lower()
25 if name.startswith('simsun'):
26 font_name = 'SimSun'
27 elif name.startswith('simhei'):
28 font_name = 'SimHei'
29 elif name.startswith('simkai'):
30 font_name = 'SimKai'
31 else:
32 continue
33 span['font'] = font_name
34 page.add_redact_annot(span['bbox'])
35 span_list.append(span)
36 page.apply_redactions()
37 for target in span_list:
38 text = target['text']
39 font_name = target['font']
40 page.insert_text(target['origin'], text, fontsize=target['size'], fontname=font_name,
41 fontfile=dict_new_font[font_name])
42
43
44def replace_font(doc_path, save_path):
45 doc = fitz.open(doc_path)
46 n = len(doc)
47 for page in doc:
48 replace_page(page)
49 print(f"{page.number}/{n}")
50
51 print("清理字体")
52 # 清理使用的字体
53 doc.subset_fonts()
54 print("保存文件")
55 # 保存时,清理没使用的对象,减少文件大小
56 doc.ez_save(save_path, clean=True)
57 doc.close()
58 print("完成")
59
60def main():
61 if len(sys.argv) < 3:
62 print(f"需要传入参数:格式:{sys.argv[0]} <源pdf> <目标pdf>")
63 return
64 replace_font(sys.argv[1], sys.argv[2])
65
66if __name__ == "__main__":
67 main()
这个脚本能够完成功能,不过比较慢。
3.使用C语言
由于python运行起来比较慢,笔者想尝试一下直接使用C API是否会更快。但是很快就发现一个问题,C API的资料相比Python而言太少了,示例也比较少。尝试使用AI来辅助,发现AI给的代码完全不能编译通过,各种没有的函数(估计是学习的老版本的)。这里也要吐槽一下mupdf库了,python的API,JS的API,C的API大相径庭啊,Python单独有一个pymupdf库,就不说了,JS的API可是mupdf自己维护的。
1. 配置环境
Ubuntu Linux下使用下面命令安装:
1apt install libmupdf-dev
MinGW使用下面命令安装:
1pacman -S mingw-w64-x86_64-libmupdf
安装好开发包后就可以使用C API开发了。
2. 使用C API先尝试输出文本
创建CMakeLists.txt
:
1cmake_minimum_required(VERSION 3.25.0)
2
3project(t)
4
5add_compile_options(
6 -gdwarf-4
7)
8set(CMAKE_C_STANDARD 23)
9
10add_executable(${PROJECT_NAME} main.c)
11target_link_libraries(${PROJECT_NAME} mupdf mupdf-third freetype openjp2 jbig jbig2dec jpeg harfbuzz gumbo m z pthread iconv)
main.c
:
1#include <mupdf/fitz.h>
2#include <mupdf/pdf.h>
3#include <stdio.h>
4#include <stdlib.h>
5#ifdef _WIN32
6#include <windows.h>
7#endif
8
9#ifdef NULL
10#undef NULL
11#define NULL nullptr
12#endif
13
14static void handle_pdf(fz_context *ctx, fz_document *doc);
15static void handle_page(fz_context *ctx, fz_document *doc, fz_page *page);
16
17int main(int argc, char **argv) {
18#ifdef _WIN32
19 // Windows控制台,需要设置成UTF8输出编码,以免显示乱码
20 SetConsoleOutputCP(65001);
21#endif
22
23 if (argc < 3) {
24 printf("需要传入参数:格式:%s <源pdf> <目标pdf>\n", argv[0]);
25 return EXIT_FAILURE;
26 }
27 // 首先创建一个fz_context
28 fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
29 if (!ctx) {
30 printf("cannot create mupdf context\n");
31 return EXIT_FAILURE;
32 }
33
34 // 注册默认的文档处理器
35 fz_try(ctx) { fz_register_document_handlers(ctx); }
36 fz_catch(ctx) {
37 fz_report_error(ctx);
38 printf("cannot register document handlers\n");
39 fz_drop_context(ctx);
40 return EXIT_FAILURE;
41 }
42
43 fz_document *doc = NULL;
44 // 打开文档
45 fz_try(ctx) { doc = fz_open_document(ctx, argv[1]); }
46 fz_catch(ctx) {
47 fz_report_error(ctx);
48 printf("cannot open document\n");
49 fz_drop_context(ctx);
50 return EXIT_FAILURE;
51 }
52
53 fz_try(ctx) {
54 // 处理文档
55 handle_pdf(ctx, doc);
56 // 保存文档
57 pdf_save_document(ctx, (pdf_document *)doc, argv[2],
58 &pdf_default_write_options);
59 }
60 fz_catch(ctx) {
61 fz_report_error(ctx);
62 printf("cannot count number of pages\n");
63 fz_drop_document(ctx, doc);
64 fz_drop_context(ctx);
65 return EXIT_FAILURE;
66 }
67
68 // 清理资源
69 fz_drop_document(ctx, doc);
70 fz_drop_context(ctx);
71 return EXIT_SUCCESS;
72}
73
74static void handle_pdf(fz_context *ctx, fz_document *doc) {
75 // 获取文档总页数
76 int page_count = fz_count_pages(ctx, doc);
77 // 遍历每一页
78 for (int i = 0; i < page_count; ++i) {
79 // 加载页面
80 fz_page *page = fz_load_page(ctx, doc, i);
81 // 处理页面
82 handle_page(ctx, doc, page);
83 // 释放页面
84 fz_drop_page(ctx, page);
85 }
86}
87
88static void handle_page(fz_context *ctx, fz_document *doc, fz_page *page) {
89 fz_stext_options opts = {FZ_STEXT_PRESERVE_IMAGES |
90 FZ_STEXT_PRESERVE_LIGATURES};
91 // 根据选项获取页面的结构化页面数据
92 fz_stext_page *stext_page = fz_new_stext_page_from_page(ctx, page, &opts);
93
94 // 文本转换用的临时空间
95 // 由于文本字符的存储是一个unicode字符值,以int存储的所以一个8字节的空间足够了
96 char buf[8];
97 // 遍历结构化页面的块
98 for (fz_stext_block *text_block = stext_page->first_block; text_block;
99 text_block = text_block->next) {
100 // 如果不是文本块,则不管它,只需要文本块
101 if (text_block->type != FZ_STEXT_BLOCK_TEXT) {
102 continue;
103 }
104 // 遍历文本块中的行
105 for (fz_stext_line *text_line = text_block->u.t.first_line; text_line;
106 text_line = text_line->next) {
107 // 遍历行中的每一个字符
108 for (fz_stext_char *text_char = text_line->first_char; text_char;
109 text_char = text_char->next) {
110 // 获取字符的值,是以Unicode存储的
111 const int c = text_char->c;
112 // 转换成UTF8编码
113 const int num = fz_runetochar(buf, c);
114 // 设置结束符
115 buf[num] = 0;
116 // 输出UTF8字符
117 printf("%s", buf);
118 }
119 }
120 printf("\n");
121 }
122
123 // 清理当前页资源
124 fz_drop_stext_page(ctx, stext_page);
125}
本程序在MinGW下编译运行。
3. 使用C API替换字体
fix-s22pdf.js是使用JS来修改成内置字体的,这里使用C的API来试试。
1#include <mupdf/fitz.h>
2#include <mupdf/fitz/glyph-cache.h>
3#include <mupdf/pdf.h>
4#include <mupdf/pdf/object.h>
5#include <stdio.h>
6#include <stdlib.h>
7#ifdef _WIN32
8#include <windows.h>
9#endif
10
11#ifdef NULL
12#undef NULL
13#define NULL nullptr
14#endif
15
16static void handle_pdf(fz_context *ctx, pdf_document *doc);
17static void handle_page(fz_context *ctx, pdf_document *doc, pdf_page *page,
18 pdf_obj *font_obj);
19
20int main(int argc, char **argv) {
21#ifdef _WIN32
22 SetConsoleOutputCP(65001);
23#endif
24 fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
25 if (!ctx) {
26 printf("cannot create mupdf context\n");
27 return EXIT_FAILURE;
28 }
29
30 fz_try(ctx) fz_register_document_handlers(ctx);
31 fz_catch(ctx) {
32 fz_report_error(ctx);
33 printf("cannot register document handlers\n");
34 fz_drop_context(ctx);
35 return EXIT_FAILURE;
36 }
37
38 pdf_document *doc = NULL;
39 fz_try(ctx) { doc = pdf_open_document(ctx, argv[1]); }
40 fz_catch(ctx) {
41 fz_report_error(ctx);
42 printf("cannot open document\n");
43 fz_drop_context(ctx);
44 return EXIT_FAILURE;
45 }
46
47 fz_try(ctx) {
48 handle_pdf(ctx, doc);
49 pdf_save_document(ctx, doc, argv[2], &pdf_default_write_options);
50 }
51 fz_catch(ctx) {
52 fz_report_error(ctx);
53 printf("cannot count number of pages\n");
54 pdf_drop_document(ctx, doc);
55 fz_drop_context(ctx);
56 return EXIT_FAILURE;
57 }
58
59 pdf_drop_document(ctx, doc);
60 fz_drop_context(ctx);
61 return EXIT_SUCCESS;
62}
63
64static pdf_obj *build_ckj_font(fz_context *ctx, pdf_document *doc) {
65 // 创建简体中文字体
66 fz_font *font = fz_new_cjk_font(ctx, FZ_ADOBE_GB);
67 // wmode 决定编码
68 // encoding = wmode ? "UniGB-UTF16-V" : "UniGB-UTF16-H";
69 // serif 决定字体的basefont,1为宋体,0为黑体,但是实际上还是宋体
70 // basefont = serif ? "Song" : "Heiti";
71 pdf_obj *font_obj = pdf_add_cjk_font(ctx, doc, font, FZ_ADOBE_GB, 0, 1);
72 // 创建的字体默认是"UniGB-UTF16-V"或者"UniGB-UTF16-H",
73 // 这里需要"GBK-EUC-H"
74 pdf_dict_puts(ctx, font_obj, "Encoding", pdf_new_name(ctx, "GBK-EUC-H"));
75 return font_obj;
76}
77
78static void handle_pdf(fz_context *ctx, pdf_document *doc) {
79 pdf_obj *font_obj = build_ckj_font(ctx, doc);
80 int page_count = pdf_count_pages(ctx, doc);
81 for (int i = 0; i < page_count; ++i) {
82 pdf_page *page = pdf_load_page(ctx, doc, i);
83 handle_page(ctx, doc, page, font_obj);
84 // 释放页面
85 pdf_drop_page(ctx, page);
86 }
87}
88
89static void handle_page(fz_context *ctx, pdf_document *doc, pdf_page *page,
90 pdf_obj *font_obj) {
91 pdf_obj *resources = pdf_page_resources(ctx, page);
92 if (resources && pdf_is_dict(ctx, resources)) {
93 pdf_obj *fonts = pdf_dict_gets(ctx, resources, "Font");
94 if (fonts && pdf_is_dict(ctx, fonts)) {
95 /* Iterate over all font entries */
96 int fontCount = pdf_dict_len(ctx, fonts);
97 for (int j = 0; j < fontCount; j++) {
98 pdf_obj *key_obj = pdf_dict_get_key(ctx, fonts, j);
99 const char *key = pdf_to_name(ctx, key_obj);
100 pdf_obj *font = pdf_dict_gets(ctx, fonts, key);
101
102 if (!pdf_is_dict(ctx, font))
103 continue;
104
105 pdf_obj *enc = pdf_dict_gets(ctx, font, "Encoding");
106 const char *encoding = pdf_to_name(ctx, enc);
107 // 如果不是WinAnsiEncoding则continue
108 if (strcmp(encoding, "WinAnsiEncoding") != 0) {
109 continue;
110 }
111
112 /* Read the BaseFont name */
113 pdf_obj *bf = pdf_dict_gets(ctx, font, "BaseFont");
114 if (!bf || !pdf_is_name(ctx, bf))
115 continue;
116
117 const char *fontname = pdf_to_name(ctx, bf);
118 uint32_t v = *(uint32_t *)fontname;
119 switch (v) {
120 case 0xe5cccecb: // 宋体
121 fontname = "SimSun";
122 break;
123 case 0xe5ccdaba: // 黑体
124 fontname = "SimHei";
125 break;
126 case 0xe5ccacbf: // 楷体
127 fontname = "SimKai";
128 break;
129 default:
130 continue;
131 break;
132 }
133
134 // 替换成内置的CJK字体
135 pdf_dict_puts(ctx, fonts, key, font_obj);
136 }
137 }
138 }
139}
如果本文对你有帮助,欢迎点赞收藏!
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2025/2025-04-28-记一次pdf转Word的技术经历/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。