ElyPDF: text blocks with ligatures can be edited (P-07)
This commit is contained in:
3 files changed
+76
-4
No files matched your search
@@ -4,6 +4,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "ely_shim.h"
|
||||
#include "mupdf/ucdn.h"
|
||||
|
||||
/* The page's text, read the same way every time. */
|
||||
static fz_stext_page *read_text(fz_context *ctx, fz_document *doc, int number)
|
||||
@@ -58,14 +59,55 @@ static void block_targets(fz_context *ctx, fz_stext_block *block, fz_matrix to_u
|
||||
}
|
||||
}
|
||||
|
||||
/* `c` as stext spells it (stext-device.c); ligatures split. */
|
||||
static int spell(int c, int *out)
|
||||
{
|
||||
static const char *named[] = { "ff", "fi", "fl", "ffi", "ffl", "st", "st" };
|
||||
int i, n = 0;
|
||||
uint32_t lig[18];
|
||||
if (c >= 0xFB00 && c <= 0xFB06)
|
||||
{
|
||||
for (i = 0; named[c - 0xFB00][i]; i++)
|
||||
out[n++] = named[c - 0xFB00][i];
|
||||
return n;
|
||||
}
|
||||
if ((c >= 0xfb00 && c <= 0xfdff) || (c >= 0xfe70 && c <= 0xfefc))
|
||||
n = ucdn_compat_decompose((uint32_t)c, lig);
|
||||
for (i = 0; i < n; i++)
|
||||
out[i] = (int)lig[i];
|
||||
if (n > 0)
|
||||
return n;
|
||||
out[0] = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Targets from `i` one glyph spells: a ligature spells several. */
|
||||
static int spelled(targets *t, int i, const int *ucs, int ucslen)
|
||||
{
|
||||
int j = 1;
|
||||
if (ucslen < 2 || ucs[0] != t->c[i])
|
||||
return 1;
|
||||
while (j < ucslen && i + j < t->n && t->c[i + j] == ucs[j] && (!t->owner || t->owner[i + j] == t->owner[i]))
|
||||
j++;
|
||||
return j;
|
||||
}
|
||||
|
||||
/* Removes a glyph drawn at one of the targets. */
|
||||
static int take_glyph(fz_context *ctx, void *opaque, int *ucs, int ucslen, fz_matrix trm, fz_matrix ctm,
|
||||
fz_rect bbox, int tr, float ca, float CA)
|
||||
{
|
||||
targets *t = opaque;
|
||||
fz_point at = fz_transform_point(fz_transform_point(fz_make_point(0, 0), trm), ctm);
|
||||
int i, k, found = 0;
|
||||
/* MuPDF gives at most PDF_MRANGE_CAP; each spells to 18. */
|
||||
int i, k, found = 0, spelt[18 * PDF_MRANGE_CAP], n = 0;
|
||||
t->glyph++;
|
||||
if (ucslen > PDF_MRANGE_CAP)
|
||||
fz_throw(ctx, FZ_ERROR_ARGUMENT, "a glyph with %d characters", ucslen);
|
||||
/* Match what stext reads: ligature code points spelled out. */
|
||||
for (k = 0; k < ucslen; k++)
|
||||
n += spell(ucs[k], spelt + n);
|
||||
ucs = spelt;
|
||||
ucslen = n;
|
||||
for (i = 0; i < t->n; i++)
|
||||
{
|
||||
int match = 0;
|
||||
@@ -77,14 +119,14 @@ static int take_glyph(fz_context *ctx, void *opaque, int *ucs, int ucslen, fz_ma
|
||||
continue;
|
||||
if (!t->owner)
|
||||
{
|
||||
t->hit++;
|
||||
t->hit += spelled(t, i, ucs, ucslen);
|
||||
return 1;
|
||||
}
|
||||
/* Every block this glyph matches counts it once. */
|
||||
if (t->seen[t->owner[i]] != t->glyph)
|
||||
{
|
||||
t->seen[t->owner[i]] = t->glyph;
|
||||
t->hits[t->owner[i]]++;
|
||||
t->hits[t->owner[i]] += spelled(t, i, ucs, ucslen);
|
||||
}
|
||||
found = 1;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,36 @@ fn a_font_whose_codes_ely_cannot_write_says_so() {
|
||||
assert!(matches!(worker.put_text(doc, 0, &helvetica, 0.0).expect("writes"), Wrote::Done(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_ligature_block_edits_like_any_other() {
|
||||
// Code 1 is one ligature glyph, in either Unicode form.
|
||||
let mut forms = vec![("00660069".to_string(), "find".to_string()), ("FB01".into(), "find".into()), ("FB03".into(), "ffind".into())];
|
||||
// Long mappings, to MuPDF's cap of 256: none cut short.
|
||||
forms.push(("0066".repeat(48), format!("{}nd", "f".repeat(48))));
|
||||
forms.push(("FB03".repeat(256), format!("{}nd", "ffi".repeat(256))));
|
||||
for (unicode, text) in forms {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let path = dir.path().join("lig.pdf");
|
||||
let cmap = format!("/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CMapName /Lig def 1 begincodespacerange <00> <FF> endcodespacerange 3 beginbfchar <01> <{unicode}> <6E> <006E> <64> <0064> endbfchar endcmap CMapName currentdict /CMap defineresource pop end end");
|
||||
let content = "BT /F1 24 Tf 72 700 Td <016E64> Tj ET";
|
||||
let pdf = one_font_page(content, &[
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding << /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [1 /fi] >> /ToUnicode 6 0 R >>".to_string(),
|
||||
format!("<< /Length {} >>\nstream\n{cmap}\nendstream", cmap.len()),
|
||||
]);
|
||||
std::fs::write(&path, pdf).expect("writes");
|
||||
let mut worker = worker();
|
||||
let doc = worker.open(&path).expect("opens").doc;
|
||||
let block = worker.text_blocks(doc, 0).expect("blocks").into_iter().next().expect("a block");
|
||||
assert_eq!(block.text, text);
|
||||
assert!(block.editable, "{unicode}: {block:?}");
|
||||
let writing = rewrite(&mut worker, doc, 0, "Yo", "helvetica");
|
||||
let Wrote::Done(mark) = worker.put_text(doc, 0, &writing, 0.0).expect("writes") else {
|
||||
panic!("{unicode}: the ligature block was not rewritten");
|
||||
};
|
||||
assert_eq!(page_text(&mut worker, doc.at(&mark).expect("a mark")), "Yo");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_font_written_into_the_page_says_it_cannot_write() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@
|
||||
- [x] P-04 `TEXT_BLOCKS` 对每个文字块重跑一遍整页内容过滤,编辑工具长时间无反应 — 整页一遍干跑判定全部文字块(1000 块 1.5 s→0.1 s);R1 转向文字后错位、共用原点只计一块;R2 PASS
|
||||
- [x] P-05 连续快画或快点被拒:“文档在此间已改动,未执行” — 按坐标的编辑排队、跑在出队时的状态上,只在页面变动时拒绝;R2 PASS
|
||||
- [x] P-06 默认字体“原字体”在 Type 1/Type 3 字体的文件上失败,只给引擎原话 — 原字体写不了新字(非 CID 编码、页面内直接字体、不在本页)时面板提示换字体;R2 PASS
|
||||
- [ ] P-07 含连字的文字块永远不可编辑
|
||||
- [x] P-07 含连字的文字块永远不可编辑 — 字形按 stext 的写法展开(多字字形、连字码点),整页、单块与删除同一判法;R3 PASS
|
||||
- [ ] P-08 页面读取失败后每帧重试、每帧弹提示
|
||||
- [ ] P-09 识别文字的写入被拒后仍弹“成功”
|
||||
- [ ] P-10 预览回复被丢弃时“应用”静默无事
|
||||
|
||||
Reference in new issue
Block a user