Skip to content

Commit

Permalink
feat: add Export to PDF to studio
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiphoseer committed Jan 8, 2025
1 parent 41fa240 commit aa48ecd
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 45 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/sdo-pdf/src/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Expand Down
1 change: 1 addition & 0 deletions crates/sdo-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 6 additions & 1 deletion crates/sdo-web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@ <h5 class="offcanvas-title" id="offcanvasLabel">Menu</h5>

<main class="container mt-5 vstack gap-3">
<div class="row g-3 justify-content-center">
<div class="col-sm-10">
<div class="col-sm-8">
<input id="upload" class="form-control" type="file" accept=".sdo, .p24, .e24, .p09" multiple>
</div>
<div class="col-sm-2">
<button id="add-to-collection" type="submit" class="form-control btn btn-primary">
Add to collection
</button>
</div>
<div class="col-sm-2">
<button id="export-to-pdf" type="submit" class="form-control btn btn-primary">
Export PDF
</button>
</div>
</div>

<div class="row g-3 justify-content-center">
Expand Down
20 changes: 16 additions & 4 deletions crates/sdo-web/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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;
Expand Down
18 changes: 7 additions & 11 deletions crates/sdo-web/src/convert.rs
Original file line number Diff line number Diff line change
@@ -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<Blob, JsValue> {
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<Blob, JsValue> {
let mut buffer = Cursor::new(Vec::<u8>::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")
}
14 changes: 12 additions & 2 deletions crates/sdo-web/src/glue.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -57,3 +57,13 @@ pub(crate) fn try_iter_async(val: &JsValue) -> Result<Option<js_sys::AsyncIterat

Ok(Some(it))
}

pub(crate) fn slice_to_blob(bytes: &[u8], mime_type: &str) -> Result<Blob, JsValue> {
// 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
})
}
77 changes: 54 additions & 23 deletions crates/sdo-web/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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;
Expand Down Expand Up @@ -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)?;
Expand Down Expand Up @@ -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)?;
Expand All @@ -289,12 +290,8 @@ impl Handle {
}

fn parse_eset<'a>(&self, data: &'a [u8]) -> Result<ESet<'a>, 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())
Expand All @@ -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)?;
}
Expand All @@ -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)?;
Expand Down Expand Up @@ -385,6 +382,7 @@ impl Handle {
#[wasm_bindgen]
pub fn reset(&mut self) -> Result<(), JsValue> {
self.output.set_inner_html("");
self.active = None;
Ok(())
}

Expand Down Expand Up @@ -422,11 +420,38 @@ impl Handle {
.unchecked_into::<FileSystemWritableFileStream>();
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<Blob, JsError> {
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<Blob, JsValue> {
if let Some(ActiveDocument { sdoc, di, pd }) = &self.active {
Expand All @@ -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}");
Expand Down Expand Up @@ -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));
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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?;
Expand Down Expand Up @@ -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)?;
}
Expand Down Expand Up @@ -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(())
Expand Down
8 changes: 4 additions & 4 deletions crates/signum/src/raster/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ pub fn render_editor_text(text: &BStr, eset: &ESet) -> Result<Page, DrawPrintErr
}
})
.sum::<u32>()
+ 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;
Expand Down

0 comments on commit aa48ecd

Please sign in to comment.