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 "fmt"
5 "gui/element"
6 "gui/font"
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
40type WidthHeight struct {
41 Width float32
42 Height float32
43}
44
45func GetWH(n element.Node) WidthHeight {
46 fs := font.GetFontSize(n.Style)
47
48 var pwh WidthHeight
49 if n.Parent != nil {
50 pwh = GetWH(*n.Parent)
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 if n.Style["height"] != "" {
60 str := strings.TrimSuffix(n.Style["height"], "px")
61 // Convert the string to float32
62 f, _ := strconv.ParseFloat(str, 32)
63 pwh.Height = float32(f)
64 }
65 }
66
67 width, _ := ConvertToPixels(n.Style["width"], fs, pwh.Width)
68 if n.Style["min-width"] != "" {
69 minWidth, _ := ConvertToPixels(n.Style["min-width"], fs, pwh.Width)
70 width = Max(width, minWidth)
71 }
72
73 if n.Style["max-width"] != "" {
74 maxWidth, _ := ConvertToPixels(n.Style["max-width"], fs, pwh.Width)
75 width = Min(width, maxWidth)
76 }
77
78 height, _ := ConvertToPixels(n.Style["height"], fs, pwh.Height)
79 if n.Style["min-height"] != "" {
80 minHeight, _ := ConvertToPixels(n.Style["min-height"], fs, pwh.Height)
81 height = Max(height, minHeight)
82 }
83
84 if n.Style["max-height"] != "" {
85 maxHeight, _ := ConvertToPixels(n.Style["max-height"], fs, pwh.Height)
86 height = Min(height, maxHeight)
87 }
88 return WidthHeight{
89 Width: width,
90 Height: height,
91 }
92}
93
94type MarginPadding struct {
95 Top float32
96 Left float32
97 Right float32
98 Bottom float32
99}
100
101func GetMP(n element.Node, t string) MarginPadding {
102 fs := font.GetFontSize(n.Style)
103 m := MarginPadding{}
104
105 wh := GetWH(n)
106
107 if n.Style[t] != "" {
108 left, right, top, bottom := convertMarginToIndividualProperties(n.Style[t])
109 if n.Style[t+"-left"] == "" {
110 n.Style[t+"-left"] = left
111 }
112 if n.Style[t+"-right"] == "" {
113 n.Style[t+"-right"] = right
114 }
115 if n.Style[t+"-top"] == "" {
116 n.Style[t+"-top"] = top
117 }
118 if n.Style[t+"-bottom"] == "" {
119 n.Style[t+"-bottom"] = bottom
120 }
121 }
122 if n.Style[t+"-left"] != "" || n.Style[t+"-right"] != "" {
123 l, _ := ConvertToPixels(n.Style[t+"-left"], fs, wh.Width)
124 r, _ := ConvertToPixels(n.Style[t+"-right"], fs, wh.Width)
125 m.Left = l
126 m.Right = r
127 }
128 if n.Style[t+"-top"] != "" || n.Style[t+"-bottom"] != "" {
129 top, _ := ConvertToPixels(n.Style[t+"-top"], fs, wh.Height)
130 b, _ := ConvertToPixels(n.Style[t+"-bottom"], fs, wh.Height)
131 m.Top = top
132 m.Bottom = b
133 }
134 if t == "margin" {
135 if n.Style["margin"] == "auto" && n.Style["margin-left"] == "" && n.Style["margin-right"] == "" {
136 // this dont work
137 m.Left = Max((n.Parent.Properties.Computed["width"]-wh.Width)/2, 0)
138 m.Right = Max((n.Parent.Properties.Computed["width"]-wh.Width)/2, 0)
139 }
140 }
141
142 return m
143}
144
145func convertMarginToIndividualProperties(margin string) (string, string, string, string) {
146 // Remove extra whitespace
147 margin = strings.TrimSpace(margin)
148
149 if margin == "" {
150 return "0px", "0px", "0px", "0px"
151 }
152
153 // Regular expression to match values with optional units
154 re := regexp.MustCompile(`(-?\d+(\.\d+)?)(\w*|\%)?`)
155
156 // Extract numerical values from the margin property
157 matches := re.FindAllStringSubmatch(margin, -1)
158
159 // Initialize variables for individual margins
160 var left, right, top, bottom string
161
162 switch len(matches) {
163 case 1:
164 // If only one value is provided, apply it to all margins
165 left = matches[0][0]
166 right = matches[0][0]
167 top = matches[0][0]
168 bottom = matches[0][0]
169 case 2:
170 // If two values are provided, apply the first to top and bottom, and the second to left and right
171 top = matches[0][0]
172 bottom = matches[0][0]
173 left = matches[1][0]
174 right = matches[1][0]
175 case 3:
176 // If three values are provided, apply the first to top, the second to left and right, and the third to bottom
177 top = matches[0][0]
178 left = matches[1][0]
179 right = matches[1][0]
180 bottom = matches[2][0]
181 case 4:
182 // If four values are provided, apply them to top, right, bottom, and left, respectively
183 top = matches[0][0]
184 right = matches[1][0]
185 bottom = matches[2][0]
186 left = matches[3][0]
187 }
188
189 return left, right, top, bottom
190}
191
192func AsPX(n element.Node, value string) float32 {
193 if n.Style[value] != "" {
194 v, _ := ConvertToPixels(n.Style[value], n.Properties.EM, n.Properties.Computed["width"])
195 return v
196 } else {
197 return 0
198 }
199}
200
201// ConvertToPixels converts a CSS measurement to pixels.
202func ConvertToPixels(value string, em, max float32) (float32, error) {
203 unitFactors := map[string]float32{
204 "px": 1,
205 "em": em,
206 "pt": 1.33,
207 "pc": 16.89,
208 "%": max / 100,
209 "vw": max / 100,
210 "vh": max / 100,
211 "cm": 37.79527559,
212 }
213
214 parts := strings.Fields(value)
215
216 if strings.HasPrefix(value, "calc(") {
217 // Handle calculation expression
218 calcResult, err := evaluateCalcExpression(value[5:len(value)-1], em, max)
219 if err != nil {
220 return 0, err
221 }
222 return calcResult, nil
223 } else if len(parts) >= 2 {
224 // Extract numeric value and unit
225 numericValue, err := strconv.ParseFloat(parts[0], 64)
226 if err != nil {
227 return 0, fmt.Errorf("error parsing numeric value: %v", err)
228 }
229 unit := parts[1]
230
231 // Calculate result
232 return float32(numericValue) * unitFactors[unit], nil
233 }
234
235 return 0, fmt.Errorf("unable to parse value")
236}
237
238// evaluateCalcExpression recursively evaluates 'calc()' expressions
239func evaluateCalcExpression(expression string, em, max float32) (float32, error) {
240 terms := strings.FieldsFunc(expression, func(c rune) bool {
241 return c == '+' || c == '-' || c == '*' || c == '/'
242 })
243
244 operators := strings.FieldsFunc(expression, func(c rune) bool {
245 return c != '+' && c != '-' && c != '*' && c != '/'
246 })
247
248 var result float32
249
250 for i, term := range terms {
251 value, err := ConvertToPixels(strings.TrimSpace(term), em, max)
252 if err != nil {
253 return 0, err
254 }
255
256 if i > 0 {
257 switch operators[i-1] {
258 case "+":
259 result += value
260 case "-":
261 result -= value
262 case "*":
263 result *= value
264 case "/":
265 if value != 0 {
266 result /= value
267 } else {
268 return 0, fmt.Errorf("division by zero in 'calc()' expression")
269 }
270 }
271 } else {
272 result = value
273 }
274 }
275
276 return result, nil
277}
278
279func GetTextBounds(text string, fontSize, width, height float32) (float32, float32) {
280 w := float32(len(text) * int(fontSize))
281 h := fontSize
282 if width > 0 && height > 0 {
283 if w > width {
284 height = Max(height, float32(math.Ceil(float64(w/width)))*h)
285 }
286 return width, height
287 } else {
288 return w, h
289 }
290
291}
292
293func Merge(m1, m2 map[string]string) map[string]string {
294 // Create a new map and copy m1 into it
295 result := make(map[string]string)
296 for k, v := range m1 {
297 result[k] = v
298 }
299
300 // Merge m2 into the new map
301 for k, v := range m2 {
302 result[k] = v
303 }
304
305 return result
306}
307
308func ExMerge(m1, m2 map[string]string) map[string]string {
309 // Create a new map and copy m1 into it
310 result := make(map[string]string)
311 for k, v := range m1 {
312 result[k] = v
313 }
314
315 // Merge m2 into the new map only if the key is not already present
316 for k, v := range m2 {
317 if result[k] == "" {
318 result[k] = v
319 }
320 }
321
322 return result
323}
324
325func Max(a, b float32) float32 {
326 if a > b {
327 return a
328 } else {
329 return b
330 }
331}
332
333func Min(a, b float32) float32 {
334 if a < b {
335 return a
336 } else {
337 return b
338 }
339}
340
341func FindRelative(n *element.Node, styleMap map[string]map[string]string) (float32, float32) {
342 pos := styleMap[n.Properties.Id]["position"]
343
344 if pos == "relative" {
345 x, _ := strconv.ParseFloat(styleMap[n.Properties.Id]["x"], 32)
346 y, _ := strconv.ParseFloat(styleMap[n.Properties.Id]["y"], 32)
347 return float32(x), float32(y)
348 } else {
349 if n.Parent != nil {
350 x, y := FindRelative(n.Parent, styleMap)
351 return x, y
352 } else {
353 return 0, 0
354 }
355 }
356}
357
358func ParseFloat(str string, def float32) float32 {
359 var a float32
360 if str == "" {
361 a = 0
362 } else {
363 v, _ := strconv.ParseFloat(str, 32)
364 a = float32(v)
365 }
366 return a
367}
368
369// getStructField uses reflection to get the value of a struct field by name
370func GetStructField(data interface{}, fieldName string) (interface{}, error) {
371 val := reflect.ValueOf(data)
372
373 // Make sure we have a pointer to a struct
374 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
375 return nil, fmt.Errorf("expected a pointer to a struct")
376 }
377
378 // Get the struct field by name
379 field := val.Elem().FieldByName(fieldName)
380
381 // Check if the field exists
382 if !field.IsValid() {
383 return nil, fmt.Errorf("field not found: %s", fieldName)
384 }
385
386 return field.Interface(), nil
387}
388
389func SetStructFieldValue(data interface{}, fieldName string, newValue interface{}) error {
390 val := reflect.ValueOf(data)
391
392 // Make sure we have a pointer to a struct
393 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
394 return fmt.Errorf("expected a pointer to a struct")
395 }
396
397 // Get the struct field by name
398 field := val.Elem().FieldByName(fieldName)
399
400 // Check if the field exists
401 if !field.IsValid() {
402 return fmt.Errorf("field not found: %s", fieldName)
403 }
404
405 // Check if the new value type is assignable to the field type
406 if !reflect.ValueOf(newValue).Type().AssignableTo(field.Type()) {
407 return fmt.Errorf("incompatible types for field %s", fieldName)
408 }
409
410 // Set the new value
411 field.Set(reflect.ValueOf(newValue))
412
413 return nil
414}
415
416func Check(e error) {
417 if e != nil {
418 panic(e)
419 }
420}
421
422func GetInnerText(n *html.Node) string {
423 var result strings.Builder
424
425 var getText func(*html.Node)
426 getText = func(n *html.Node) {
427 if n.Type == html.TextNode {
428 result.WriteString(n.Data)
429 }
430
431 for c := n.FirstChild; c != nil; c = c.NextSibling {
432 getText(c)
433 }
434 }
435
436 getText(n)
437
438 return result.String()
439}
440
441func GetPositionOffsetNode(n *element.Node) *element.Node {
442 pos := n.Style["position"]
443
444 if pos == "relative" {
445 return n
446 } else {
447 if n.Parent.TagName != "ROOT" {
448 if n.Parent.Style != nil {
449 return GetPositionOffsetNode(n.Parent)
450 } else {
451 return nil
452 }
453 } else {
454 return n.Parent
455 }
456 }
457}
458
459func IsParent(n element.Node, name string) bool {
460 if n.Parent.TagName != "ROOT" {
461 if n.Parent.TagName == name {
462 return true
463 } else {
464 return IsParent(*n.Parent, name)
465 }
466 } else {
467 return false
468 }
469}