ElyPDF: the document's font writes through its own object and codes; shared glyphs keep their letters (R2.17)
This commit is contained in:
7 files changed
+298
-221
No files matched your search
@@ -262,5 +262,5 @@ cargo run -p ely-doc 启动一个应用(ElyDoc 先 cargo build -p ely
|
||||
- 2026-10-06:ElyPDF 阅读(C11.1,PDF-01)。MuPDF 只链进 `ely-pdf-worker`(`crates/ely-pdf-engine`,build.rs 把 C 垫片 `shim/ely_mupdf.c` 与 `libmupdf.a`、`libmupdf-third.a` 只链给这个二进制),应用不链 MuPDF。build.rs 以协议、垫片与 worker 源码的散列为版本戳,应用启动 worker 先握手(`HELLO`),戳不同即拒绝(单测不重建 worker,旧二进制曾被误用)。worker 在打开第一个文件时才启动(PERF-01),`tests/scripts/pdf-idle.sh` 查空白窗口无子进程。垫片每个调用都在 `fz_try`/`fz_catch` 里,返回码加最后一条错误信息,MuPDF 的跳转从不越过 Rust。worker 与应用之间是 `ely-ipc` 的帧(从 ely-lok 抽出,两者共用):打开(锁定即报)、解锁、页数、页框、渲染一块(设备像素、缩放、旋转、深色)、按阅读顺序的字符与外框、页内搜索(每处命中及其全部四边形,跨行短语算一处,数量不设上限)、书签(JSON)、关闭;文件读不出只报错,worker 照常工作。查看器:打开时只读页数、前 100 页尺寸与书签,其余尺寸后台每批 100 页补齐;视图只排屏上的页(连续、单页、双页;适合宽度、适合页面、实际大小与缩放档;视图旋转 90°),按 512 设备像素一块请求,一次一块、由近及远,只留屏上的块(横纵两向),后台回来的块不在屏上即弃;画不出的块记下不再请求,换文档或重开前不重试;深色外观下页面按亮度反相(MuPDF `fz_invert_pixmap_luminance`,色相保留)。文字选择按字符、可跨页,复制按行换行,未读的页先读;查找每批 20 页在后台进行,命中逐个跳转并标出,Esc 关闭(组件库 617a8d6);书签栏用 `lists::Outline`,点即跳到页与位置。切换视图、缩放、旋转后仍停在正在读的那页。加密文件打开时问密码(外壳的密码框,见上一条),错了再问,取消即不打开。没有页的文件报错并关闭,不开标签。双页时同一行在视口内的两页都可为当前页;当前页只在横纵两向都可见的页里定,跳转在目标页左缘不在视口时横向移过去。图块按视口半开区间取。首次按下时文字未读,读到后照原按下处起选;查找结果按查找实例与所属标签核对,只在该标签为当前时跳转。书签为空时不留左栏。启动参数里的文件直接打开。
|
||||
- 2026-10-09(用户定:“打开方式”里不得出现 LibreOffice):系统会登记磁盘上能找到的 .app、挂载镜像里的 .app 与运行过的包,构建目录里的 `LibreOffice.app` 与测试挂载的官方镜像曾因此成为 xlsx 等的默认程序。现构建后由 `scripts/inert-lo.sh` 去掉其全部文件与 URL 声明、快速查看与聚焦插件,注销它,并在 `engines/` 放 `.metadata_never_index`;Ely 只取其 Frameworks 与 Resources。任何脚本与测试都不挂载 LibreOffice 镜像:LOK 探测用本构建的内核,单测所需的官方内核由 `scripts/extract-official-lo.sh` 以 7zz 解成无包的文件夹。`tests/scripts/lo-unregistered.sh` 以 NSWorkspace 核对 docx、xlsx、pptx、odt、csv 的“打开方式”里没有 LibreOffice。
|
||||
- 2026-10-09(用户报:ElyTable 点格跳到别处、拖不出选区):Calc 在 `sc_print_twips_msgs` 下以打印 twips(行高原值)报光标、选区与 `.uno:SheetGeometryData`,图块与鼠标却在图块 twips(每行按 `ScViewData::ToPixel` 截成整像素),两者随行号漂移(5000 行处约 140 行)。视图对外一律打印 twips;`LokView::set_geometry` 收下工作表几何后,`sheet::Axis::tile`、`print` 照内核的截断换算(每像素 twips 取图块的像素与 twips 之比),绘制、显示、跳转与冻结都先换成图块 twips,鼠标以图块 twips 发出(`tile_point`),拖动句柄仍为打印 twips;`.uno:` 读回与应用(表头、填充柄)照旧用打印 twips,表头的起点取 `LokView::scrolled_start`。内核读鼠标用的比例原取最后一块图块,worker 现于每次 `setClientVisibleArea` 前以 `setClientZoom` 告知图块比例,点击不再依赖先画过图块。
|
||||
- 2026-10-09(用户报:ElyPDF 改字报“这种字体画不出 V”):文档自带的子集字体常无可用的 Unicode 表(LibreOffice 导出的子集表为占位,0x00 起逐一对应字形号),按 Unicode 找字形一律失败。现以“文档自带字体”写字时,从该页字体资源里找到同一字体(`pdf_load_font` 按对象缓存,与文字层的字体同一对象),按文字提取的读法(ToUnicode 或 cid_to_ucs、`pdf_font_cid_to_gid`)倒出 Unicode 到字形号的表,写字只用页面上已有的字形;新字体的 `/ToUnicode` 以这张表重写(MuPDF 按子集的占位表生成的那份会把新字读成乱码)。页面上没有的字仍报缺字。
|
||||
- 2026-10-09(用户报:ElyPDF 改字报“这种字体画不出 V”):文档自带的子集字体常无可用的 Unicode 表(LibreOffice 导出的子集表为占位,0x00 起逐一对应字形号),按 Unicode 找字形一律失败。现以“文档自带字体”写字时直接用页面上的那个字体对象(`shim/ely_pen.c`;`pdf_load_font` 按对象缓存,与文字层的字体同一对象),以它自己的编码写:按文字提取的读法(ToUnicode 或 cid_to_ucs)倒出 Unicode 到编码的表,排版取它的宽度(`pdf_lookup_hmtx`),原有的 ToUnicode 与宽度不动,两个字共用一个字形时各写各的编码(原先按字形号写、重建 ToUnicode,共用字形的字被改写,且 CMap 漏写条目数,PDFKit 读不出字)。删去原文字块后页面资源里可能已无该字体,故按对象号以新名字重新引用。只支持编码即 CID 的字体(简单字体与 Identity-H),其余拒绝并说明;页面上没有的字仍报缺字。排版按同一套宽度量出的框,比较时留 0.01 点的余量(原块宽度即这些宽度之和,浮点误差曾使末字折行)。
|
||||
- 2026-10-09:ElyTable 的自动筛选(R2.9)。内核每次打开筛选列表都把未命名的筛选区向下扩到紧邻的数据行(为接上后来追加的行),分不出选区时就在下方的合计行,合计因此进了列表、随数据一起被隐藏。补丁 `0040-lok-calc-autofilter-keeps-a-marked-block.patch`:LOK 下对跨行的选区开自动筛选时,筛选区记下“选定的行”(不存入文件),此后不再扩展;单格开筛选照旧取整个数据区。已知限制:存后重开的文件不带此记号,其筛选区照内核原样扩展。
|
||||
@@ -13,6 +13,7 @@ fn main() {
|
||||
"shim/ely_insert.c",
|
||||
"shim/ely_text.c",
|
||||
"shim/ely_write.c",
|
||||
"shim/ely_pen.c",
|
||||
"shim/ely_place.c",
|
||||
"shim/ely_annot.c",
|
||||
"shim/ely_form.c",
|
||||
@@ -68,6 +69,7 @@ fn main() {
|
||||
.file("shim/ely_insert.c")
|
||||
.file("shim/ely_text.c")
|
||||
.file("shim/ely_write.c")
|
||||
.file("shim/ely_pen.c")
|
||||
.file("shim/ely_place.c")
|
||||
.file("shim/ely_annot.c")
|
||||
.file("shim/ely_form.c")
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/* The font new text is written in (PDF-03). */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "ely_shim.h"
|
||||
|
||||
/* The script face for typed signatures, from the worker. */
|
||||
static const unsigned char *script_data;
|
||||
static size_t script_len;
|
||||
|
||||
void ely_script_font(const unsigned char *data, size_t len)
|
||||
{
|
||||
script_data = data;
|
||||
script_len = len;
|
||||
}
|
||||
|
||||
/* Unicode and code pairs of `desc`; its codes are CIDs. */
|
||||
static void own_codes(fz_context *ctx, pdf_font_desc *desc, ely_pen *p)
|
||||
{
|
||||
pdf_cmap *enc = desc->encoding;
|
||||
int cid, end;
|
||||
if (!enc || strcmp(enc->cmap_name, "Identity-H") || enc->codespace_len != 1 || enc->codespace[0].n > 2)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "the document's font uses codes Ely cannot write");
|
||||
p->bytes = enc->codespace[0].n;
|
||||
end = 1 << (8 * p->bytes);
|
||||
p->map = fz_malloc_array(ctx, 2 * end, int);
|
||||
for (cid = 0; cid < end; cid++)
|
||||
{
|
||||
int u = desc->to_unicode ? pdf_lookup_cmap(desc->to_unicode, cid)
|
||||
: cid < (int)desc->cid_to_ucs_len ? desc->cid_to_ucs[cid] : 0;
|
||||
if (u > 0 && pdf_font_cid_to_gid(ctx, desc, cid) > 0)
|
||||
p->map[2 * p->mapped] = u, p->map[2 * p->mapped + 1] = cid, p->mapped++;
|
||||
}
|
||||
}
|
||||
|
||||
/* The page font drawn as `own`, with its codes. */
|
||||
static void own_font(fz_context *ctx, pdf_page *page, fz_font *own, ely_pen *p)
|
||||
{
|
||||
pdf_obj *res = pdf_page_resources(ctx, page);
|
||||
pdf_resource_stack stack = { NULL, res };
|
||||
pdf_obj *fonts = pdf_dict_get(ctx, res, PDF_NAME(Font));
|
||||
int i, n = pdf_dict_len(ctx, fonts);
|
||||
for (i = 0; i < n && !p->desc; i++)
|
||||
{
|
||||
pdf_font_desc *desc = pdf_load_font(ctx, page->doc, &stack, pdf_dict_get_val(ctx, fonts, i));
|
||||
if (desc->font != own)
|
||||
{
|
||||
pdf_drop_font(ctx, desc);
|
||||
continue;
|
||||
}
|
||||
p->desc = desc;
|
||||
p->num = pdf_to_num(ctx, pdf_dict_get_val(ctx, fonts, i));
|
||||
if (!p->num)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "the document's font is not an object of its own");
|
||||
own_codes(ctx, desc, p);
|
||||
}
|
||||
if (!p->desc)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "the block's font is not a font of its page");
|
||||
}
|
||||
|
||||
void ely_choose_pen(fz_context *ctx, const char *choice, fz_font *own, pdf_page *page, ely_pen *p)
|
||||
{
|
||||
static const struct { const char *choice, *name; } latin[] = {
|
||||
{ "helvetica", "Helvetica" }, { "times", "Times-Roman" }, { "courier", "Courier" } };
|
||||
static const struct { const char *choice; int ordering; } cjk[] = {
|
||||
{ "cjk-sc", FZ_ADOBE_GB }, { "cjk-tc", FZ_ADOBE_CNS }, { "cjk-jp", FZ_ADOBE_JAPAN }, { "cjk-kr", FZ_ADOBE_KOREA } };
|
||||
size_t i;
|
||||
if (!strcmp(choice, "script"))
|
||||
{
|
||||
if (!script_data)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "no script font was given");
|
||||
p->kind = PEN_CID;
|
||||
p->font = fz_new_font_from_memory(ctx, "GreatVibes", script_data, (int)script_len, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(choice, "original"))
|
||||
{
|
||||
if (!own)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "the block has no font of its own");
|
||||
p->kind = PEN_OWN;
|
||||
own_font(ctx, page, own, p);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < nelem(latin); i++)
|
||||
if (!strcmp(choice, latin[i].choice))
|
||||
{
|
||||
p->kind = PEN_SIMPLE;
|
||||
p->font = fz_new_base14_font(ctx, latin[i].name);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < nelem(cjk); i++)
|
||||
if (!strcmp(choice, cjk[i].choice))
|
||||
{
|
||||
p->kind = PEN_CJK;
|
||||
p->ordering = cjk[i].ordering;
|
||||
p->font = fz_new_cjk_font(ctx, cjk[i].ordering);
|
||||
return;
|
||||
}
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "no font %s", choice);
|
||||
}
|
||||
|
||||
const char *ely_pen_resource(fz_context *ctx, pdf_document *pdf, pdf_page *page, ely_pen *p, char *name, size_t room)
|
||||
{
|
||||
pdf_drop_obj(ctx, p->ref);
|
||||
p->ref = NULL;
|
||||
switch (p->kind)
|
||||
{
|
||||
case PEN_OWN:
|
||||
/* Removing the block may drop the font from the page. */
|
||||
p->ref = pdf_new_indirect(ctx, pdf, p->num, 0);
|
||||
break;
|
||||
case PEN_CID:
|
||||
p->ref = pdf_add_cid_font(ctx, pdf, p->font);
|
||||
break;
|
||||
case PEN_SIMPLE:
|
||||
p->ref = pdf_add_simple_font(ctx, pdf, p->font, PDF_SIMPLE_ENCODING_LATIN);
|
||||
break;
|
||||
case PEN_CJK:
|
||||
p->ref = pdf_add_cjk_font(ctx, pdf, p->font, p->ordering, 0, 0);
|
||||
break;
|
||||
}
|
||||
return ely_add_resource(ctx, pdf, page->obj, PDF_NAME(Font), "ElyF", p->ref, name, room);
|
||||
}
|
||||
|
||||
/* The own font's code for `u`, or -1. */
|
||||
static int own_code(ely_pen *p, int u)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < p->mapped; i++)
|
||||
if (p->map[2 * i] == u)
|
||||
return p->map[2 * i + 1];
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* The new font's glyph for `u`, or 0. */
|
||||
static int glyph(fz_context *ctx, ely_pen *p, int u)
|
||||
{
|
||||
if (p->kind == PEN_SIMPLE && fz_windows_1252_from_unicode(u) < 0)
|
||||
return 0;
|
||||
return fz_encode_character(ctx, p->font, u);
|
||||
}
|
||||
|
||||
int ely_pen_has(fz_context *ctx, ely_pen *p, int u)
|
||||
{
|
||||
return p->kind == PEN_OWN ? own_code(p, u) >= 0 : glyph(ctx, p, u) > 0;
|
||||
}
|
||||
|
||||
float ely_pen_advance(fz_context *ctx, ely_pen *p, int u, float size)
|
||||
{
|
||||
if (p->kind == PEN_OWN)
|
||||
return pdf_lookup_hmtx(ctx, p->desc, own_code(p, u)).w * size / 1000;
|
||||
return fz_advance_glyph(ctx, p->font, glyph(ctx, p, u), 0) * size;
|
||||
}
|
||||
|
||||
void ely_pen_code(fz_context *ctx, fz_buffer *buf, ely_pen *p, int u)
|
||||
{
|
||||
switch (p->kind)
|
||||
{
|
||||
case PEN_OWN:
|
||||
fz_append_printf(ctx, buf, p->bytes == 1 ? "%02x" : "%04x", own_code(p, u));
|
||||
break;
|
||||
case PEN_CID:
|
||||
fz_append_printf(ctx, buf, "%04x", glyph(ctx, p, u));
|
||||
break;
|
||||
case PEN_SIMPLE:
|
||||
fz_append_printf(ctx, buf, "%02x", fz_windows_1252_from_unicode(u));
|
||||
break;
|
||||
case PEN_CJK:
|
||||
if (u > 0xffff)
|
||||
fz_append_printf(ctx, buf, "%04x%04x", 0xd800 + ((u - 0x10000) >> 10), 0xdc00 + ((u - 0x10000) & 0x3ff));
|
||||
else
|
||||
fz_append_printf(ctx, buf, "%04x", u);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ely_drop_pen(fz_context *ctx, ely_pen *p)
|
||||
{
|
||||
fz_drop_font(ctx, p->font);
|
||||
pdf_drop_obj(ctx, p->ref);
|
||||
pdf_drop_font(ctx, p->desc);
|
||||
fz_free(ctx, p->map);
|
||||
}
|
||||
@@ -53,4 +53,34 @@ int ely_watch_end(fz_context *ctx);
|
||||
const char *ely_open_warnings(void);
|
||||
void ely_drop_alt(fz_context *ctx, pdf_document *pdf, pdf_page *page);
|
||||
|
||||
/* A font to write with: new, or the page's own. */
|
||||
typedef enum
|
||||
{
|
||||
PEN_CID,
|
||||
PEN_SIMPLE,
|
||||
PEN_CJK,
|
||||
PEN_OWN
|
||||
} ely_pen_kind;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ely_pen_kind kind;
|
||||
fz_font *font;
|
||||
int ordering;
|
||||
pdf_obj *ref;
|
||||
/* The page's own font: object number, description, code width. */
|
||||
int num;
|
||||
pdf_font_desc *desc;
|
||||
int bytes;
|
||||
/* Unicode and code pairs of the own font. */
|
||||
int *map, mapped;
|
||||
} ely_pen;
|
||||
|
||||
void ely_choose_pen(fz_context *ctx, const char *choice, fz_font *own, pdf_page *page, ely_pen *p);
|
||||
const char *ely_pen_resource(fz_context *ctx, pdf_document *pdf, pdf_page *page, ely_pen *p, char *name, size_t room);
|
||||
int ely_pen_has(fz_context *ctx, ely_pen *p, int u);
|
||||
float ely_pen_advance(fz_context *ctx, ely_pen *p, int u, float size);
|
||||
void ely_pen_code(fz_context *ctx, fz_buffer *buf, ely_pen *p, int u);
|
||||
void ely_drop_pen(fz_context *ctx, ely_pen *p);
|
||||
|
||||
#endif
|
||||
@@ -5,24 +5,6 @@
|
||||
|
||||
#include "ely_shim.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PEN_CID,
|
||||
PEN_SIMPLE,
|
||||
PEN_CJK
|
||||
} pen_kind;
|
||||
|
||||
/* A font to write with and its resource. */
|
||||
typedef struct
|
||||
{
|
||||
pen_kind kind;
|
||||
fz_font *font;
|
||||
int ordering;
|
||||
pdf_obj *ref;
|
||||
/* For the page's own font: Unicode and glyph pairs. */
|
||||
int *map, mapped;
|
||||
} pen;
|
||||
|
||||
/* What to write, where, in what. */
|
||||
typedef struct
|
||||
{
|
||||
@@ -34,188 +16,6 @@ typedef struct
|
||||
float box[3];
|
||||
} text_args;
|
||||
|
||||
/* The script face for typed signatures, from the worker. */
|
||||
static const unsigned char *script_data;
|
||||
static size_t script_len;
|
||||
|
||||
void ely_script_font(const unsigned char *data, size_t len)
|
||||
{
|
||||
script_data = data;
|
||||
script_len = len;
|
||||
}
|
||||
|
||||
/* Unicode to glyph for the page font drawn as `own`. */
|
||||
static void own_glyphs(fz_context *ctx, pdf_page *page, fz_font *own, pen *p)
|
||||
{
|
||||
pdf_obj *res = pdf_page_resources(ctx, page);
|
||||
pdf_resource_stack stack = { NULL, res };
|
||||
pdf_obj *fonts = pdf_dict_get(ctx, res, PDF_NAME(Font));
|
||||
int i, n = pdf_dict_len(ctx, fonts);
|
||||
for (i = 0; i < n && !p->map; i++)
|
||||
{
|
||||
pdf_font_desc *desc = pdf_load_font(ctx, page->doc, &stack, pdf_dict_get_val(ctx, fonts, i));
|
||||
fz_try(ctx)
|
||||
{
|
||||
int cid, end = desc->to_unicode ? 65536 : (int)desc->cid_to_ucs_len;
|
||||
if (desc->font == own)
|
||||
{
|
||||
p->map = fz_malloc_array(ctx, 2 * end + 2, int);
|
||||
for (cid = 0; cid < end; cid++)
|
||||
{
|
||||
int u = desc->to_unicode ? pdf_lookup_cmap(desc->to_unicode, cid) : desc->cid_to_ucs[cid];
|
||||
int gid = u > 0 ? pdf_font_cid_to_gid(ctx, desc, cid) : 0;
|
||||
if (gid > 0)
|
||||
p->map[2 * p->mapped] = u, p->map[2 * p->mapped + 1] = gid, p->mapped++;
|
||||
}
|
||||
}
|
||||
}
|
||||
fz_always(ctx)
|
||||
pdf_drop_font(ctx, desc);
|
||||
fz_catch(ctx)
|
||||
fz_rethrow(ctx);
|
||||
}
|
||||
if (!p->map)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "the block's font is not a font of its page");
|
||||
}
|
||||
|
||||
/* The pen named `choice`; "original" writes in `own`. */
|
||||
static void choose_pen(fz_context *ctx, const char *choice, fz_font *own, pdf_page *page, pen *p)
|
||||
{
|
||||
static const struct { const char *choice, *name; } latin[] = {
|
||||
{ "helvetica", "Helvetica" }, { "times", "Times-Roman" }, { "courier", "Courier" } };
|
||||
static const struct { const char *choice; int ordering; } cjk[] = {
|
||||
{ "cjk-sc", FZ_ADOBE_GB }, { "cjk-tc", FZ_ADOBE_CNS }, { "cjk-jp", FZ_ADOBE_JAPAN }, { "cjk-kr", FZ_ADOBE_KOREA } };
|
||||
size_t i;
|
||||
if (!strcmp(choice, "script"))
|
||||
{
|
||||
if (!script_data)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "no script font was given");
|
||||
p->kind = PEN_CID;
|
||||
p->font = fz_new_font_from_memory(ctx, "GreatVibes", script_data, (int)script_len, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(choice, "original"))
|
||||
{
|
||||
if (!own || !pdf_font_writing_supported(ctx, own))
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "the document's font cannot write new text");
|
||||
p->kind = PEN_CID;
|
||||
p->font = fz_keep_font(ctx, own);
|
||||
own_glyphs(ctx, page, own, p);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < nelem(latin); i++)
|
||||
if (!strcmp(choice, latin[i].choice))
|
||||
{
|
||||
p->kind = PEN_SIMPLE;
|
||||
p->font = fz_new_base14_font(ctx, latin[i].name);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < nelem(cjk); i++)
|
||||
if (!strcmp(choice, cjk[i].choice))
|
||||
{
|
||||
p->kind = PEN_CJK;
|
||||
p->ordering = cjk[i].ordering;
|
||||
p->font = fz_new_cjk_font(ctx, cjk[i].ordering);
|
||||
return;
|
||||
}
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "no font %s", choice);
|
||||
}
|
||||
|
||||
/* A ToUnicode CMap from the pen's map. */
|
||||
static pdf_obj *map_to_unicode(fz_context *ctx, pdf_document *pdf, pen *p)
|
||||
{
|
||||
fz_buffer *buf = fz_new_buffer(ctx, 256);
|
||||
pdf_obj *ref = NULL;
|
||||
fz_var(ref);
|
||||
fz_try(ctx)
|
||||
{
|
||||
int i, j, block = 0;
|
||||
fz_append_string(ctx, buf, "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n"
|
||||
"/CIDSystemInfo <</Registry(Adobe)/Ordering(UCS)/Supplement 0>> def\n"
|
||||
"/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n"
|
||||
"1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n");
|
||||
for (i = 0; i < p->mapped; i++)
|
||||
{
|
||||
int u = p->map[2 * i], gid = p->map[2 * i + 1], seen = 0;
|
||||
for (j = 0; j < i && !seen; j++)
|
||||
seen = p->map[2 * j + 1] == gid;
|
||||
if (seen)
|
||||
continue;
|
||||
if (block % 100 == 0)
|
||||
fz_append_printf(ctx, buf, "%sbeginbfchar\n", block ? "endbfchar\n" : "");
|
||||
if (u > 0xffff)
|
||||
fz_append_printf(ctx, buf, "<%04x> <%04x%04x>\n", gid, 0xd800 + ((u - 0x10000) >> 10), 0xdc00 + ((u - 0x10000) & 0x3ff));
|
||||
else
|
||||
fz_append_printf(ctx, buf, "<%04x> <%04x>\n", gid, u);
|
||||
block++;
|
||||
}
|
||||
/* Each beginbfchar holds 100 entries at most. */
|
||||
fz_append_printf(ctx, buf, "%sendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n",
|
||||
block ? "endbfchar\n" : "");
|
||||
ref = pdf_add_stream(ctx, pdf, buf, NULL, 0);
|
||||
}
|
||||
fz_always(ctx)
|
||||
fz_drop_buffer(ctx, buf);
|
||||
fz_catch(ctx)
|
||||
fz_rethrow(ctx);
|
||||
return ref;
|
||||
}
|
||||
|
||||
/* The pen's font as a resource of `pdf`. */
|
||||
static void add_pen(fz_context *ctx, pdf_document *pdf, pen *p)
|
||||
{
|
||||
switch (p->kind)
|
||||
{
|
||||
case PEN_CID:
|
||||
p->ref = pdf_add_cid_font(ctx, pdf, p->font);
|
||||
/* Subset fonts carry bogus tables; the page's codes are true. */
|
||||
if (p->map)
|
||||
pdf_dict_put_drop(ctx, p->ref, PDF_NAME(ToUnicode), map_to_unicode(ctx, pdf, p));
|
||||
break;
|
||||
case PEN_SIMPLE:
|
||||
p->ref = pdf_add_simple_font(ctx, pdf, p->font, PDF_SIMPLE_ENCODING_LATIN);
|
||||
break;
|
||||
case PEN_CJK:
|
||||
p->ref = pdf_add_cjk_font(ctx, pdf, p->font, p->ordering, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* The pen's glyph for `u`; 0 when it has none. */
|
||||
static int glyph(fz_context *ctx, pen *p, int u)
|
||||
{
|
||||
int i;
|
||||
if (p->kind == PEN_SIMPLE && fz_windows_1252_from_unicode(u) < 0)
|
||||
return 0;
|
||||
/* The page's own codes beat a subset font's tables. */
|
||||
for (i = 0; i < p->mapped; i++)
|
||||
if (p->map[2 * i] == u)
|
||||
return p->map[2 * i + 1];
|
||||
if (p->map)
|
||||
return 0;
|
||||
return fz_encode_character(ctx, p->font, u);
|
||||
}
|
||||
|
||||
/* `u` as the pen's code, in hex. */
|
||||
static void code(fz_context *ctx, fz_buffer *buf, pen *p, int u, int gid)
|
||||
{
|
||||
switch (p->kind)
|
||||
{
|
||||
case PEN_CID:
|
||||
fz_append_printf(ctx, buf, "%04x", gid);
|
||||
break;
|
||||
case PEN_SIMPLE:
|
||||
fz_append_printf(ctx, buf, "%02x", fz_windows_1252_from_unicode(u));
|
||||
break;
|
||||
case PEN_CJK:
|
||||
if (u > 0xffff)
|
||||
fz_append_printf(ctx, buf, "%04x%04x", 0xd800 + ((u - 0x10000) >> 10), 0xdc00 + ((u - 0x10000) & 0x3ff));
|
||||
else
|
||||
fz_append_printf(ctx, buf, "%04x", u);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* `text` as code points; the count is returned. */
|
||||
static int runes(fz_context *ctx, const char *text, int **out)
|
||||
{
|
||||
@@ -229,13 +29,8 @@ static int runes(fz_context *ctx, const char *text, int **out)
|
||||
return n;
|
||||
}
|
||||
|
||||
static float advance(fz_context *ctx, pen *p, int u, float size)
|
||||
{
|
||||
return fz_advance_glyph(ctx, p->font, glyph(ctx, p, u), 0) * size;
|
||||
}
|
||||
|
||||
/* Lines no wider than `width`: [from, to) each. */
|
||||
static int lay_out(fz_context *ctx, pen *p, const int *u, int n, float size, float width, int *from, int *to,
|
||||
static int lay_out(fz_context *ctx, ely_pen *p, const int *u, int n, float size, float width, int *from, int *to,
|
||||
float *widest)
|
||||
{
|
||||
int i = 0, lines = 0;
|
||||
@@ -246,8 +41,9 @@ static int lay_out(fz_context *ctx, pen *p, const int *u, int n, float size, flo
|
||||
float run = 0, before = 0;
|
||||
for (j = i; j < n && u[j] != '\n'; j++)
|
||||
{
|
||||
float w = advance(ctx, p, u[j], size);
|
||||
if (run + w > width && j > i)
|
||||
float w = ely_pen_advance(ctx, p, u[j], size);
|
||||
/* A box measured from these widths fits them. */
|
||||
if (run + w > width + 0.01f && j > i)
|
||||
break;
|
||||
if (u[j] == ' ')
|
||||
space = j, before = run;
|
||||
@@ -344,7 +140,7 @@ pdf_obj *ely_append_content(fz_context *ctx, pdf_document *pdf, pdf_obj *page, f
|
||||
/* Text laid out: the pen, code points, lines, placement. */
|
||||
typedef struct
|
||||
{
|
||||
pen p;
|
||||
ely_pen p;
|
||||
int *u, n;
|
||||
int *from, *to, lines;
|
||||
float widest;
|
||||
@@ -354,9 +150,7 @@ typedef struct
|
||||
|
||||
static void drop_laid(fz_context *ctx, laid *l)
|
||||
{
|
||||
fz_drop_font(ctx, l->p.font);
|
||||
pdf_drop_obj(ctx, l->p.ref);
|
||||
fz_free(ctx, l->p.map);
|
||||
ely_drop_pen(ctx, &l->p);
|
||||
fz_free(ctx, l->u);
|
||||
fz_free(ctx, l->from);
|
||||
fz_free(ctx, l->to);
|
||||
@@ -366,10 +160,10 @@ static void drop_laid(fz_context *ctx, laid *l)
|
||||
static int lay_text(fz_context *ctx, text_args *a, fz_font *own, pdf_page *page, fz_point first, float gap, laid *l)
|
||||
{
|
||||
int i;
|
||||
choose_pen(ctx, a->font, own, page, &l->p);
|
||||
ely_choose_pen(ctx, a->font, own, page, &l->p);
|
||||
l->n = runes(ctx, a->text, &l->u);
|
||||
for (i = 0; i < l->n; i++)
|
||||
if (l->u[i] != '\n' && !glyph(ctx, &l->p, l->u[i]))
|
||||
if (l->u[i] != '\n' && !ely_pen_has(ctx, &l->p, l->u[i]))
|
||||
return l->u[i];
|
||||
l->from = fz_malloc_array(ctx, l->n + 2, int);
|
||||
l->to = fz_malloc_array(ctx, l->n + 2, int);
|
||||
@@ -395,10 +189,7 @@ static void draw_laid(fz_context *ctx, pdf_document *pdf, pdf_page *page, laid *
|
||||
fz_matrix to_user = ely_to_user(ctx, page);
|
||||
char name[32];
|
||||
int i, line;
|
||||
pdf_drop_obj(ctx, l->p.ref);
|
||||
l->p.ref = NULL;
|
||||
add_pen(ctx, pdf, &l->p);
|
||||
ely_add_resource(ctx, pdf, page->obj, PDF_NAME(Font), "ElyF", l->p.ref, name, sizeof name);
|
||||
ely_pen_resource(ctx, pdf, page, &l->p, name, sizeof name);
|
||||
buf = fz_new_buffer(ctx, 256);
|
||||
fz_append_string(ctx, buf, "q\n");
|
||||
for (line = 0; line < l->lines; line++)
|
||||
@@ -408,7 +199,7 @@ static void draw_laid(fz_context *ctx, pdf_document *pdf, pdf_page *page, laid *
|
||||
((a->rgb >> 16) & 255) / 255.0f, ((a->rgb >> 8) & 255) / 255.0f, (a->rgb & 255) / 255.0f,
|
||||
tm.a, tm.b, tm.c, tm.d, tm.e, tm.f);
|
||||
for (i = l->from[line]; i < l->to[line]; i++)
|
||||
code(ctx, buf, &l->p, l->u[i], glyph(ctx, &l->p, l->u[i]));
|
||||
ely_pen_code(ctx, buf, &l->p, l->u[i]);
|
||||
fz_append_string(ctx, buf, "> Tj ET\n");
|
||||
}
|
||||
fz_append_string(ctx, buf, "Q\n");
|
||||
@@ -476,11 +267,12 @@ int ely_put_text(fz_context *ctx, fz_document *doc, int number, int block, const
|
||||
pdf_document *pdf = NULL;
|
||||
pdf_page *page = NULL;
|
||||
ely_shape shape = { NULL };
|
||||
laid l = { { PEN_SIMPLE, NULL, 0, NULL } };
|
||||
laid l;
|
||||
int begun = 0;
|
||||
fz_var(pdf);
|
||||
fz_var(page);
|
||||
fz_var(begun);
|
||||
memset(&l, 0, sizeof l);
|
||||
*pixels = NULL;
|
||||
*grew = 0;
|
||||
fz_try(ctx)
|
||||
|
||||
@@ -291,3 +291,48 @@ fn a_subset_font_writes_with_its_own_glyphs_and_reads_back() {
|
||||
let now = page_text(&mut worker, doc);
|
||||
assert!(now.contains("TEXT BUNDLE"), "the new words read back: {now:?}");
|
||||
}
|
||||
|
||||
/// "AyB", where "x" and "y" share one glyph.
|
||||
fn shared_glyph_pdf() -> Vec<u8> {
|
||||
let font = std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR")).join("fonts/GreatVibes-Regular.ttf")).expect("the font");
|
||||
let stream = |dict: &str, data: &[u8]| [format!("<<{dict}/Length {}>>stream\n", data.len()).as_bytes(), data, b"\nendstream"].concat();
|
||||
let unicode = b"/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CMapName /U def\n1 begincodespacerange <0000> <FFFF> endcodespacerange\n4 beginbfchar <0001> <0041> <0002> <0078> <0003> <0079> <0004> <0042> endbfchar\nendcmap CMapName currentdict /CMap defineresource pop end end";
|
||||
let gids: Vec<u8> = [0u16, 36, 16, 16, 37].iter().flat_map(|g| g.to_be_bytes()).collect();
|
||||
let objects: Vec<Vec<u8>> = vec![
|
||||
b"<</Type/Catalog/Pages 2 0 R>>".to_vec(),
|
||||
b"<</Type/Pages/Kids[3 0 R]/Count 1>>".to_vec(),
|
||||
b"<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]/Resources<</Font<</F1 4 0 R>>>>/Contents 5 0 R>>".to_vec(),
|
||||
b"<</Type/Font/Subtype/Type0/BaseFont/GreatVibes/Encoding/Identity-H/DescendantFonts[6 0 R]/ToUnicode 7 0 R>>".to_vec(),
|
||||
stream("", b"BT /F1 24 Tf 72 700 Td <000100030004> Tj ET"),
|
||||
b"<</Type/Font/Subtype/CIDFontType2/BaseFont/GreatVibes/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>/FontDescriptor 8 0 R/CIDToGIDMap 9 0 R/DW 600>>".to_vec(),
|
||||
stream("", unicode),
|
||||
b"<</Type/FontDescriptor/FontName/GreatVibes/Flags 4/FontBBox[-200 -300 1200 1000]/ItalicAngle 0/Ascent 900/Descent -300/CapHeight 700/StemV 80/FontFile2 10 0 R>>".to_vec(),
|
||||
stream("", &gids),
|
||||
stream(&format!("/Length1 {}", font.len()), &font),
|
||||
];
|
||||
let mut pdf = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (at, body) in objects.iter().enumerate() {
|
||||
offsets.push(pdf.len());
|
||||
pdf.extend([format!("{} 0 obj\n", at + 1).as_bytes(), body, b"\nendobj\n"].concat());
|
||||
}
|
||||
let xref = pdf.len();
|
||||
pdf.extend(format!("xref\n0 {}\n0000000000 65535 f \n", objects.len() + 1).as_bytes());
|
||||
offsets.iter().for_each(|at| pdf.extend(format!("{at:010} 00000 n \n").as_bytes()));
|
||||
pdf.extend(format!("trailer\n<</Size {}/Root 1 0 R>>\nstartxref\n{xref}\n%%EOF\n", objects.len() + 1).as_bytes());
|
||||
pdf
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_letter_that_shares_its_glyph_keeps_its_own_code() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let path = dir.path().join("shared.pdf");
|
||||
std::fs::write(&path, shared_glyph_pdf()).expect("writes");
|
||||
let mut worker = worker();
|
||||
let mut doc = worker.open(&path).expect("opens").doc;
|
||||
assert_eq!(page_text(&mut worker, doc), "AyB", "the sample reads as written");
|
||||
let writing = rewrite(&mut worker, doc, 0, "ByA", "original");
|
||||
assert!(matches!(worker.put_text(doc, 0, &writing, 0.0).expect("answers"), Wrote::Done(_)));
|
||||
doc = worker.now(doc).expect("the state");
|
||||
assert_eq!(page_text(&mut worker, doc), "ByA", "y stays y, not the glyph's other letter");
|
||||
}
|
||||
@@ -61,3 +61,28 @@ fn text_a_field_and_page_order_read_back_elsewhere() {
|
||||
assert!(pages[1].0.contains("Changed words") && !pages[1].0.contains("First page"), "{pages:?}");
|
||||
assert_eq!(pages[1].1, vec!["Ada".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_in_a_subset_font_reads_back_elsewhere() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/office/t.pdf");
|
||||
let mut worker = worker();
|
||||
let mut doc = worker.open(&path).expect("opens").doc;
|
||||
let blocks = worker.text_blocks(doc, 0).expect("blocks");
|
||||
let (block, read) = blocks.iter().enumerate().find(|(_, b)| b.text.contains("BUNDLE")).expect("the block");
|
||||
let writing = Writing {
|
||||
block: Some(block),
|
||||
expected: read.text.clone(),
|
||||
text: "TEXT BUNDLE".into(),
|
||||
font: "original".into(),
|
||||
size: read.size,
|
||||
rgb: read.rgb,
|
||||
bx: [read.rect[0], read.rect[1], (read.rect[2] - read.rect[0]) * 2.0],
|
||||
};
|
||||
assert!(matches!(worker.put_text(doc, 0, &writing, 0.0).expect("writes"), Wrote::Done(_)));
|
||||
doc = worker.now(doc).expect("the state");
|
||||
let out = dir.path().join("edited.pdf");
|
||||
worker.save(doc, &out).expect("saves");
|
||||
let pages = read_back(&out);
|
||||
assert!(pages[0].0.contains("TEXT BUNDLE"), "{pages:?}");
|
||||
}
|
||||
Reference in new issue
Block a user