Diff
diff --git a/docs/utils/index.html b/docs/utils/index.html
index 3a1989e..75d1001 100644
--- a/docs/utils/index.html
+++ b/docs/utils/index.html
@@ -21,0 +22,2 @@
+# ComputeStyleString?(go)
+# ComputeStyleMap?(go)
@@ -24,0 +27 @@
+# SetWH?(go)
@@ -57,66 +60,66 @@
- 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)
+ 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 }
@@ -124,10 +127,10 @@
- 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}
+ 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)
@@ -135,39 +138,39 @@
- 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
+ 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)
@@ -175,18 +178,18 @@
-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 }
+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}
@@ -194,5 +197,5 @@
-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)
+153func GetMP(n element.Node, t string) MarginPadding {
+154 fs := font.GetFontSize(n.Style)
+155 m := MarginPadding{}
+156
+157 wh := GetWH(n)
@@ -200,41 +203,41 @@
-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}
+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)
@@ -242,12 +245,12 @@
-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 }
+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
@@ -255,29 +258,29 @@
-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 })
+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}
@@ -285,46 +288,46 @@
-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 }
+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 })
@@ -332 +335 @@
-291}
+291 var result float32
@@ -334,28 +337,28 @@
-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 }
+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}
@@ -363,35 +366,35 @@
-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}
+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 }
@@ -399,50 +402,50 @@
-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)
+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)
@@ -450,6 +453,6 @@
-409
-410 // Set the new value
-411 field.Set(reflect.ValueOf(newValue))
-412
-413 return nil
-414}
+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)
@@ -457,3 +460,3 @@
-416func Check(e error) {
-417 if e != nil {
-418 panic(e)
+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")
@@ -461,37 +464,37 @@
-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 }
+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
@@ -500,11 +503,54 @@
-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}
+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}