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