use dazzle_core::fot::FotBuilder;
use std::io::{Result, Write};
#[derive(Debug)]
pub struct RtfBackend<W: Write + std::fmt::Debug> {
output: W,
header_written: bool,
in_paragraph: bool,
current_buffer: String,
}
impl<W: Write + std::fmt::Debug> RtfBackend<W> {
#[cfg(test)]
fn into_inner(self) -> W {
use std::mem::ManuallyDrop;
use std::ptr;
let this = ManuallyDrop::new(self);
unsafe {
ptr::read(&this.output)
}
}
pub fn new(output: W) -> Result<Self> {
Ok(RtfBackend {
output,
header_written: false,
in_paragraph: false,
current_buffer: String::new(),
})
}
fn write_header(&mut self) -> Result<()> {
if self.header_written {
return Ok(());
}
writeln!(self.output, "{{\\rtf1\\ansi\\deff0")?;
writeln!(self.output, "{{\\fonttbl{{\\f1\\fnil\\fcharset0 Helvetica;}}")?;
writeln!(self.output, "{{\\f6\\fnil\\fcharset161 Helvetica Greek;}}")?;
writeln!(self.output, "{{\\f3\\fnil\\fcharset2 Symbol;}}")?;
writeln!(self.output, "{{\\f2\\fnil\\fcharset0 Courier;}}")?;
writeln!(self.output, "{{\\f5\\fnil\\fcharset161 Courier Greek;}}")?;
writeln!(self.output, "{{\\f0\\fnil\\fcharset0 Times New Roman;}}")?;
writeln!(self.output, "{{\\f4\\fnil\\fcharset161 Times New Roman Greek;}}")?;
writeln!(self.output, "}}")?;
writeln!(self.output, "{{\\colortbl;}}")?;
write!(self.output, "{{\\stylesheet")?;
write!(self.output, "{{\\s1 Heading 1;}}")?;
write!(self.output, "{{\\s2 Heading 2;}}")?;
write!(self.output, "{{\\s3 Heading 3;}}")?;
write!(self.output, "{{\\s4 Heading 4;}}")?;
write!(self.output, "{{\\s5 Heading 5;}}")?;
write!(self.output, "{{\\s6 Heading 6;}}")?;
write!(self.output, "{{\\s7 Heading 7;}}")?;
write!(self.output, "{{\\s8 Heading 8;}}")?;
write!(self.output, "{{\\s9 Heading 9;}}")?;
writeln!(self.output, "}}")?;
writeln!(self.output, "\\deflang1024\\notabind\\facingp\\hyphauto1\\widowctrl")?;
self.header_written = true;
Ok(())
}
pub fn finish(&mut self) -> Result<()> {
self.write_header()?;
if self.in_paragraph {
writeln!(self.output, "\\par}}")?;
self.in_paragraph = false;
}
writeln!(self.output, "}}")?;
self.output.flush()?;
Ok(())
}
}
impl<W: Write + std::fmt::Debug> FotBuilder for RtfBackend<W> {
fn entity(&mut self, system_id: &str, _content: &str) -> Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
format!("entity flow object not supported by RTF backend (attempted to write: {})", system_id),
))
}
fn formatting_instruction(&mut self, data: &str) -> Result<()> {
self.current_buffer.push_str(data);
Ok(())
}
fn directory(&mut self, _path: &str) -> Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"directory flow object not supported by RTF backend",
))
}
fn current_output(&self) -> &str {
&self.current_buffer
}
fn clear_buffer(&mut self) {
self.current_buffer.clear();
}
fn start_paragraph(&mut self) -> Result<()> {
self.write_header()?;
if self.in_paragraph {
writeln!(self.output, "\\par")?;
}
write!(self.output, "\\pard")?;
self.in_paragraph = true;
Ok(())
}
fn end_paragraph(&mut self) -> Result<()> {
if !self.in_paragraph {
return Ok(());
}
writeln!(self.output, "\\par")?;
self.in_paragraph = false;
Ok(())
}
fn literal(&mut self, text: &str) -> Result<()> {
if !self.in_paragraph {
self.start_paragraph()?;
}
for ch in text.chars() {
match ch {
'\\' => write!(self.output, "\\\\")?,
'{' => write!(self.output, "\\{{")?,
'}' => write!(self.output, "\\}}")?,
'\n' => write!(self.output, "\\line ")?,
'\t' => write!(self.output, "\\tab ")?,
c if c as u32 > 127 => {
write!(self.output, "\\u{}?", c as u32)?;
}
c => write!(self.output, "{}", c)?,
}
}
Ok(())
}
fn start_sequence(&mut self) -> Result<()> {
Ok(())
}
fn end_sequence(&mut self) -> Result<()> {
Ok(())
}
fn start_display_group(&mut self) -> Result<()> {
Ok(())
}
fn end_display_group(&mut self) -> Result<()> {
Ok(())
}
fn start_simple_page_sequence(&mut self) -> Result<()> {
self.write_header()?;
write!(self.output, "\\sectd\\plain")?;
write!(self.output, "\\pgwsxn12240\\pghsxn15840\\marglsxn2160\\margrsxn1440\\margtsxn1440\\margbsxn1920")?;
write!(self.output, "\\headery0\\footery0\\pgndec")?;
Ok(())
}
fn end_simple_page_sequence(&mut self) -> Result<()> {
Ok(())
}
fn start_line_field(&mut self) -> Result<()> {
Ok(())
}
fn end_line_field(&mut self) -> Result<()> {
Ok(())
}
}
impl<W: Write + std::fmt::Debug> Drop for RtfBackend<W> {
fn drop(&mut self) {
let _ = self.finish();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_create_backend() {
let cursor = Cursor::new(Vec::new());
let backend = RtfBackend::new(cursor);
assert!(backend.is_ok());
}
#[test]
fn test_basic_paragraph() -> Result<()> {
let cursor = Cursor::new(Vec::new());
let mut backend = RtfBackend::new(cursor)?;
backend.start_paragraph()?;
backend.literal("Hello, world!")?;
backend.end_paragraph()?;
backend.finish()?;
let output = String::from_utf8(backend.into_inner().into_inner()).unwrap();
assert!(output.contains("{\\rtf1\\ansi\\deff0"));
assert!(output.contains("\\pard"));
assert!(output.contains("Hello, world!"));
assert!(output.contains("\\par"));
Ok(())
}
#[test]
fn test_character_escaping() -> Result<()> {
let cursor = Cursor::new(Vec::new());
let mut backend = RtfBackend::new(cursor)?;
backend.start_paragraph()?;
backend.literal("Test { } \\ special")?;
backend.end_paragraph()?;
backend.finish()?;
let output = String::from_utf8(backend.into_inner().into_inner()).unwrap();
assert!(output.contains("\\{"));
assert!(output.contains("\\}"));
assert!(output.contains("\\\\"));
Ok(())
}
}