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