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