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