use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::io::Write;
use std::path::Path;
use crate::err::*;
use crate::inch::*;
use crate::mrg::*;
use crate::sze::*;
use crate::unit::*;
use google_fonts::Font;
use serde::{Deserialize, Serialize};
use skia_safe::{
pdf,
textlayout::{
FontCollection, ParagraphBuilder, ParagraphStyle, PlaceholderAlignment, PlaceholderStyle,
TextAlign, TextBaseline, TextStyle, TypefaceFontProvider,
},
Document, FontMgr, FontStyle, Paint, Point,
};
use std::collections::hash_map::Entry::Vacant;
pub fn new_ansi_letter() -> Doc {
Doc::default()
.set_sze(ANSI_LETTER)
.set_mrg(MRG_IN_1)
.set_ind(In(0.5))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Doc {
pub sze: Sze,
pub mrg: Mrg,
pub ind: In,
pub fnt: Font,
pub fnt_sze: f32,
pub fnt_sty: Style,
pub aln: Align,
pub spc_lne: LineSpace,
pub spc_par_aft: LineSpace,
pub has_ind: bool,
pub elms: Vec<Elm>,
}
impl Default for Doc {
fn default() -> Self {
Doc {
sze: Sze::default(),
mrg: Mrg::default(),
ind: In::default(),
fnt: Font::DomineVariable,
fnt_sze: 12.0,
fnt_sty: Style::Normal,
aln: Align::Justify,
spc_lne: LineSpace::Custom(1.35),
spc_par_aft: LineSpace::Custom(1.35),
has_ind: true,
elms: Vec::new(),
}
}
}
impl Doc {
pub fn save_json<P>(&self, pth: P) -> Result<(), DocError>
where
P: AsRef<Path>,
{
let json_str = serde_json::to_string_pretty(self).map_err(DocError::from)?;
let file_path = pth.as_ref().with_extension("json");
let mut file = File::create(file_path).map_err(DocError::from)?;
file.write_all(json_str.as_bytes())
.map_err(DocError::FileError)?;
Ok(())
}
pub fn read_json<P>(&self, pth: P) -> Result<Doc, DocError>
where
P: AsRef<Path>,
{
let file_path = pth.as_ref().with_extension("json");
let fle = File::open(file_path).map_err(DocError::from)?;
let rdr = BufReader::new(fle);
let ret: Doc = serde_json::from_reader(rdr).map_err(DocError::from)?;
Ok(ret)
}
pub fn save_pdf<P>(&self, pth: P) -> Result<(), DocError>
where
P: AsRef<Path>,
{
let mut memory = Vec::new();
let mut pdf = pdf::new_document(&mut memory, None);
let mut fnts: HashMap<Font, FontCollection> = HashMap::new();
let font_mgr = FontMgr::new();
let pags = self.seg_pags();
for pars in pags {
pdf = self.wrt_pag(pars, pdf, &mut fnts, &font_mgr)?;
}
pdf.close();
let file_path = pth.as_ref().with_extension("pdf");
let mut file = File::create(file_path).map_err(DocError::from)?;
file.write_all(&memory).map_err(DocError::FileError)?;
Ok(())
}
pub fn wrt_pag<'a>(
&'a self,
pars: Vec<Par>,
pdf: Document<'a>,
fnts: &mut HashMap<Font, FontCollection>,
font_mgr: &FontMgr,
) -> Result<Document<'a>, DocError> {
let mut pdf_pag = pdf.begin_page(self.sze.pt(), None);
let par_wid = self.sze.width - self.mrg.width();
let mut y: f32 = self.mrg.top.pt();
for par in pars {
let fnt = par.fnt.unwrap_or(self.fnt);
if let Vacant(e) = fnts.entry(fnt) {
e.insert(create_fnt_col(fnt, font_mgr)?);
}
let cur_fnt_col = fnts.get(&fnt).unwrap().clone();
let fnt_sze = par.fnt_sze.unwrap_or(self.fnt_sze);
let mut cur_ts = TextStyle::new();
cur_ts.set_font_families(&[par.fnt.unwrap_or(self.fnt).to_string()]);
cur_ts.set_font_size(fnt_sze);
cur_ts.set_height(par.spc_lne.unwrap_or(self.spc_lne).val());
cur_ts.set_height_override(true);
cur_ts.set_foreground_paint(&Paint::default());
par.fnt_sty.unwrap_or(self.fnt_sty).set(&mut cur_ts);
let mut cur_par_sty = ParagraphStyle::new();
par.aln.unwrap_or(self.aln).set(&mut cur_par_sty);
let mut par_bld = ParagraphBuilder::new(&cur_par_sty, &cur_fnt_col);
par_bld.push_style(&cur_ts);
if par.has_ind.unwrap_or(self.has_ind) {
let ind = par.ind.as_ref().unwrap_or(&self.ind);
par_bld.add_placeholder(&PlaceholderStyle {
width: ind.pt(),
height: 0.0,
alignment: PlaceholderAlignment::Baseline,
baseline_offset: 0.0,
baseline: TextBaseline::Alphabetic,
});
}
par_bld.add_text(&par.txt);
let mut paragraph = par_bld.build();
paragraph.layout(par_wid.pt());
paragraph.paint(
pdf_pag.canvas(),
Point {
x: self.mrg.lft.pt(),
y,
},
);
let par_spc_aft = par.spc_aft.unwrap_or(self.spc_par_aft);
y += paragraph.get_line_metrics_at(0).unwrap().height as f32 * par_spc_aft.val();
y += paragraph.height();
}
Ok(pdf_pag.end_page())
}
pub fn seg_pags(&self) -> Vec<Vec<Par>> {
let mut pages: Vec<Vec<Par>> = vec![];
let mut current_page: Vec<Par> = vec![];
for elm in &self.elms {
match elm {
Elm::Par(par) => current_page.push(par.clone()),
Elm::PagBrk => {
if !current_page.is_empty() {
pages.push(current_page);
current_page = vec![];
}
}
}
}
if !current_page.is_empty() {
pages.push(current_page);
}
pages
}
pub fn copy_pars(&mut self, doc: Doc) {
self.elms.extend(doc.elms.iter().cloned())
}
pub fn add_par(&mut self, par: Par) {
self.elms.push(Elm::Par(par));
}
pub fn add_pag_brk(&mut self) {
self.elms.push(Elm::PagBrk);
}
pub fn replace_par_at(&mut self, idx: usize, from: &str, to: &str) {
if let Some(Elm::Par(ref mut par)) = self.elms.get_mut(idx) {
par.txt = par.txt.replace(from, to);
}
}
pub fn clone_clear(&self) -> Self {
let mut ret = self.clone();
ret.elms.clear();
ret
}
pub fn set_sze(mut self, sze: Sze) -> Self {
self.sze = sze;
self
}
pub fn set_mrg(mut self, mrg: Mrg) -> Self {
self.mrg = mrg;
self
}
pub fn set_ind(mut self, ind: In) -> Self {
self.ind = ind;
self
}
pub fn set_fnt(mut self, fnt: Font) -> Self {
self.fnt = fnt;
self
}
pub fn set_fnt_sze(mut self, fnt_sze: f32) -> Self {
self.fnt_sze = fnt_sze;
self
}
pub fn set_fnt_sty(mut self, sty: Style) -> Self {
self.fnt_sty = sty;
self
}
pub fn set_aln(mut self, aln: Align) -> Self {
self.aln = aln;
self
}
pub fn set_spc_lne(mut self, spc_lne: LineSpace) -> Self {
self.spc_lne = spc_lne;
self
}
pub fn set_spc_par_aft(mut self, spc_par_aft: LineSpace) -> Self {
self.spc_par_aft = spc_par_aft;
self
}
pub fn set_has_ind(mut self, has_ind: bool) -> Self {
self.has_ind = has_ind;
self
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum Style {
#[default]
Normal,
Italic,
Bold,
BoldItalic,
}
impl Style {
pub fn set(self, ts: &mut TextStyle) {
match self {
Style::Normal => ts.set_font_style(FontStyle::normal()),
Style::Italic => ts.set_font_style(FontStyle::italic()),
Style::Bold => ts.set_font_style(FontStyle::bold()),
Style::BoldItalic => ts.set_font_style(FontStyle::bold_italic()),
};
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum Align {
#[default]
Left,
Right,
Center,
Justify,
}
impl Align {
pub fn set(self, ps: &mut ParagraphStyle) {
match self {
Align::Left => ps.set_text_align(TextAlign::Left),
Align::Right => ps.set_text_align(TextAlign::Right),
Align::Center => ps.set_text_align(TextAlign::Center),
Align::Justify => ps.set_text_align(TextAlign::Justify),
};
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, Copy, PartialEq)]
pub enum LineSpace {
#[default]
Single,
Double,
Custom(f32),
}
impl LineSpace {
pub fn val(self) -> f32 {
match self {
LineSpace::Single => 1.0,
LineSpace::Double => 2.0,
LineSpace::Custom(val) => val,
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Par {
#[serde(skip_serializing_if = "Option::is_none")]
pub ind: Option<In>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fnt: Option<Font>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fnt_sze: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fnt_sty: Option<Style>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aln: Option<Align>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spc_lne: Option<LineSpace>,
#[serde(skip_serializing_if = "Option::is_none")]
pub spc_aft: Option<LineSpace>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_ind: Option<bool>,
pub txt: String,
}
pub fn par(txt: &str) -> Par {
Par::default().set_txt(txt.into())
}
impl Par {
pub fn replace(&mut self, from: &str, to: &str) {
self.txt = self.txt.replace(from, to)
}
pub fn set_ind(mut self, ind: Option<In>) -> Self {
self.ind = ind;
self
}
pub fn set_fnt(mut self, fnt: Option<Font>) -> Self {
self.fnt = fnt;
self
}
pub fn set_fnt_sze(mut self, fnt_sze: Option<f32>) -> Self {
self.fnt_sze = fnt_sze;
self
}
pub fn set_fnt_sty(mut self, sty: Option<Style>) -> Self {
self.fnt_sty = sty;
self
}
pub fn set_aln(mut self, aln: Option<Align>) -> Self {
self.aln = aln;
self
}
pub fn set_spc_lne(mut self, spc_lne: Option<LineSpace>) -> Self {
self.spc_lne = spc_lne;
self
}
pub fn set_spc_aft(mut self, spc_aft: Option<LineSpace>) -> Self {
self.spc_aft = spc_aft;
self
}
pub fn set_has_ind(mut self, has_ind: Option<bool>) -> Self {
self.has_ind = has_ind;
self
}
pub fn set_txt(mut self, txt: String) -> Self {
self.txt = txt;
self
}
}
pub fn create_fnt_col(font: Font, font_mgr: &FontMgr) -> Result<FontCollection, DocError> {
let font_data = font.get_with_cache().map_err(DocError::from)?;
if let Some(typeface) = font_mgr.new_from_data(&font_data, None) {
let mut tfp = TypefaceFontProvider::new();
tfp.register_typeface(typeface, Some(font.to_string().as_str()));
let mut fnt_col = FontCollection::new();
fnt_col.set_default_font_manager(Some(tfp.into()), None);
return Ok(fnt_col);
}
Err(DocError::from(
format!("Unable to parse font `{}`.", font).as_str(),
))
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Elm {
Par(Par),
PagBrk,
}