forked from 1b-agent/opencadstudio-upstream
web: raster images, WBLOCK/SAVEALL/SCRIPT/CTB/LAYTRANS/INSERT, filled TTF text
A fresh web-vs-desktop audit found the remaining gaps; this closes the top ten. Raster images: IMAGE attach read the picked file via image::open on a name-only path (dead on wasm), and raster references inside opened drawings decoded the same way. Picked bytes are now registered in a name-keyed store (the PDF-underlay pattern), decoding runs from memory, and dropped .png/.jpg/.bmp/.tif files register by name so unresolved references in opened drawings re-resolve and render (and export to PDF). Command gaps fixed with the bytes-at-pick / download_bytes pattern: - WBLOCK now downloads the extracted block instead of failing on fs. - SAVEALL saves every dirty tab instead of printing a no-op message. - SCRIPT picks and reads the .scr file through the browser picker. - CENTERLTYPEFILE picks the .lin/.dnl file and populates from text. - Custom .ctb tables picked on web now parse, register for the session, appear in the plot dialog list, and apply (load_named previously returned builtin-only on wasm). - LAYTRANS 'Load standards file' parses the picked drawing on the existing web worker. - INSERT 'from file' in the block palette parses picked bytes and feeds the shared import path. Text fidelity: TTF-named styles rendered as thin stroke glyphs on web because the font database was empty. Fetched Noto faces are now registered into it with a family alias table (Arial, Times New Roman, Cambria, ... -> Noto Sans/Serif), so those styles render filled, shape, export to PDF as vector fills, and appear in the style picker. ViewCube labels request semibold again on web. docs/native-vs-web.md: the hatch and font rows were stale (both work on web now), the GPU row claimed WebGPU is used where available (the backend is pinned to OpenGL), and the parallelism row missed the parse worker — all corrected.
This commit is contained in:
1 parent
fa7ef87f00
commit
c643a10ed9
43 files changed
+3216
-1336
No files matched your search
+32
-14
@@ -326,6 +326,12 @@ pub fn fallback_glyph(ch: char) -> Option<Arc<Glyph>> {
|
||||
pub fn clear_fallback_cache() {
|
||||
fallback_cache().lock().unwrap().clear();
|
||||
shape_cache().lock().unwrap().clear();
|
||||
// Web only: the per-family outline cache can also hold stale `None`s —
|
||||
// the family only became resolvable when the font joined the sysfont
|
||||
// database (`register_web_font`). On desktop the font set never changes,
|
||||
// so the cache is kept.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
cache().lock().unwrap().clear();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
crate::scene::text::sdf_atlas::reset_font_entries();
|
||||
}
|
||||
@@ -433,10 +439,8 @@ fn build_shaped(_family: &str, text: &str) -> Option<ShapedRun> {
|
||||
database.load_font_data((*bytes).clone());
|
||||
}
|
||||
database.set_sans_serif_family(crate::scene::text::web_font::primary_script().family());
|
||||
let mut font_system = FontSystem::new_with_locale_and_db(
|
||||
crate::i18n::active_language_tag(),
|
||||
database,
|
||||
);
|
||||
let mut font_system =
|
||||
FontSystem::new_with_locale_and_db(crate::i18n::active_language_tag(), database);
|
||||
let attrs = Attrs::new().family(Family::SansSerif);
|
||||
let mut buffer = Buffer::new(&mut font_system, Metrics::new(SHAPE_FS, SHAPE_FS));
|
||||
buffer.set_size(&mut font_system, None, None);
|
||||
@@ -464,10 +468,7 @@ fn build_shaped(_family: &str, text: &str) -> Option<ShapedRun> {
|
||||
let pen_y = -(SHAPE_FS * glyph.y_offset) * px_to_9;
|
||||
let mut flattener = OutlineFlattener::new(scale);
|
||||
flattener.offset = [pen_x, pen_y];
|
||||
face.outline_glyph(
|
||||
ttf_parser::GlyphId(glyph.glyph_id),
|
||||
&mut flattener,
|
||||
);
|
||||
face.outline_glyph(ttf_parser::GlyphId(glyph.glyph_id), &mut flattener);
|
||||
flattener.flush();
|
||||
if flattener.contours.is_empty() {
|
||||
continue;
|
||||
@@ -589,7 +590,6 @@ fn build_shaped(family: &str, text: &str) -> Option<ShapedRun> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
#[test]
|
||||
fn shape_run_falls_back_for_missing_script() {
|
||||
let fams = sysfont::families();
|
||||
@@ -612,18 +612,36 @@ mod tests {
|
||||
#[test]
|
||||
fn shape_run_positions_glyphs() {
|
||||
let fams = sysfont::families();
|
||||
if fams.is_empty() { eprintln!("no fonts; skip"); return; }
|
||||
let fam = fams.iter().find(|f| glyph(f, 'A').is_some()).expect("family");
|
||||
if fams.is_empty() {
|
||||
eprintln!("no fonts; skip");
|
||||
return;
|
||||
}
|
||||
let fam = fams
|
||||
.iter()
|
||||
.find(|f| glyph(f, 'A').is_some())
|
||||
.expect("family");
|
||||
let run = shape_run(fam, "AVA").expect("shaped");
|
||||
eprintln!("glyphs={} advance={:.3}", run.glyphs.len(), run.advance);
|
||||
assert_eq!(run.glyphs.len(), 3);
|
||||
assert!(run.advance > 0.0);
|
||||
// glyphs must be laid out left-to-right: each glyph's strokes sit at a
|
||||
// greater x than the previous glyph's start.
|
||||
let xs: Vec<f32> = run.glyphs.iter()
|
||||
.filter_map(|g| g.strokes.iter().flatten().map(|p| p[0]).fold(None, |m,x| Some(m.map_or(x, |mm:f32| mm.min(x)))))
|
||||
let xs: Vec<f32> = run
|
||||
.glyphs
|
||||
.iter()
|
||||
.filter_map(|g| {
|
||||
g.strokes
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|p| p[0])
|
||||
.fold(None, |m, x| Some(m.map_or(x, |mm: f32| mm.min(x))))
|
||||
})
|
||||
.collect();
|
||||
assert!(xs.windows(2).all(|w| w[1] >= w[0] - 1.0), "glyphs not L->R: {:?}", xs);
|
||||
assert!(
|
||||
xs.windows(2).all(|w| w[1] >= w[0] - 1.0),
|
||||
"glyphs not L->R: {:?}",
|
||||
xs
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user