1use deckmint::objects::image::ImageOptionsBuilder;
2use deckmint::objects::shape::ShapeOptionsBuilder;
3use deckmint::objects::table::{TableCell, TableOptions, TableRow};
4use deckmint::objects::text::TextOptionsBuilder;
5use deckmint::{Presentation, ShapeType};
6use wasm_bindgen::prelude::*;
7
8#[wasm_bindgen(start)]
9pub fn start() {
10 console_error_panic_hook::set_once();
11}
12
13#[wasm_bindgen]
15pub struct JsPresentation {
16 inner: Presentation,
17}
18
19#[wasm_bindgen]
20impl JsPresentation {
21 #[wasm_bindgen(constructor)]
22 pub fn new() -> Self {
23 JsPresentation {
24 inner: Presentation::new(),
25 }
26 }
27
28 pub fn set_title(&mut self, title: &str) {
30 self.inner.title = title.to_string();
31 }
32
33 pub fn set_author(&mut self, author: &str) {
35 self.inner.author = author.to_string();
36 }
37
38 pub fn set_subject(&mut self, subject: &str) {
40 self.inner.subject = subject.to_string();
41 }
42
43 pub fn set_company(&mut self, company: &str) {
45 self.inner.company = company.to_string();
46 }
47
48 pub fn add_slide(&mut self) -> usize {
50 self.inner.add_slide();
51 self.inner.slide_count() - 1
52 }
53
54 pub fn add_text(&mut self, slide_idx: usize, text: &str, opts_json: &str) -> Result<(), JsValue> {
57 let opts = parse_text_opts(opts_json)?;
58 if let Some(slide) = self.inner.slide_mut(slide_idx) {
59 slide.add_text(text, opts);
60 }
61 Ok(())
62 }
63
64 pub fn add_shape(&mut self, slide_idx: usize, shape_type: &str, opts_json: &str) -> Result<(), JsValue> {
68 let shape = parse_shape_type(shape_type)?;
69 let opts = parse_shape_opts(opts_json)?;
70 if let Some(slide) = self.inner.slide_mut(slide_idx) {
71 slide.add_shape(shape, opts);
72 }
73 Ok(())
74 }
75
76 pub fn add_image_base64(
80 &mut self,
81 slide_idx: usize,
82 b64: &str,
83 extension: &str,
84 opts_json: &str,
85 ) -> Result<(), JsValue> {
86 let opts = parse_image_opts(opts_json)?;
87 if let Some(slide) = self.inner.slide_mut(slide_idx) {
88 slide.add_image_base64(b64, extension, opts)
89 .map_err(|e| JsValue::from_str(&e.to_string()))?;
90 }
91 Ok(())
92 }
93
94 pub fn add_table(
98 &mut self,
99 slide_idx: usize,
100 rows_json: &str,
101 opts_json: &str,
102 ) -> Result<(), JsValue> {
103 let rows = parse_table_rows(rows_json)?;
104 let opts = parse_table_opts(opts_json)?;
105 if let Some(slide) = self.inner.slide_mut(slide_idx) {
106 slide.add_table(rows, opts);
107 }
108 Ok(())
109 }
110
111 pub fn set_background_color(&mut self, slide_idx: usize, color: &str) {
113 if let Some(slide) = self.inner.slide_mut(slide_idx) {
114 slide.set_background_color(color);
115 }
116 }
117
118 pub fn add_notes(&mut self, slide_idx: usize, notes: &str) {
120 if let Some(slide) = self.inner.slide_mut(slide_idx) {
121 slide.add_notes(notes);
122 }
123 }
124
125 pub fn write(&self) -> Result<js_sys::Uint8Array, JsValue> {
127 let bytes = self.inner.write()
128 .map_err(|e| JsValue::from_str(&e.to_string()))?;
129 Ok(js_sys::Uint8Array::from(bytes.as_slice()))
130 }
131}
132
133fn get_f64(obj: &serde_json::Value, key: &str) -> Option<f64> {
136 obj.get(key)?.as_f64()
137}
138
139fn get_str<'a>(obj: &'a serde_json::Value, key: &str) -> Option<&'a str> {
140 obj.get(key)?.as_str()
141}
142
143fn get_bool(obj: &serde_json::Value, key: &str) -> Option<bool> {
144 obj.get(key)?.as_bool()
145}
146
147fn parse_text_opts(json: &str) -> Result<deckmint::objects::text::TextOptions, JsValue> {
148 let v: serde_json::Value = serde_json::from_str(json)
149 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
150
151 let mut b = TextOptionsBuilder::new();
152 if let Some(x) = get_f64(&v, "x") { b = b.x(x); }
153 if let Some(y) = get_f64(&v, "y") { b = b.y(y); }
154 if let Some(w) = get_f64(&v, "w") { b = b.w(w); }
155 if let Some(h) = get_f64(&v, "h") { b = b.h(h); }
156 if let Some(fs) = get_f64(&v, "fontSize") { b = b.font_size(fs); }
157 if get_bool(&v, "bold") == Some(true) { b = b.bold(); }
158 if get_bool(&v, "italic") == Some(true) { b = b.italic(); }
159 if let Some(color) = get_str(&v, "color") { b = b.color(color); }
160 if let Some(fill) = get_str(&v, "fill") { b = b.fill(fill); }
161 if let Some(align) = get_str(&v, "align") {
162 let a = match align {
163 "center" | "ctr" => deckmint::AlignH::Center,
164 "right" | "r" => deckmint::AlignH::Right,
165 "justify" | "just" => deckmint::AlignH::Justify,
166 _ => deckmint::AlignH::Left,
167 };
168 b = b.align(a);
169 }
170 Ok(b.build())
171}
172
173fn parse_shape_type(s: &str) -> Result<ShapeType, JsValue> {
174 let st = match s.to_lowercase().as_str() {
175 "rect" | "rectangle" => ShapeType::Rect,
176 "ellipse" | "oval" => ShapeType::Ellipse,
177 "triangle" => ShapeType::Triangle,
178 "roundrect" | "round_rect" => ShapeType::RoundRect,
179 "diamond" => ShapeType::Diamond,
180 "pentagon" => ShapeType::Pentagon,
181 "hexagon" => ShapeType::Hexagon,
182 "heptagon" => ShapeType::Heptagon,
183 "octagon" => ShapeType::Octagon,
184 "star4" => ShapeType::Star4,
185 "star5" => ShapeType::Star5,
186 "star6" => ShapeType::Star6,
187 "star7" => ShapeType::Star7,
188 "star8" => ShapeType::Star8,
189 "star10" => ShapeType::Star10,
190 "star12" => ShapeType::Star12,
191 "star16" => ShapeType::Star16,
192 "star24" => ShapeType::Star24,
193 "star32" => ShapeType::Star32,
194 "arrow_right" | "rightarrow" => ShapeType::RightArrow,
195 "arrow_left" | "leftarrow" => ShapeType::LeftArrow,
196 "arrow_up" | "uparrow" => ShapeType::UpArrow,
197 "arrow_down" | "downarrow" => ShapeType::DownArrow,
198 "line" => ShapeType::Line,
199 _ => return Err(JsValue::from_str(&format!("Unknown shape type: {s}"))),
200 };
201 Ok(st)
202}
203
204fn parse_shape_opts(json: &str) -> Result<deckmint::objects::shape::ShapeOptions, JsValue> {
205 let v: serde_json::Value = serde_json::from_str(json)
206 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
207
208 let mut b = ShapeOptionsBuilder::new();
209 if let Some(x) = get_f64(&v, "x") { b = b.x(x); }
210 if let Some(y) = get_f64(&v, "y") { b = b.y(y); }
211 if let Some(w) = get_f64(&v, "w") { b = b.w(w); }
212 if let Some(h) = get_f64(&v, "h") { b = b.h(h); }
213 if let Some(fill) = get_str(&v, "fill") { b = b.fill_color(fill); }
214 if get_bool(&v, "no_fill") == Some(true) { b = b.no_fill(); }
215 if let Some(lc) = get_str(&v, "line_color") { b = b.line_color(lc); }
216 if let Some(lw) = get_f64(&v, "line_width") { b = b.line_width(lw); }
217 if let Some(r) = get_f64(&v, "rotate") { b = b.rotate(r); }
218 Ok(b.build())
219}
220
221fn parse_image_opts(json: &str) -> Result<deckmint::objects::image::ImageOptions, JsValue> {
222 let v: serde_json::Value = serde_json::from_str(json)
223 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
224
225 let mut b = ImageOptionsBuilder::new();
226 if let Some(x) = get_f64(&v, "x") { b = b.x(x); }
227 if let Some(y) = get_f64(&v, "y") { b = b.y(y); }
228 if let Some(w) = get_f64(&v, "w") { b = b.w(w); }
229 if let Some(h) = get_f64(&v, "h") { b = b.h(h); }
230 if let Some(alt) = get_str(&v, "alt_text") { b = b.alt_text(alt); }
231 if let Some(t) = get_f64(&v, "transparency") { b = b.transparency(t); }
232 if get_bool(&v, "rounding") == Some(true) { b = b.rounding(); }
233 let (opts, _, _, _) = b.build();
234 Ok(opts)
235}
236
237fn parse_table_rows(json: &str) -> Result<Vec<TableRow>, JsValue> {
238 let arr: serde_json::Value = serde_json::from_str(json)
239 .map_err(|e| JsValue::from_str(&format!("Invalid table rows JSON: {e}")))?;
240
241 let rows_arr = arr.as_array()
242 .ok_or_else(|| JsValue::from_str("rows_json must be an array"))?;
243
244 let mut rows: Vec<TableRow> = Vec::new();
245 for row_val in rows_arr {
246 let cells_arr = row_val.as_array()
247 .ok_or_else(|| JsValue::from_str("Each row must be an array of cells"))?;
248 let mut row: TableRow = Vec::new();
249 for cell_val in cells_arr {
250 if cell_val.get("merge").and_then(|v| v.as_bool()) == Some(true) {
251 row.push(TableCell::merged());
252 continue;
253 }
254 let text = cell_val.get("text")
255 .and_then(|v| v.as_str())
256 .unwrap_or("")
257 .to_string();
258 let mut cell = TableCell::new(text);
259 if let Some(colspan) = cell_val.get("colspan").and_then(|v| v.as_u64()) {
260 cell.options.colspan = Some(colspan as u32);
261 }
262 if let Some(rowspan) = cell_val.get("rowspan").and_then(|v| v.as_u64()) {
263 cell.options.rowspan = Some(rowspan as u32);
264 }
265 if let Some(fill) = cell_val.get("fill").and_then(|v| v.as_str()) {
266 cell.options.fill = Some(fill.to_string());
267 }
268 if let Some(color) = cell_val.get("color").and_then(|v| v.as_str()) {
269 cell.options.color = Some(color.to_string());
270 }
271 if let Some(bold) = cell_val.get("bold").and_then(|v| v.as_bool()) {
272 cell.options.bold = Some(bold);
273 }
274 if let Some(italic) = cell_val.get("italic").and_then(|v| v.as_bool()) {
275 cell.options.italic = Some(italic);
276 }
277 row.push(cell);
278 }
279 rows.push(row);
280 }
281 Ok(rows)
282}
283
284fn parse_table_opts(json: &str) -> Result<TableOptions, JsValue> {
285 let v: serde_json::Value = serde_json::from_str(json)
286 .unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
287
288 let mut b = deckmint::objects::table::TableOptionsBuilder::new();
289 if let Some(x) = get_f64(&v, "x") { b = b.x(x); }
290 if let Some(y) = get_f64(&v, "y") { b = b.y(y); }
291 if let Some(w) = get_f64(&v, "w") { b = b.w(w); }
292 if let Some(h) = get_f64(&v, "h") { b = b.h(h); }
293 if let Some(col_w) = v.get("col_w").and_then(|v| v.as_array()) {
294 let col_widths: Vec<f64> = col_w.iter()
295 .filter_map(|v| v.as_f64())
296 .collect();
297 b = b.col_w(col_widths);
298 }
299 Ok(b.build())
300}