diff --git a/Cargo.lock b/Cargo.lock index b8a3880..b83c58b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1105,6 +1105,7 @@ dependencies = [ "image", "js-sys", "log", + "sdo-pdf", "sdo-util", "serde-wasm-bindgen", "signum", diff --git a/crates/sdo-pdf/src/info.rs b/crates/sdo-pdf/src/info.rs index 1f7900e..6dcc7a4 100644 --- a/crates/sdo-pdf/src/info.rs +++ b/crates/sdo-pdf/src/info.rs @@ -4,6 +4,7 @@ use pdf_create::encoding::{pdf_doc_encode, PDFDocEncodingError}; use pdf_create::high::{Handle, Info}; /// Information to add into the PDF `/Info` dictionary +#[derive(Debug, Clone, Default)] pub struct MetaInfo { /// Title pub title: Option, diff --git a/crates/sdo-web/Cargo.toml b/crates/sdo-web/Cargo.toml index e20cd7b..64fdf31 100644 --- a/crates/sdo-web/Cargo.toml +++ b/crates/sdo-web/Cargo.toml @@ -37,6 +37,7 @@ web-sys = { version = "0.3.68", features = [ ] } signum = { path = "../signum", features = ["image"] } sdo-util = { path = "../sdo-util" } +sdo-pdf = { path = "../sdo-pdf" } serde-wasm-bindgen = "0.4.5" console_log = { version = "1.0.0", features = ["wasm-bindgen"] } log = "0.4.17" diff --git a/crates/sdo-web/index.html b/crates/sdo-web/index.html index 4f8851f..d2be0f7 100644 --- a/crates/sdo-web/index.html +++ b/crates/sdo-web/index.html @@ -50,7 +50,7 @@
Menu
-
+
@@ -58,6 +58,11 @@
Menu
Add to collection
+
+ +
diff --git a/crates/sdo-web/index.mjs b/crates/sdo-web/index.mjs index eb15124..b789718 100644 --- a/crates/sdo-web/index.mjs +++ b/crates/sdo-web/index.mjs @@ -7,6 +7,11 @@ async function run() { const paginationEl = document.getElementById("pagination"); const progressEl = document.getElementById("progress"); const inputField = document.getElementById("upload"); + + // Buttons + const addToCollectionBtn = document.getElementById('add-to-collection'); + const exportToPdfBtn = document.getElementById('export-to-pdf'); + const h = new Handle(outputEl, inputField); await h.init(); @@ -49,10 +54,17 @@ async function run() { await h.addToCollection(); } - const addToCollectionBtn = document.getElementById("add-to-collection"); - addToCollectionBtn.addEventListener('click', (event) => { - addToCollection(); - }); + async function exportToPdf() { + return await h.exportToPdf(); + } + + addToCollectionBtn.addEventListener('click', addToCollection); + exportToPdfBtn.addEventListener('click', (_event) => exportToPdf().then(pdf => { + const url = URL.createObjectURL(pdf); + window.open(url); + //console.log(pdf); + + }).catch(console.error)); let pages = []; let pageCount = 0; diff --git a/crates/sdo-web/src/convert.rs b/crates/sdo-web/src/convert.rs index ad20429..425e355 100644 --- a/crates/sdo-web/src/convert.rs +++ b/crates/sdo-web/src/convert.rs @@ -1,22 +1,18 @@ use std::io::Cursor; use image::ImageOutputFormat; -use js_sys::{Array, Uint8Array}; use signum::raster; use wasm_bindgen::JsValue; -use web_sys::{Blob, BlobPropertyBag}; +use web_sys::Blob; -pub(super) fn page_as_blob(page: &raster::Page) -> Result { +use crate::glue::slice_to_blob; + +/// Convert a raster page to a PNG image blob +pub(super) fn page_to_blob(page: &raster::Page) -> Result { let mut buffer = Cursor::new(Vec::::new()); page.to_alpha_image() .write_to(&mut buffer, ImageOutputFormat::Png) .unwrap(); - Blob::new_with_u8_array_sequence_and_options( - &Array::from_iter([Uint8Array::from(buffer.get_ref().as_slice())]), - &{ - let bag = BlobPropertyBag::new(); - bag.set_type("image/png"); - bag - }, - ) + let bytes: &[u8] = buffer.get_ref(); + slice_to_blob(bytes, "image/png") } diff --git a/crates/sdo-web/src/glue.rs b/crates/sdo-web/src/glue.rs index 8de8b40..226a6cc 100644 --- a/crates/sdo-web/src/glue.rs +++ b/crates/sdo-web/src/glue.rs @@ -1,7 +1,7 @@ -use js_sys::{ArrayBuffer, Function, Reflect, Symbol, Uint8Array}; +use js_sys::{Array, ArrayBuffer, Function, Reflect, Symbol, Uint8Array}; use wasm_bindgen::{JsCast, JsError, JsValue}; use wasm_bindgen_futures::JsFuture; -use web_sys::{FileList, FileSystemFileHandle, HtmlInputElement}; +use web_sys::{Blob, BlobPropertyBag, FileList, FileSystemFileHandle, HtmlInputElement}; pub(crate) async fn fs_file_handle_get_file( file_handle: &FileSystemFileHandle, @@ -57,3 +57,13 @@ pub(crate) fn try_iter_async(val: &JsValue) -> Result Result { + // SAFETY: the UInt8Array is used to initialize the blob but does not leave this function + let parts = Array::from_iter([unsafe { Uint8Array::view(bytes) }]); + Blob::new_with_u8_array_sequence_and_options(&parts, &{ + let bag = BlobPropertyBag::new(); + bag.set_type(mime_type); + bag + }) +} diff --git a/crates/sdo-web/src/lib.rs b/crates/sdo-web/src/lib.rs index f62e4ea..c9c1cef 100644 --- a/crates/sdo-web/src/lib.rs +++ b/crates/sdo-web/src/lib.rs @@ -1,11 +1,12 @@ #![allow(non_snake_case)] // wasm_bindgen macro use bstr::BStr; -use convert::page_as_blob; +use convert::page_to_blob; use dom::blob_image_el; -use glue::{js_file_data, js_input_files_iter}; +use glue::{js_file_data, js_input_files_iter, slice_to_blob}; use js_sys::{Array, JsString, Uint8Array}; use log::{info, warn, Level}; +use sdo_pdf::{generate_pdf, MetaInfo}; use sdo_util::keymap::{KB_DRAW, NP_DRAW}; use signum::{ chsets::{ @@ -21,12 +22,12 @@ use signum::{ hcim::{parse_image, Hcim, ImageSite}, header, pbuf, tebu::PageText, - DocumentInfo, GenerationContext, SDoc, + DocumentInfo, GenerationContext, Overrides, SDoc, }, raster::{self, render_doc_page, render_editor_text}, util::FourCC, }; -use std::{ffi::OsStr, fmt::Write}; +use std::{ffi::OsStr, fmt::Write, io::BufWriter}; use vfs::{DirEntry, OriginPrivateFS}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::JsFuture; @@ -200,7 +201,7 @@ impl Handle { for (i, im) in hcim.images.iter().enumerate() { match parse_image(im) { Ok((_rest, image)) => { - let blob = page_as_blob(&image.image.into())?; + let blob = page_to_blob(&image.image.into())?; let el_figure = self.document.create_element("figure")?; let el_image = blob_image_el(&blob)?; @@ -277,8 +278,8 @@ impl Handle { .or(Err("Failed to draw Keyboard Map"))?; let np_img = NP_DRAW.to_page(eset).or(Err("Failed to draw Numpad Map"))?; - let kb_blob = page_as_blob(&kb_img)?; - let np_blob = page_as_blob(&np_img)?; + let kb_blob = page_to_blob(&kb_img)?; + let np_blob = page_to_blob(&np_img)?; let kb_img_el = blob_image_el(&kb_blob)?; let np_img_el = blob_image_el(&np_blob)?; @@ -289,12 +290,8 @@ impl Handle { } fn parse_eset<'a>(&self, data: &'a [u8]) -> Result, JsValue> { - log::info!("Signum Editor Bitmap Font"); match parse_eset(data) { - Ok((_, eset)) => { - log::info!("Parsed Editor Font"); - Ok(eset) - } + Ok((_, eset)) => Ok(eset), Err(e) => { log::error!("Failed to parse editor font: {}", e); Err(JsError::new("Failed to parse editor font").into()) @@ -318,7 +315,7 @@ impl Handle { el_tr.append_child(&el_td)?; if c.height > 0 { let page = raster::Page::from(c); - let blob = page_as_blob(&page)?; + let blob = page_to_blob(&page)?; let img_el = blob_image_el(&blob)?; el_td.append_child(&img_el)?; } @@ -327,7 +324,7 @@ impl Handle { let char_capital_a = &pset.chars[b'A' as usize]; let page = raster::Page::from(char_capital_a); - let blob = page_as_blob(&page)?; + let blob = page_to_blob(&page)?; let window = window().ok_or("expected window")?; let _p = window.create_image_bitmap_with_blob(&blob)?; @@ -385,6 +382,7 @@ impl Handle { #[wasm_bindgen] pub fn reset(&mut self) -> Result<(), JsValue> { self.output.set_inner_html(""); + self.active = None; Ok(()) } @@ -422,11 +420,38 @@ impl Handle { .unchecked_into::(); let o = JsFuture::from(w.write_with_buffer_source(&data)?).await?; assert_eq!(o, JsValue::UNDEFINED); + let o = JsFuture::from(w.close()).await?; + assert_eq!(o, JsValue::UNDEFINED); console::info_3(&"Added".into(), &name.into(), &"to collection!".into()); } Ok(()) } + #[wasm_bindgen(js_name = exportToPdf)] + pub async fn export_to_pdf(&mut self) -> Result { + let active_doc = self + .active + .as_ref() + .ok_or_else(|| JsError::new("no active document"))?; + let overrides = Overrides { + xoffset: 0, + yoffset: 0, + }; + let meta = MetaInfo::default(); + let pk = match active_doc.pd { + FontKind::Editor => Err(JsError::new("editor font not supported")), + FontKind::Printer(printer_kind) => Ok(printer_kind), + }?; + let pdf = generate_pdf(&self.fc, pk, &meta, &overrides, active_doc)?; + let vec = Vec::new(); + let mut writer = BufWriter::new(vec); + pdf.write(&mut writer)?; + let bytes = writer.into_inner()?; + let blob = slice_to_blob(&bytes, "application/pdf") + .map_err(|_v| JsError::new("failed to create pdf blob"))?; + Ok(blob) + } + #[wasm_bindgen] pub async fn render(&mut self, requested_index: usize) -> Result { if let Some(ActiveDocument { sdoc, di, pd }) = &self.active { @@ -443,7 +468,7 @@ impl Handle { &self.fc, ); - let blob = page_as_blob(&page)?; + let blob = page_to_blob(&page)?; Ok(blob) } else { warn!("Missing page {index}"); @@ -471,8 +496,7 @@ impl Handle { #[wasm_bindgen] pub async fn open(&mut self, fragment: &str) -> Result<(), JsValue> { - self.output.set_inner_html(""); - self.active = None; + self.reset()?; if let Some(rest) = fragment.strip_prefix("#/staged/") { let heading = self.document.create_element("h2")?; heading.set_text_content(Some(rest)); @@ -485,6 +509,7 @@ impl Handle { match four_cc { FourCC::SDOC => { let sdoc = self.parse_sdoc(&data)?; + self.fc.reset(); let dfci = self.fc.load(&self.fs, &sdoc.cset).await; let pd = dfci .print_driver(None) @@ -520,7 +545,8 @@ impl Handle { let file = self.fs.open_dir_entry(entry).await?; let data = js_file_data(&file).await?.to_vec(); - let (_, four_cc) = four_cc(&data).map_err(JsError::from)?; + let (_, four_cc) = four_cc(&data).map_err(|_| JsError::new("Failed to parse FourCC"))?; + info!("Loading {} ({})", name, four_cc); let href = format!("#/CHSETS/{}", name); let card = self.card(name, four_cc, &href)?; if let Err(e) = self.card_preview(&card, name, four_cc, &data).await { @@ -540,16 +566,18 @@ impl Handle { let entry = next?; if self.fs.is_file_entry(&entry) { if let Err(e) = self.list_chset_entry(&entry).await { - console::log_1(&e); + let path = entry.path(); + let path = path.to_string_lossy(); + console::log_2(&JsValue::from_str(&path), &e); } } } + info!("Done listing charsets"); Ok(()) } #[wasm_bindgen] pub async fn on_change(&mut self) -> Result<(), JsValue> { - self.reset()?; for file in js_input_files_iter(&self.input)? { let file = file?; let arr = js_file_data(&file).await?; @@ -581,6 +609,7 @@ impl Handle { self.sdoc_card(card, &doc).await?; } FourCC::ESET => { + log::info!("{}: Signum Editor Bitmap Font", name); let eset = self.parse_eset(data)?; self.eset_card(card, &eset, name)?; } @@ -638,9 +667,11 @@ impl Handle { ) -> Result<(), JsValue> { let chset = name.split_once('.').map(|a| a.0).unwrap_or(name); let text = BStr::new(chset.as_bytes()); - let page = render_editor_text(text, eset) - .map_err(|_| JsError::new("Failed to render editor font name"))?; - let blob = page_as_blob(&page)?; + let page = render_editor_text(text, eset).map_err(|v| { + let err = format!("Failed to render editor font name: {}", v); + JsError::new(&err) + })?; + let blob = page_to_blob(&page)?; let img = blob_image_el(&blob)?; list_item.append_child(&img)?; Ok(()) diff --git a/crates/signum/src/raster/font.rs b/crates/signum/src/raster/font.rs index 488dd01..b26762b 100644 --- a/crates/signum/src/raster/font.rs +++ b/crates/signum/src/raster/font.rs @@ -17,12 +17,12 @@ pub fn render_editor_text(text: &BStr, eset: &ESet) -> Result() - + 8; - let mut x = 4; - let mut page = Page::new(width, 24); + + 12; + let mut x = 6; + let mut page = Page::new(width, 30); for ci in text.iter() { if let Some(ch) = eset.chars.get(*ci as usize) { - page.draw_echar(x, 0, ch)?; + page.draw_echar(x, 2, ch)?; x += u16::from(ch.width) + 1; } else { x += 16;