openpencil/desktop/src/fig_container.rs
Danila Poyarkov 5190a64017
fix(tauri): transfer FIG exports over binary IPC (#509)
- Return native FIG archives as raw IPC responses instead of JSON byte arrays

- Decode the response as an ArrayBuffer and cover the binary contract

- Prevent large desktop exports from multiplying memory use during save
2026-08-13 22:23:37 +03:00

68 lines
2.3 KiB
Rust

use serde::Deserialize;
use std::io::{Cursor, Write};
#[derive(Deserialize)]
pub struct ImageEntry {
name: String,
data: Vec<u8>,
}
#[tauri::command]
pub fn build_fig_file(
schema_deflated: Vec<u8>,
kiwi_data: Vec<u8>,
thumbnail_png: Vec<u8>,
meta_json: String,
images: Option<Vec<ImageEntry>>,
fig_kiwi_version: Option<u32>,
) -> Result<tauri::ipc::Response, String> {
let mut encoder = zstd::Encoder::new(Vec::new(), 3).map_err(|e| e.to_string())?;
encoder
.include_contentsize(true)
.map_err(|e| e.to_string())?;
encoder
.set_pledged_src_size(Some(kiwi_data.len() as u64))
.map_err(|e| e.to_string())?;
encoder.write_all(&kiwi_data).map_err(|e| e.to_string())?;
let zstd_data = encoder.finish().map_err(|e| e.to_string())?;
let version: u32 = fig_kiwi_version.unwrap_or(101);
let fig_kiwi_len = 8 + 4 + 4 + schema_deflated.len() + 4 + zstd_data.len();
let mut fig_kiwi = Vec::with_capacity(fig_kiwi_len);
fig_kiwi.extend_from_slice(b"fig-kiwi");
fig_kiwi.extend_from_slice(&version.to_le_bytes());
fig_kiwi.extend_from_slice(&(schema_deflated.len() as u32).to_le_bytes());
fig_kiwi.extend_from_slice(&schema_deflated);
fig_kiwi.extend_from_slice(&(zstd_data.len() as u32).to_le_bytes());
fig_kiwi.extend_from_slice(&zstd_data);
let buf = Cursor::new(Vec::new());
let mut zip = zip::ZipWriter::new(buf);
let options =
zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
zip.start_file("canvas.fig", options)
.map_err(|e| e.to_string())?;
zip.write_all(&fig_kiwi).map_err(|e| e.to_string())?;
zip.start_file("thumbnail.png", options)
.map_err(|e| e.to_string())?;
zip.write_all(&thumbnail_png).map_err(|e| e.to_string())?;
zip.start_file("meta.json", options)
.map_err(|e| e.to_string())?;
zip.write_all(meta_json.as_bytes())
.map_err(|e| e.to_string())?;
if let Some(image_entries) = images {
for entry in image_entries {
zip.start_file(&entry.name, options)
.map_err(|e| e.to_string())?;
zip.write_all(&entry.data).map_err(|e| e.to_string())?;
}
}
let result = zip.finish().map_err(|e| e.to_string())?;
Ok(tauri::ipc::Response::new(result.into_inner()))
}