use vobject::vcard::VcardBuilder; use chrono::Local; use uuid::Uuid; use vobject::Vcard; use crate::model::property_groups::address::Address; use crate::model::property_groups::communication::Communication; use crate::model::property_groups::name::Name; use crate::model::property_groups::organizational::Organizational; use crate::model::property_groups::other_identification::OtherIdentification; use crate::model::property_groups::telephone::Telephone; use vobject::error::Result as VResult; use crate::model::VCardPropertyInputGroupObject; use boolinator::Boolinator; use vobject::parameters; use genpdf::Element as _; use genpdf::{elements, fonts, style}; /// Type that represents the data structure of a vcard. #[derive(Clone, Debug)] pub struct VCardData { pub names: Vec, pub addresses: Vec
, pub telephones: Vec, pub communications: Vec, pub other_identifications: Vec, pub organizationals: Vec, } macro_rules! make_vec_adder_fn { ( fn $fnname:ident $property:ident => $($arg_name:ident : $arg_type:ty),* ) => { pub fn $fnname(&mut self, $( $arg_name : $arg_type ),*) { $(self.$property.push($arg_name);)* } }; } impl VCardData { pub fn new() -> Self { Self { names: Vec::new(), addresses: Vec::new(), telephones: Vec::new(), communications: Vec::new(), other_identifications: Vec::new(), organizationals: Vec::new(), } } make_vec_adder_fn!( fn add_name names => name: Name ); make_vec_adder_fn!( fn add_address addresses => address: Address ); make_vec_adder_fn!( fn add_telephone telephones => telephone: Telephone ); make_vec_adder_fn!( fn add_communication communications => communication: Communication ); make_vec_adder_fn!( fn add_other_identification other_identifications => other_identification: OtherIdentification ); make_vec_adder_fn!( fn add_organizational organizationals => organizational: Organizational ); pub fn build_vcard(&self) -> VResult { let vcard_data = self.clone(); let mut builder = VcardBuilder::new(); for name in vcard_data.names { if !name.is_empty() { builder = builder.with_fullname(name.generate_fn()).with_name( parameters!(), (!name.last_name.is_empty()).as_some(name.last_name.clone()), (!name.first_name.is_empty()).as_some(name.first_name.clone()), (!name.middle_name.is_empty()).as_some(name.middle_name.clone()), (!name.prefix.is_empty()).as_some(name.prefix.clone()), (!name.suffix.is_empty()).as_some(name.suffix.clone()), ); } } for address in vcard_data.addresses { if !address.is_empty() { let mut types = String::new(); if address.work { types.push_str("WORK"); } if address.home { if !types.is_empty() { types.push(','); } types.push_str("HOME") } let params = if types.is_empty() { parameters!() } else { parameters!("TYPE" => types) }; builder = builder.with_adr( params, (!address.post_office_box.is_empty()) .as_some(address.post_office_box.clone()), (!address.extension.is_empty()).as_some(address.extension.clone()), (!address.street.is_empty()).as_some(address.street.clone()), (!address.locality.is_empty()).as_some(address.locality.clone()), (!address.region.is_empty()).as_some(address.region.clone()), (!address.code.is_empty()).as_some(address.code.clone()), (!address.country.is_empty()).as_some(address.country.clone()), ); } } for telephone in vcard_data.telephones { if !telephone.is_empty() { let mut types = String::new(); if telephone.work { types.push_str("WORK"); } if telephone.home { if !types.is_empty() { types.push(','); } types.push_str("HOME") } if telephone.text { if !types.is_empty() { types.push(','); } types.push_str("TEXT") } if telephone.voice { if !types.is_empty() { types.push(','); } types.push_str("VOICE") } if telephone.fax { if !types.is_empty() { types.push(','); } types.push_str("FAX") } if telephone.cell { if !types.is_empty() { types.push(','); } types.push_str("CELL") } if telephone.video { if !types.is_empty() { types.push(','); } types.push_str("VIDEO") } if telephone.pager { if !types.is_empty() { types.push(','); } types.push_str("PAGER") } if telephone.text_phone { if !types.is_empty() { types.push(','); } types.push_str("TEXTPHONE") } let params = if types.is_empty() { parameters!() } else { parameters!("TYPE" => types) }; builder = builder.with_tel(params, telephone.number.clone()); } } for communication in vcard_data.communications { if !communication.email_address.is_empty() { builder = builder.with_email(communication.email_address.clone()); } if !communication.impp.is_empty() { builder = builder.with_impp(communication.impp.clone()); } } for other_identification in vcard_data.other_identifications { if !other_identification.nickname.is_empty() { builder = builder.with_nickname(parameters!(), other_identification.nickname); } match other_identification.photo { Some(file) => builder = builder.with_photo(parameters!(), file.content), None => (), }; if !other_identification.anniversary.is_empty() { builder = builder.with_anniversary(other_identification.anniversary); } if !other_identification.birthday.is_empty() { builder = builder.with_bday(parameters!(), other_identification.birthday); } if !other_identification.gender.is_empty() { builder = builder.with_gender(parameters!(), other_identification.gender); } } for organizational in vcard_data.organizationals { if !organizational.org.is_empty() { builder = builder.with_org(vec![organizational.org]); } match organizational.logo { Some(file) => builder = builder.with_logo(file.content), None => (), }; if !organizational.title.is_empty() { builder = builder.with_title(organizational.title); } if !organizational.role.is_empty() { builder = builder.with_role(organizational.role); } if !organizational.member.is_empty() { builder = builder.with_member(organizational.member); } if !organizational.related.is_empty() { builder = builder.with_related(organizational.related); } } let uid = format!("urn:uuid:{}", Uuid::new_v4()); let rev = Local::now().to_string(); builder .with_version("4.0".to_string()) .with_rev(rev) .with_uid(uid) .build() } pub fn generate_pdf(&self) -> Result { let regular_bytes = include_bytes!("/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"); let regular_font_data = fonts::FontData::new( regular_bytes.to_vec(), Some(printpdf::BuiltinFont::Helvetica), ) .expect("font data should be correct"); let bold_bytes = include_bytes!("/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf"); let bold_font_data = fonts::FontData::new( bold_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBold), ) .expect("font data should be correct"); let italic_bytes = include_bytes!("/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf"); let italic_font_data = fonts::FontData::new( italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaOblique), ) .expect("font data should be correct"); let bold_italic_bytes = include_bytes!("/usr/share/fonts/truetype/liberation/LiberationSans-BoldItalic.ttf"); let bold_italic_font_data = fonts::FontData::new( bold_italic_bytes.to_vec(), Some(printpdf::BuiltinFont::HelveticaBoldOblique), ) .expect("font data should be correct"); let font_family = fonts::FontFamily { regular: regular_font_data, bold: bold_font_data, italic: italic_font_data, bold_italic: bold_italic_font_data, }; let mut doc = genpdf::Document::new(font_family); doc.set_title("wasm-card test"); doc.set_minimal_conformance(); let mut decorator = genpdf::SimplePageDecorator::new(); decorator.set_margins(10); doc.set_page_decorator(decorator); doc.set_line_spacing(1.25); doc.push( elements::Paragraph::new("genpdf Demo Document") .aligned(elements::Alignment::Center) .styled(style::Style::new().bold().with_font_size(20)), ); let mut table = elements::TableLayout::new(vec![1,1]); table.set_cell_decorator( elements::FrameCellDecorator::new(false, false, false) ); for _i in 0..4 { table .row() .element(self.create_card_element()) .element(self.create_card_element()) .push() .expect("invalid table row"); } doc.push(table); // TODO fill doc with real data let mut buf: Vec = Vec::new(); match doc.render(&mut buf) { Ok(_) => Ok(match String::from_utf8(buf) { Ok(s) => s, Err(_) => return Err(()), }), Err(_) => Err(()), } } fn create_card_element(&self) -> impl genpdf::Element { let mut layout = elements::LinearLayout::vertical(); if let Some(name) = self.names.get(0) { layout.push(elements::Text::new(style::StyledString::new( name.generate_fn(), style::Effect::Bold ))); } if let Some(communication) = self.communications.get(0) { layout.push(elements::Text::new(&communication.email_address)); } if let Some(address) = self.addresses.get(0) { layout.push(elements::Text::new(&address.street)); layout.push(elements::Text::new( format!("{} {} {}", address.code, address.locality, address.extension) )); layout.push(elements::Text::new( format!("{} {}", address.region, address.country) )); } for telephone in &self.telephones { layout.push(elements::Text::new(&telephone.number)); } elements::FramedElement::new(layout) } }