ifc_lite_wasm/zero_copy/
symbolic_from_data.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The canonical → wasm conversion for symbolic primitives.
6//!
7//! Split out of `symbolic.rs` (as `symbolic_truncation.rs` was, #2938) because
8//! it is the one place in that file with a failure story of its own: everything
9//! else there is a field and its getter, while this is a hand-written
10//! transcription between two struct families that are field-for-field parallel
11//! but share no type. Nothing in the compiler can see a pair transposed or a
12//! field left behind here — only `symbolic_tests.rs` can — so the conversion and
13//! its tests belong side by side rather than buried among the accessors.
14
15use super::symbolic::{
16    SymbolicCircle, SymbolicFillArea, SymbolicPolyline, SymbolicRepresentationCollection,
17    SymbolicText,
18};
19
20impl SymbolicRepresentationCollection {
21    /// Convert from the canonical `ifc_lite_processing::SymbolicData`
22    /// (issue #843 follow-up — full parity refactor). The WASM-side
23    /// extractor now delegates to the processing crate and converts the
24    /// result here so the browser and the HTTP server produce bit-
25    /// identical symbol streams from one canonical implementation.
26    ///
27    /// "Bit-identical" is the contract, so every field of every primitive has
28    /// to cross — a field this function forgets is a field the browser silently
29    /// never sees, while the server's JSON for the same file still carries it.
30    pub fn from_data(data: ifc_lite_processing::SymbolicData) -> Self {
31        Self::from_data_and_items(data, &[])
32    }
33
34    /// Convert canonical symbols with the extraction-bound fill sidecar.
35    pub fn from_data_with_provenance(data: ifc_lite_processing::SymbolicDataWithProvenance) -> Self {
36        let (data, items) = data.into_parts();
37        Self::from_data_and_items(data, &items)
38    }
39
40    fn from_data_and_items(data: ifc_lite_processing::SymbolicData, items: &[Option<u32>]) -> Self {
41        let mut collection = Self::with_capacity(data.polylines.len(), data.circles.len());
42        collection.truncated = data.truncated.clone();
43        for p in data.polylines {
44            collection.add_polyline(SymbolicPolyline::new(
45                p.express_id,
46                p.ifc_type,
47                p.points,
48                p.closed,
49                p.world_y,
50                p.representation,
51            ));
52        }
53        for c in data.circles {
54            collection.add_circle(SymbolicCircle::new(
55                c.express_id,
56                c.ifc_type,
57                c.center_x,
58                c.center_y,
59                c.radius,
60                c.world_y,
61                c.start_angle,
62                c.end_angle,
63                c.representation,
64            ));
65        }
66        for t in data.texts {
67            collection.add_text(SymbolicText::new_styled(
68                t.express_id,
69                t.ifc_type,
70                t.x,
71                t.y,
72                t.dir_x,
73                t.dir_y,
74                t.height,
75                t.content,
76                t.alignment,
77                t.world_y,
78                t.color,
79                t.target_px,
80                t.representation,
81            ));
82        }
83        for (ordinal, f) in data.fills.into_iter().enumerate() {
84            let fill = SymbolicFillArea::new(
85                f.express_id,
86                f.ifc_type,
87                f.points,
88                f.holes_offsets,
89                f.fill_color,
90                f.world_y,
91                f.representation,
92            ).with_geometry_item_id(items.get(ordinal).copied().flatten());
93            // `SymbolicFillArea::new` defaults to unhatched, so the hatching
94            // style has to be applied explicitly. The viewer reads these five
95            // fields straight off this object
96            // (`apps/viewer/src/lib/overlay-parse/symbolic-flat.ts`), so
97            // dropping them here draws every hatched region as a flat solid in
98            // the browser while the JSON path carries the style intact.
99            //
100            // `hatch_angle_secondary` is NaN when there is no cross-hatch, and
101            // NaN is exactly what the builder's `None` restores — so route the
102            // absent case back through `Option` rather than handing the
103            // sentinel on as though it were a real angle.
104            collection.add_fill(if f.has_hatching {
105                fill.with_hatching(
106                    f.hatch_spacing,
107                    f.hatch_angle,
108                    Some(f.hatch_angle_secondary).filter(|a| !a.is_nan()),
109                    f.hatch_line_width,
110                )
111            } else {
112                fill
113            });
114        }
115        collection
116    }
117}