diff --git a/proto/anki/image_occlusion.proto b/proto/anki/image_occlusion.proto index 79ea2fe9f..fdab4e139 100644 --- a/proto/anki/image_occlusion.proto +++ b/proto/anki/image_occlusion.proto @@ -59,11 +59,15 @@ message GetImageOcclusionNoteResponse { string value = 2; } - message ImageOcclusion { + message ImageOcclusionShape { string shape = 1; repeated ImageOcclusionProperty properties = 2; } + message ImageOcclusion { + repeated ImageOcclusionShape shapes = 1; + } + message ImageOcclusionNote { bytes image_data = 1; repeated ImageOcclusion occlusions = 2; diff --git a/rslib/src/cloze.rs b/rslib/src/cloze.rs index c8a8b9023..cc6111ba9 100644 --- a/rslib/src/cloze.rs +++ b/rslib/src/cloze.rs @@ -2,10 +2,12 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use std::borrow::Cow; +use std::collections::HashMap; use std::collections::HashSet; use std::fmt::Write; use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageOcclusion; +use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageOcclusionShape; use htmlescape::encode_attribute; use lazy_static::lazy_static; use nom::branch::alt; @@ -317,14 +319,20 @@ fn render_image_occlusion(text: &str, question_side: bool, active: bool, ordinal } pub fn parse_image_occlusions(text: &str) -> Vec { - parse_text_with_clozes(text) - .iter() - .filter_map(|node| match node { - TextOrCloze::Cloze(cloze) if cloze.image_occlusion().is_some() => { - parse_image_cloze(cloze.image_occlusion().unwrap()) + let mut occlusions: HashMap> = HashMap::new(); + for node in parse_text_with_clozes(text) { + if let TextOrCloze::Cloze(cloze) = node { + if cloze.image_occlusion().is_some() { + if let Some(shape) = parse_image_cloze(cloze.image_occlusion().unwrap()) { + occlusions.entry(cloze.ordinal).or_default().push(shape); + } } - _ => None, - }) + } + } + + occlusions + .values() + .map(|v| ImageOcclusion { shapes: v.to_vec() }) .collect() } diff --git a/rslib/src/image_occlusion/imageocclusion.rs b/rslib/src/image_occlusion/imageocclusion.rs index 7fffd73b6..507abcfea 100644 --- a/rslib/src/image_occlusion/imageocclusion.rs +++ b/rslib/src/image_occlusion/imageocclusion.rs @@ -3,8 +3,8 @@ use std::fmt::Write; -use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageOcclusion; 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; @@ -18,7 +18,7 @@ fn unescape(text: &str) -> String { text.replace("\\:", ":") } -pub fn parse_image_cloze(text: &str) -> Option { +pub fn parse_image_cloze(text: &str) -> Option { if let Some((shape, _)) = text.split_once(':') { let mut properties = vec![]; let mut remaining = &text[shape.len()..]; @@ -36,7 +36,7 @@ pub fn parse_image_cloze(text: &str) -> Option { }) } - return Some(ImageOcclusion { + return Some(ImageOcclusionShape { shape: shape.to_string(), properties, }); diff --git a/ts/image-occlusion/Toolbar.svelte b/ts/image-occlusion/Toolbar.svelte index 8081e328b..f287fe547 100644 --- a/ts/image-occlusion/Toolbar.svelte +++ b/ts/image-occlusion/Toolbar.svelte @@ -200,6 +200,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {iconSize} on:click={() => { tool.action(canvas); + emitChangeSignal(); }} > {@html tool.icon} diff --git a/ts/image-occlusion/shapes/from-cloze.ts b/ts/image-occlusion/shapes/from-cloze.ts index 3cd6413c7..74a335b4b 100644 --- a/ts/image-occlusion/shapes/from-cloze.ts +++ b/ts/image-occlusion/shapes/from-cloze.ts @@ -18,9 +18,17 @@ export function extractShapesFromClozedField( ): ShapeOrShapes[] { const output: ShapeOrShapes[] = []; for (const occlusion of occlusions) { - if (isValidType(occlusion.shape)) { - const props = Object.fromEntries(occlusion.properties.map(prop => [prop.name, prop.value])); - output.push(buildShape(occlusion.shape, props)); + const group: Shape[] = []; + for (const shape of occlusion.shapes) { + if (isValidType(shape.shape)) { + const props = Object.fromEntries(shape.properties.map(prop => [prop.name, prop.value])); + group.push(buildShape(shape.shape, props)); + } + } + if (group.length > 1) { + output.push(group); + } else { + output.push(group[0]); } }