Diff
diff --git a/docs/utils/index.html b/docs/utils/index.html
index d083b6c..b558c5a 100644
--- a/docs/utils/index.html
+++ b/docs/utils/index.html
@@ -53,63 +53,63 @@
- 7 "reflect"
- 8 "regexp"
- 9 "strconv"
- 10 "strings"
- 11
- 12 "golang.org/x/net/html"
- 13)
- 14
- 15func GetXY(n *element.Node, state *map[string]element.State) (float32, float32) {
- 16 s := *state
- 17 // self := s[n.Properties.Id]
- 18
- 19 offsetX := float32(0)
- 20 offsetY := float32(0)
- 21
- 22 if n.Parent != nil {
- 23 parent := s[n.Parent.Properties.Id]
- 24 // x, y := GetXY(n.Parent, state)
- 25 offsetX += parent.Border.Width + parent.Padding.Left
- 26 offsetY += parent.Border.Width + parent.Padding.Top
- 27 }
- 28
- 29 return offsetX, offsetY
- 30}
- 31
- 32type WidthHeight struct {
- 33 Width float32
- 34 Height float32
- 35}
- 36
- 37func GetWH(n element.Node, state *map[string]element.State) WidthHeight {
- 38 s := *state
- 39 self := s[n.Properties.Id]
- 40 parent := element.State{}
- 41
- 42 if n.Style == nil {
- 43 n.Style = map[string]string{}
- 44 }
- 45
- 46 fs := self.EM
- 47
- 48 var pwh WidthHeight
- 49 if n.Parent != nil {
- 50 parent = s[n.Parent.Properties.Id]
- 51 pwh = GetWH(*n.Parent, state)
- 52 } else {
- 53 pwh = WidthHeight{}
- 54 if n.Style["width"] != "" {
- 55 str := strings.TrimSuffix(n.Style["width"], "px")
- 56 // Convert the string to float32
- 57 f, _ := strconv.ParseFloat(str, 32)
- 58 pwh.Width = float32(f)
- 59 }
- 60
- 61 if n.Style["height"] != "" {
- 62 str := strings.TrimSuffix(n.Style["height"], "px")
- 63 // Convert the string to float32
- 64 f, _ := strconv.ParseFloat(str, 32)
- 65 pwh.Height = float32(f)
- 66 }
- 67 }
- 68
- 69 wStyle := n.Style["width"]
+ 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
+ 40func GetXY(n *element.Node, state *map[string]element.State) (float32, float32) {
+ 41 s := *state
+ 42 // self := s[n.Properties.Id]
+ 43
+ 44 offsetX := float32(0)
+ 45 offsetY := float32(0)
+ 46
+ 47 if n.Parent != nil {
+ 48 parent := s[n.Parent.Properties.Id]
+ 49 // x, y := GetXY(n.Parent, state)
+ 50 offsetX += parent.Border.Width + parent.Padding.Left
+ 51 offsetY += parent.Border.Width + parent.Padding.Top
+ 52 }
+ 53
+ 54 return offsetX, offsetY
+ 55}
+ 56
+ 57type WidthHeight struct {
+ 58 Width float32
+ 59 Height float32
+ 60}
+ 61
+ 62func GetWH(n element.Node, state *map[string]element.State) WidthHeight {
+ 63 s := *state
+ 64 self := s[n.Properties.Id]
+ 65 parent := element.State{}
+ 66
+ 67 if n.Style == nil {
+ 68 n.Style = map[string]string{}
+ 69 }
@@ -117,25 +117,25 @@
- 71 if wStyle == "" && n.Style["display"] != "inline" {
- 72 wStyle = "100%"
- 73 }
- 74
- 75 width, _ := ConvertToPixels(wStyle, fs, pwh.Width)
- 76 height, _ := ConvertToPixels(n.Style["height"], fs, pwh.Height)
- 77
- 78 if n.Style["min-width"] != "" {
- 79 minWidth, _ := ConvertToPixels(n.Style["min-width"], fs, pwh.Width)
- 80 width = Max(width, minWidth)
- 81 }
- 82
- 83 if n.Style["max-width"] != "" {
- 84 maxWidth, _ := ConvertToPixels(n.Style["max-width"], fs, pwh.Width)
- 85 width = Min(width, maxWidth)
- 86 }
- 87 if n.Style["min-height"] != "" {
- 88 minHeight, _ := ConvertToPixels(n.Style["min-height"], fs, pwh.Height)
- 89 height = Max(height, minHeight)
- 90 }
- 91
- 92 if n.Style["max-height"] != "" {
- 93 maxHeight, _ := ConvertToPixels(n.Style["max-height"], fs, pwh.Height)
- 94 height = Min(height, maxHeight)
- 95 }
+ 71 fs := self.EM
+ 72
+ 73 var pwh WidthHeight
+ 74 if n.Parent != nil {
+ 75 parent = s[n.Parent.Properties.Id]
+ 76 pwh = GetWH(*n.Parent, state)
+ 77 } else {
+ 78 pwh = WidthHeight{}
+ 79 if n.Style["width"] != "" {
+ 80 str := strings.TrimSuffix(n.Style["width"], "px")
+ 81 // Convert the string to float32
+ 82 f, _ := strconv.ParseFloat(str, 32)
+ 83 pwh.Width = float32(f)
+ 84 }
+ 85
+ 86 if n.Style["height"] != "" {
+ 87 str := strings.TrimSuffix(n.Style["height"], "px")
+ 88 // Convert the string to float32
+ 89 f, _ := strconv.ParseFloat(str, 32)
+ 90 pwh.Height = float32(f)
+ 91 }
+ 92 }
+ 93
+ 94 width, _ := ConvertToPixels(n.Style["width"], fs, pwh.Width)
+ 95 height, _ := ConvertToPixels(n.Style["height"], fs, pwh.Height)
@@ -143,3 +143,3 @@
- 97 wh := WidthHeight{
- 98 Width: width,
- 99 Height: height,
+ 97 if n.Style["min-width"] != "" {
+ 98 minWidth, _ := ConvertToPixels(n.Style["min-width"], fs, pwh.Width)
+ 99 width = Max(width, minWidth)
@@ -148,12 +148,12 @@
-102 if n.Parent != nil {
-103 wh.Width += self.Padding.Left + self.Padding.Right
-104 wh.Height += self.Padding.Top + self.Padding.Bottom
-105 // fmt.Println(n.Properties.Id, wh, p)
-106 }
-107
-108 if wStyle == "100%" {
-109 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))
-110 }
-111
-112 if n.Style["height"] == "100%" {
-113 wh.Height -= (self.Margin.Top + self.Margin.Bottom) + (parent.Padding.Top + parent.Padding.Bottom)
+102 if n.Style["max-width"] != "" {
+103 maxWidth, _ := ConvertToPixels(n.Style["max-width"], fs, pwh.Width)
+104 width = Min(width, maxWidth)
+105 }
+106 if n.Style["min-height"] != "" {
+107 minHeight, _ := ConvertToPixels(n.Style["min-height"], fs, pwh.Height)
+108 height = Max(height, minHeight)
+109 }
+110
+111 if n.Style["max-height"] != "" {
+112 maxHeight, _ := ConvertToPixels(n.Style["max-height"], fs, pwh.Height)
+113 height = Min(height, maxHeight)
@@ -162,41 +162,41 @@
-116 return wh
-117}
-118
-119func GetMP(n element.Node, wh WidthHeight, state *map[string]element.State, t string) element.MarginPadding {
-120 s := *state
-121 self := s[n.Properties.Id]
-122 fs := self.EM
-123 m := element.MarginPadding{}
-124
-125 if n.Style[t] != "" {
-126 left, right, top, bottom := convertMarginToIndividualProperties(n.Style[t])
-127 if n.Style[t+"-left"] == "" {
-128 n.Style[t+"-left"] = left
-129 }
-130 if n.Style[t+"-right"] == "" {
-131 n.Style[t+"-right"] = right
-132 }
-133 if n.Style[t+"-top"] == "" {
-134 n.Style[t+"-top"] = top
-135 }
-136 if n.Style[t+"-bottom"] == "" {
-137 n.Style[t+"-bottom"] = bottom
-138 }
-139 }
-140 if n.Style[t+"-left"] != "" || n.Style[t+"-right"] != "" {
-141 l, _ := ConvertToPixels(n.Style[t+"-left"], fs, wh.Width)
-142 r, _ := ConvertToPixels(n.Style[t+"-right"], fs, wh.Width)
-143 m.Left = l
-144 m.Right = r
-145 }
-146 if n.Style[t+"-top"] != "" || n.Style[t+"-bottom"] != "" {
-147 top, _ := ConvertToPixels(n.Style[t+"-top"], fs, wh.Height)
-148 b, _ := ConvertToPixels(n.Style[t+"-bottom"], fs, wh.Height)
-149 m.Top = top
-150 m.Bottom = b
-151 }
-152 if t == "margin" {
-153 if n.Style["margin"] == "auto" && n.Style["margin-left"] == "" && n.Style["margin-right"] == "" {
-154 pwh := GetWH(*n.Parent, state)
-155 m.Left = Max((pwh.Width-wh.Width)/2, 0)
-156 m.Right = m.Left
+116 wh := WidthHeight{
+117 Width: width,
+118 Height: height,
+119 }
+120
+121 if n.Parent != nil {
+122 wh.Width += self.Padding.Left + self.Padding.Right
+123 wh.Height += self.Padding.Top + self.Padding.Bottom
+124 // fmt.Println(n.Properties.Id, wh, p)
+125 }
+126
+127 if n.Style["width"] == "100%" {
+128 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))
+129 }
+130
+131 if n.Style["height"] == "100%" {
+132 wh.Height -= (self.Margin.Top + self.Margin.Bottom) + (parent.Padding.Top + parent.Padding.Bottom)
+133 }
+134
+135 return wh
+136}
+137
+138func GetMP(n element.Node, wh WidthHeight, state *map[string]element.State, t string) element.MarginPadding {
+139 s := *state
+140 self := s[n.Properties.Id]
+141 fs := self.EM
+142 m := element.MarginPadding{}
+143
+144 if n.Style[t] != "" {
+145 left, right, top, bottom := convertMarginToIndividualProperties(n.Style[t])
+146 if n.Style[t+"-left"] == "" {
+147 n.Style[t+"-left"] = left
+148 }
+149 if n.Style[t+"-right"] == "" {
+150 n.Style[t+"-right"] = right
+151 }
+152 if n.Style[t+"-top"] == "" {
+153 n.Style[t+"-top"] = top
+154 }
+155 if n.Style[t+"-bottom"] == "" {
+156 n.Style[t+"-bottom"] = bottom
@@ -205,82 +205,82 @@
-159
-160 return m
-161}
-162
-163func convertMarginToIndividualProperties(margin string) (string, string, string, string) {
-164 // Remove extra whitespace
-165 margin = strings.TrimSpace(margin)
-166
-167 if margin == "" {
-168 return "0px", "0px", "0px", "0px"
-169 }
-170
-171 // Regular expression to match values with optional units
-172 re := regexp.MustCompile(`(-?\d+(\.\d+)?)(\w*|\%)?`)
-173
-174 // Extract numerical values from the margin property
-175 matches := re.FindAllStringSubmatch(margin, -1)
-176
-177 // Initialize variables for individual margins
-178 var left, right, top, bottom string
-179
-180 switch len(matches) {
-181 case 1:
-182 // If only one value is provided, apply it to all margins
-183 left = matches[0][0]
-184 right = matches[0][0]
-185 top = matches[0][0]
-186 bottom = matches[0][0]
-187 case 2:
-188 // If two values are provided, apply the first to top and bottom, and the second to left and right
-189 top = matches[0][0]
-190 bottom = matches[0][0]
-191 left = matches[1][0]
-192 right = matches[1][0]
-193 case 3:
-194 // If three values are provided, apply the first to top, the second to left and right, and the third to bottom
-195 top = matches[0][0]
-196 left = matches[1][0]
-197 right = matches[1][0]
-198 bottom = matches[2][0]
-199 case 4:
-200 // If four values are provided, apply them to top, right, bottom, and left, respectively
-201 top = matches[0][0]
-202 right = matches[1][0]
-203 bottom = matches[2][0]
-204 left = matches[3][0]
-205 }
-206
-207 return left, right, top, bottom
-208}
-209
-210// ConvertToPixels converts a CSS measurement to pixels.
-211func ConvertToPixels(value string, em, max float32) (float32, error) {
-212 unitFactors := map[string]float32{
-213 "px": 1,
-214 "em": em,
-215 "pt": 1.33,
-216 "pc": 16.89,
-217 "%": max / 100,
-218 "vw": max / 100,
-219 "vh": max / 100,
-220 "cm": 37.79527559,
-221 "in": 96,
-222 }
-223
-224 if strings.HasPrefix(value, "calc(") {
-225 // Handle calculation expression
-226 calcResult, err := evaluateCalcExpression(value[5:len(value)-1], em, max)
-227 if err != nil {
-228 return 0, err
-229 }
-230 return calcResult, nil
-231 } else {
-232 // Extract numeric value and unit
-233 for k, v := range unitFactors {
-234 if strings.HasSuffix(value, k) {
-235 cutStr, _ := strings.CutSuffix(value, k)
-236 numericValue, _ := strconv.ParseFloat(cutStr, 64)
-237 return float32(numericValue) * v, nil
-238 }
-239 }
-240 return 0, fmt.Errorf("unable to parse value")
+159 if n.Style[t+"-left"] != "" || n.Style[t+"-right"] != "" {
+160 l, _ := ConvertToPixels(n.Style[t+"-left"], fs, wh.Width)
+161 r, _ := ConvertToPixels(n.Style[t+"-right"], fs, wh.Width)
+162 m.Left = l
+163 m.Right = r
+164 }
+165 if n.Style[t+"-top"] != "" || n.Style[t+"-bottom"] != "" {
+166 top, _ := ConvertToPixels(n.Style[t+"-top"], fs, wh.Height)
+167 b, _ := ConvertToPixels(n.Style[t+"-bottom"], fs, wh.Height)
+168 m.Top = top
+169 m.Bottom = b
+170 }
+171 if t == "margin" {
+172 if n.Style["margin"] == "auto" && n.Style["margin-left"] == "" && n.Style["margin-right"] == "" {
+173 pwh := GetWH(*n.Parent, state)
+174 m.Left = Max((pwh.Width-wh.Width)/2, 0)
+175 m.Right = m.Left
+176 }
+177 }
+178
+179 return m
+180}
+181
+182func convertMarginToIndividualProperties(margin string) (string, string, string, string) {
+183 // Remove extra whitespace
+184 margin = strings.TrimSpace(margin)
+185
+186 if margin == "" {
+187 return "0px", "0px", "0px", "0px"
+188 }
+189
+190 // Regular expression to match values with optional units
+191 re := regexp.MustCompile(`(-?\d+(\.\d+)?)(\w*|\%)?`)
+192
+193 // Extract numerical values from the margin property
+194 matches := re.FindAllStringSubmatch(margin, -1)
+195
+196 // Initialize variables for individual margins
+197 var left, right, top, bottom string
+198
+199 switch len(matches) {
+200 case 1:
+201 // If only one value is provided, apply it to all margins
+202 left = matches[0][0]
+203 right = matches[0][0]
+204 top = matches[0][0]
+205 bottom = matches[0][0]
+206 case 2:
+207 // If two values are provided, apply the first to top and bottom, and the second to left and right
+208 top = matches[0][0]
+209 bottom = matches[0][0]
+210 left = matches[1][0]
+211 right = matches[1][0]
+212 case 3:
+213 // If three values are provided, apply the first to top, the second to left and right, and the third to bottom
+214 top = matches[0][0]
+215 left = matches[1][0]
+216 right = matches[1][0]
+217 bottom = matches[2][0]
+218 case 4:
+219 // If four values are provided, apply them to top, right, bottom, and left, respectively
+220 top = matches[0][0]
+221 right = matches[1][0]
+222 bottom = matches[2][0]
+223 left = matches[3][0]
+224 }
+225
+226 return left, right, top, bottom
+227}
+228
+229// ConvertToPixels converts a CSS measurement to pixels.
+230func ConvertToPixels(value string, em, max float32) (float32, error) {
+231 unitFactors := map[string]float32{
+232 "px": 1,
+233 "em": em,
+234 "pt": 1.33,
+235 "pc": 16.89,
+236 "%": max / 100,
+237 "vw": max / 100,
+238 "vh": max / 100,
+239 "cm": 37.79527559,
+240 "in": 96,
@@ -289,37 +289,37 @@
-243}
-244
-245// evaluateCalcExpression recursively evaluates 'calc()' expressions
-246func evaluateCalcExpression(expression string, em, max float32) (float32, error) {
-247 terms := strings.FieldsFunc(expression, func(c rune) bool {
-248 return c == '+' || c == '-' || c == '*' || c == '/'
-249 })
-250
-251 operators := strings.FieldsFunc(expression, func(c rune) bool {
-252 return c != '+' && c != '-' && c != '*' && c != '/'
-253 })
-254
-255 var result float32
-256
-257 for i, term := range terms {
-258 value, err := ConvertToPixels(strings.TrimSpace(term), em, max)
-259 if err != nil {
-260 return 0, err
-261 }
-262
-263 if i > 0 {
-264 switch operators[i-1] {
-265 case "+":
-266 result += value
-267 case "-":
-268 result -= value
-269 case "*":
-270 result *= value
-271 case "/":
-272 if value != 0 {
-273 result /= value
-274 } else {
-275 return 0, fmt.Errorf("division by zero in 'calc()' expression")
-276 }
-277 }
-278 } else {
-279 result = value
+243 if strings.HasPrefix(value, "calc(") {
+244 // Handle calculation expression
+245 calcResult, err := evaluateCalcExpression(value[5:len(value)-1], em, max)
+246 if err != nil {
+247 return 0, err
+248 }
+249 return calcResult, nil
+250 } else {
+251 // Extract numeric value and unit
+252 for k, v := range unitFactors {
+253 if strings.HasSuffix(value, k) {
+254 cutStr, _ := strings.CutSuffix(value, k)
+255 numericValue, _ := strconv.ParseFloat(cutStr, 64)
+256 return float32(numericValue) * v, nil
+257 }
+258 }
+259 return 0, fmt.Errorf("unable to parse value")
+260 }
+261
+262}
+263
+264// evaluateCalcExpression recursively evaluates 'calc()' expressions
+265func evaluateCalcExpression(expression string, em, max float32) (float32, error) {
+266 terms := strings.FieldsFunc(expression, func(c rune) bool {
+267 return c == '+' || c == '-' || c == '*' || c == '/'
+268 })
+269
+270 operators := strings.FieldsFunc(expression, func(c rune) bool {
+271 return c != '+' && c != '-' && c != '*' && c != '/'
+272 })
+273
+274 var result float32
+275
+276 for i, term := range terms {
+277 value, err := ConvertToPixels(strings.TrimSpace(term), em, max)
+278 if err != nil {
+279 return 0, err
@@ -327,35 +327,35 @@
-281 }
-282
-283 return result, nil
-284}
-285
-286func Merge(m1, m2 map[string]string) map[string]string {
-287 // Create a new map and copy m1 into it
-288 result := make(map[string]string)
-289 for k, v := range m1 {
-290 result[k] = v
-291 }
-292
-293 // Merge m2 into the new map
-294 for k, v := range m2 {
-295 result[k] = v
-296 }
-297
-298 return result
-299}
-300
-301func Max(a, b float32) float32 {
-302 if a > b {
-303 return a
-304 } else {
-305 return b
-306 }
-307}
-308
-309func Min(a, b float32) float32 {
-310 if a < b {
-311 return a
-312 } else {
-313 return b
-314 }
-315}
+281
+282 if i > 0 {
+283 switch operators[i-1] {
+284 case "+":
+285 result += value
+286 case "-":
+287 result -= value
+288 case "*":
+289 result *= value
+290 case "/":
+291 if value != 0 {
+292 result /= value
+293 } else {
+294 return 0, fmt.Errorf("division by zero in 'calc()' expression")
+295 }
+296 }
+297 } else {
+298 result = value
+299 }
+300 }
+301
+302 return result, nil
+303}
+304
+305func GetTextBounds(text string, fontSize, width, height float32) (float32, float32) {
+306 w := float32(len(text) * int(fontSize))
+307 h := fontSize
+308 if width > 0 && height > 0 {
+309 if w > width {
+310 height = Max(height, float32(math.Ceil(float64(w/width)))*h)
+311 }
+312 return width, height
+313 } else {
+314 return w, h
+315 }
@@ -363,7 +363,7 @@
-317// getStructField uses reflection to get the value of a struct field by name
-318func GetStructField(data interface{}, fieldName string) (interface{}, error) {
-319 val := reflect.ValueOf(data)
-320
-321 // Make sure we have a pointer to a struct
-322 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
-323 return nil, fmt.Errorf("expected a pointer to a struct")
+317}
+318
+319func Merge(m1, m2 map[string]string) map[string]string {
+320 // Create a new map and copy m1 into it
+321 result := make(map[string]string)
+322 for k, v := range m1 {
+323 result[k] = v
@@ -372,7 +372,7 @@
-326 // Get the struct field by name
-327 field := val.Elem().FieldByName(fieldName)
-328
-329 // Check if the field exists
-330 if !field.IsValid() {
-331 return nil, fmt.Errorf("field not found: %s", fieldName)
-332 }
+326 // Merge m2 into the new map
+327 for k, v := range m2 {
+328 result[k] = v
+329 }
+330
+331 return result
+332}
@@ -380,13 +380,13 @@
-334 return field.Interface(), nil
-335}
-336
-337func SetStructFieldValue(data interface{}, fieldName string, newValue interface{}) error {
-338 val := reflect.ValueOf(data)
-339
-340 // Make sure we have a pointer to a struct
-341 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
-342 return fmt.Errorf("expected a pointer to a struct")
-343 }
-344
-345 // Get the struct field by name
-346 field := val.Elem().FieldByName(fieldName)
+334func ExMerge(m1, m2 map[string]string) map[string]string {
+335 // Create a new map and copy m1 into it
+336 result := make(map[string]string)
+337 for k, v := range m1 {
+338 result[k] = v
+339 }
+340
+341 // Merge m2 into the new map only if the key is not already present
+342 for k, v := range m2 {
+343 if result[k] == "" {
+344 result[k] = v
+345 }
+346 }
@@ -394,8 +394,8 @@
-348 // Check if the field exists
-349 if !field.IsValid() {
-350 return fmt.Errorf("field not found: %s", fieldName)
-351 }
-352
-353 // Check if the new value type is assignable to the field type
-354 if !reflect.ValueOf(newValue).Type().AssignableTo(field.Type()) {
-355 return fmt.Errorf("incompatible types for field %s", fieldName)
+348 return result
+349}
+350
+351func Max(a, b float32) float32 {
+352 if a > b {
+353 return a
+354 } else {
+355 return b
@@ -403,41 +403,41 @@
-357
-358 // Set the new value
-359 field.Set(reflect.ValueOf(newValue))
-360
-361 return nil
-362}
-363
-364// func Check(e error) {
-365// if e != nil {
-366// panic(e)
-367// }
-368// }
-369
-370func GetInnerText(n *html.Node) string {
-371 var result strings.Builder
-372
-373 var getText func(*html.Node)
-374 getText = func(n *html.Node) {
-375 // Skip processing if the node is a head tag
-376 if n.Type == html.ElementNode && n.Data == "head" {
-377 return
-378 }
-379
-380 // If it's a text node, append its content
-381 if n.Type == html.TextNode {
-382 result.WriteString(n.Data)
-383 }
-384
-385 // Traverse child nodes recursively
-386 for c := n.FirstChild; c != nil; c = c.NextSibling {
-387 getText(c)
-388 }
-389 }
-390
-391 getText(n)
-392
-393 return result.String()
-394}
-395
-396func GetPositionOffsetNode(n *element.Node) *element.Node {
-397 pos := n.Style["position"]
+357}
+358
+359func Min(a, b float32) float32 {
+360 if a < b {
+361 return a
+362 } else {
+363 return b
+364 }
+365}
+366
+367func FindRelative(n *element.Node, styleMap map[string]map[string]string) (float32, float32) {
+368 pos := styleMap[n.Properties.Id]["position"]
+369
+370 if pos == "relative" {
+371 x, _ := strconv.ParseFloat(styleMap[n.Properties.Id]["x"], 32)
+372 y, _ := strconv.ParseFloat(styleMap[n.Properties.Id]["y"], 32)
+373 return float32(x), float32(y)
+374 } else {
+375 if n.Parent != nil {
+376 x, y := FindRelative(n.Parent, styleMap)
+377 return x, y
+378 } else {
+379 return 0, 0
+380 }
+381 }
+382}
+383
+384func ParseFloat(str string, def float32) float32 {
+385 var a float32
+386 if str == "" {
+387 a = 0
+388 } else {
+389 v, _ := strconv.ParseFloat(str, 32)
+390 a = float32(v)
+391 }
+392 return a
+393}
+394
+395// getStructField uses reflection to get the value of a struct field by name
+396func GetStructField(data interface{}, fieldName string) (interface{}, error) {
+397 val := reflect.ValueOf(data)
@@ -445,26 +445,26 @@
-399 if pos == "relative" {
-400 return n
-401 } else {
-402 if n.Parent.TagName != "ROOT" {
-403 if n.Parent.Style != nil {
-404 return GetPositionOffsetNode(n.Parent)
-405 } else {
-406 return nil
-407 }
-408 } else {
-409 return n.Parent
-410 }
-411 }
-412}
-413
-414func IsParent(n element.Node, name string) bool {
-415 if n.Parent.TagName != "ROOT" {
-416 if n.Parent.TagName == name {
-417 return true
-418 } else {
-419 return IsParent(*n.Parent, name)
-420 }
-421 } else {
-422 return false
-423 }
-424}
+399 // Make sure we have a pointer to a struct
+400 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
+401 return nil, fmt.Errorf("expected a pointer to a struct")
+402 }
+403
+404 // Get the struct field by name
+405 field := val.Elem().FieldByName(fieldName)
+406
+407 // Check if the field exists
+408 if !field.IsValid() {
+409 return nil, fmt.Errorf("field not found: %s", fieldName)
+410 }
+411
+412 return field.Interface(), nil
+413}
+414
+415func SetStructFieldValue(data interface{}, fieldName string, newValue interface{}) error {
+416 val := reflect.ValueOf(data)
+417
+418 // Make sure we have a pointer to a struct
+419 if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
+420 return fmt.Errorf("expected a pointer to a struct")
+421 }
+422
+423 // Get the struct field by name
+424 field := val.Elem().FieldByName(fieldName)
@@ -472,12 +472,12 @@
-426func ChildrenHaveText(n *element.Node) bool {
-427 for _, child := range n.Children {
-428 if len(strings.TrimSpace(child.InnerText)) != 0 {
-429 return true
-430 }
-431 // Recursively check if any child nodes have text
-432 if ChildrenHaveText(&child) {
-433 return true
-434 }
-435 }
-436 return false
-437}
+426 // Check if the field exists
+427 if !field.IsValid() {
+428 return fmt.Errorf("field not found: %s", fieldName)
+429 }
+430
+431 // Check if the new value type is assignable to the field type
+432 if !reflect.ValueOf(newValue).Type().AssignableTo(field.Type()) {
+433 return fmt.Errorf("incompatible types for field %s", fieldName)
+434 }
+435
+436 // Set the new value
+437 field.Set(reflect.ValueOf(newValue))
@@ -485,11 +485,11 @@
-439func NodeToHTML(node element.Node) (string, string) {
-440 // if node.TagName == "notaspan" {
-441 // return node.InnerText + " ", ""
-442 // }
-443
-444 var buffer bytes.Buffer
-445 buffer.WriteString("<" + node.TagName)
-446
-447 if node.Properties.Editable {
-448 buffer.WriteString(" contentEditable=\"true\"")
-449 }
+439 return nil
+440}
+441
+442func Check(e error) {
+443 if e != nil {
+444 panic(e)
+445 }
+446}
+447
+448func GetInnerText(n *html.Node) string {
+449 var result strings.Builder
@@ -497,19 +497,19 @@
-451 // Add ID if present
-452 if node.Id != "" {
-453 buffer.WriteString(" id=\"" + node.Id + "\"")
-454 }
-455
-456 // Add ID if present
-457 if node.Title != "" {
-458 buffer.WriteString(" title=\"" + node.Title + "\"")
-459 }
-460
-461 // Add ID if present
-462 if node.Src != "" {
-463 buffer.WriteString(" src=\"" + node.Src + "\"")
-464 }
-465
-466 // Add ID if present
-467 if node.Href != "" {
-468 buffer.WriteString(" href=\"" + node.Href + "\"")
-469 }
+451 var getText func(*html.Node)
+452 getText = func(n *html.Node) {
+453 // Skip processing if the node is a head tag
+454 if n.Type == html.ElementNode && n.Data == "head" {
+455 return
+456 }
+457
+458 // If it's a text node, append its content
+459 if n.Type == html.TextNode {
+460 result.WriteString(n.Data)
+461 }
+462
+463 // Traverse child nodes recursively
+464 for c := n.FirstChild; c != nil; c = c.NextSibling {
+465 getText(c)
+466 }
+467 }
+468
+469 getText(n)
@@ -517,27 +517,27 @@
-471 // Add class list if present
-472 if len(node.ClassList.Classes) > 0 || node.ClassList.Value != "" {
-473 classes := ""
-474 for _, v := range node.ClassList.Classes {
-475 if string(v[0]) != ":" {
-476 classes += v + " "
-477 }
-478 }
-479 classes = strings.TrimSpace(classes)
-480 if len(classes) > 0 {
-481 buffer.WriteString(" class=\"" + classes + "\"")
-482 }
-483 }
-484
-485 // Add style if present
-486 if len(node.Style) > 0 {
-487
-488 style := ""
-489 for key, value := range node.Style {
-490 if key != "inlineText" {
-491 style += key + ":" + value + ";"
-492 }
-493 }
-494 style = strings.TrimSpace(style)
-495
-496 if len(style) > 0 {
-497 buffer.WriteString(" style=\"" + style + "\"")
+471 return result.String()
+472}
+473
+474func GetPositionOffsetNode(n *element.Node) *element.Node {
+475 pos := n.Style["position"]
+476
+477 if pos == "relative" {
+478 return n
+479 } else {
+480 if n.Parent.TagName != "ROOT" {
+481 if n.Parent.Style != nil {
+482 return GetPositionOffsetNode(n.Parent)
+483 } else {
+484 return nil
+485 }
+486 } else {
+487 return n.Parent
+488 }
+489 }
+490}
+491
+492func IsParent(n element.Node, name string) bool {
+493 if n.Parent.TagName != "ROOT" {
+494 if n.Parent.TagName == name {
+495 return true
+496 } else {
+497 return IsParent(*n.Parent, name)
@@ -545,14 +545,14 @@
-499 }
-500
-501 // Add other attributes if present
-502 for key, value := range node.Attribute {
-503 if strings.TrimSpace(value) != "" {
-504 buffer.WriteString(" " + key + "=\"" + value + "\"")
-505 }
-506 }
-507
-508 buffer.WriteString(">")
-509
-510 // Add inner text if present
-511 if node.InnerText != "" && !ChildrenHaveText(&node) {
-512 buffer.WriteString(node.InnerText)
+499 } else {
+500 return false
+501 }
+502}
+503
+504func ChildrenHaveText(n *element.Node) bool {
+505 for _, child := range n.Children {
+506 if len(strings.TrimSpace(child.InnerText)) != 0 {
+507 return true
+508 }
+509 // Recursively check if any child nodes have text
+510 if ChildrenHaveText(&child) {
+511 return true
+512 }
@@ -560 +560 @@
-514 return buffer.String(), "</" + node.TagName + ">"
+514 return false
@@ -563,10 +563,10 @@
-517func OuterHTML(node element.Node) string {
-518 var buffer bytes.Buffer
-519
-520 tag, closing := NodeToHTML(node)
-521
-522 buffer.WriteString(tag)
-523
-524 // Recursively add children
-525 for _, child := range node.Children {
-526 buffer.WriteString(OuterHTML(child))
+517func NodeToHTML(node element.Node) (string, string) {
+518 // if node.TagName == "notaspan" {
+519 // return node.InnerText + " ", ""
+520 // }
+521
+522 var buffer bytes.Buffer
+523 buffer.WriteString("<" + node.TagName)
+524
+525 if node.Properties.Editable {
+526 buffer.WriteString(" contentEditable=\"true\"")
@@ -575,4 +575,4 @@
-529 buffer.WriteString(closing)
-530
-531 return buffer.String()
-532}
+529 // Add ID if present
+530 if node.Id != "" {
+531 buffer.WriteString(" id=\"" + node.Id + "\"")
+532 }
@@ -580,8 +580,86 @@
-534func InnerHTML(node element.Node) string {
-535 var buffer bytes.Buffer
-536 // Recursively add children
-537 for _, child := range node.Children {
-538 buffer.WriteString(OuterHTML(child))
-539 }
-540 return buffer.String()
-541}
+534 // Add ID if present
+535 if node.Title != "" {
+536 buffer.WriteString(" title=\"" + node.Title + "\"")
+537 }
+538
+539 // Add ID if present
+540 if node.Src != "" {
+541 buffer.WriteString(" src=\"" + node.Src + "\"")
+542 }
+543
+544 // Add ID if present
+545 if node.Href != "" {
+546 buffer.WriteString(" href=\"" + node.Href + "\"")
+547 }
+548
+549 // Add class list if present
+550 if len(node.ClassList.Classes) > 0 || node.ClassList.Value != "" {
+551 classes := ""
+552 for _, v := range node.ClassList.Classes {
+553 if string(v[0]) != ":" {
+554 classes += v + " "
+555 }
+556 }
+557 classes = strings.TrimSpace(classes)
+558 if len(classes) > 0 {
+559 buffer.WriteString(" class=\"" + classes + "\"")
+560 }
+561 }
+562
+563 // Add style if present
+564 if len(node.Style) > 0 {
+565
+566 style := ""
+567 for key, value := range node.Style {
+568 if key != "inlineText" {
+569 style += key + ":" + value + ";"
+570 }
+571 }
+572 style = strings.TrimSpace(style)
+573
+574 if len(style) > 0 {
+575 buffer.WriteString(" style=\"" + style + "\"")
+576 }
+577 }
+578
+579 // Add other attributes if present
+580 for key, value := range node.Attribute {
+581 if strings.TrimSpace(value) != "" {
+582 buffer.WriteString(" " + key + "=\"" + value + "\"")
+583 }
+584 }
+585
+586 buffer.WriteString(">")
+587
+588 // Add inner text if present
+589 if node.InnerText != "" && !ChildrenHaveText(&node) {
+590 buffer.WriteString(node.InnerText)
+591 }
+592 return buffer.String(), "</" + node.TagName + ">"
+593}
+594
+595func OuterHTML(node element.Node) string {
+596 var buffer bytes.Buffer
+597
+598 tag, closing := NodeToHTML(node)
+599
+600 buffer.WriteString(tag)
+601
+602 // Recursively add children
+603 for _, child := range node.Children {
+604 buffer.WriteString(OuterHTML(child))
+605 }
+606
+607 buffer.WriteString(closing)
+608
+609 return buffer.String()
+610}
+611
+612func InnerHTML(node element.Node) string {
+613 var buffer bytes.Buffer
+614 // Recursively add children
+615 for _, child := range node.Children {
+616 buffer.WriteString(OuterHTML(child))
+617 }
+618 return buffer.String()
+619}