1use crate::ast::AtomFamily;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct SpacingSpec {
9 pub thick: String,
10 pub medium: String,
11 pub thin: String,
12 pub operator: String,
13}
14
15pub fn atom_family_name(family: AtomFamily) -> String {
17 match family {
18 AtomFamily::Mord => "mord",
19 AtomFamily::Mop => "mop",
20 AtomFamily::Mbin => "mbin",
21 AtomFamily::Mrel => "mrel",
22 AtomFamily::Mopen => "mopen",
23 AtomFamily::Mclose => "mclose",
24 AtomFamily::Mpunct => "mpunct",
25 AtomFamily::Minner => "minner",
26 }
27 .to_string()
28}
29
30fn normal_spacing<'a>(left: &str, right: &str, spec: &'a SpacingSpec) -> Option<&'a str> {
32 match (left, right) {
33 ("mord", "mop") | ("mord", "mbig") => Some(&spec.thin),
34 ("mord", "mbin") => Some(&spec.medium),
35 ("mord", "mrel") => Some(&spec.thick),
36 ("mord", "minner") => Some(&spec.thin),
37 ("mop", "mord") => Some(&spec.operator),
38 ("mop", "mop") | ("mop", "mbig") => Some(&spec.operator),
39 ("mop", "mrel") => Some(&spec.thick),
40 ("mop", "minner") => Some(&spec.thin),
41 ("mbig", "mord") | ("mbig", "mop") | ("mbig", "mopen") | ("mbig", "minner") => {
42 Some(&spec.medium)
43 }
44 ("mbig", "mbin") => Some(&spec.medium),
45 ("mbig", "mrel") => Some(&spec.thick),
46 ("mbig", "mclose") | ("mbig", "mpunct") => Some(&spec.thin),
47 ("mbin", "mord") => Some(&spec.medium),
48 ("mbin", "mop") | ("mbin", "mbig") => Some(&spec.medium),
49 ("mbin", "mopen") => Some(&spec.medium),
50 ("mbin", "minner") => Some(&spec.medium),
51 ("mrel", "mord") => Some(&spec.thick),
52 ("mrel", "mop") | ("mrel", "mbig") => Some(&spec.thick),
53 ("mrel", "mopen") => Some(&spec.thick),
54 ("mrel", "minner") => Some(&spec.thick),
55 ("mclose", "mop") | ("mclose", "mbig") => Some(&spec.thin),
56 ("mclose", "mbin") => Some(&spec.medium),
57 ("mclose", "mrel") => Some(&spec.thick),
58 ("mclose", "minner") => Some(&spec.thin),
59 ("mpunct", "mord") => Some(&spec.thin),
60 ("mpunct", "mop") | ("mpunct", "mbig") => Some(&spec.thin),
61 ("mpunct", "mrel") => Some(&spec.thick),
62 ("mpunct", "mopen") => Some(&spec.thin),
63 ("mpunct", "mclose") => Some(&spec.thin),
64 ("mpunct", "mpunct") => Some(&spec.thin),
65 ("mpunct", "minner") => Some(&spec.thin),
66 ("minner", "mord") => Some(&spec.thin),
67 ("minner", "mop") | ("minner", "mbig") => Some(&spec.thin),
68 ("minner", "mbin") => Some(&spec.medium),
69 ("minner", "mrel") => Some(&spec.thick),
70 ("minner", "mopen") => Some(&spec.thin),
71 ("minner", "mpunct") => Some(&spec.thin),
72 ("minner", "minner") => Some(&spec.thin),
73 _ => None,
74 }
75}
76
77fn tight_spacing<'a>(left: &str, right: &str, spec: &'a SpacingSpec) -> Option<&'a str> {
79 match (left, right) {
80 ("mord", "mop") => Some(&spec.thin),
81 ("mop", "mord") => Some(&spec.thin),
82 ("mop", "mop") => Some(&spec.thin),
83 ("mbig", "mord") | ("mbig", "mop") | ("mbig", "minner") => Some(&spec.thin),
84 ("mclose", "mop") => Some(&spec.thin),
85 ("minner", "mop") => Some(&spec.thin),
86 _ => None,
87 }
88}
89
90pub fn math_spacing(
92 left_type: &str,
93 right_type: &str,
94 tight: bool,
95 spec: &SpacingSpec,
96) -> Option<String> {
97 if tight {
98 tight_spacing(left_type, right_type, spec)
99 } else {
100 normal_spacing(left_type, right_type, spec)
101 }
102 .map(|s| s.to_string())
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct SpacableItem {
108 pub atom_type: Option<String>,
109 pub text: String,
110 pub baseline: usize,
111}
112
113fn cancels_left_of_bin(ty: &str) -> bool {
116 matches!(ty, "leftmost" | "mbin" | "mopen" | "mrel" | "mop" | "mpunct" | "mbig")
117}
118
119fn cancels_right_of_bin(ty: &str) -> bool {
121 matches!(ty, "rightmost" | "mrel" | "mclose" | "mpunct")
122}
123
124pub fn cancel_bin_atoms(items: Vec<SpacableItem>) -> Vec<SpacableItem> {
126 let n = items.len();
127 if n == 0 {
128 return items;
129 }
130 let mut items = items;
131
132 if items[0].atom_type.as_deref() == Some("mbin") {
133 items[0].atom_type = Some("mord".to_string());
134 }
135
136 for i in 1..n {
137 let prev_ty = items[i - 1].atom_type.clone();
138 let curr_ty = items[i].atom_type.clone();
139 match (prev_ty.as_deref(), curr_ty.as_deref()) {
140 (Some("mbin"), Some(right)) => {
141 if cancels_right_of_bin(right) {
142 items[i - 1].atom_type = Some("mord".to_string());
143 }
144 }
145 (Some(left), Some("mbin"))
146 if cancels_left_of_bin(left) => {
147 items[i].atom_type = Some("mord".to_string());
148 }
149 _ => (),
150 }
151 }
152
153 if n >= 1 && items[n - 1].atom_type.as_deref() == Some("mbin") {
154 items[n - 1].atom_type = Some("mord".to_string());
155 }
156
157 items
158}
159
160pub fn join_with_spacing(items: &[SpacableItem], tight: bool, spec: &SpacingSpec) -> String {
163 let n = items.len();
164 if n == 0 {
165 return String::new();
166 }
167 if n == 1 {
168 return items[0].text.clone();
169 }
170
171 let mut result = items[0].text.clone();
172 for i in 1..n {
173 let prev = &items[i - 1];
174 let curr = &items[i];
175 match (&prev.atom_type, &curr.atom_type) {
176 (Some(left), Some(right)) => {
177 if let Some(space) = math_spacing(left, right, tight, spec) {
178 result.push_str(&space);
179 }
180 result.push_str(&curr.text);
181 }
182 _ => result.push_str(&curr.text),
183 }
184 }
185 result
186}