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