Utils
dsa
# MapToInline?(go)
# InlineToMap?(go)
# GetWH?(go)
# GetMP?(go)
# convertMarginToIndividualProperties?(go)
# ConvertToPixels?(go)
# evaluateCalcExpression?(go)
# GetTextBounds?(go)
# Merge?(go)
# ExMerge?(go)
# Max?(go)
# Min?(go)
# FindRelative?(go)
# ParseFloat?(go)
# GetStructField?(go)
# SetStructFieldValue?(go)
# Check?(go)
# GetInnerText?(go)
# GetPositionOffsetNode?(go)
# IsParent?(go)
1package utils
2
3import (
4 "fmt"
5 "gui/element"
6 "math"
7 "reflect"
8 "regexp"
9 "strconv"
10 "strings"
11
12 "golang.org/x/net/html"
13)
14
15// MapToInlineCSS converts a map[string]string to a string formatted like inline CSS style
16func MapToInline(m map[string]string) string {
17 var cssStrings []string
18 for key, value := range m {
19 cssStrings = append(cssStrings, fmt.Sprintf("%s: %s;", key, value))
20 }
21 return strings.Join(cssStrings, " ")
22}
23
24// InlineCSSToMap converts a string formatted like inline CSS style to a map[string]string
25func InlineToMap(cssString string) map[string]string {
26 cssMap := make(map[string]string)
27 declarations := strings.Split(cssString, ";")
28 for _, declaration := range declarations {
29 parts := strings.Split(strings.TrimSpace(declaration), ":")
30 if len(parts) == 2 {
31 key := strings.TrimSpace(parts[0])
32 value := strings.TrimSpace(parts[1])
33 cssMap[key] = value
34 }
35 }
36 return cssMap
37}
38
39func GetXY(n *element.Node, state *map[string]element.State) (float32, float32) {
40 s := *state
41 // self := s[n.Properties.Id]
42
43 offsetX := float32(0)
44 offsetY := float32(0)
45
46 if n.Parent != nil {
47 parent := s[n.Parent.Properties.Id]
48 // x, y := GetXY(n.Parent, state)
49 offsetX += parent.Border.Width + parent.Padding.Left
50 offsetY += parent.Border.Width + parent.Padding.Top
51 }
52
53 return offsetX, offsetY
54}
55
56type WidthHeight struct {
57 Width float32
58 Height float32
59}
60
61func GetWH(n element.Node, state *map[string]element.State) WidthHeight {
62 s := *state
63 self := s[n.Properties.Id]
64
65 if self.Style == nil {
66 self.Style = map[string]string{}
67 }
68
69 fs := self.EM
70
71 var pwh WidthHeight
72 if n.Parent != nil {
73 pwh = GetWH(*n.Parent, state)
74 } else {
75 pwh = WidthHeight{}
76 if self.Style["width"] != "" {
77 str := strings.TrimSuffix(self.Style["width"], "px")
78 // Convert the string to float32
79 f, _ := strconv.ParseFloat(str, 32)
80 pwh.Width = float32(f)
81 }
82
83 if self.Style["height"] != "" {
84 str := strings.TrimSuffix(self.Style["height"], "px")
85 // Convert the string to float32
86 f, _ := strconv.ParseFloat(str, 32)
87 pwh.Height = float32(f)
88 }
89 }
90
91 width, _ := ConvertToPixels(self.Style["width"], fs, pwh.Width)
92 height, _ := ConvertToPixels(self.Style["height"], fs, pwh.Height)
93
94 if self.Style["min-width"] != "" {
95 minWidth, _ := ConvertToPixels(self.Style["min-width"], fs, pwh.Width)
96 width = Max(width, minWidth)
97 }
98
99 if self.Style["max-width"] != "" {
100 maxWidth, _ := ConvertToPixels(self.Style["max-width"], fs, pwh.Width)
101 width = Min(width, maxWidth)
102 }
103 if self.Style["min-height"] != "" {
104 minHeight, _ := ConvertToPixels(self.Style["min-height"], fs, pwh.Height)
105 height = Max(height, minHeight)
106 }
107
108 if self.Style["max-height"] != "" {
109 maxHeight, _ := ConvertToPixels(self.Style["max-height"], fs, pwh.Height)
110 height = Min(height, maxHeight)
111 }
112
113 wh := WidthHeight{
114 Width: width,
115 Height: height,
116 }
117
118 if n.Parent != nil {
119 wh.Width += self.Padding.Left + self.Padding.Right
120 wh.Height += self.Padding.Top + self.Padding.Bottom
121 // fmt.Println(n.Properties.Id, wh, p)
122 }
123
124 if self.Style["width"] == "100%" {
125 wh.Width -= (self.Margin.Right + self.Margin.Left + (self.Border.Width * 2))
126 }
127
128 if self.Style["height"] == "100%" {
129 wh.Height -= (self.Margin.Top + self.Margin.Bottom)
130 }
131
132 return wh
133}
134
135func GetMP(n element.Node, wh WidthHeight, state *map[string]element.State, t string) element.MarginPadding {
136 s := *state
137 self := s[n.Properties.Id]
138 fs := self.EM
139 m := element.MarginPadding{}
140
141 if self.Style[t] != "" {
142 left, right, top, bottom := convertMarginToIndividualProperties(self.Style[t])
143 if self.Style[t+"-left"] == "" {
144 self.Style[t+"-left"] = left
145 }
146 if self.Style[t+"-right"] == "" {
147 self.Style[t+"-right"] = right
148 }
149 if self.Style[t+"-top"] == "" {
150 self.Style[t+"-top"] = top
151 }
152 if self.Style[t+"-bottom"] == "" {
153 self.Style[t+"-bottom"] = bottom
154 }
155 }
156 if self.Style[t+"-left"] != "" || self.Style[t+"-right"] != "" {
157 l, _ := ConvertToPixels(self.Style[t+"-left"], fs, wh.Width)
158 r, _ := ConvertToPixels(self.Style[t+"-right"], fs, wh.Width)
159 m.Left = l
160 m.Right = r
161 }
162 if self.Style[t+"-top"] != "" || self.Style[t+"-bottom"] != "" {
163 top, _ := ConvertToPixels(self.Style[t+"-top"], fs, wh.Height)
164 b, _ := ConvertToPixels(self.Style[t+"-bottom"], fs, wh.Height)
165 m.Top = top
166 m.Bottom = b
167 }
168 if t == "margin" {
169 if self.Style["margin"] == "auto" && self.Style["margin-left"] == "" && self.Style["margin-right"] == "" {
170 pwh := GetWH(*n.Parent, state)
171 m.Left = Max((pwh.Width-wh.Width)/2, 0)
172 m.Right = m.Left
173 }
174 }
175
176 return m
177}
178
179func convertMarginToIndividualProperties(margin string) (string, string, string, string) {
180 // Remove extra whitespace
181 margin = strings.TrimSpace(margin)
182
183 if margin == "" {
184 return "0px", "0px", "0px", "0px"
185 }
186
187 // Regular expression to match values with optional units
188 re := regexp.MustCompile(`(-?\d+(\.\d+)?)(\w*|\%)?`)
189
190 // Extract numerical values from the margin property
191 matches := re.FindAllStringSubmatch(margin, -1)
192
193 // Initialize variables for individual margins
194 var left, right, top, bottom string
195
196 switch len(matches) {
197 case 1:
198 // If only one value is provided, apply it to all margins
199 left = matches[0][0]
200 right = matches[0][0]
201 top = matches[0][0]
202 bottom = matches[0][0]
203 case 2:
204 // If two values are provided, apply the first to top and bottom, and the second to left and right
205 top = matches[0][0]
206 bottom = matches[0][0]
207 left = matches[1][0]
208 right = matches[1][0]
209 case 3:
210 // If three values are provided, apply the first to top, the second to left and right, and the third to bottom
211 top = matches[0][0]
212 left = matches[1][0]
213 right = matches[1][0]
214 bottom = matches[2][0]
215 case 4:
216 // If four values are provided, apply them to top, right, bottom, and left, respectively
217 top = matches[0][0]
218 right = matches[1][0]
219 bottom = matches[2][0]
220 left = matches[3][0]
221 }
222
223 return left, right, top, bottom
224}
225
226// ConvertToPixels converts a CSS measurement to pixels.
227func ConvertToPixels(value string, em, max float32) (float32, error) {
228 unitFactors := map[string]float32{
229 "px": 1,
230 "em": em,
231 "pt": 1.33,
232 "pc": 16.89,
233 "%": max / 100,
234 "vw": max / 100,
235 "vh": max / 100,
236 "cm": 37.79527559,
237 "in": 96,
238 }
239
240 if strings.HasPrefix(value, "calc(") {
241 // Handle calculation expression
242 calcResult, err := evaluateCalcExpression(value[5:len(value)-1], em, max)
243 if err != nil {
244 return 0, err
245 }
246 return calcResult, nil
247 } else {
248 // Extract numeric value and unit
249 for k, v := range unitFactors {
250 if strings.HasSuffix(value, k) {
251 cutStr, _ := strings.CutSuffix(value, k)
252 numericValue, _ := strconv.ParseFloat(cutStr, 64)
253 return float32(numericValue) * v, nil
254 }
255 }
256 return 0, fmt.Errorf("unable to parse value")
257 }
258
259}
260
261// evaluateCalcExpression recursively evaluates 'calc()' expressions
262func evaluateCalcExpression(expression string, em, max float32) (float32, error) {
263 terms := strings.FieldsFunc(expression, func(c rune) bool {
264 return c == '+' || c == '-' || c == '*' || c == '/'
265 })
266
267 operators := strings.FieldsFunc(expression, func(c rune) bool {
268 return c != '+' && c != '-' && c != '*' && c != '/'
269 })
270
271 var result float32
272
273 for i, term := range terms {
274 value, err := ConvertToPixels(strings.TrimSpace(term), em, max)
275 if err != nil {
276 return 0, err
277 }
278
279 if i > 0 {
280 switch operators[i-1] {
281 case "+":
282 result += value
283 case "-":
284 result -= value
285 case "*":
286 result *= value
287 case "/":
288 if value != 0 {
289 result /= value
290 } else {
291 return 0, fmt.Errorf("division by zero in 'calc()' expression")
292 }
293 }
294 } else {
295 result = value
296 }
297 }
298
299 return result, nil
300}
301
302func GetTextBounds(text string, fontSize, width, height float32) (float32, float32) {
303 w := float32(len(text) * int(fontSize))
304 h := fontSize
305 if width > 0 && height > 0 {
306 if w > width {
307 height = Max(height, float32(math.Ceil(float64(w/width)))*h)
308 }
309 return width, height
310 } else {
311 return w, h
312 }
313
314}
315
316func Merge(m1, m2 map[string]string) map[string]string {
317 // Create a new map and copy m1 into it
318 result := make(map[string]string)
319 for k, v := range m1 {
320 result[k] = v
321 }
322
323 // Merge m2 into the new map
324 for k, v := range m2 {
325 result[k] = v
326 }
327
328 return result
329}
330
331func ExMerge(m1, m2 map[string]string) map[string]string {
332 // Create a new map and copy m1 into it
333 result := make(map[string]string)
334 for k, v := range m1 {
335 result[k] = v
336 }
337
338 // Merge m2 into the new map only if the key is not already present
339 for k, v := range m2 {
340 if result[k] == "" {
341 result[k] = v
342 }
343 }
344
345 return result
346}
347
348func Max(a, b float32) float32 {
349 if a > b {
350 return a
351 } else {
352 return b
353 }
354}
355
356func Min(a, b float32) float32 {
357 if a < b {
358 return a
359 } else {
360 return b
361 }
362}
363
364func FindRelative(n *element.Node, styleMap map[string]map[string]string) (float32, float32) {
365 pos := styleMap[n.Properties.Id]["position"]
366
367 if pos == "relative" {
368 x, _ := strconv.ParseFloat(styleMap[n.Properties.Id]["x"], 32)
369 y, _ := strconv.ParseFloat(styleMap[n.Properties.Id]["y"], 32)
370 return float32(x), float32(y)
371 } else {
372 if n.Parent != nil {
373 x, y := FindRelative(n.Parent, styleMap)
374 return x, y
375 } else {
376 return 0, 0
377 }
378 }
379}
380
381func ParseFloat(str string, def float32) float32 {
382 var a float32
383 if str == "" {
384 a = 0
385 } else {
386 v, _ := strconv.ParseFloat(str, 32)
387 a = float32(v)
388 }
389 return a
390}
391
392// getStructField uses reflection to get the value of a struct field by name
393func GetStructField(data interface{}, fieldName string) (interface{}, error) {
394 val := reflect.ValueOf(data)
395
396 // Make sure we have a pointer to a struct
397 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
398 return nil, fmt.Errorf("expected a pointer to a struct")
399 }
400
401 // Get the struct field by name
402 field := val.Elem().FieldByName(fieldName)
403
404 // Check if the field exists
405 if !field.IsValid() {
406 return nil, fmt.Errorf("field not found: %s", fieldName)
407 }
408
409 return field.Interface(), nil
410}
411
412func SetStructFieldValue(data interface{}, fieldName string, newValue interface{}) error {
413 val := reflect.ValueOf(data)
414
415 // Make sure we have a pointer to a struct
416 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
417 return fmt.Errorf("expected a pointer to a struct")
418 }
419
420 // Get the struct field by name
421 field := val.Elem().FieldByName(fieldName)
422
423 // Check if the field exists
424 if !field.IsValid() {
425 return fmt.Errorf("field not found: %s", fieldName)
426 }
427
428 // Check if the new value type is assignable to the field type
429 if !reflect.ValueOf(newValue).Type().AssignableTo(field.Type()) {
430 return fmt.Errorf("incompatible types for field %s", fieldName)
431 }
432
433 // Set the new value
434 field.Set(reflect.ValueOf(newValue))
435
436 return nil
437}
438
439func Check(e error) {
440 if e != nil {
441 panic(e)
442 }
443}
444
445func GetInnerText(n *html.Node) string {
446 var result strings.Builder
447
448 var getText func(*html.Node)
449 getText = func(n *html.Node) {
450 if n.Type == html.TextNode {
451 result.WriteString(n.Data)
452 }
453
454 for c := n.FirstChild; c != nil; c = c.NextSibling {
455 getText(c)
456 }
457 }
458
459 getText(n)
460
461 return result.String()
462}
463
464func GetPositionOffsetNode(n *element.Node, state *map[string]element.State) *element.Node {
465 s := *state
466 self := s[n.Properties.Id]
467 parent := s[n.Parent.Properties.Id]
468 pos := self.Style["position"]
469
470 if pos == "relative" {
471 return n
472 } else {
473 if n.Parent.TagName != "ROOT" {
474 if parent.Style != nil {
475 return GetPositionOffsetNode(n.Parent, state)
476 } else {
477 return nil
478 }
479 } else {
480 return n.Parent
481 }
482 }
483}
484
485func IsParent(n element.Node, name string) bool {
486 if n.Parent.TagName != "ROOT" {
487 if n.Parent.TagName == name {
488 return true
489 } else {
490 return IsParent(*n.Parent, name)
491 }
492 } else {
493 return false
494 }
495}
496
497func ChildrenHaveText(n *element.Node) bool {
498 for _, child := range n.Children {
499 if len(strings.TrimSpace(child.InnerText)) != 0 {
500 return true
501 }
502 // Recursively check if any child nodes have text
503 if ChildrenHaveText(&child) {
504 return true
505 }
506 }
507 return false
508}