From d8aa80a817fab000c1129f612f98cc7501db2d3a Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Wed, 22 Nov 2023 19:38:04 -0600 Subject: [PATCH] feat: break, emphasis, mark --- src/break.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ src/element.rs | 9 +++---- src/emphasis.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 +++++ src/mark.rs | 26 +++++++++++++++++++ src/visit.rs | 39 +++++++++++++++++++++++----- src/visit_mut.rs | 39 +++++++++++++++++++++++----- 7 files changed, 231 insertions(+), 19 deletions(-) create mode 100644 src/break.rs create mode 100644 src/emphasis.rs create mode 100644 src/mark.rs diff --git a/src/break.rs b/src/break.rs new file mode 100644 index 0000000..27aee9e --- /dev/null +++ b/src/break.rs @@ -0,0 +1,66 @@ +use crate::{Serialize, SerializeOptions, TimeDesignation, XmlWriter}; + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum BreakStrength { + None, + ExtraWeak, + Weak, + #[default] + Medium, + Strong, + ExtraStrong +} + +#[derive(Debug, Clone)] +pub enum Break { + Strength(BreakStrength), + Time(TimeDesignation) +} + +impl Break { + pub fn new_with_strength(strength: BreakStrength) -> Self { + Break::Strength(strength) + } + + pub fn new_with_time(time: impl Into) -> Self { + Break::Time(time.into()) + } +} + +impl From for Break { + fn from(value: BreakStrength) -> Self { + Break::new_with_strength(value) + } +} + +impl From for Break +where + S: Into +{ + fn from(value: S) -> Self { + Break::new_with_time(value) + } +} + +impl Serialize for Break { + fn serialize_xml(&self, writer: &mut XmlWriter<'_>, _: &SerializeOptions) -> crate::Result<()> { + writer.element("break", |writer| match self { + Break::Strength(strength) => writer.attr( + "strength", + match strength { + BreakStrength::None => "none", + BreakStrength::ExtraWeak => "x-weak", + BreakStrength::Weak => "weak", + BreakStrength::Medium => "medium", + BreakStrength::Strong => "strong", + BreakStrength::ExtraStrong => "x-strong" + } + ), + Break::Time(time) => writer.attr("time", time.to_string()) + }) + } +} + +pub fn breaks(value: impl Into) -> Break { + value.into() +} diff --git a/src/element.rs b/src/element.rs index 6543269..f483479 100644 --- a/src/element.rs +++ b/src/element.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use dyn_clone::DynClone; -use crate::{Audio, Meta, Serialize, SerializeOptions, Text, Voice, XmlWriter}; +use crate::{Audio, Break, Emphasis, Mark, Meta, Serialize, SerializeOptions, Text, Voice, XmlWriter}; macro_rules! el { ( @@ -47,20 +47,19 @@ el! { Audio(Audio), Voice(Voice), Meta(Meta), + Break(Break), + Emphasis(Emphasis), + Mark(Mark), /// A dyn element can be used to implement your own custom elements outside of the `ssml` crate. See /// [`DynElement`] for more information and examples. Dyn(Box) - // Break(BreakElement), - // Emphasis(EmphasisElement), // Lang(LangElement), - // Mark(MarkElement), // Paragraph(ParagraphElement), // Phoneme(PhonemeElement), // Prosody(ProsodyElement), // SayAs(SayAsElement), // Sub(SubElement), // Sentence(SentenceElement), - // Voice(VoiceElement), // Word(WordElement) } } diff --git a/src/emphasis.rs b/src/emphasis.rs new file mode 100644 index 0000000..73e59d3 --- /dev/null +++ b/src/emphasis.rs @@ -0,0 +1,65 @@ +use crate::{Element, Serialize, SerializeOptions, XmlWriter}; + +#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum EmphasisLevel { + Reduced, + None, + #[default] + Moderate, + Strong +} + +#[derive(Clone, Default, Debug)] +pub struct Emphasis { + level: EmphasisLevel, + pub(crate) children: Vec +} + +impl Emphasis { + pub fn new, I: IntoIterator>(level: EmphasisLevel, elements: I) -> Self { + Self { + level, + children: elements.into_iter().map(|f| f.into()).collect() + } + } + + pub fn push(&mut self, element: impl Into) { + self.children.push(element.into()); + } + + pub fn extend, I: IntoIterator>(&mut self, elements: I) { + self.children.extend(elements.into_iter().map(|f| f.into())); + } + + pub fn level(&self) -> &EmphasisLevel { + &self.level + } + + pub fn children(&self) -> &[Element] { + &self.children + } + + pub fn children_mut(&mut self) -> &mut [Element] { + &mut self.children + } +} + +impl Serialize for Emphasis { + fn serialize_xml(&self, writer: &mut XmlWriter<'_>, _: &SerializeOptions) -> crate::Result<()> { + writer.element("emphasis", |writer| { + writer.attr( + "level", + match self.level { + EmphasisLevel::Reduced => "reduced", + EmphasisLevel::None => "none", + EmphasisLevel::Moderate => "moderate", + EmphasisLevel::Strong => "strong" + } + ) + }) + } +} + +pub fn emphasis, I: IntoIterator>(level: EmphasisLevel, elements: I) -> Emphasis { + Emphasis::new(level, elements) +} diff --git a/src/lib.rs b/src/lib.rs index f5c31a0..73fe3c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,8 +29,11 @@ use std::{fmt::Debug, io::Write}; mod audio; +mod r#break; mod element; +mod emphasis; mod error; +mod mark; pub mod mstts; mod speak; mod text; @@ -44,8 +47,11 @@ mod xml; pub(crate) use self::error::error; pub use self::{ audio::{audio, Audio, AudioRepeat}, + r#break::{breaks, Break, BreakStrength}, element::{DynElement, Element}, + emphasis::{emphasis, Emphasis, EmphasisLevel}, error::{Error, Result}, + mark::{mark, Mark}, speak::{speak, Speak}, text::{text, Text}, unit::{Decibels, DecibelsError, TimeDesignation, TimeDesignationError}, diff --git a/src/mark.rs b/src/mark.rs new file mode 100644 index 0000000..65d4843 --- /dev/null +++ b/src/mark.rs @@ -0,0 +1,26 @@ +use crate::{Serialize, SerializeOptions, XmlWriter}; + +#[derive(Debug, Clone)] +pub struct Mark { + name: String +} + +impl Mark { + pub fn new(name: impl ToString) -> Self { + Self { name: name.to_string() } + } + + pub fn name(&self) -> &str { + &self.name + } +} + +impl Serialize for Mark { + fn serialize_xml(&self, writer: &mut XmlWriter<'_>, _: &SerializeOptions) -> crate::Result<()> { + writer.element("mark", |writer| writer.attr("name", &self.name)) + } +} + +pub fn mark(name: impl ToString) -> Mark { + Mark::new(name) +} diff --git a/src/visit.rs b/src/visit.rs index 1c9be34..8114d83 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -32,7 +32,7 @@ //! # } //! ``` -use crate::{Audio, DynElement, Element, Meta, Speak, Text, Voice}; +use crate::{Audio, Break, DynElement, Element, Emphasis, Mark, Meta, Speak, Text, Voice}; pub trait Visit<'s> { fn visit_speak(&mut self, node: &'s Speak) { @@ -55,18 +55,30 @@ pub trait Visit<'s> { self::visit_voice(self, node) } + fn visit_break(&mut self, node: &'s Break) { + self::visit_break(self, node) + } + + fn visit_emphasis(&mut self, node: &'s Emphasis) { + self::visit_emphasis(self, node) + } + + fn visit_mark(&mut self, node: &'s Mark) { + self::visit_mark(self, node) + } + fn visit_dyn(&mut self, node: &'s dyn DynElement) { self::visit_dyn(self, node) } - fn visit_speakable(&mut self, node: &'s Element) { - self::visit_speakable(self, node) + fn visit_element(&mut self, node: &'s Element) { + self::visit_element(self, node) } } pub fn visit_audio<'s, V: Visit<'s> + ?Sized>(v: &mut V, node: &'s Audio) { for node in node.alternate() { - v.visit_speakable(node); + v.visit_element(node); } } @@ -76,24 +88,37 @@ pub fn visit_text<'s, V: Visit<'s> + ?Sized>(_v: &mut V, _node: &'s Text) {} pub fn visit_voice<'s, V: Visit<'s> + ?Sized>(v: &mut V, node: &'s Voice) { for node in node.children() { - v.visit_speakable(node); + v.visit_element(node); } } +pub fn visit_break<'s, V: Visit<'s> + ?Sized>(_v: &mut V, _node: &'s Break) {} + +pub fn visit_emphasis<'s, V: Visit<'s> + ?Sized>(v: &mut V, node: &'s Emphasis) { + for node in node.children() { + v.visit_element(node); + } +} + +pub fn visit_mark<'s, V: Visit<'s> + ?Sized>(_v: &mut V, _node: &'s Mark) {} + pub fn visit_dyn<'s, V: Visit<'s> + ?Sized>(_v: &mut V, _node: &'s dyn DynElement) {} -pub fn visit_speakable<'s, V: Visit<'s> + ?Sized>(v: &mut V, node: &'s Element) { +pub fn visit_element<'s, V: Visit<'s> + ?Sized>(v: &mut V, node: &'s Element) { match node { Element::Audio(node) => visit_audio(v, node), Element::Meta(node) => visit_meta(v, node), Element::Text(node) => visit_text(v, node), Element::Voice(node) => visit_voice(v, node), + Element::Break(node) => visit_break(v, node), + Element::Emphasis(node) => visit_emphasis(v, node), + Element::Mark(node) => visit_mark(v, node), Element::Dyn(node) => visit_dyn(v, node.as_ref()) } } pub fn visit_speak<'s, V: Visit<'s> + ?Sized>(v: &mut V, node: &'s Speak) { for node in node.children() { - v.visit_speakable(node); + v.visit_element(node); } } diff --git a/src/visit_mut.rs b/src/visit_mut.rs index 67c7795..9838a5f 100644 --- a/src/visit_mut.rs +++ b/src/visit_mut.rs @@ -1,4 +1,4 @@ -use crate::{Audio, DynElement, Element, Meta, Speak, Text, Voice}; +use crate::{Audio, Break, DynElement, Element, Emphasis, Mark, Meta, Speak, Text, Voice}; pub trait VisitMut<'s> { fn visit_speak_mut(&mut self, node: &'s mut Speak) { @@ -21,18 +21,30 @@ pub trait VisitMut<'s> { self::visit_voice_mut(self, node) } + fn visit_break_mut(&mut self, node: &'s mut Break) { + self::visit_break_mut(self, node) + } + + fn visit_emphasis_mut(&mut self, node: &'s mut Emphasis) { + self::visit_emphasis_mut(self, node) + } + + fn visit_mark_mut(&mut self, node: &'s mut Mark) { + self::visit_mark_mut(self, node) + } + fn visit_dyn_mut(&mut self, node: &'s mut dyn DynElement) { self::visit_dyn_mut(self, node) } - fn visit_speakable_mut(&mut self, node: &'s mut Element) { - self::visit_speakable_mut(self, node) + fn visit_element_mut(&mut self, node: &'s mut Element) { + self::visit_element_mut(self, node) } } pub fn visit_audio_mut<'s, V: VisitMut<'s> + ?Sized>(v: &mut V, node: &'s mut Audio) { for node in node.alternate_mut() { - v.visit_speakable_mut(node); + v.visit_element_mut(node); } } @@ -42,24 +54,37 @@ pub fn visit_text_mut<'s, V: VisitMut<'s> + ?Sized>(_v: &mut V, _node: &'s mut T pub fn visit_voice_mut<'s, V: VisitMut<'s> + ?Sized>(v: &mut V, node: &'s mut Voice) { for node in node.children_mut() { - v.visit_speakable_mut(node); + v.visit_element_mut(node); } } +pub fn visit_break_mut<'s, V: VisitMut<'s> + ?Sized>(_v: &mut V, _node: &'s mut Break) {} + +pub fn visit_emphasis_mut<'s, V: VisitMut<'s> + ?Sized>(v: &mut V, node: &'s mut Emphasis) { + for node in node.children_mut() { + v.visit_element_mut(node); + } +} + +pub fn visit_mark_mut<'s, V: VisitMut<'s> + ?Sized>(_v: &mut V, _node: &'s mut Mark) {} + pub fn visit_dyn_mut<'s, V: VisitMut<'s> + ?Sized>(_v: &mut V, _node: &'s mut dyn DynElement) {} -pub fn visit_speakable_mut<'s, V: VisitMut<'s> + ?Sized>(v: &mut V, node: &'s mut Element) { +pub fn visit_element_mut<'s, V: VisitMut<'s> + ?Sized>(v: &mut V, node: &'s mut Element) { match node { Element::Audio(node) => visit_audio_mut(v, node), Element::Meta(node) => visit_meta_mut(v, node), Element::Text(node) => visit_text_mut(v, node), Element::Voice(node) => visit_voice_mut(v, node), + Element::Break(node) => visit_break_mut(v, node), + Element::Emphasis(node) => visit_emphasis_mut(v, node), + Element::Mark(node) => visit_mark_mut(v, node), Element::Dyn(node) => visit_dyn_mut(v, node.as_mut()) } } pub fn visit_speak_mut<'s, V: VisitMut<'s> + ?Sized>(v: &mut V, node: &'s mut Speak) { for node in node.children_mut() { - v.visit_speakable_mut(node); + v.visit_element_mut(node); } }