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 == nil {
457 return false
458 }
459 if n.Parent.TagName != "ROOT" {
460 if n.Parent.TagName == name {
461 return true
462 } else {
463 return IsParent(*n.Parent, name)
464 }
465 } else {
466 return false
467 }
468}
469
470func ChildrenHaveText(n *element.Node) bool {
471 for _, child := range n.Children {
472 if len(strings.TrimSpace(child.InnerText)) != 0 {
473 return true
474 }
475 // Recursively check if any child nodes have text
476 if ChildrenHaveText(child) {
477 return true
478 }
479 }
480 return false
481}
482
483func NodeToHTML(node *element.Node) (string, string) {
484 // if node.TagName == "notaspan" {
485 // return node.InnerText + " ", ""
486 // }
487
488 var buffer bytes.Buffer
489 buffer.WriteString("<" + node.TagName)
490
491 if node.Properties.Editable {
492 buffer.WriteString(" contentEditable=\"true\"")
493 }
494
495 // Add ID if present
496 if node.Id != "" {
497 buffer.WriteString(" id=\"" + node.Id + "\"")
498 }
499
500 // Add ID if present
501 if node.Title != "" {
502 buffer.WriteString(" title=\"" + node.Title + "\"")
503 }
504
505 // Add ID if present
506 if node.Src != "" {
507 buffer.WriteString(" src=\"" + node.Src + "\"")
508 }
509
510 // Add ID if present
511 if node.Href != "" {
512 buffer.WriteString(" href=\"" + node.Href + "\"")
513 }
514
515 // Add class list if present
516 if len(node.ClassList.Classes) > 0 || node.ClassList.Value != "" {
517 classes := ""
518 for _, v := range node.ClassList.Classes {
519 if len(v) > 0 {
520 if string(v[0]) != ":" {
521 classes += v + " "
522 }
523 }
524 }
525 classes = strings.TrimSpace(classes)
526 if len(classes) > 0 {
527 buffer.WriteString(" class=\"" + classes + "\"")
528 }
529 }
530
531 // Add style if present
532 if len(node.Style) > 0 {
533
534 style := ""
535 for key, value := range node.Style {
536 if key != "inlineText" {
537 style += key + ":" + value + ";"
538 }
539 }
540 style = strings.TrimSpace(style)
541
542 if len(style) > 0 {
543 buffer.WriteString(" style=\"" + style + "\"")
544 }
545 }
546
547 // Add other attributes if present
548 for key, value := range node.Attribute {
549 if strings.TrimSpace(value) != "" {
550 buffer.WriteString(" " + key + "=\"" + value + "\"")
551 }
552 }
553
554 buffer.WriteString(">")
555
556 // Add inner text if present
557 if node.InnerText != "" && !ChildrenHaveText(node) {
558 buffer.WriteString(node.InnerText)
559 }
560 return buffer.String(), "</" + node.TagName + ">"
561}
562
563func OuterHTML(node *element.Node) string {
564 var buffer bytes.Buffer
565
566 tag, closing := NodeToHTML(node)
567
568 buffer.WriteString(tag)
569
570 // Recursively add children
571 for _, child := range node.Children {
572 buffer.WriteString(OuterHTML(child))
573 }
574
575 buffer.WriteString(closing)
576
577 return buffer.String()
578}
579
580func InnerHTML(node *element.Node) string {
581 var buffer bytes.Buffer
582 // Recursively add children
583 for _, child := range node.Children {
584 buffer.WriteString(OuterHTML(child))
585 }
586 return buffer.String()
587}
588
589func ReverseSlice[T any](s []T) {
590 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
591 s[i], s[j] = s[j], s[i]
592 }
593}
594
595func ParentStyleProp(n *element.Node, prop string, selector func(string) bool) bool {
596 if n.Parent != nil {
597 if selector(n.Parent.Style[prop]) {
598 return true
599 } else {
600 return ParentStyleProp(n.Parent, prop, selector)
601 }
602 }
603 return false
604}