Anki/rslib/src/image_occlusion/imageocclusion.rs
Abdo 351aa96dfc
Fix IO text size in review screen (#2986)
* Store relative font size

* Handle multi-line text

Thanks to @glutanimate
2024-04-17 20:45:40 +10:00

172 lines
6.1 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::fmt::Write;
use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageOcclusionProperty;
use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageOcclusionShape;
use htmlescape::encode_attribute;
use nom::bytes::complete::escaped;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::char;
use nom::error::ErrorKind;
use nom::sequence::preceded;
use nom::sequence::separated_pair;
fn unescape(text: &str) -> String {
text.replace("\\:", ":")
}
pub fn parse_image_cloze(text: &str) -> Option<ImageOcclusionShape> {
if let Some((shape, _)) = text.split_once(':') {
let mut properties = vec![];
let mut remaining = &text[shape.len()..];
while let Ok((rem, (name, value))) = separated_pair::<_, _, _, _, (_, ErrorKind), _, _, _>(
preceded(tag(":"), is_not("=")),
tag("="),
escaped(is_not("\\:"), '\\', char(':')),
)(remaining)
{
remaining = rem;
let value = unescape(value);
properties.push(ImageOcclusionProperty {
name: name.to_string(),
value,
})
}
return Some(ImageOcclusionShape {
shape: shape.to_string(),
properties,
});
}
None
}
// convert text like
// rect:left=.2325:top=.3261:width=.202:height=.0975
// to something like
// result = "data-shape="rect" data-left="399.01" data-top="99.52"
// data-width="167.09" data-height="33.78"
pub fn get_image_cloze_data(text: &str) -> String {
let mut result = String::new();
if let Some(occlusion) = parse_image_cloze(text) {
if !occlusion.shape.is_empty()
&& matches!(
occlusion.shape.as_str(),
"rect" | "ellipse" | "polygon" | "text"
)
{
result.push_str(&format!("data-shape=\"{}\" ", occlusion.shape));
}
for property in occlusion.properties {
match property.name.as_str() {
"left" => {
if !property.value.is_empty() {
result.push_str(&format!("data-left=\"{}\" ", property.value));
}
}
"top" => {
if !property.value.is_empty() {
result.push_str(&format!("data-top=\"{}\" ", property.value));
}
}
"width" => {
if !is_empty_or_zero(&property.value) {
result.push_str(&format!("data-width=\"{}\" ", property.value));
}
}
"height" => {
if !is_empty_or_zero(&property.value) {
result.push_str(&format!("data-height=\"{}\" ", property.value));
}
}
"rx" => {
if !is_empty_or_zero(&property.value) {
result.push_str(&format!("data-rx=\"{}\" ", property.value));
}
}
"ry" => {
if !is_empty_or_zero(&property.value) {
result.push_str(&format!("data-ry=\"{}\" ", property.value));
}
}
"points" => {
if !property.value.is_empty() {
let mut point_str = String::new();
for point_pair in property.value.split(' ') {
let Some((x, y)) = point_pair.split_once(',') else {
continue;
};
write!(&mut point_str, "{},{} ", x, y).unwrap();
}
// remove the trailing space
point_str.pop();
if !point_str.is_empty() {
result.push_str(&format!("data-points=\"{point_str}\" "));
}
}
}
"oi" => {
if !property.value.is_empty() {
result.push_str(&format!("data-occludeInactive=\"{}\" ", property.value));
}
}
"text" => {
if !property.value.is_empty() {
result.push_str(&format!(
"data-text=\"{}\" ",
encode_attribute(&property.value)
));
}
}
"scale" => {
if !is_empty_or_zero(&property.value) {
result.push_str(&format!("data-scale=\"{}\" ", property.value));
}
}
"fs" => {
if !property.value.is_empty() {
result.push_str(&format!("data-font-size=\"{}\" ", property.value));
}
}
_ => {}
}
}
}
result
}
fn is_empty_or_zero(text: &str) -> bool {
text.is_empty() || text == "0"
}
//----------------------------------------
// Tests
//----------------------------------------
#[test]
fn test_get_image_cloze_data() {
assert_eq!(
get_image_cloze_data("rect:left=10:top=20:width=30:height=10"),
format!(
r#"data-shape="rect" data-left="10" data-top="20" data-width="30" data-height="10" "#,
)
);
assert_eq!(
get_image_cloze_data("ellipse:left=15:top=20:width=10:height=20:rx=10:ry=5"),
r#"data-shape="ellipse" data-left="15" data-top="20" data-width="10" data-height="20" data-rx="10" data-ry="5" "#,
);
assert_eq!(
get_image_cloze_data("polygon:points=0,0 10,10 20,0"),
r#"data-shape="polygon" data-points="0,0 10,10 20,0" "#,
);
assert_eq!(
get_image_cloze_data("text:text=foo\\:bar:left=10"),
r#"data-shape="text" data-text="foo&#x3A;bar" data-left="10" "#,
);
}