CStyle
CStyle is a single pass style computer.
# StyleSheet?(go)
# StyleTag?(go)
# GetStyles?(go)
# AddPlugin?(go)
See /cstyle/plugins/
# ComputeNodeStyle?(go)
if !utils.ChildrenHaveText(n) The utils.ChildrenHaveText function is called here instead of checking if the node directly has text is because in the case below
1<em><b>Text</b></em>
The em
element does not have text but it has a element with text insid, but the element will still need to be rendered as text.
# parseBorderShorthand?(go)
# CompleteBorder?(go)
# genTextNode?(go)
1package cstyle
2
3import (
4 "fmt"
5 "gui/color"
6 "gui/element"
7 "gui/font"
8 "gui/parser"
9 "gui/utils"
10 "os"
11 "slices"
12 "sort"
13 "strconv"
14 "strings"
15)
16
17// !TODO: Make a fine selector to target tags and if it has children or not etc
18// + could copy the transformers but idk
19type Plugin struct {
20 Selector func(*element.Node) bool
21 Level int
22 Handler func(*element.Node, *map[string]element.State)
23}
24
25type Transformer struct {
26 Selector func(*element.Node) bool
27 Handler func(element.Node, *CSS) element.Node
28}
29
30type CSS struct {
31 Width float32
32 Height float32
33 StyleSheets []map[string]map[string]string
34 Plugins []Plugin
35 Transformers []Transformer
36 Document *element.Node
37}
38
39func (c *CSS) Transform(n element.Node) element.Node {
40 for _, v := range c.Transformers {
41 if v.Selector(&n) {
42 n = v.Handler(n, c)
43 }
44 }
45 for i := 0; i < len(n.Children); i++ {
46 v := n.Children[i]
47 tc := c.Transform(v)
48 n = *tc.Parent
49 n.Children[i] = tc
50 }
51
52 return n
53}
54
55func (c *CSS) StyleSheet(path string) {
56 // Parse the CSS file
57 dat, _ := os.ReadFile(path)
58 styles := parser.ParseCSS(string(dat))
59
60 c.StyleSheets = append(c.StyleSheets, styles)
61}
62
63func (c *CSS) StyleTag(css string) {
64 styles := parser.ParseCSS(css)
65 c.StyleSheets = append(c.StyleSheets, styles)
66}
67
68var inheritedProps = []string{
69 "color",
70 "cursor",
71 "font",
72 "font-family",
73 "font-size",
74 "font-style",
75 "font-weight",
76 "letter-spacing",
77 "line-height",
78 // "text-align",
79 "text-indent",
80 "text-justify",
81 "text-shadow",
82 "text-transform",
83 "text-decoration",
84 "visibility",
85 "word-spacing",
86 "display",
87}
88
89func (c *CSS) GetStyles(n element.Node) map[string]string {
90 styles := map[string]string{}
91
92 if n.Parent != nil {
93 ps := c.GetStyles(*n.Parent)
94 for _, v := range inheritedProps {
95 if ps[v] != "" {
96 styles[v] = ps[v]
97 }
98 }
99 }
100 for k, v := range n.Style {
101 styles[k] = v
102 }
103 hovered := false
104 if slices.Contains(n.ClassList.Classes, ":hover") {
105 hovered = true
106 }
107
108 for _, styleSheet := range c.StyleSheets {
109 for selector := range styleSheet {
110 // fmt.Println(selector, n.Properties.Id)
111 key := selector
112 if strings.Contains(selector, ":hover") && hovered {
113 selector = strings.Replace(selector, ":hover", "", -1)
114 }
115 if element.TestSelector(selector, &n) {
116 for k, v := range styleSheet[key] {
117 styles[k] = v
118 }
119 }
120
121 }
122 }
123
124 // This is different than node.Style
125 // temp1 = <span style=​"color:​#a6e22e">​CSS​</span>​
126 // temp1.style == CSSStyleDeclaration {0: 'color', accentColor: '', additiveSymbols: '', alignContent: '', alignItems: '', alignSelf: '', …}
127 // temp1.getAttribute("style") == 'color:#a6e22e'
128 inline := parser.ParseStyleAttribute(n.GetAttribute("style") + ";")
129 styles = utils.Merge(styles, inline)
130 // add hover and focus css events
131
132 if n.Parent != nil {
133 if styles["z-index"] == "" && n.Parent.Style["z-index"] != "" {
134 z, _ := strconv.Atoi(n.Parent.Style["z-index"])
135 z += 1
136 styles["z-index"] = strconv.Itoa(z)
137 }
138 }
139
140 return styles
141}
142
143func (c *CSS) AddPlugin(plugin Plugin) {
144 c.Plugins = append(c.Plugins, plugin)
145}
146
147func (c *CSS) AddTransformer(transformer Transformer) {
148 c.Transformers = append(c.Transformers, transformer)
149}
150
151func CheckNode(n *element.Node, state *map[string]element.State) {
152 s := *state
153 self := s[n.Properties.Id]
154
155 fmt.Println(n.TagName, n.Properties.Id)
156 fmt.Printf("ID: %v\n", n.Id)
157 fmt.Printf("EM: %v\n", self.EM)
158 fmt.Printf("Parent: %v\n", n.Parent.TagName)
159 fmt.Printf("Classes: %v\n", n.ClassList.Classes)
160 fmt.Printf("Text: %v\n", n.InnerText)
161 fmt.Printf("X: %v, Y: %v, Z: %v\n", self.X, self.Y, self.Z)
162 fmt.Printf("Width: %v, Height: %v\n", self.Width, self.Height)
163 fmt.Printf("Styles: %v\n", n.Style)
164 // fmt.Printf("Background: %v\n", self.Background)
165 // fmt.Printf("Border: %v\n\n\n", self.Border)
166}
167
168func (c *CSS) ComputeNodeStyle(n *element.Node, state *map[string]element.State) *element.Node {
169
170 // Head is not renderable
171 if utils.IsParent(*n, "head") {
172 return n
173 }
174
175 plugins := c.Plugins
176
177 s := *state
178 self := s[n.Properties.Id]
179 parent := s[n.Parent.Properties.Id]
180
181 self.Background = color.Parse(n.Style, "background")
182 self.Border, _ = CompleteBorder(n.Style, self, parent)
183
184 fs, _ := utils.ConvertToPixels(n.Style["font-size"], parent.EM, parent.Width)
185 self.EM = fs
186
187 if n.Style["display"] == "none" {
188 self.X = 0
189 self.Y = 0
190 self.Width = 0
191 self.Height = 0
192 return n
193 }
194
195 // Set Z index value to be sorted in window
196 if n.Style["z-index"] != "" {
197 z, _ := strconv.Atoi(n.Style["z-index"])
198 self.Z = float32(z)
199 }
200
201 if parent.Z > 0 {
202 self.Z = parent.Z + 1
203 }
204
205 (*state)[n.Properties.Id] = self
206
207 wh := utils.GetWH(*n, state)
208 width := wh.Width
209 height := wh.Height
210
211 x, y := parent.X, parent.Y
212 // !NOTE: Would like to consolidate all XY function into this function like WH
213 offsetX, offsetY := utils.GetXY(n, state)
214 x += offsetX
215 y += offsetY
216
217 var top, left, right, bottom bool = false, false, false, false
218
219 m := utils.GetMP(*n, wh, state, "margin")
220 p := utils.GetMP(*n, wh, state, "padding")
221
222 self.Margin = m
223 self.Padding = p
224
225 if n.Style["position"] == "absolute" {
226 bas := utils.GetPositionOffsetNode(n)
227 base := s[bas.Properties.Id]
228 if n.Style["top"] != "" {
229 v, _ := utils.ConvertToPixels(n.Style["top"], self.EM, parent.Width)
230 y = v + base.Y
231 top = true
232 }
233 if n.Style["left"] != "" {
234 v, _ := utils.ConvertToPixels(n.Style["left"], self.EM, parent.Width)
235 x = v + base.X
236 left = true
237 }
238 if n.Style["right"] != "" {
239 v, _ := utils.ConvertToPixels(n.Style["right"], self.EM, parent.Width)
240 x = (base.Width - width) - v
241 right = true
242 }
243 if n.Style["bottom"] != "" {
244 v, _ := utils.ConvertToPixels(n.Style["bottom"], self.EM, parent.Width)
245 y = (base.Height - height) - v
246 bottom = true
247 }
248
249 } else {
250 for i, v := range n.Parent.Children {
251 if v.Style["position"] != "absolute" {
252 if v.Properties.Id == n.Properties.Id {
253 if i-1 > 0 {
254 sib := n.Parent.Children[i-1]
255 sibling := s[sib.Properties.Id]
256 if sib.Style["position"] != "absolute" {
257 if n.Style["display"] == "inline" {
258 if sib.Style["display"] == "inline" {
259 y = sibling.Y
260 } else {
261 y = sibling.Y + sibling.Height
262 }
263 } else {
264 y = sibling.Y + sibling.Height + (sibling.Border.Width * 2) + sibling.Margin.Bottom
265 }
266 }
267
268 }
269 break
270 } else if n.Style["display"] != "inline" {
271 vState := s[v.Properties.Id]
272 y += vState.Margin.Top + vState.Margin.Bottom + vState.Padding.Top + vState.Padding.Bottom + vState.Height + (self.Border.Width)
273 }
274 }
275 }
276 }
277
278 // Display modes need to be calculated here
279
280 relPos := !top && !left && !right && !bottom
281
282 if left || relPos {
283 x += m.Left
284 }
285 if top || relPos {
286 y += m.Top
287 }
288 if right {
289 x -= m.Right
290 }
291 if bottom {
292 y -= m.Bottom
293 }
294
295 self.X = x
296 self.Y = y
297 self.Width = width
298 self.Height = height
299 (*state)[n.Properties.Id] = self
300
301 if !utils.ChildrenHaveText(n) && len(n.InnerText) > 0 {
302 // Confirm text exists
303 if len(strings.TrimSpace(n.InnerText)) > 0 {
304 n.InnerText = strings.TrimSpace(n.InnerText)
305 self = genTextNode(n, state)
306 }
307 }
308
309 (*state)[n.Properties.Id] = self
310 (*state)[n.Parent.Properties.Id] = parent
311
312 // Call children here
313 // !TODO: Make something that stops rendering things out of fov
314 fmt.Println("!HER", n.Properties.Id, self.Width, self.Height)
315
316 if self.Y < c.Height {
317 var childYOffset float32
318 for i := 0; i < len(n.Children); i++ {
319 v := n.Children[i]
320 v.Parent = n
321 // This is were the tainting comes from
322 n.Children[i] = *c.ComputeNodeStyle(&v, state)
323
324 cState := (*state)[n.Children[i].Properties.Id]
325 if n.Style["height"] == "" {
326 if v.Style["position"] != "absolute" && cState.Y+cState.Height > childYOffset {
327 childYOffset = cState.Y + cState.Height
328 self.Height = (cState.Y - self.Border.Width) - (self.Y) + cState.Height
329 self.Height += cState.Margin.Top
330 self.Height += cState.Margin.Bottom
331 self.Height += cState.Padding.Top
332 self.Height += cState.Padding.Bottom
333 }
334 }
335 if cState.Width > self.Width {
336 self.Width = cState.Width
337 }
338 }
339 } else {
340 return n
341 }
342
343 self.Height += self.Padding.Bottom
344
345 (*state)[n.Properties.Id] = self
346
347 // Sorting the array by the Level field
348 sort.Slice(plugins, func(i, j int) bool {
349 return plugins[i].Level < plugins[j].Level
350 })
351
352 for _, v := range plugins {
353 if v.Selector(n) {
354 v.Handler(n, state)
355 }
356 }
357
358 CheckNode(n, state)
359
360 return n
361}
362
363func CompleteBorder(cssProperties map[string]string, self, parent element.State) (element.Border, error) {
364 // Split the shorthand into components
365 borderComponents := strings.Fields(cssProperties["border"])
366
367 // Ensure there are at least 1 component (width or style or color)
368 if len(borderComponents) >= 1 {
369 width := "0px" // Default width
370 style := "solid"
371 borderColor := "#000000" // Default color
372
373 // Extract style and color if available
374 if len(borderComponents) >= 1 {
375 width = borderComponents[0]
376 }
377
378 // Extract style and color if available
379 if len(borderComponents) >= 2 {
380 style = borderComponents[1]
381 }
382 if len(borderComponents) >= 3 {
383 borderColor = borderComponents[2]
384 }
385
386 parsedColor, _ := color.Color(borderColor)
387
388 w, _ := utils.ConvertToPixels(width, self.EM, parent.Width)
389
390 return element.Border{
391 Width: w,
392 Style: style,
393 Color: parsedColor,
394 Radius: cssProperties["border-radius"],
395 }, nil
396 }
397
398 return element.Border{}, fmt.Errorf("invalid border shorthand format")
399}
400
401func genTextNode(n *element.Node, state *map[string]element.State) element.State {
402 s := *state
403 self := s[n.Properties.Id]
404 parent := s[n.Parent.Properties.Id]
405
406 text := element.Text{}
407
408 bold, italic := false, false
409
410 if n.Style["font-weight"] == "bold" {
411 bold = true
412 }
413
414 if n.Style["font-style"] == "italic" {
415 italic = true
416 }
417
418 if text.Font == nil {
419 f, _ := font.LoadFont(n.Style["font-family"], int(self.EM), bold, italic)
420 text.Font = f
421 }
422
423 letterSpacing, _ := utils.ConvertToPixels(n.Style["letter-spacing"], self.EM, parent.Width)
424 wordSpacing, _ := utils.ConvertToPixels(n.Style["word-spacing"], self.EM, parent.Width)
425 lineHeight, _ := utils.ConvertToPixels(n.Style["line-height"], self.EM, parent.Width)
426 if lineHeight == 0 {
427 lineHeight = self.EM + 3
428 }
429
430 text.LineHeight = int(lineHeight)
431 text.WordSpacing = int(wordSpacing)
432 text.LetterSpacing = int(letterSpacing)
433 wb := " "
434
435 if n.Style["word-wrap"] == "break-word" {
436 wb = ""
437 }
438
439 if n.Style["text-wrap"] == "wrap" || n.Style["text-wrap"] == "balance" {
440 wb = ""
441 }
442
443 var dt float32
444
445 if n.Style["text-decoration-thickness"] == "auto" || n.Style["text-decoration-thickness"] == "" {
446 dt = self.EM / 7
447 } else {
448 dt, _ = utils.ConvertToPixels(n.Style["text-decoration-thickness"], self.EM, parent.Width)
449 }
450
451 col := color.Parse(n.Style, "font")
452
453 self.Color = col
454
455 text.Color = col
456 text.DecorationColor = color.Parse(n.Style, "decoration")
457 text.Align = n.Style["text-align"]
458 text.WordBreak = wb
459 text.WordSpacing = int(wordSpacing)
460 text.LetterSpacing = int(letterSpacing)
461 text.WhiteSpace = n.Style["white-space"]
462 text.DecorationThickness = int(dt)
463 text.Overlined = n.Style["text-decoration"] == "overline"
464 text.Underlined = n.Style["text-decoration"] == "underline"
465 text.LineThrough = n.Style["text-decoration"] == "linethrough"
466 text.EM = int(self.EM)
467 text.Width = int(parent.Width)
468 text.Text = n.InnerText
469
470 if n.Style["word-spacing"] == "" {
471 text.WordSpacing = font.MeasureSpace(&text)
472 }
473
474 img, width := font.Render(&text)
475 self.Texture = img
476
477 if n.Style["height"] == "" {
478 self.Height = float32(text.LineHeight)
479 }
480
481 if n.Style["width"] == "" {
482 self.Width = float32(width)
483 }
484
485 return self
486}