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