ElyPDF worker: text blocks are always valid JSON, whatever the text or font name holds (P-14)
This commit is contained in:
4 files changed
+81
-12
No files matched your search
@@ -9,18 +9,34 @@ static const char *stamps[] = { "Approved", "AsIs", "Confidential", "Departmenta
|
||||
"Expired", "Final", "ForComment", "ForPublicRelease", "NotApproved", "NotForPublicRelease", "Sold",
|
||||
"TopSecret", NULL };
|
||||
|
||||
/* Valid UTF-8 always: a stray byte becomes U+FFFD. */
|
||||
void ely_json_string(fz_context *ctx, fz_output *out, const char *s)
|
||||
{
|
||||
char utf8[FZ_UTFMAX];
|
||||
int rune;
|
||||
fz_write_byte(ctx, out, '"');
|
||||
for (; *s; s++)
|
||||
while (*s)
|
||||
{
|
||||
unsigned char c = (unsigned char)*s;
|
||||
if (c == '"' || c == '\\')
|
||||
fz_write_printf(ctx, out, "\\%c", c);
|
||||
else if (c < 32)
|
||||
fz_write_printf(ctx, out, "\\u%04x", c);
|
||||
s += fz_chartorune(&rune, s);
|
||||
/* A surrogate pair joins; a lone one is no character. */
|
||||
if (rune >= 0xD800 && rune < 0xDC00)
|
||||
{
|
||||
int low;
|
||||
int n = fz_chartorune(&low, s);
|
||||
if (low >= 0xDC00 && low < 0xE000)
|
||||
{
|
||||
rune = 0x10000 + ((rune - 0xD800) << 10) + (low - 0xDC00);
|
||||
s += n;
|
||||
}
|
||||
}
|
||||
if ((rune >= 0xD800 && rune < 0xE000) || rune > 0x10FFFF)
|
||||
rune = FZ_REPLACEMENT_CHARACTER;
|
||||
if (rune == '"' || rune == '\\')
|
||||
fz_write_printf(ctx, out, "\\%c", rune);
|
||||
else if (rune < 32)
|
||||
fz_write_printf(ctx, out, "\\u%04x", rune);
|
||||
else
|
||||
fz_write_byte(ctx, out, c);
|
||||
fz_write_data(ctx, out, utf8, fz_runetochar(utf8, rune));
|
||||
}
|
||||
fz_write_byte(ctx, out, '"');
|
||||
}
|
||||
|
||||
@@ -313,10 +313,13 @@ int ely_text_blocks(fz_context *ctx, fz_document *doc, int number, unsigned char
|
||||
words = fz_new_buffer(ctx, 256);
|
||||
block_text(ctx, words, block);
|
||||
fz_terminate_buffer(ctx, words);
|
||||
fz_write_printf(ctx, out, "%s{\"rect\":[%g,%g,%g,%g],\"text\":%q,\"font\":%q,\"size\":%g,\"color\":%d,\"editable\":%s}",
|
||||
first ? "" : ",", block->bbox.x0, block->bbox.y0, block->bbox.x1, block->bbox.y1,
|
||||
fz_string_from_buffer(ctx, words), fz_font_name(ctx, ch->font), ch->size, (int)(ch->argb & 0xffffff),
|
||||
editable ? "true" : "false");
|
||||
fz_write_printf(ctx, out, "%s{\"rect\":[%g,%g,%g,%g],\"text\":",
|
||||
first ? "" : ",", block->bbox.x0, block->bbox.y0, block->bbox.x1, block->bbox.y1);
|
||||
ely_json_string(ctx, out, fz_string_from_buffer(ctx, words));
|
||||
fz_write_string(ctx, out, ",\"font\":");
|
||||
ely_json_string(ctx, out, fz_font_name(ctx, ch->font));
|
||||
fz_write_printf(ctx, out, ",\"size\":%g,\"color\":%d,\"editable\":%s}",
|
||||
ch->size, (int)(ch->argb & 0xffffff), editable ? "true" : "false");
|
||||
first = 0;
|
||||
}
|
||||
fz_write_string(ctx, out, "]");
|
||||
|
||||
@@ -110,6 +110,56 @@ fn a_ligature_block_edits_like_any_other() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_control_character_in_text_still_reads() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let path = dir.path().join("control.pdf");
|
||||
// Code 1 names U+0001, as broken encodings do.
|
||||
let content = "BT /F1 24 Tf 72 700 Td <41014243> Tj ET";
|
||||
let pdf = one_font_page(content, &[
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding << /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [1 /uni0001] >> >>".to_string(),
|
||||
]);
|
||||
std::fs::write(&path, pdf).expect("writes");
|
||||
let mut worker = worker();
|
||||
let doc = worker.open(&path).expect("opens").doc;
|
||||
let text = page_text(&mut worker, doc);
|
||||
let blocks = worker.text_blocks(doc, 0).expect("the blocks parse");
|
||||
assert_eq!(blocks.len(), 1);
|
||||
assert_eq!(blocks[0].text, text, "the same text as the page reads");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_font_name_with_a_stray_byte_still_reads() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let path = dir.path().join("name.pdf");
|
||||
// #E9 alone is not UTF-8.
|
||||
let content = "BT /F1 24 Tf 72 700 Td (ABC) Tj ET";
|
||||
let pdf = one_font_page(content, &[
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Review#E9Font /Encoding /WinAnsiEncoding >>".to_string(),
|
||||
]);
|
||||
std::fs::write(&path, pdf).expect("writes");
|
||||
let mut worker = worker();
|
||||
let doc = worker.open(&path).expect("opens").doc;
|
||||
let blocks = worker.text_blocks(doc, 0).expect("the blocks parse");
|
||||
assert_eq!((blocks[0].text.as_str(), blocks[0].font.as_str()), ("ABC", "Review\u{FFFD}Font"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_font_name_in_surrogate_halves_still_reads() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let content = "BT /F1 24 Tf 72 700 Td (ABC) Tj ET";
|
||||
let mut worker = worker();
|
||||
// A pair joins; a lone half is no character.
|
||||
for (base, want) in [("Review#ED#A0#BD#ED#B8#80Font", "Review\u{1F600}Font"), ("Lone#ED#A0#BDFont", "Lone\u{FFFD}Font")] {
|
||||
let path = dir.path().join(format!("{want}.pdf"));
|
||||
let font = format!("<< /Type /Font /Subtype /Type1 /BaseFont /{base} /Encoding /WinAnsiEncoding >>");
|
||||
std::fs::write(&path, one_font_page(content, &[font])).expect("writes");
|
||||
let doc = worker.open(&path).expect("opens").doc;
|
||||
let blocks = worker.text_blocks(doc, 0).expect("the blocks parse");
|
||||
assert_eq!(blocks[0].font, want);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_font_written_into_the_page_says_it_cannot_write() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
- [x] P-11 撤销或重做在 MuPDF 侧失败后 worker 的步数不再同步 — 撤销失败后重读历史标记
|
||||
- [x] P-12 `unhook` 中途抛出时泄漏保留的对象 — 保留的链接随保留随计数
|
||||
- [x] P-13 优化导出的“之前”大小取磁盘文件而非当前文档 — 有未保存编辑时不与磁盘文件比大小
|
||||
- [ ] P-14 文字块含控制字符时 JSON 回复不可解析
|
||||
- [x] P-14 文字块含控制字符时 JSON 回复不可解析 — 共用 JSON 写入:控制字符转义,非法字节与孤立代理为 U+FFFD
|
||||
- [x] D-01 导出或打印对话框开着时关闭其标签即崩溃 — Docs 实现 tab_closed,关标签即撤下其导出对话框;R1 PASS
|
||||
- [x] D-02 邮件合并以当前标签为模板,预览后生成的每份都是同一条记录 — 选数据时记下模板标签,预览副本不作模板;R1 测试绕过选 CSV 入口;R2 PASS
|
||||
- [ ] D-03 界面线程同步持 worker 锁读内核(样式、字体、书签、公式、分节名)
|
||||
|
||||
Reference in new issue
Block a user