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 if strings.HasPrefix(value, "calc(") {
261 // Handle calculation expression
262 calcResult := evaluateCalcExpression(value[5:len(value)-1], em, max)
263 return calcResult
264 } else {
265 // Extract numeric value and unit
266 for k, v := range unitFactors {
267 if strings.HasSuffix(value, k) {
268 cutStr, _ := strings.CutSuffix(value, k)
269 numericValue, _ := strconv.ParseFloat(cutStr, 64)
270 return float32(numericValue) * v
271 }
272 }
273 return 0
274 }
275
276}
277
278// evaluateCalcExpression recursively evaluates 'calc()' expressions
279func evaluateCalcExpression(expression string, em, max float32) float32 {
280 terms := strings.FieldsFunc(expression, func(c rune) bool {
281 return c == '+' || c == '-' || c == '*' || c == '/'
282 })
283
284 operators := strings.FieldsFunc(expression, func(c rune) bool {
285 return c != '+' && c != '-' && c != '*' && c != '/'
286 })
287
288 var result float32
289
290 for i, term := range terms {
291 value := ConvertToPixels(strings.TrimSpace(term), em, max)
292
293 if i > 0 {
294 switch operators[i-1] {
295 case "+":
296 result += value
297 case "-":
298 result -= value
299 case "*":
300 result *= value
301 case "/":
302 if value != 0 {
303 result /= value
304 } else {
305 return 0
306 }
307 }
308 } else {
309 result = value
310 }
311 }
312
313 return result
314}
315
316func Merge(m1, m2 map[string]string) map[string]string {
317 // Create a new map and copy m1 into it
318 result := make(map[string]string)
319 for k, v := range m1 {
320 result[k] = v
321 }
322
323 // Merge m2 into the new map
324 for k, v := range m2 {
325 result[k] = v
326 }
327
328 return result
329}
330
331func Max(a, b float32) float32 {
332 if a > b {
333 return a
334 } else {
335 return b
336 }
337}
338
339func Min(a, b float32) float32 {
340 if a < b {
341 return a
342 } else {
343 return b
344 }
345}
346
347// getStructField uses reflection to get the value of a struct field by name
348func GetStructField(data interface{}, fieldName string) interface{} {
349 val := reflect.ValueOf(data)
350
351 // Make sure we have a pointer to a struct
352 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
353 return nil
354 }
355
356 // Get the struct field by name
357 field := val.Elem().FieldByName(fieldName)
358
359 // Check if the field exists
360 if !field.IsValid() {
361 return nil
362 }
363
364 return field.Interface()
365}
366
367func SetStructFieldValue(data interface{}, fieldName string, newValue interface{}) {
368 val := reflect.ValueOf(data)
369
370 // Make sure we have a pointer to a struct
371 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
372 return
373 }
374
375 // Get the struct field by name
376 field := val.Elem().FieldByName(fieldName)
377
378 // Check if the field exists
379 if !field.IsValid() {
380 return
381 }
382
383 // Check if the new value type is assignable to the field type
384 if !reflect.ValueOf(newValue).Type().AssignableTo(field.Type()) {
385 return
386 }
387
388 // Set the new value
389 field.Set(reflect.ValueOf(newValue))
390
391}
392
393// func Check(e error) {
394// if e != nil {
395// panic(e)
396// }
397// }
398
399func GetInnerText(n *html.Node) string {
400 var result strings.Builder
401
402 var getText func(*html.Node)
403 getText = func(n *html.Node) {
404 // Skip processing if the node is a head tag
405 if n.Type == html.ElementNode && n.Data == "head" {
406 return
407 }
408
409 // If it's a text node, append its content
410 if n.Type == html.TextNode {
411 result.WriteString(n.Data)
412 }
413
414 // Traverse child nodes recursively
415 for c := n.FirstChild; c != nil; c = c.NextSibling {
416 getText(c)
417 }
418 }
419
420 getText(n)
421
422 return result.String()
423}
424
425func GetPositionOffsetNode(n *element.Node) *element.Node {
426 pos := n.Style["position"]
427
428 if pos == "relative" {
429 return n
430 } else {
431 if n.Parent.TagName != "ROOT" {
432 if n.Parent.Style != nil {
433 return GetPositionOffsetNode(n.Parent)
434 } else {
435 return nil
436 }
437 } else {
438 return n.Parent
439 }
440 }
441}
442
443func IsParent(n element.Node, name string) bool {
444 if n.Parent.TagName != "ROOT" {
445 if n.Parent.TagName == name {
446 return true
447 } else {
448 return IsParent(*n.Parent, name)
449 }
450 } else {
451 return false
452 }
453}
454
455func ChildrenHaveText(n *element.Node) bool {
456 for _, child := range n.Children {
457 if len(strings.TrimSpace(child.InnerText)) != 0 {
458 return true
459 }
460 // Recursively check if any child nodes have text
461 if ChildrenHaveText(&child) {
462 return true
463 }
464 }
465 return false
466}
467
468func NodeToHTML(node element.Node) (string, string) {
469 // if node.TagName == "notaspan" {
470 // return node.InnerText + " ", ""
471 // }
472
473 var buffer bytes.Buffer
474 buffer.WriteString("<" + node.TagName)
475
476 if node.Properties.Editable {
477 buffer.WriteString(" contentEditable=\"true\"")
478 }
479
480 // Add ID if present
481 if node.Id != "" {
482 buffer.WriteString(" id=\"" + node.Id + "\"")
483 }
484
485 // Add ID if present
486 if node.Title != "" {
487 buffer.WriteString(" title=\"" + node.Title + "\"")
488 }
489
490 // Add ID if present
491 if node.Src != "" {
492 buffer.WriteString(" src=\"" + node.Src + "\"")
493 }
494
495 // Add ID if present
496 if node.Href != "" {
497 buffer.WriteString(" href=\"" + node.Href + "\"")
498 }
499
500 // Add class list if present
501 if len(node.ClassList.Classes) > 0 || node.ClassList.Value != "" {
502 classes := ""
503 for _, v := range node.ClassList.Classes {
504 if len(v) > 0 {
505 if string(v[0]) != ":" {
506 classes += v + " "
507 }
508 }
509 }
510 classes = strings.TrimSpace(classes)
511 if len(classes) > 0 {
512 buffer.WriteString(" class=\"" + classes + "\"")
513 }
514 }
515
516 // Add style if present
517 if len(node.Style) > 0 {
518
519 style := ""
520 for key, value := range node.Style {
521 if key != "inlineText" {
522 style += key + ":" + value + ";"
523 }
524 }
525 style = strings.TrimSpace(style)
526
527 if len(style) > 0 {
528 buffer.WriteString(" style=\"" + style + "\"")
529 }
530 }
531
532 // Add other attributes if present
533 for key, value := range node.Attribute {
534 if strings.TrimSpace(value) != "" {
535 buffer.WriteString(" " + key + "=\"" + value + "\"")
536 }
537 }
538
539 buffer.WriteString(">")
540
541 // Add inner text if present
542 if node.InnerText != "" && !ChildrenHaveText(&node) {
543 buffer.WriteString(node.InnerText)
544 }
545 return buffer.String(), "</" + node.TagName + ">"
546}
547
548func OuterHTML(node element.Node) string {
549 var buffer bytes.Buffer
550
551 tag, closing := NodeToHTML(node)
552
553 buffer.WriteString(tag)
554
555 // Recursively add children
556 for _, child := range node.Children {
557 buffer.WriteString(OuterHTML(child))
558 }
559
560 buffer.WriteString(closing)
561
562 return buffer.String()
563}
564
565func InnerHTML(node element.Node) string {
566 var buffer bytes.Buffer
567 // Recursively add children
568 for _, child := range node.Children {
569 buffer.WriteString(OuterHTML(child))
570 }
571 return buffer.String()
572}
573
574func ReverseSlice[T any](s []T) {
575 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
576 s[i], s[j] = s[j], s[i]
577 }
578}
579
580func ParentStyleProp(n *element.Node, prop string, selector func(string) bool) bool {
581 if n.Parent != nil {
582 if selector(n.Parent.Style[prop]) {
583 return true
584 } else {
585 return ParentStyleProp(n.Parent, prop, selector)
586 }
587 }
588 return false
589}