use super::error::Result;
use super::parser::Record;
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Default)]
pub struct HwpDocument {
pub sections: Vec<Section>,
pub char_shapes: Vec<CharShape>,
pub images: Vec<HwpImage>,
pub summary_info: Option<SummaryInfo>,
pub warnings: Vec<String>,
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Section {
pub paragraphs: Vec<Paragraph>,
#[serde(default)]
pub tables: Vec<HwpTable>,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct HwpTable {
pub rows: Vec<Vec<String>>,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Default, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SummaryInfo {
pub title: Option<String>,
pub subject: Option<String>,
pub author: Option<String>,
pub keywords: Option<String>,
pub comments: Option<String>,
pub last_author: Option<String>,
pub created: Option<String>,
pub modified: Option<String>,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Paragraph {
pub text: Option<ParaText>,
pub outline_level: u8,
pub char_shape_runs: Vec<(u32, u16)>,
pub equations: Vec<String>,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Clone, Copy, Default)]
pub struct CharShape {
pub bold: bool,
pub italic: bool,
pub underline: bool,
}
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, Clone, Default)]
pub struct HwpImage {
pub name: String,
pub data: Vec<u8>,
}
pub(crate) const EQUATION_PLACEHOLDER: char = '\u{E000}';
const EQED_CTRL_ID: [u16; 2] = [0x6564, 0x6571];
#[cfg_attr(alef, alef(skip))]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct ParaText {
pub content: String,
}
impl ParaText {
pub(crate) fn from_record(record: &Record) -> Result<Self> {
let mut reader = record.data_reader();
let mut chars: Vec<u16> = Vec::with_capacity(record.data.len() / 2);
while reader.remaining() >= 2 {
chars.push(reader.read_u16()?);
}
let mut content = String::with_capacity(chars.len());
let mut i = 0;
while i < chars.len() {
let ch = chars[i];
match ch {
0x0000 => {}
0x0001..=0x0008 => {
i += 7;
}
0x0009 => {
content.push('\t');
i += 7;
}
0x000A => content.push('\n'),
0x000D => {}
0x000B => {
if chars.get(i + 1..i + 3) == Some(&EQED_CTRL_ID) {
content.push(EQUATION_PLACEHOLDER);
}
i += 7;
}
0x000C | 0x000E..=0x001F => {
i += 7;
}
0xF020..=0xF07F => {}
_ => {
if let Some(c) = char::from_u32(ch as u32) {
content.push(c);
}
}
}
i += 1;
}
Ok(Self { content })
}
}
pub(crate) fn fill_next_equation_placeholder(content: &mut String, replacement: &str) -> bool {
if let Some(pos) = content.find(EQUATION_PLACEHOLDER) {
content.replace_range(pos..pos + EQUATION_PLACEHOLDER.len_utf8(), replacement);
true
} else {
false
}
}
#[cfg(test)]
mod equation_placeholder_tests {
use super::*;
#[test]
fn should_replace_leftmost_placeholder_first() {
let mut content = format!("a{EQUATION_PLACEHOLDER}b{EQUATION_PLACEHOLDER}c");
assert!(fill_next_equation_placeholder(&mut content, "X"));
assert_eq!(content, format!("aXb{EQUATION_PLACEHOLDER}c"));
assert!(fill_next_equation_placeholder(&mut content, "Y"));
assert_eq!(content, "aXbYc");
}
#[test]
fn should_return_false_when_no_placeholder_present() {
let mut content = String::from("plain text");
assert!(!fill_next_equation_placeholder(&mut content, "X"));
assert_eq!(content, "plain text");
}
fn utf16le(s: &str) -> Vec<u8> {
s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect()
}
#[test]
fn should_insert_equation_placeholder_for_eqed_anchor() {
let mut data = utf16le("Result: ");
data.extend_from_slice(&0x000Bu16.to_le_bytes());
data.extend_from_slice(b"deqe"); data.extend_from_slice(&[0u8; 10]); data.extend_from_slice(&utf16le(" done"));
let record = Record {
tag_id: 0x43,
level: 1,
data,
};
let para_text = ParaText::from_record(&record).expect("decode must succeed");
assert_eq!(para_text.content, format!("Result: {EQUATION_PLACEHOLDER} done"));
}
#[test]
fn should_not_insert_placeholder_for_other_extended_controls() {
let mut data = utf16le("Before ");
data.extend_from_slice(&0x000Bu16.to_le_bytes());
data.extend_from_slice(b" osg");
data.extend_from_slice(&[0u8; 10]);
data.extend_from_slice(&utf16le("after"));
let record = Record {
tag_id: 0x43,
level: 1,
data,
};
let para_text = ParaText::from_record(&record).expect("decode must succeed");
assert_eq!(para_text.content, "Before after");
assert!(!para_text.content.contains(EQUATION_PLACEHOLDER));
}
}