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