mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
doc_create with from=text wrote every character outside WinAnsiEncoding (Korean, Chinese, Japanese, emoji) as `?` with no error. It now refuses such text with the same message page_add_text already gives, naming the first undrawable character and its code point. Line breaks, page breaks and tabs are layout and still pass. create_from_text itself is unchanged, so opening .txt files, comment summaries and compare reports behave as before.
This commit is contained in:
1 parent
86e74ec121
commit
2fbce7028c
5 files changed
+39
-7
No files matched your search
@@ -1081,6 +1081,10 @@ impl Automation {
|
||||
}
|
||||
(None, None) => return Err(ToolError::InvalidArgs("text needs `text` or `path`".into())),
|
||||
};
|
||||
// The page font is Helvetica/WinAnsi: refuse what it would silently turn into `?`.
|
||||
if let Some(c) = pdfcraft_engine::first_undrawable_plain(&text) {
|
||||
return Err(ToolError::InvalidArgs(pdfcraft_engine::undrawable_message(c)));
|
||||
}
|
||||
(format!("{title}.pdf"), self.session.create_from_text(&title, &text).map_err(failed)?)
|
||||
}
|
||||
other => return Err(ToolError::InvalidArgs(format!("unknown source {other:?}"))),
|
||||
|
||||
@@ -479,7 +479,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
t(
|
||||
"page_add_text",
|
||||
"Add text to a page",
|
||||
"Add text as page content (not a comment). Place it with at [x, y] (top-left, points from the top-left of the displayed page) and width (wrap width, default 200), or rect. Newlines start new lines; long lines wrap. Style: font helvetica/times/courier, size, bold, italic, color (#RRGGBB or a name), align left/center/right. It stays editable with content_update. Undoable.",
|
||||
"Add text as page content (not a comment). Place it with at [x, y] (top-left, points from the top-left of the displayed page) and width (wrap width, default 200), or rect. Newlines start new lines; long lines wrap. Style: font helvetica/times/courier, size, bold, italic, color (#RRGGBB or a name), align left/center/right. Text must be Western European (WinAnsi) characters; others are refused. It stays editable with content_update. Undoable.",
|
||||
)
|
||||
.cmd("edit.text")
|
||||
.with(schema(
|
||||
@@ -985,7 +985,7 @@ pub fn tools() -> Vec<ToolDef> {
|
||||
t(
|
||||
"doc_create",
|
||||
"Create a PDF",
|
||||
"Create a new, unsaved document and return it like doc_open: `blank` (pages, width, height in points; default 1 US Letter page), `images` (paths of PNG, JPEG, TIFF (every page), GIF or BMP files, one page each at the image's resolution) or `text` (a .txt path, or `text` directly). Save it with doc_save and a path.",
|
||||
"Create a new, unsaved document and return it like doc_open: `blank` (pages, width, height in points; default 1 US Letter page), `images` (paths of PNG, JPEG, TIFF (every page), GIF or BMP files, one page each at the image's resolution) or `text` (a .txt path, or `text` directly; Western European (WinAnsi) characters only, others are refused). Save it with doc_save and a path.",
|
||||
)
|
||||
.with(schema(
|
||||
json!({
|
||||
|
||||
@@ -1915,6 +1915,26 @@ fn creating_and_reducing_through_tools() {
|
||||
assert!(matches!(a.call("doc_create", &json!({ "from": "images", "paths": ["notes.txt"] })), Err(ToolError::Failed(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creating_from_text_refuses_what_the_font_cannot_draw() {
|
||||
let dir = workdir("create-undrawable");
|
||||
std::fs::write(dir.join("kr.txt"), "ABC 제안서 시험").unwrap();
|
||||
let mut a = auto(&dir);
|
||||
let refused = |r: Result<Vec<Content>, ToolError>| match r {
|
||||
Err(ToolError::InvalidArgs(m) | ToolError::Failed(m)) => assert!(m.contains("U+C81C"), "{m}"),
|
||||
other => panic!("expected a refusal, got {other:?}"),
|
||||
};
|
||||
assert!(matches!(a.call("doc_create", &json!({ "from": "text", "text": "ABC 제안서 시험" })), Err(ToolError::InvalidArgs(_))));
|
||||
refused(a.call("doc_create", &json!({ "from": "text", "text": "ABC 제안서 시험" })));
|
||||
refused(a.call("doc_create", &json!({ "from": "text", "path": "kr.txt" })));
|
||||
// WinAnsi characters, line breaks and tabs are still fine.
|
||||
let d = ok(&mut a, "doc_create", json!({ "from": "text", "text": "Price: 5 € at café\r\n\tnext\u{c}page two" }))["doc"].as_u64().unwrap();
|
||||
assert_eq!(page_text(&mut a, d).len(), 2);
|
||||
// #959: page_add_text refuses it too.
|
||||
let doc = ok(&mut a, "doc_create", json!({ "from": "blank" }))["doc"].as_u64().unwrap();
|
||||
refused(a.call("page_add_text", &json!({ "doc": doc, "page": 1, "text": "ABC 제안서 시험", "at": [20, 20] })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creating_from_multiple_files_through_tools() {
|
||||
let dir = workdir("create-multiple");
|
||||
|
||||
+12
-4
@@ -387,10 +387,18 @@ pub(crate) fn drawable<'a>(lines: impl IntoIterator<Item = &'a str>) -> Result<(
|
||||
|
||||
/// The refusal for page text with `c`, which nothing here can draw.
|
||||
pub(crate) fn undrawable(c: char) -> EditError {
|
||||
EditError::Invalid(format!(
|
||||
"the standard fonts can't draw \"{c}\" (U+{:04X}); only Western European characters can be added as text for now",
|
||||
u32::from(c)
|
||||
))
|
||||
EditError::Invalid(undrawable_message(c))
|
||||
}
|
||||
|
||||
/// The wording of the refusal for text with `c`, which the standard fonts can't draw.
|
||||
pub fn undrawable_message(c: char) -> String {
|
||||
format!("the standard fonts can't draw \"{c}\" (U+{:04X}); only Western European characters can be added as text for now", u32::from(c))
|
||||
}
|
||||
|
||||
/// The first character of plain `text` that Helvetica/WinAnsi can't draw (line and page breaks
|
||||
/// and tabs are layout, not glyphs).
|
||||
pub fn first_undrawable_plain(text: &str) -> Option<char> {
|
||||
text.chars().find(|c| !matches!(c, '\r' | '\n' | '\t' | '\u{c}') && !pdfcraft_fonts::win_ansi_encodable(*c))
|
||||
}
|
||||
|
||||
fn text_op(x: f64, y: f64, text: &str) -> Vec<u8> {
|
||||
|
||||
@@ -46,7 +46,7 @@ pub use pdfcraft_forms::{
|
||||
};
|
||||
|
||||
pub use pdfcraft_a11y as a11y;
|
||||
pub use pdfcraft_edit::{BlockStyle, PageImage, TextBlock, TextLine, first_undrawable};
|
||||
pub use pdfcraft_edit::{BlockStyle, PageImage, TextBlock, TextLine, first_undrawable, first_undrawable_plain, undrawable_message};
|
||||
pub use pdfcraft_measure as measure;
|
||||
pub use pdfcraft_xfa::Report as XfaLayout;
|
||||
|
||||
|
||||
Reference in new issue
Block a user