Skip to content

Commit dca1a00

Browse files
Merge pull request #432 from gridaco/canary
Grida Canvas - Layout (prep) - Scene Graph
2 parents b1d0793 + 9705206 commit dca1a00

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1913
-1255
lines changed

Cargo.lock

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/grida-canvas-fonts/examples/ttf_parser_chained_sequence_features.rs

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ fn main() {
102102
#[derive(Debug, Clone)]
103103
struct ChainedFeature {
104104
pub tag: String,
105-
pub name: String,
106105
pub glyphs: Vec<String>,
107-
pub source_table: String,
108106
}
109107

110108
fn build_glyph_map(face: &ttf_parser::Face) -> std::collections::HashMap<u16, char> {
@@ -150,7 +148,7 @@ fn extract_features_via_chained_sequences(
150148
}
151149

152150
fn extract_gsub_features_simplified(
153-
face: &ttf_parser::Face,
151+
_face: &ttf_parser::Face,
154152
gsub_table: ttf_parser::opentype_layout::LayoutTable,
155153
glyph_map: &std::collections::HashMap<u16, char>,
156154
) -> Vec<ChainedFeature> {
@@ -160,7 +158,6 @@ fn extract_gsub_features_simplified(
160158
for i in 0..gsub_table.features.len() {
161159
if let Some(feature) = gsub_table.features.get(i as u16) {
162160
let tag = feature.tag.to_string();
163-
let name = get_feature_name_from_font(face, &tag);
164161

165162
// Extract glyphs from all lookups in this feature
166163
let mut all_glyphs = std::collections::HashSet::new();
@@ -224,9 +221,7 @@ fn extract_gsub_features_simplified(
224221

225222
features.push(ChainedFeature {
226223
tag,
227-
name,
228224
glyphs: glyph_chars,
229-
source_table: "GSUB".to_string(),
230225
});
231226
}
232227
}
@@ -235,7 +230,7 @@ fn extract_gsub_features_simplified(
235230
}
236231

237232
fn extract_gpos_features_simplified(
238-
face: &ttf_parser::Face,
233+
_face: &ttf_parser::Face,
239234
gpos_table: ttf_parser::opentype_layout::LayoutTable,
240235
glyph_map: &std::collections::HashMap<u16, char>,
241236
) -> Vec<ChainedFeature> {
@@ -245,7 +240,6 @@ fn extract_gpos_features_simplified(
245240
for i in 0..gpos_table.features.len() {
246241
if let Some(feature) = gpos_table.features.get(i as u16) {
247242
let tag = feature.tag.to_string();
248-
let name = get_feature_name_from_font(face, &tag);
249243

250244
// Extract glyphs from all lookups in this feature
251245
let mut all_glyphs = std::collections::HashSet::new();
@@ -301,9 +295,7 @@ fn extract_gpos_features_simplified(
301295

302296
features.push(ChainedFeature {
303297
tag,
304-
name,
305298
glyphs: glyph_chars,
306-
source_table: "GPOS".to_string(),
307299
});
308300
}
309301
}
@@ -337,31 +329,3 @@ fn extract_coverage_glyphs(coverage: &ttf_parser::opentype_layout::Coverage) ->
337329

338330
glyphs
339331
}
340-
341-
fn get_feature_name_from_font(_face: &ttf_parser::Face, tag: &str) -> String {
342-
// Simplified feature name mapping
343-
match tag {
344-
"kern" => "Kerning".to_string(),
345-
"liga" => "Ligatures".to_string(),
346-
"ss01" => "Stylistic Set 1".to_string(),
347-
"cv01" => "Character Variant 1".to_string(),
348-
"locl" => "Localized Forms".to_string(),
349-
"zero" => "Slashed Zero".to_string(),
350-
"sinf" => "Scientific Inferiors".to_string(),
351-
"aalt" => "Access All Alternates".to_string(),
352-
"numr" => "Numerators".to_string(),
353-
"ordn" => "Ordinals".to_string(),
354-
"case" => "Case-Sensitive Forms".to_string(),
355-
"pnum" => "Proportional Numbers".to_string(),
356-
"ccmp" => "Glyph Composition/Decomposition".to_string(),
357-
"dlig" => "Discretionary Ligatures".to_string(),
358-
"sups" => "Superscript".to_string(),
359-
"tnum" => "Tabular Numbers".to_string(),
360-
"subs" => "Subscript".to_string(),
361-
"salt" => "Stylistic Alternates".to_string(),
362-
"dnom" => "Denominators".to_string(),
363-
"frac" => "Fractions".to_string(),
364-
"calt" => "Contextual Alternates".to_string(),
365-
_ => format!("Feature {}", tag),
366-
}
367-
}

crates/grida-canvas/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ futures = "0.3.31"
1919
gl = "0.14.0"
2020
figma-api = { version = "0.31.3", optional = true }
2121
seahash = "4.1.0"
22+
taffy = "0.9.1"
2223

2324

2425
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@@ -50,4 +51,4 @@ harness = false
5051

5152
[[example]]
5253
name = "app_figma"
53-
required-features = ["figma"]
54+
required-features = ["figma"]

crates/grida-canvas/benches/bench_rectangles.rs

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,70 @@
11
use cg::cg::types::*;
2-
use cg::node::repository::NodeRepository;
2+
use cg::node::scene_graph::{Parent, SceneGraph};
33
use cg::node::schema::*;
44
use cg::runtime::camera::Camera2D;
55
use cg::runtime::scene::{Backend, Renderer};
66
use criterion::{black_box, criterion_group, criterion_main, Criterion};
77
use math2::transform::AffineTransform;
88

99
fn create_rectangles(count: usize, with_effects: bool) -> Scene {
10-
let mut repository = NodeRepository::new();
11-
let mut ids = Vec::new();
10+
let mut graph = SceneGraph::new();
1211

1312
// Create rectangles
14-
for i in 0..count {
15-
let id = format!("rect-{}", i);
16-
ids.push(id.clone());
17-
18-
let rect = RectangleNodeRec {
19-
id: id.clone(),
20-
name: None,
21-
active: true,
22-
opacity: 1.0,
23-
blend_mode: LayerBlendMode::default(),
24-
mask: None,
25-
transform: AffineTransform::identity(),
26-
size: Size {
27-
width: 100.0,
28-
height: 100.0,
29-
},
30-
corner_radius: RectangularCornerRadius::zero(),
31-
fills: Paints::new([Paint::from(CGColor(255, 0, 0, 255))]),
32-
strokes: Paints::default(),
33-
stroke_width: 1.0,
34-
stroke_align: StrokeAlign::Inside,
35-
stroke_dash_array: None,
36-
effects: if with_effects {
37-
LayerEffects::from_array(vec![FilterEffect::DropShadow(FeShadow {
38-
dx: 2.0,
39-
dy: 2.0,
40-
blur: 4.0,
41-
spread: 0.0,
42-
color: CGColor(0, 0, 0, 128),
43-
})])
44-
} else {
45-
LayerEffects::default()
46-
},
47-
};
48-
49-
repository.insert(Node::Rectangle(rect));
50-
}
13+
let rectangles: Vec<Node> = (0..count)
14+
.map(|i| {
15+
let id = format!("rect-{}", i);
16+
17+
Node::Rectangle(RectangleNodeRec {
18+
id: id.clone(),
19+
name: None,
20+
active: true,
21+
opacity: 1.0,
22+
blend_mode: LayerBlendMode::default(),
23+
mask: None,
24+
transform: AffineTransform::identity(),
25+
size: Size {
26+
width: 100.0,
27+
height: 100.0,
28+
},
29+
corner_radius: RectangularCornerRadius::zero(),
30+
fills: Paints::new([Paint::from(CGColor(255, 0, 0, 255))]),
31+
strokes: Paints::default(),
32+
stroke_width: 1.0,
33+
stroke_align: StrokeAlign::Inside,
34+
stroke_dash_array: None,
35+
effects: if with_effects {
36+
LayerEffects::from_array(vec![FilterEffect::DropShadow(FeShadow {
37+
dx: 2.0,
38+
dy: 2.0,
39+
blur: 4.0,
40+
spread: 0.0,
41+
color: CGColor(0, 0, 0, 128),
42+
})])
43+
} else {
44+
LayerEffects::default()
45+
},
46+
})
47+
})
48+
.collect();
5149

5250
// Create root group
5351
let root_group = GroupNodeRec {
5452
id: "root".to_string(),
5553
name: Some("Root Group".to_string()),
5654
active: true,
5755
transform: None,
58-
children: ids.clone(),
5956
opacity: 1.0,
6057
blend_mode: LayerBlendMode::default(),
6158
mask: None,
6259
};
6360

64-
repository.insert(Node::Group(root_group));
61+
let root_id = graph.append_child(Node::Group(root_group), Parent::Root);
62+
graph.append_children(rectangles, Parent::NodeId(root_id));
6563

6664
Scene {
67-
id: "scene".to_string(),
68-
name: "Test Scene".to_string(),
69-
children: vec!["root".to_string()],
70-
nodes: repository,
65+
name: "Test Scene".into(),
7166
background_color: None,
67+
graph,
7268
}
7369
}
7470

crates/grida-canvas/examples/app_figma.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,8 @@ async fn main() {
241241
.expect("Failed to load scene");
242242

243243
println!("Rendering scene: {}", scene.name);
244-
println!("Scene ID: {}", scene.id);
245-
println!("Number of children: {}", scene.children.len());
246-
println!("Total nodes in repository: {}", scene.nodes.len());
244+
println!("Number of roots: {}", scene.graph.roots().len());
245+
println!("Total nodes in graph: {}", scene.graph.node_count());
247246

248247
// Load webfonts metadata and find matching font files
249248
let webfonts_metadata = load_webfonts_metadata()

crates/grida-canvas/examples/app_grida.rs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use cg::cg::types::*;
22
use cg::io::io_grida::parse;
3+
use cg::node::scene_graph::SceneGraph;
34
use cg::node::schema::*;
45
use cg::window;
56
use clap::Parser;
@@ -40,37 +41,37 @@ async fn load_scene_from_file(file_path: &str) -> Scene {
4041
.and_then(|c| c.clone())
4142
.unwrap_or_default();
4243

43-
// Convert nodes to repository, filtering out scene nodes and populating children from links
44-
let mut node_repo = cg::node::repository::NodeRepository::new();
45-
for (node_id, json_node) in canvas_file.document.nodes {
46-
// Skip scene nodes - they're handled separately
47-
if matches!(json_node, cg::io::io_grida::JSONNode::Scene(_)) {
48-
continue;
49-
}
44+
// Build scene graph from nodes and links using snapshot
45+
let scene_node_ids: std::collections::HashSet<String> = canvas_file
46+
.document
47+
.nodes
48+
.iter()
49+
.filter(|(_, json_node)| matches!(json_node, cg::io::io_grida::JSONNode::Scene(_)))
50+
.map(|(id, _)| id.clone())
51+
.collect();
5052

51-
let mut node: cg::node::schema::Node = json_node.into();
53+
// Convert all nodes (skip scene nodes)
54+
let nodes: Vec<cg::node::schema::Node> = canvas_file
55+
.document
56+
.nodes
57+
.into_iter()
58+
.filter(|(_, json_node)| !matches!(json_node, cg::io::io_grida::JSONNode::Scene(_)))
59+
.map(|(_, json_node)| json_node.into())
60+
.collect();
5261

53-
// Populate children from links
54-
if let Some(children_opt) = links.get(&node_id) {
55-
if let Some(children) = children_opt {
56-
match &mut node {
57-
cg::node::schema::Node::Container(n) => n.children = children.clone(),
58-
cg::node::schema::Node::Group(n) => n.children = children.clone(),
59-
cg::node::schema::Node::BooleanOperation(n) => n.children = children.clone(),
60-
_ => {} // Other nodes don't have children
61-
}
62-
}
63-
}
62+
// Filter links (skip scene nodes as parents)
63+
let filtered_links: std::collections::HashMap<String, Vec<String>> = links
64+
.into_iter()
65+
.filter(|(parent_id, _)| !scene_node_ids.contains(parent_id))
66+
.filter_map(|(parent_id, children_opt)| children_opt.map(|children| (parent_id, children)))
67+
.collect();
6468

65-
node_repo.insert(node);
66-
}
69+
let graph = SceneGraph::new_from_snapshot(nodes, filtered_links, scene_children);
6770

6871
Scene {
69-
nodes: node_repo,
70-
id: scene_id,
7172
name: scene_name,
72-
children: scene_children,
7373
background_color: Some(CGColor(230, 230, 230, 255)),
74+
graph,
7475
}
7576
}
7677

0 commit comments

Comments
 (0)