forked from 1b-agent/opencadstudio-upstream
Merge pull request #1774 from megabrain-agent/feat/font-register
feat(web): font_register control op for host-supplied font files
This commit is contained in:
commit
592289f4fd
3 files changed
+183
-5
No files matched your search
+97
-1
@@ -204,6 +204,26 @@ fn open_bytes_request(req: &Value) -> Result<(String, Vec<u8>), Value> {
|
||||
}
|
||||
Ok((name.to_string(), bytes))
|
||||
}
|
||||
/// `{"op":"font_register","file_name":"Brand-Regular.ttf","data_base64":"…"}`:
|
||||
/// a host hands over a TrueType/OpenType file that text styles reference by
|
||||
/// name. The web build has no font folder, so this is how such styles get
|
||||
/// their real face instead of a fallback.
|
||||
fn font_register_request(req: &Value) -> Result<(String, Vec<u8>), Value> {
|
||||
use base64::Engine as _;
|
||||
let name = string(req, "file_name")?;
|
||||
let name = name.rsplit(['/', '\\']).next().unwrap_or(name).trim();
|
||||
if name.is_empty() {
|
||||
return Err(failure("invalid_request", "Missing file_name"));
|
||||
}
|
||||
let bytes = base64::engine::general_purpose::STANDARD
|
||||
.decode(req["data_base64"].as_str().unwrap_or_default())
|
||||
.map_err(|e| failure("invalid_request", format!("data_base64: {e}")))?;
|
||||
if bytes.is_empty() {
|
||||
return Err(failure("invalid_request", "data_base64 is empty"));
|
||||
}
|
||||
Ok((name.to_string(), bytes))
|
||||
}
|
||||
|
||||
fn string<'a>(req: &'a Value, key: &str) -> Result<&'a str, Value> {
|
||||
req[key]
|
||||
.as_str()
|
||||
@@ -775,7 +795,12 @@ impl OpenCADStudio {
|
||||
}
|
||||
// Interactive requests operate on the active tab by definition, so
|
||||
// a client may omit document_id (the GUI-hosted REST channel does).
|
||||
} else if !query && !matches!(op, "new" | "open" | "stop" | "user_select" | "getpoint") {
|
||||
} else if !query
|
||||
&& !matches!(
|
||||
op,
|
||||
"new" | "open" | "font_register" | "stop" | "user_select" | "getpoint"
|
||||
)
|
||||
{
|
||||
return (
|
||||
failure("document_required", "Read state and supply document_id"),
|
||||
Task::none(),
|
||||
@@ -979,6 +1004,24 @@ impl OpenCADStudio {
|
||||
"open" => self.update(Message::OpenExternal(std::path::PathBuf::from(string(
|
||||
req, "path",
|
||||
)?))),
|
||||
"font_register" => {
|
||||
let (name, bytes) = font_register_request(req)?;
|
||||
let (family, faces) =
|
||||
crate::scene::text::sysfont::register_font_file(&name, &bytes)
|
||||
.ok_or_else(|| failure("font_invalid", "data_base64 holds no font face"))?;
|
||||
// Same invalidation as a fetched web font: styles that fell
|
||||
// back while the file was missing lay out again.
|
||||
crate::scene::text::ttf_glyph::clear_fallback_cache();
|
||||
for tab in self.tabs.iter_mut() {
|
||||
tab.scene.invalidate_text_geometry_dependencies();
|
||||
}
|
||||
self.set_control_result(json!({
|
||||
"file_name": name,
|
||||
"family": family,
|
||||
"faces": faces,
|
||||
}));
|
||||
Task::none()
|
||||
}
|
||||
"activate" | "switch_document" => {
|
||||
let i = self
|
||||
.tabs
|
||||
@@ -2306,6 +2349,59 @@ mod tests {
|
||||
let bad = open_bytes_request(&json!({"name":"a.dwg","data_base64":"not base64!"}));
|
||||
assert_eq!(bad.unwrap_err()["code"], "invalid_request");
|
||||
}
|
||||
#[test]
|
||||
fn font_register_validates_the_request() {
|
||||
let ok = font_register_request(
|
||||
&json!({"file_name":"C:\\Fonts\\Brand.ttf","data_base64":"AAEC"}),
|
||||
);
|
||||
assert_eq!(ok, Ok(("Brand.ttf".to_string(), vec![0, 1, 2])));
|
||||
for req in [
|
||||
json!({"data_base64":"AAEC"}),
|
||||
json!({"file_name":"fonts/","data_base64":"AAEC"}),
|
||||
json!({"file_name":"a.ttf","data_base64":""}),
|
||||
json!({"file_name":"a.ttf","data_base64":"not base64!"}),
|
||||
] {
|
||||
assert_eq!(
|
||||
font_register_request(&req).unwrap_err()["code"],
|
||||
"invalid_request",
|
||||
"{req}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_register_makes_a_style_file_resolve() {
|
||||
use base64::Engine as _;
|
||||
let bytes = std::fs::read(concat!(env!("CARGO_MANIFEST_DIR"), "/web/fonts/greek.ttf"))
|
||||
.expect("bundled web font");
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
request(&mut app, json!({"op":"new"}));
|
||||
let reply = request(
|
||||
&mut app,
|
||||
json!({
|
||||
"op":"font_register",
|
||||
"file_name":"Control-Registered.ttf",
|
||||
"data_base64": base64::engine::general_purpose::STANDARD.encode(&bytes),
|
||||
}),
|
||||
);
|
||||
assert_eq!(reply["ok"], true, "{reply}");
|
||||
let family = reply["result"]["family"]
|
||||
.as_str()
|
||||
.expect("family")
|
||||
.to_string();
|
||||
assert!(reply["result"]["faces"].as_u64().unwrap() >= 1, "{reply}");
|
||||
assert_eq!(
|
||||
crate::scene::text::sysfont::family_for_reference("control-registered.ttf"),
|
||||
Some(family)
|
||||
);
|
||||
|
||||
let refused = request(
|
||||
&mut app,
|
||||
json!({"op":"font_register","file_name":"x.ttf","data_base64":"AAEC"}),
|
||||
);
|
||||
assert_eq!(refused["code"], "font_invalid", "{refused}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_from_bytes_is_refused_natively_without_touching_documents() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
|
||||
Reference in new issue
Block a user