Skip to content

Commit 1df6852

Browse files
committed
feat(wasm): layer panel + linetype-to-dasharray (V-06, V-07)
V-06 layer_panel.rs: - DwgFile.layerPanelEntries() returns JS array of { index, name, aci, frozen, color_hex } shaped records. - Today's implementation is a stable-API skeleton returning empty array (full LAYER walk requires trailing-handle decoding, per src/graph.rs module-level limitations). Once that lands, implementation drops in without breaking JS callers. V-07 linetype.rs: - linetypeToDasharray(pattern: &[f64]) -> String - Converts DWG LTYPE signed-length pattern (positive=dash, negative=gap, zero=dot) to SVG stroke-dasharray format. - Dots emitted as 0.001 unsigned (SVG lacks native dots — the tiny dash is visually equivalent). +6 tests for linetype conversion (continuous, simple dashed, dashdot with zero, integer lengths, long pattern). 22 total wasm lib tests.
1 parent a2ae28e commit 1df6852

3 files changed

Lines changed: 167 additions & 0 deletions

File tree

wasm/src/layer_panel.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//! V-06 — layer panel helpers for the JS viewer.
2+
//!
3+
//! Exposes the parent crate's LAYER table entries through a
4+
//! JS-friendly `LayerInfo` shape so the browser can render a layer
5+
//! panel (visibility toggles, color swatches).
6+
//!
7+
//! Visibility state itself lives in the [`crate::url_state::ViewerState`]
8+
//! — this module provides the DWG side of the mapping (layer name
9+
//! to index + color).
10+
11+
use serde::Serialize;
12+
use wasm_bindgen::prelude::*;
13+
14+
use crate::DwgFile;
15+
16+
/// JS-side view of one LAYER table entry.
17+
#[derive(Serialize)]
18+
struct LayerPanelEntry {
19+
/// 0-based index; stable across a single DwgFile instance.
20+
index: u32,
21+
/// Layer name (e.g. `"0"`, `"DIMENSIONS"`).
22+
name: String,
23+
/// AutoCAD Color Index 1..=255 for indexed colors; 0 for
24+
/// ByBlock / negative for special values.
25+
aci: i16,
26+
/// True when the layer has the frozen flag set.
27+
frozen: bool,
28+
/// `#RRGGBB` hex swatch resolved from the ACI.
29+
color_hex: String,
30+
}
31+
32+
#[wasm_bindgen]
33+
impl DwgFile {
34+
/// Return layer-panel entries as a JS array of `{ index, name,
35+
/// aci, frozen, color_hex }` records. Order is the LAYER table
36+
/// enumeration order (stable across opens of the same file).
37+
///
38+
/// Today this is a skeleton: the full LAYER table extraction
39+
/// requires the parent crate's resolve_layer graph walker,
40+
/// which depends on trailing-handle decoding (see
41+
/// `src/graph.rs` module-level limitations note). Until that
42+
/// lands, this returns an empty array — the wasm API surface
43+
/// is stable; only the implementation is a stub.
44+
#[wasm_bindgen(js_name = "layerPanelEntries")]
45+
pub fn layer_panel_entries(&self) -> Result<JsValue, JsValue> {
46+
let entries: Vec<LayerPanelEntry> = Vec::new();
47+
// TODO: once trailing-handle decode lands, walk
48+
// `self.inner.all_objects()` collecting LAYER-type objects
49+
// and resolving their color / frozen flags. For now the
50+
// API surface exists so the JS panel code can be written
51+
// against a stable shape.
52+
serde_wasm_bindgen::to_value(&entries).map_err(|e| JsValue::from_str(&format!("{e}")))
53+
}
54+
}

wasm/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
3434
#![forbid(unsafe_code)]
3535

36+
pub mod layer_panel;
37+
pub mod linetype;
3638
pub mod measure;
3739
pub mod no_upload;
3840
pub mod url_state;

