cqlib_core/visualization/circuit/style.rs
1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Gate style maps for figure visualization.
14//!
15//! This module loads built-in JSON style dictionaries (`default`, `gray`) and merges
16//! optional runtime overrides for figure rendering.
17
18use serde::Deserialize;
19use std::collections::HashMap;
20
21/// Per-gate visual style settings (compatible with Python style JSON schema).
22#[derive(Debug, Clone, Deserialize, Default)]
23pub struct GateStyle {
24 /// Optional gate border color in CSS color syntax.
25 #[serde(default)]
26 pub border_color: Option<String>,
27 /// Optional gate fill color in CSS color syntax.
28 #[serde(default)]
29 pub background_color: Option<String>,
30 /// Optional gate label font size.
31 #[serde(default)]
32 pub font_size: Option<f64>,
33 /// Optional gate label text color in CSS color syntax.
34 #[serde(default)]
35 pub text_color: Option<String>,
36 /// Optional connector or wire color in CSS color syntax.
37 #[serde(default)]
38 pub line_color: Option<String>,
39 /// Optional connector or wire stroke width.
40 #[serde(default)]
41 pub line_width: Option<f64>,
42}
43
44/// Style dictionary keyed by gate name (with mandatory `default` fallback).
45#[derive(Debug, Clone)]
46pub struct StyleBook {
47 styles: HashMap<String, GateStyle>,
48 default_style: GateStyle,
49}
50
51impl StyleBook {
52 /// Load a built-in style map by name and apply optional runtime overrides.
53 ///
54 /// Supported built-in names are `default` and `gray`. Unknown names fall back to
55 /// `default`.
56 pub fn new(style_name: &str, overrides: &HashMap<String, GateStyle>) -> Self {
57 let mut styles = load_style(style_name);
58 for (k, v) in overrides {
59 styles.insert(k.clone(), v.clone());
60 }
61 if !styles.contains_key("default") {
62 styles.insert("default".to_string(), GateStyle::default());
63 }
64 let default_style = styles.get("default").cloned().unwrap_or_default();
65 Self {
66 styles,
67 default_style,
68 }
69 }
70
71 /// Return style for gate name, falling back to `default`.
72 pub fn get(&self, gate_name: &str) -> &GateStyle {
73 self.styles.get(gate_name).unwrap_or(&self.default_style)
74 }
75}
76
77/// Load built-in style JSON by name.
78///
79/// Supported names:
80/// - `default`
81/// - `gray`
82///
83/// Unknown names fall back to `default`.
84fn load_style(style_name: &str) -> HashMap<String, GateStyle> {
85 let style_key = style_name.trim().to_ascii_lowercase();
86 let json = match style_key.as_str() {
87 "gray" => include_str!("styles/gray.json"),
88 _ => include_str!("styles/default.json"),
89 };
90 serde_json::from_str(json).unwrap_or_else(|_| {
91 let mut fallback = HashMap::new();
92 fallback.insert("default".to_string(), GateStyle::default());
93 fallback
94 })
95}