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 self.Style == nil {
67 self.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 self.Style["width"] != "" {
78 str := strings.TrimSuffix(self.Style["width"], "px")
79 // Convert the string to float32
80 f, _ := strconv.ParseFloat(str, 32)
81 pwh.Width = float32(f)
82 }
83
84 if self.Style["height"] != "" {
85 str := strings.TrimSuffix(self.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(self.Style["width"], fs, pwh.Width)
93 height, _ := ConvertToPixels(self.Style["height"], fs, pwh.Height)
94
95 if self.Style["min-width"] != "" {
96 minWidth, _ := ConvertToPixels(self.Style["min-width"], fs, pwh.Width)
97 width = Max(width, minWidth)
98 }
99
100 if self.Style["max-width"] != "" {
101 maxWidth, _ := ConvertToPixels(self.Style["max-width"], fs, pwh.Width)
102 width = Min(width, maxWidth)
103 }
104 if self.Style["min-height"] != "" {
105 minHeight, _ := ConvertToPixels(self.Style["min-height"], fs, pwh.Height)
106 height = Max(height, minHeight)
107 }
108
109 if self.Style["max-height"] != "" {
110 maxHeight, _ := ConvertToPixels(self.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 self.Style["width"] == "100%" {
126 wh.Width -= (self.Margin.Right + self.Margin.Left + (self.Border.Width * 2))
127 }
128
129 if self.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 self.Style[t] != "" {
143 left, right, top, bottom := convertMarginToIndividualProperties(self.Style[t])
144 if self.Style[t+"-left"] == "" {
145 self.Style[t+"-left"] = left
146 }
147 if self.Style[t+"-right"] == "" {
148 self.Style[t+"-right"] = right
149 }
150 if self.Style[t+"-top"] == "" {
151 self.Style[t+"-top"] = top
152 }
153 if self.Style[t+"-bottom"] == "" {
154 self.Style[t+"-bottom"] = bottom
155 }
156 }
157 if self.Style[t+"-left"] != "" || self.Style[t+"-right"] != "" {
158 l, _ := ConvertToPixels(self.Style[t+"-left"], fs, wh.Width)
159 r, _ := ConvertToPixels(self.Style[t+"-right"], fs, wh.Width)
160 m.Left = l
161 m.Right = r
162 }
163 if self.Style[t+"-top"] != "" || self.Style[t+"-bottom"] != "" {
164 top, _ := ConvertToPixels(self.Style[t+"-top"], fs, wh.Height)
165 b, _ := ConvertToPixels(self.Style[t+"-bottom"], fs, wh.Height)
166 m.Top = top
167 m.Bottom = b
168 }
169 if t == "margin" {
170 if self.Style["margin"] == "auto" && self.Style["margin-left"] == "" && self.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, state *map[string]element.State) *element.Node {
466 s := *state
467 self := s[n.Properties.Id]
468 parent := s[n.Parent.Properties.Id]
469 pos := self.Style["position"]
470
471 if pos == "relative" {
472 return n
473 } else {
474 if n.Parent.TagName != "ROOT" {
475 if parent.Style != nil {
476 return GetPositionOffsetNode(n.Parent, state)
477 } else {
478 return nil
479 }
480 } else {
481 return n.Parent
482 }
483 }
484}
485
486func IsParent(n element.Node, name string) bool {
487 if n.Parent.TagName != "ROOT" {
488 if n.Parent.TagName == name {
489 return true
490 } else {
491 return IsParent(*n.Parent, name)
492 }
493 } else {
494 return false
495 }
496}
497
498func ChildrenHaveText(n *element.Node) bool {
499 for _, child := range n.Children {
500 if len(strings.TrimSpace(child.InnerText)) != 0 {
501 return true
502 }
503 // Recursively check if any child nodes have text
504 if ChildrenHaveText(&child) {
505 return true
506 }
507 }
508 return false
509}
510
511func NodeToHTML(node element.Node) (string, string) {
512 // if node.TagName == "notaspan" {
513 // return node.InnerText + " ", ""
514 // }
515
516 var buffer bytes.Buffer
517 buffer.WriteString("<" + node.TagName)
518
519 if node.Properties.Editable {
520 buffer.WriteString(" contentEditable=\"true\"")
521 }
522
523 // Add ID if present
524 if node.Id != "" {
525 buffer.WriteString(" id=\"" + node.Id + "\"")
526 }
527
528 // Add ID if present
529 if node.Title != "" {
530 buffer.WriteString(" title=\"" + node.Title + "\"")
531 }
532
533 // Add ID if present
534 if node.Src != "" {
535 buffer.WriteString(" src=\"" + node.Src + "\"")
536 }
537
538 // Add ID if present
539 if node.Href != "" {
540 buffer.WriteString(" href=\"" + node.Href + "\"")
541 }
542
543 // Add class list if present
544 if len(node.ClassList.Classes) > 0 || node.ClassList.Value != "" {
545 classes := ""
546 for _, v := range node.ClassList.Classes {
547 if string(v[0]) != ":" {
548 classes += v + " "
549 }
550 }
551 classes = strings.TrimSpace(classes)
552 if len(classes) > 0 {
553 buffer.WriteString(" class=\"" + classes + "\"")
554 }
555 }
556
557 // Add style if present
558 if len(node.Style) > 0 {
559
560 style := ""
561 for key, value := range node.Style {
562 if key != "inlineText" {
563 style += key + ":" + value + ";"
564 }
565 }
566 style = strings.TrimSpace(style)
567
568 if len(style) > 0 {
569 buffer.WriteString(" style=\"" + style + "\"")
570 }
571 }
572
573 // Add other attributes if present
574 for key, value := range node.Attribute {
575 if strings.TrimSpace(value) != "" {
576 buffer.WriteString(" " + key + "=\"" + value + "\"")
577 }
578 }
579
580 buffer.WriteString(">")
581
582 // Add inner text if present
583 if node.InnerText != "" && !ChildrenHaveText(&node) {
584 buffer.WriteString(node.InnerText)
585 }
586 return buffer.String(), "</" + node.TagName + ">"
587}
588
589func OuterHTML(node element.Node) string {
590 var buffer bytes.Buffer
591
592 tag, closing := NodeToHTML(node)
593
594 buffer.WriteString(tag)
595
596 // Recursively add children
597 for _, child := range node.Children {
598 buffer.WriteString(OuterHTML(child))
599 }
600
601 buffer.WriteString(closing)
602
603 return buffer.String()
604}
605
606func InnerHTML(node element.Node) string {
607 var buffer bytes.Buffer
608 // Recursively add children
609 for _, child := range node.Children {
610 buffer.WriteString(OuterHTML(child))
611 }
612 return buffer.String()
613}