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