forked from 1b-agent/opencadstudio-upstream
feat(wasm): open files in the browser
Wire up file open for the web build: - `io::pick_and_load_web`: browser file picker → read bytes → parse via `load_bytes` → build caches, returning the same payload as the native loader so it feeds the shared `Message::FileOpened` handler. - `Message::OpenFile` cfg-dispatches: native picks a path then loads on a worker thread; web picks + parses in one async step. The web branch also sets `opening` (the handler drops results when no open is in progress). - Disable the cosmic-text shaping / fallback paths on wasm: the web has no system fonts, so cosmic-text panics with "no default font found". LFF stroke fonts (embedded) still render; LFF-missing glyphs are skipped. Native build + 75 tests unchanged. Hatches still don't draw on the web. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
c49ad887b8
commit
6996343e84
3 files changed
+69
-1
No files matched your search
+23
-1
@@ -148,7 +148,29 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
Message::OpenFile => {
|
||||
Task::perform(crate::io::pick_open_path(), Message::OpenPathPicked)
|
||||
// Native: pick a path, then load on a worker thread. Web: the
|
||||
// browser hands back bytes, so pick + parse in one step and feed
|
||||
// the shared `FileOpened` handler directly.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Task::perform(crate::io::pick_open_path(), Message::OpenPathPicked)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
// `FileOpened` only installs the result when an open is in
|
||||
// progress, so mark one. The browser picker + parse happen
|
||||
// inside `pick_and_load_web`; the real name is unknown until
|
||||
// then, so show a generic label meanwhile.
|
||||
self.opening = Some(super::OpenProgress {
|
||||
name: "Opening…".into(),
|
||||
size_bytes: 0,
|
||||
phase: std::sync::Arc::new(std::sync::atomic::AtomicU8::new(
|
||||
super::OPEN_PHASE_READING,
|
||||
)),
|
||||
started: Instant::now(),
|
||||
});
|
||||
Task::perform(crate::io::pick_and_load_web(), Message::FileOpened)
|
||||
}
|
||||
}
|
||||
|
||||
Message::OpenPathPicked(None) => Task::none(),
|
||||
|
||||
@@ -86,6 +86,31 @@ pub async fn open_path_with_phase(
|
||||
Ok((name, path, doc, caches))
|
||||
}
|
||||
|
||||
/// Web file open: show the browser picker, read the chosen file's bytes, parse
|
||||
/// it, and build the derived caches — producing the same payload as the native
|
||||
/// `open_path_with_phase` so it can feed the existing `Message::FileOpened`
|
||||
/// handler. There is no filesystem path on the web, so a name-only `PathBuf`
|
||||
/// stands in for the document path.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn pick_and_load_web(
|
||||
) -> Result<(String, PathBuf, CadDocument, DerivedCaches), String> {
|
||||
let handle = rfd::AsyncFileDialog::new()
|
||||
.set_title("Open CAD file")
|
||||
.add_filter("CAD Files", &["dwg", "dxf", "DWG", "DXF"])
|
||||
.add_filter("All Files", &["*"])
|
||||
.pick_file()
|
||||
.await
|
||||
.ok_or_else(|| "Cancelled".to_string())?;
|
||||
let name = handle.file_name();
|
||||
let bytes = handle.read().await;
|
||||
let mut doc = load_bytes(&name, bytes)?;
|
||||
let dropped = purge_corrupt_entities(&mut doc);
|
||||
let mut caches = crate::scene::build_derived_caches(&doc);
|
||||
caches.corrupt_dropped = dropped;
|
||||
let path = PathBuf::from(&name);
|
||||
Ok((name, path, doc, caches))
|
||||
}
|
||||
|
||||
/// Parse a CAD document from in-memory bytes, choosing the format from
|
||||
/// `name`'s extension. Used by the web build, where files arrive as bytes from
|
||||
/// a browser file picker (no filesystem path). Shares the post-load fixups with
|
||||
|
||||
@@ -209,6 +209,11 @@ const SHAPE_FS: f32 = 1000.0;
|
||||
|
||||
/// Shared cosmic-text layout engine, loaded once with the system fonts. Held
|
||||
/// behind a mutex because `set_text`/`shape` need `&mut FontSystem`.
|
||||
///
|
||||
/// Native only: on the web there are no system fonts, so cosmic-text would
|
||||
/// panic with "no default font found" — the shaping / fallback paths are
|
||||
/// disabled there instead (LFF stroke fonts still render).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn font_system() -> &'static Mutex<cosmic_text::FontSystem> {
|
||||
static FS: OnceLock<Mutex<cosmic_text::FontSystem>> = OnceLock::new();
|
||||
FS.get_or_init(|| Mutex::new(cosmic_text::FontSystem::new()))
|
||||
@@ -258,6 +263,14 @@ pub fn fallback_glyph(ch: char) -> Option<Arc<Glyph>> {
|
||||
built
|
||||
}
|
||||
|
||||
/// Web: no system fonts → no cosmic-text fallback (LFF-missing glyphs simply
|
||||
/// do not render).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn build_fallback(_ch: char) -> Option<Arc<Glyph>> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn build_fallback(ch: char) -> Option<Arc<Glyph>> {
|
||||
use cosmic_text::{Attrs, Buffer, Metrics, Shaping};
|
||||
let mut fs = font_system().lock().unwrap();
|
||||
@@ -292,6 +305,14 @@ fn build_fallback(ch: char) -> Option<Arc<Glyph>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Web: no system fonts → no shaping (TTF faces don't occur on the web anyway,
|
||||
/// since system-font discovery is empty).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn build_shaped(_family: &str, _text: &str) -> Option<ShapedRun> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn build_shaped(family: &str, text: &str) -> Option<ShapedRun> {
|
||||
use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping};
|
||||
|
||||
|
||||
Reference in new issue
Block a user