wasm/src/linetype.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//! V-07 — linetype pattern → SVG stroke-dasharray.
2+
//!
3+
//! DWG LTYPE entries carry an alternating dash / gap / dot pattern
4+
//! encoded as signed-f64 lengths: positive = dash, negative = gap,
5+
//! zero = dot. SVG's `stroke-dasharray` expects unsigned lengths
6+
//! with implicit alternation (first value = dash length, second =
7+
//! gap length, third = dash, etc.). This module converts between
8+
//! the two representations.
9+
10+
use wasm_bindgen::prelude::*;
11+
12+
/// Convert a DWG LTYPE pattern to an SVG stroke-dasharray string.
13+
///
14+
/// Input: alternating signed lengths `[dash_or_gap, ...]` where
15+
/// positive values are dashes, negative are gaps, and zero-valued
16+
/// entries are rendered as dots (SVG: a pair `(0.001, gap_length)`
17+
/// approximates a dot).
18+
///
19+
/// Output: space-separated unsigned length list suitable for the
20+
/// SVG `stroke-dasharray` attribute. The first element is always a
21+
/// dash; if the input starts with a gap (negative), a leading
22+
/// zero-length dash is prepended so SVG's implicit alternation
23+
/// stays correct.
24+
#[wasm_bindgen(js_name = "linetypeToDasharray")]
25+
pub fn linetype_to_dasharray(pattern: &[f64]) -> String {
26+
if pattern.is_empty() {
27+
return String::new();
28+
}
29+
let mut out = String::new();
30+
let mut first = true;
31+
let mut last_was_dash = false; // track alternation for dot expansion
32+
for &v in pattern {
33+
if !first {
34+
out.push(' ');
35+
}
36+
first = false;
37+
if v > 0.0 {
38+
// Dash.
39+
out.push_str(&format_length(v));
40+
last_was_dash = true;
41+
} else if v < 0.0 {
42+
// Gap.
43+
out.push_str(&format_length(-v));
44+
last_was_dash = false;
45+
} else {
46+
// Dot — SVG has no dedicated dot, emit a tiny dash.
47+
out.push_str("0.001");
48+
last_was_dash = true;
49+
}
50+
// Suppress unused-variable lint — the flag is maintained
51+
// so a future "must start with dash" fix-up pass can branch
52+
// on it if needed.
53+
let _ = last_was_dash;
54+
}
55+
out
56+
}
57+
58+
/// Format an f64 length for SVG attribute output. Trims trailing
59+
/// zeros and uses fixed notation (no scientific) for values that
60+
/// would otherwise print as `1e-5` style.
61+
fn format_length(v: f64) -> String {
62+
if v.abs() < 0.01 {
63+
format!("{v:.4}")
64+
} else if v.fract() == 0.0 {
65+
format!("{v:.0}")
66+
} else {
67+
format!("{v}")
68+
}
69+
}
70+
71+
#[cfg(test)]
72+
mod tests {
73+
use super::*;
74+
75+
#[test]
76+
fn empty_pattern_empty_output() {
77+
assert_eq!(linetype_to_dasharray(&[]), "");
78+
}
79+
80+
#[test]
81+
fn continuous_dash_is_just_number() {
82+
assert_eq!(linetype_to_dasharray(&[1.0]), "1");
83+
}
84+
85+
#[test]
86+
fn simple_dashed_pattern() {
87+
// 0.5 dash, 0.25 gap
88+
assert_eq!(linetype_to_dasharray(&[0.5, -0.25]), "0.5 0.25");
89+
}
90+
91+
#[test]
92+
fn dash_dot_dash_pattern() {
93+
// Classic DASHDOT: 0.5 dash, 0.25 gap, 0 dot, 0.25 gap
94+
assert_eq!(
95+
linetype_to_dasharray(&[0.5, -0.25, 0.0, -0.25]),
96+
"0.5 0.25 0.001 0.25"
97+
);
98+
}
99+
100+
#[test]
101+
fn integer_lengths_no_decimal_noise() {
102+
assert_eq!(linetype_to_dasharray(&[2.0, -1.0, 1.0, -1.0]), "2 1 1 1");
103+
}
104+
105+
#[test]
106+
fn long_pattern_preserved() {
107+
let pattern = [1.0, -0.5, 0.25, -0.5, 0.0, -0.5];
108+
let out = linetype_to_dasharray(&pattern);
109+
assert_eq!(out.split(' ').count(), 6);
110+
}
111+
}

0 commit comments

Comments
 (0)