mirror of
https://github.com/ankitects/anki.git
synced 2025-09-23 00:12:25 -04:00
172 lines
6.1 KiB
Rust
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:bar" data-left="10" "#,
|
|
);
|
|
}
|