use crate::errors::{ErrorKind, Result};
use crate::fsshttpb::data::exguid::ExGuid;
use crate::one::property::charset::Charset;
use crate::one::property::color_ref::ColorRef;
use crate::one::property::layout_alignment::LayoutAlignment;
use crate::one::property::paragraph_alignment::ParagraphAlignment;
use crate::one::property_set::{
embedded_ink_container, math_inline_object, paragraph_style_object, rich_text_node,
text_run_data,
};
use crate::onenote::ink::{Ink, InkBoundingBox, parse_ink_data};
use crate::onenote::math_inline_object::{MathInlineObject, parse_math_inline_object};
use crate::onenote::note_tag::{NoteTag, parse_note_tags};
use crate::onestore::object_space::ObjectSpace;
use itertools::Itertools;
#[derive(Clone, Debug)]
pub struct RichText {
pub(crate) text: String,
pub(crate) text_run_formatting: Vec<ParagraphStyling>,
pub(crate) text_run_indices: Vec<u32>,
pub(crate) paragraph_style: ParagraphStyling,
pub(crate) paragraph_space_before: f32,
pub(crate) paragraph_space_after: f32,
pub(crate) paragraph_line_spacing_exact: Option<f32>,
pub(crate) paragraph_alignment: ParagraphAlignment,
pub(crate) layout_alignment_in_parent: Option<LayoutAlignment>,
pub(crate) layout_alignment_self: Option<LayoutAlignment>,
pub(crate) note_tags: Vec<NoteTag>,
pub(crate) embedded_objects: Vec<EmbeddedObject>,
pub(crate) math_inline_objects: Vec<MathInlineObject>,
}
impl RichText {
pub fn text(&self) -> &str {
&self.text
}
pub fn text_run_formatting(&self) -> &[ParagraphStyling] {
&self.text_run_formatting
}
pub fn text_run_indices(&self) -> &[u32] {
&self.text_run_indices
}
pub fn paragraph_style(&self) -> &ParagraphStyling {
&self.paragraph_style
}
pub fn paragraph_space_before(&self) -> f32 {
self.paragraph_space_before
}
pub fn paragraph_space_after(&self) -> f32 {
self.paragraph_space_after
}
pub fn paragraph_line_spacing_exact(&self) -> Option<f32> {
self.paragraph_line_spacing_exact
}
pub fn paragraph_alignment(&self) -> ParagraphAlignment {
self.paragraph_alignment
}
pub fn layout_alignment_in_parent(&self) -> Option<LayoutAlignment> {
self.layout_alignment_in_parent
}
pub fn layout_alignment_self(&self) -> Option<LayoutAlignment> {
self.layout_alignment_self
}
pub fn note_tags(&self) -> &[NoteTag] {
&self.note_tags
}
pub fn embedded_objects(&self) -> &[EmbeddedObject] {
&self.embedded_objects
}
pub fn math_inline_objects(&self) -> &[MathInlineObject] {
&self.math_inline_objects
}
}
#[derive(Clone, Debug)]
pub enum EmbeddedObject {
Ink(EmbeddedInkContainer),
InkSpace(EmbeddedInkSpace),
InkLineBreak,
}
#[derive(Clone, Debug)]
pub struct EmbeddedInkContainer {
pub(crate) ink: Ink,
pub(crate) bounding_box: Option<InkBoundingBox>,
}
impl EmbeddedInkContainer {
pub fn ink(&self) -> &Ink {
&self.ink
}
pub fn bounding_box(&self) -> Option<&InkBoundingBox> {
self.bounding_box.as_ref()
}
}
#[derive(Clone, Debug)]
pub struct EmbeddedInkSpace {
height: f32,
width: f32,
}
impl EmbeddedInkSpace {
pub fn height(&self) -> f32 {
self.height
}
pub fn width(&self) -> f32 {
self.width
}
}
#[derive(Clone, Debug)]
pub struct ParagraphStyling {
pub(crate) charset: Option<Charset>,
pub(crate) bold: bool,
pub(crate) italic: bool,
pub(crate) underline: bool,
pub(crate) strikethrough: bool,
pub(crate) superscript: bool,
pub(crate) subscript: bool,
pub(crate) font: Option<String>,
pub(crate) font_size: Option<u16>,
pub(crate) font_color: Option<ColorRef>,
pub(crate) highlight: Option<ColorRef>,
pub(crate) next_style: Option<String>,
pub(crate) style_id: Option<String>,
pub(crate) paragraph_alignment: Option<ParagraphAlignment>,
pub(crate) paragraph_space_before: Option<f32>,
pub(crate) paragraph_space_after: Option<f32>,
pub(crate) paragraph_line_spacing_exact: Option<f32>,
pub(crate) language_code: Option<u32>,
pub(crate) math_formatting: bool,
pub(crate) hyperlink: bool,
}
impl ParagraphStyling {
pub fn charset(&self) -> Option<Charset> {
self.charset
}
pub fn bold(&self) -> bool {
self.bold
}
pub fn italic(&self) -> bool {
self.italic
}
pub fn underline(&self) -> bool {
self.underline
}
pub fn strikethrough(&self) -> bool {
self.strikethrough
}
pub fn superscript(&self) -> bool {
self.superscript
}
pub fn subscript(&self) -> bool {
self.subscript
}
pub fn font(&self) -> Option<&str> {
self.font.as_deref()
}
pub fn font_size(&self) -> Option<u16> {
self.font_size
}
pub fn font_color(&self) -> Option<ColorRef> {
self.font_color
}
pub fn highlight(&self) -> Option<ColorRef> {
self.highlight
}
pub fn next_style(&self) -> Option<&str> {
self.next_style.as_deref()
}
pub fn style_id(&self) -> Option<&str> {
self.style_id.as_deref()
}
pub fn paragraph_alignment(&self) -> Option<ParagraphAlignment> {
self.paragraph_alignment
}
pub fn paragraph_space_before(&self) -> Option<f32> {
self.paragraph_space_before
}
pub fn paragraph_space_after(&self) -> Option<f32> {
self.paragraph_space_after
}
pub fn paragraph_line_spacing_exact(&self) -> Option<f32> {
self.paragraph_line_spacing_exact
}
pub fn language_code(&self) -> Option<u32> {
self.language_code
}
pub fn math_formatting(&self) -> bool {
self.math_formatting
}
pub fn hyperlink(&self) -> bool {
self.hyperlink
}
}
const INK_SPACE_BLOB: u32 = 0x00020026;
const INK_END_OF_LINE_BLOB: u32 = 0x00020027;
pub(crate) fn parse_rich_text(content_id: ExGuid, space: &ObjectSpace) -> Result<RichText> {
let object = space
.get_object(content_id)
.ok_or_else(|| ErrorKind::MalformedOneNoteData("rich text content is missing".into()))?;
let data = rich_text_node::parse(object)?;
let paragraph_style_object = space
.get_object(data.paragraph_style)
.ok_or_else(|| ErrorKind::MalformedOneNoteData("paragraph styling is missing".into()))?;
let paragraph_style_data = paragraph_style_object::parse(paragraph_style_object)?;
let paragraph_style = parse_style(paragraph_style_data);
let styles_data: Vec<paragraph_style_object::Data> = data
.text_run_formatting
.iter()
.map(|style_id| {
space
.get_object(*style_id)
.ok_or_else(|| ErrorKind::MalformedOneNoteData("styling is missing".into()).into())
})
.map(|style_object| style_object.and_then(|object| paragraph_style_object::parse(object)))
.collect::<Result<Vec<_>>>()?;
let text_run_data = text_run_data::parse(object)?.unwrap_or_default();
let math_inline_objects = text_run_data
.iter()
.zip(&styles_data)
.map(|(text_run_data, style_data)| {
if style_data.math_formatting {
let math_data = math_inline_object::Data::parse(text_run_data)?;
let math_inline_object = parse_math_inline_object(math_data)?;
return Ok(Some(math_inline_object));
}
Ok(None)
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten()
.collect_vec();
let objects = text_run_data
.into_iter()
.zip(&styles_data)
.map(|(embedded_object, style_data)| {
if style_data.text_run_is_embedded_object {
let object_data = embedded_ink_container::Data::parse(embedded_object)?;
return Ok(Some((style_data.text_run_object_type, object_data)));
}
Ok(None)
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten()
.collect_vec();
let mut objects_without_ref = 0;
let embedded_objects: Vec<_> = objects
.into_iter()
.enumerate()
.map(|(i, (object_type, embedded_data))| match object_type {
Some(INK_END_OF_LINE_BLOB) => {
objects_without_ref += 1;
Ok(Some(EmbeddedObject::InkLineBreak))
}
Some(INK_SPACE_BLOB) => {
objects_without_ref += 1;
parse_embedded_ink_space(embedded_data)
.map(|space| Some(EmbeddedObject::InkSpace(space)))
}
None => {
if !data.text_run_data_object.is_empty() {
return parse_embedded_ink_data(
data.text_run_data_object[i - objects_without_ref],
space,
embedded_data,
)
.map(|container| Some(EmbeddedObject::Ink(container)));
}
Ok(None)
}
Some(v) => Err(ErrorKind::MalformedOneNoteFileData(
format!("unknown embedded object type: {:x}", v).into(),
)
.into()),
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.flatten()
.collect_vec();
let styles = styles_data.into_iter().map(parse_style).collect_vec();
let text = if !embedded_objects.is_empty() {
"".to_string()
} else {
data.text.unwrap_or_default()
};
let text = RichText {
text,
embedded_objects,
text_run_formatting: styles,
text_run_indices: data.text_run_indices,
paragraph_style,
paragraph_space_before: data.paragraph_space_before,
paragraph_space_after: data.paragraph_space_after,
paragraph_line_spacing_exact: data.paragraph_line_spacing_exact,
paragraph_alignment: data.paragraph_alignment,
layout_alignment_in_parent: data.layout_alignment_in_parent,
layout_alignment_self: data.layout_alignment_self,
note_tags: parse_note_tags(data.note_tags, space)?,
math_inline_objects,
};
Ok(text)
}
fn parse_embedded_ink_data(
embedded_id: ExGuid,
space: &ObjectSpace,
data: embedded_ink_container::Data,
) -> Result<EmbeddedInkContainer> {
let (strokes, bb) = parse_ink_data(embedded_id, space, None, None)?;
let display_bb = data
.start_x
.zip(data.start_y)
.zip(data.height)
.zip(data.width)
.map(|(((x, y), height), width)| InkBoundingBox {
x,
y,
height,
width,
});
let data = EmbeddedInkContainer {
ink: Ink {
ink_strokes: strokes,
bounding_box: bb,
offset_horizontal: data.offset_horiz,
offset_vertical: data.offset_vert,
},
bounding_box: display_bb,
};
Ok(data)
}
fn parse_embedded_ink_space(data: embedded_ink_container::Data) -> Result<EmbeddedInkSpace> {
let width = data.space_width.ok_or_else(|| {
ErrorKind::MalformedOneNoteFileData("embedded ink space has no width".into())
})?;
let height = data.space_height.ok_or_else(|| {
ErrorKind::MalformedOneNoteFileData("embedded ink space has no height".into())
})?;
Ok(EmbeddedInkSpace { height, width })
}
fn parse_style(data: paragraph_style_object::Data) -> ParagraphStyling {
ParagraphStyling {
charset: data.charset,
bold: data.bold,
italic: data.italic,
underline: data.underline,
strikethrough: data.strikethrough,
superscript: data.superscript,
subscript: data.subscript,
font: data.font,
font_size: data.font_size,
font_color: data.font_color,
highlight: data.highlight,
next_style: data.next_style,
style_id: data.style_id,
paragraph_alignment: data.paragraph_alignment,
paragraph_space_before: data.paragraph_space_before,
paragraph_space_after: data.paragraph_space_after,
paragraph_line_spacing_exact: data.paragraph_line_spacing_exact,
language_code: data.language_code,
math_formatting: data.math_formatting,
hyperlink: data.hyperlink,
}
}