From ed19bef94a247d3eb501ae9f3bcd9496be229d80 Mon Sep 17 00:00:00 2001 From: jelemux Date: Fri, 5 Mar 2021 20:09:25 +0100 Subject: get pdf generation to work and refactor code a little --- src/model/vcard.rs | 325 +++++++++++++++++++++++++++++++++++ src/view/main.rs | 485 +++++++++-------------------------------------------- 2 files changed, 406 insertions(+), 404 deletions(-) (limited to 'src') diff --git a/src/model/vcard.rs b/src/model/vcard.rs index 4670fe7..ea66224 100644 --- a/src/model/vcard.rs +++ b/src/model/vcard.rs @@ -1,9 +1,19 @@ +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)] @@ -35,10 +45,325 @@ impl VCardData { 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/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/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/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/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 card_data = elements::Paragraph::new(""); + + let vcard_data = self.clone(); + + if let Some(name) = vcard_data.names.get(0) { + card_data.push_styled( + name.generate_fn(), + style::Effect::Bold + ); + } + card_data.push( + "" + ); + if let Some(communication) = vcard_data.communications.get(0) { + card_data.push( + communication.email_address.clone() + ); + } + card_data.push( + "" + ); + if let Some(address) = vcard_data.addresses.get(0) { + card_data.push( + address.street.clone() + ); + card_data.push( + format!("{} {} {}", address.code, address.locality, address.extension) + ); + card_data.push( + format!("{} {}", address.region, address.country) + ); + } + card_data.push( + "" + ); + for telephone in vcard_data.telephones { + card_data.push( + telephone.number + ); + } + + let framed_card_data = elements::FramedElement::new( + card_data + ); + + 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(framed_card_data.clone()) + .element(framed_card_data.clone()) + .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(()), + } + } } diff --git a/src/view/main.rs b/src/view/main.rs index 40aaa34..3eb9677 100644 --- a/src/view/main.rs +++ b/src/view/main.rs @@ -7,18 +7,10 @@ use crate::model::property_groups::telephone::*; use crate::model::utility::*; use crate::model::vcard::VCardData; use crate::model::Error; -use crate::model::VCardPropertyInputGroupObject; use crate::view::property_group::PropertyGroupInputComponent; use crate::view::weak_links::WeakComponentLink; -use boolinator::Boolinator; -use chrono::prelude::*; -use genpdf::Element as _; -use genpdf::{elements, fonts, style}; use qrcodegen::QrCode; use qrcodegen::QrCodeEcc; -use uuid::Uuid; -use vobject::parameters; -use vobject::vcard::VcardBuilder; use yew::prelude::*; use yew::services::ConsoleService; use yewtil::ptr::Mrc; @@ -156,64 +148,36 @@ impl Component for MainView { false } Msg::Generate => { - if self.selected_option == DownloadOption::VCard - || self.selected_option == DownloadOption::QrCode - { - for name_link in self.name_links.iter() { - let name_link = name_link.borrow().clone().unwrap(); - name_link.send_message(NameMsg::Generate); - } - - for address_link in self.address_links.iter() { - let address_link = address_link.borrow().clone().unwrap(); - address_link.send_message(AddressMsg::Generate); - } + + for name_link in self.name_links.iter() { + let name_link = name_link.borrow().clone().unwrap(); + name_link.send_message(NameMsg::Generate); + } - for telephone_link in self.telephone_links.iter() { - let telephone_link = telephone_link.borrow().clone().unwrap(); - telephone_link.send_message(TelephoneMsg::Generate); - } + for address_link in self.address_links.iter() { + let address_link = address_link.borrow().clone().unwrap(); + address_link.send_message(AddressMsg::Generate); + } - for other_identifications_link in self.other_identifications_links.iter() { - let other_identifications_link = - other_identifications_link.borrow().clone().unwrap(); - other_identifications_link.send_message(OtherIdentificationMsg::Generate) - } + for telephone_link in self.telephone_links.iter() { + let telephone_link = telephone_link.borrow().clone().unwrap(); + telephone_link.send_message(TelephoneMsg::Generate); + } - for organizational_link in self.organizational_links.iter() { - let organizational_link = organizational_link.borrow().clone().unwrap(); - organizational_link.send_message(OrganizationalMsg::Generate) - } + for other_identifications_link in self.other_identifications_links.iter() { + let other_identifications_link = + other_identifications_link.borrow().clone().unwrap(); + other_identifications_link.send_message(OtherIdentificationMsg::Generate) + } - for communcation_link in self.communcation_links.iter() { - let communcation_link = communcation_link.borrow().clone().unwrap(); - communcation_link.send_message(CommunicationMsg::Generate) - } + for organizational_link in self.organizational_links.iter() { + let organizational_link = organizational_link.borrow().clone().unwrap(); + organizational_link.send_message(OrganizationalMsg::Generate) } - if self.selected_option == DownloadOption::PDF { - match self.generate_pdf() { - Ok(pdf) => { - let mut vcard_name = String::new(); - if let Some(vcard_data) = self.vcard_data.get_mut() { - if let Some(name) = vcard_data.names.get(0) { - vcard_name = name.generate_fn(); - } - } - self.download = Some( - Download { - file_name: format!("Visitenkarten {}.pdf", vcard_name), - content: pdf, - mime_type: MimeType::PDF, - } - ); - }, - Err(_) => self.error = Some( - Error { - msg: String::from("Unexpected error while generating the PDF. Please contact me about it.") - } - ), - } + for communcation_link in self.communcation_links.iter() { + let communcation_link = communcation_link.borrow().clone().unwrap(); + communcation_link.send_message(CommunicationMsg::Generate) } true @@ -291,229 +255,71 @@ impl Component for MainView { Msg::GenerationComplete => { self.answer_count = 0; - let vcard_data = self.vcard_data.clone_inner(); - - 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(','); + if let DownloadOption::PDF = self.selected_option { + match self.vcard_data.generate_pdf() { + Ok(pdf) => { + let mut vcard_name = String::new(); + if let Some(vcard_data) = self.vcard_data.get_mut() { + if let Some(name) = vcard_data.names.get(0) { + vcard_name = name.generate_fn(); + } } - 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(','); + self.download = Some( + Download { + file_name: format!("Visitenkarten {}.pdf", vcard_name), + content: pdf, + mime_type: MimeType::PDF, + } + ); + }, + Err(_) => self.error = Some( + Error { + msg: String::from("Unexpected error while generating the PDF. Please contact me about it.") } - types.push_str("PAGER") + ), + }; + } else { + match self.vcard_data.build_vcard() { + Ok(vcard) => { + match self.selected_option { + DownloadOption::VCard => { + self.download = Some(Download { + file_name: String::from("VCard.vcs"), + content: vobject::write_component(&vcard), + mime_type: MimeType::VCard, + }); + } + DownloadOption::QrCode => { + match QrCode::encode_text( + &vobject::write_component(&vcard), + QrCodeEcc::Low, + ) { + Ok(qr) => { + self.download = Some(Download { + file_name: String::from("QR-Code VCard.svg"), + content: qr.to_svg_string(4), + mime_type: MimeType::SVG, + }) + } + Err(_) => { + self.error = Some(Error { + msg: String::from("Sorry, VCard is too long!"), + }) + } + }; + } + _ => (), + }; } - if telephone.text_phone { - if !types.is_empty() { - types.push(','); - } - types.push_str("TEXTPHONE") + Err(err) => { + self.error = Some(Error { + msg: err.to_string(), + }) } - - 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(); - - match builder - .with_version("4.0".to_string()) - .with_rev(rev) - .with_uid(uid) - .build() - { - Ok(vcard) => { - match self.selected_option { - DownloadOption::VCard => { - self.download = Some(Download { - file_name: String::from("VCard.vcs"), - content: vobject::write_component(&vcard), - mime_type: MimeType::VCard, - }); - } - DownloadOption::QrCode => { - match QrCode::encode_text( - &vobject::write_component(&vcard), - QrCodeEcc::Low, - ) { - Ok(qr) => { - self.download = Some(Download { - file_name: String::from("QR-Code VCard.svg"), - content: qr.to_svg_string(4), - mime_type: MimeType::SVG, - }) - } - Err(_) => { - self.error = Some(Error { - msg: String::from("Sorry, VCard is too long!"), - }) - } - }; - } - _ => (), - }; - } - Err(err) => { - self.error = Some(Error { - msg: err.to_string(), - }) - } - }; - match self.vcard_data.get_mut() { Some(vcard_data) => *vcard_data = VCardData::new(), None => ConsoleService::info("Couldn't reset VCardData"), @@ -810,135 +616,6 @@ impl MainView { } } - fn generate_pdf(&self) -> Result { - let regular_bytes = - include_bytes!("/usr/share/fonts/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/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/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/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 card_data = elements::Paragraph::new(""); - - if let Ok(vcard_data) = self.vcard_data.irc().try_unwrap() { - if let Some(name) = vcard_data.names.get(0) { - card_data.push_styled( - name.generate_fn(), - style::Effect::Bold - ); - } - card_data.push( - "" - ); - if let Some(communication) = vcard_data.communications.get(0) { - card_data.push( - communication.email_address.clone() - ); - } - card_data.push( - "" - ); - if let Some(address) = vcard_data.addresses.get(0) { - card_data.push( - address.street.clone() - ); - card_data.push( - format!("{} {} {}", address.code, address.locality, address.extension) - ); - card_data.push( - format!("{} {}", address.region, address.country) - ); - } - card_data.push( - "" - ); - for telephone in vcard_data.telephones { - card_data.push( - telephone.number - ); - } - } else { - ConsoleService::log("Could not unwrap vcard_data."); - } - - let framed_card_data = elements::FramedElement::new( - card_data - ); - - 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(framed_card_data.clone()) - .element(framed_card_data.clone()) - .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 get_subcomponent_count(&self) -> usize { self.name_links.len() + self.address_links.len() -- cgit v1.2.3