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