ElyPDF: the document's font writes through its own object and codes; shared glyphs keep their letters (R2.17)

This commit is contained in:
ZacharyZhang-NY committed 2026-10-09 03:17:08 -04:00
1 parent fbcf654812
commit f4c1a1665b
7 files changed
+298 -221

No files matched your search

+1 -1
View File
@@ -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 下对跨行的选区开自动筛选时,筛选区记下“选定的行”(不存入文件),此后不再扩展;单格开筛选照旧取整个数据区。已知限制:存后重开的文件不带此记号,其筛选区照内核原样扩展。
+2
View File
@@ -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")
+183
View File
@@ -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);
}
+30
View File
@@ -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
+12 -220
View File
@@ -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)
+45
View File
@@ -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:?}");
}