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