diff --git a/utils.go b/utils.go
deleted file mode 100644
index 4a33e95..0000000
--- a/utils.go
+++ /dev/null
@@ -1,397 +0,0 @@
-package grim
-
-import (
- "fmt"
- ic "image/color"
- "strconv"
- "strings"
-
- "golang.org/x/net/html"
-)
-
-func GetXY(n Node, state map[string]State) (float32, float32) {
- s := state
- // self := s[n.Properties.Id]
-
- offsetX := float32(0)
- offsetY := float32(0)
- p := n.Parent()
- if p != nil {
- parent := s[p.Properties.Id]
- offsetX += parent.Border.Left.Width + parent.Padding.Left
- offsetY += parent.Border.Top.Width + parent.Padding.Top
- }
-
- return offsetX, offsetY
-}
-
-type BoxSizing struct {
- Width float32
- Height float32
-}
-
-func FindBounds(n Node, style map[string]string, state *map[string]State) (BoxSizing, BoxSpacing, BoxSpacing) {
- s := *state
- self := s[n.Properties.Id]
- var parent State
-
- fs := self.EM
-
- p := n.Parent()
- var pwh BoxSizing
- if p != nil {
- parent = s[p.Properties.Id]
- pwh = BoxSizing{
- Width: parent.Width,
- Height: parent.Height,
- }
- } else {
- pwh = BoxSizing{}
- if width, exists := style["width"]; exists {
- if f, err := strconv.ParseFloat(strings.TrimSuffix(width, "px"), 32); err == nil {
- pwh.Width = float32(f)
- }
- }
- if height, exists := style["height"]; exists {
- if f, err := strconv.ParseFloat(strings.TrimSuffix(height, "px"), 32); err == nil {
- pwh.Height = float32(f)
- }
- }
- }
-
- wStyle := style["width"]
-
- if wStyle == "" && style["display"] != "inline" {
- wStyle = "100%"
- }
-
- width := ConvertToPixels(wStyle, fs, pwh.Width)
- height := ConvertToPixels(style["height"], fs, pwh.Height)
-
- if minWidth, exists := style["min-width"]; exists {
- width = Max(width, ConvertToPixels(minWidth, fs, pwh.Width))
- }
- if maxWidth, exists := style["max-width"]; exists {
- width = Min(width, ConvertToPixels(maxWidth, fs, pwh.Width))
- }
- if minHeight, exists := style["min-height"]; exists {
- height = Max(height, ConvertToPixels(minHeight, fs, pwh.Height))
- }
- if maxHeight, exists := style["max-height"]; exists {
- height = Min(height, ConvertToPixels(maxHeight, fs, pwh.Height))
- }
-
- wh := BoxSizing{
- Width: width,
- Height: height,
- }
-
- m := getMP(n, style, wh, state, "margin")
- padding := getMP(n, style, wh, state, "padding")
-
- if p != nil {
- wh.Width += padding.Left + padding.Right
- wh.Height += padding.Top + padding.Bottom
- }
-
- if wStyle == "100%" && style["position"] != "absolute" {
- wh.Width -= (m.Right + m.Left + self.Border.Left.Width + self.Border.Right.Width + parent.Padding.Left + parent.Padding.Right + padding.Left + padding.Right)
- }
-
- if style["height"] == "100%" {
- if style["position"] == "absolute" {
- wh.Height -= (m.Top + m.Bottom)
- } else {
- wh.Height -= (m.Top + m.Bottom + parent.Padding.Top + parent.Padding.Bottom)
- }
- }
-
- return wh, m, padding
-}
-
-func getMP(n Node, style map[string]string, wh BoxSizing, state *map[string]State, t string) BoxSpacing {
- s := *state
- self := s[n.Properties.Id]
- fs := self.EM
- m := BoxSpacing{}
-
- // Cache style properties
- leftKey, rightKey, topKey, bottomKey := t+"-left", t+"-right", t+"-top", t+"-bottom"
-
- leftStyle := style[leftKey]
- rightStyle := style[rightKey]
- topStyle := style[topKey]
- bottomStyle := style[bottomKey]
-
- // Convert left and right properties
- if leftStyle != "" || rightStyle != "" {
- m.Left = ConvertToPixels(leftStyle, fs, wh.Width)
- m.Right = ConvertToPixels(rightStyle, fs, wh.Width)
- }
-
- // Convert top and bottom properties
- if topStyle != "" || bottomStyle != "" {
- m.Top = ConvertToPixels(topStyle, fs, wh.Height)
- m.Bottom = ConvertToPixels(bottomStyle, fs, wh.Height)
- }
-
- p := n.Parent()
-
- if t == "margin" {
- siblingMargin := float32(0)
- firstChild := false
- // Margin Collapse
- // !ISSUE: Check margin collapse
- if p != nil {
- sibIndex := -1
- for i, v := range p.Children {
- if v.Properties.Id == n.Properties.Id {
- sibIndex = i - 1
-
- break
- }
- }
- if sibIndex > -1 {
- sib := s[p.Children[sibIndex].Properties.Id]
- siblingMargin = sib.Margin.Bottom
- }
- }
-
- // Handle top margin collapse
- for i, v := range p.Children {
- if v.Properties.Id == n.Properties.Id {
- if i == 0 {
- firstChild = true
- }
- break
- }
- }
- if firstChild {
- parent := s[p.Properties.Id]
- if parent.Margin.Top < m.Top {
- parent.Margin.Top = m.Top
- (*state)[p.Properties.Id] = parent
- }
- m.Top = 0
- } else {
- if m.Top != 0 {
- if m.Top < 0 {
- m.Top += siblingMargin
- } else {
- m.Top = Max(m.Top-siblingMargin, 0)
- }
- }
- }
-
- // Handle auto margins
- if leftStyle == "auto" && rightStyle == "auto" {
- parent := s[p.Properties.Id]
- pwh := BoxSizing{
- Width: parent.Width,
- }
- m.Left = Max((pwh.Width-wh.Width)/2, 0)
- m.Right = m.Left
- }
- }
-
- return m
-}
-
-var unitFactors = map[string]float64{
- "px": 1,
- "rem": -1, // special handling
- "em": -1, // special handling
- "pt": 1.33,
- "pc": 16.89,
- "%": -1, // special handling
- "vw": -1, // special handling
- "vh": -1, // special handling
- "cm": 37.79527559,
- "in": 96,
- "auto": -1,
-}
-
-// ConvertToPixels converts a CSS measurement to pixels.
-func ConvertToPixels(value string, em, max float32) float32 {
- // Quick check for predefined units
- switch value {
- case "thick":
- return 5
- case "medium":
- return 3
- case "thin":
- return 1
- }
-
- // Handle calculation expression
- if len(value) > 5 && value[:5] == "calc(" {
- return evaluateCalcExpression(value[5:len(value)-1], em, max)
- }
-
- for unit, factor := range unitFactors {
- if strings.HasSuffix(value, unit) {
- if unit == "em" && strings.HasSuffix(value, "rem") {
- continue
- }
- cutStr := strings.TrimSuffix(value, unit)
- numericValue, err := strconv.ParseFloat(cutStr, 64)
- if err != nil && value != "auto" {
- return 0
- }
- // Handle special units like "em", "%" etc.
- if factor == -1 {
- switch unit {
- case "em":
- return float32(numericValue) * em
- // !ISSUE: REM not properly impleamented
- case "rem":
- return float32(numericValue) * em
- case "%", "vw", "vh":
- return float32(numericValue) * (max / 100)
- case "pt":
- return (float32(numericValue) * 96) / 72
- case "auto":
- return max
- }
- }
- return float32(numericValue) * float32(factor)
- }
- }
-
- // Default return if no match
- return 0
-}
-
-// evaluateCalcExpression recursively evaluates 'calc()' expressions
-func evaluateCalcExpression(expression string, em, max float32) float32 {
- terms := strings.FieldsFunc(expression, func(c rune) bool {
- return c == '+' || c == '-' || c == '*' || c == '/'
- })
-
- operators := strings.FieldsFunc(expression, func(c rune) bool {
- return c != '+' && c != '-' && c != '*' && c != '/'
- })
-
- var result float32
-
- for i, term := range terms {
- value := ConvertToPixels(strings.TrimSpace(term), em, max)
-
- if i > 0 {
- switch operators[i-1] {
- case "+":
- result += value
- case "-":
- result -= value
- case "*":
- result *= value
- case "/":
- if value != 0 {
- result /= value
- } else {
- return 0
- }
- }
- } else {
- result = value
- }
- }
-
- return result
-}
-
-func Max(a, b float32) float32 {
- if a > b {
- return a
- } else {
- return b
- }
-}
-
-func Min(a, b float32) float32 {
- if a < b {
- return a
- } else {
- return b
- }
-}
-
-func GetInnerText(n *html.Node) string {
- var result strings.Builder
-
- var getText func(*html.Node)
- getText = func(n *html.Node) {
- // Skip processing if the node is a head tag
- if n.Type == html.ElementNode && n.Data == "head" {
- return
- }
-
- // If it's a text node, append its content
- if n.Type == html.TextNode {
- result.WriteString(n.Data)
- }
-
- // Traverse child nodes recursively
- for c := n.FirstChild; c != nil; c = c.NextSibling {
- getText(c)
- }
- }
-
- getText(n)
-
- return result.String()
-}
-
-func RGBAtoString(c ic.RGBA) string {
- return fmt.Sprintf("R%d%d%d%d", c.R, c.G, c.B, c.A)
-}
-
-func SplitByComma(input string) []string {
- var result []string
- var current strings.Builder
-
- // Track nesting level for each bracket type
- squareBrackets := 0 // []
- curlyBraces := 0 // {}
- parentheses := 0 // ()
-
- for _, char := range input {
- switch char {
- case '[':
- squareBrackets++
- current.WriteRune(char)
- case ']':
- squareBrackets--
- current.WriteRune(char)
- case '{':
- curlyBraces++
- current.WriteRune(char)
- case '}':
- curlyBraces--
- current.WriteRune(char)
- case '(':
- parentheses++
- current.WriteRune(char)
- case ')':
- parentheses--
- current.WriteRune(char)
- case ',':
- // Only split on comma if we're not inside any brackets
- if squareBrackets == 0 && curlyBraces == 0 && parentheses == 0 {
- result = append(result, strings.TrimSpace(current.String()))
- current.Reset()
- } else {
- current.WriteRune(char)
- }
- default:
- current.WriteRune(char)
- }
- }
-
- // Add the last segment if there's anything left
- if current.Len() > 0 {
- result = append(result, strings.TrimSpace(current.String()))
- }
-
- return result
-}
package grim
import (
"fmt"
ic "image/color"
"strconv"
"strings"
"golang.org/x/net/html"
)
func GetXY(n Node, state map[string]State) (float32, float32) {
s := state
// self := s[n.Properties.Id]
offsetX := float32(0)
offsetY := float32(0)
p := n.Parent()
if p != nil {
parent := s[p.Properties.Id]
offsetX += parent.Border.Left.Width + parent.Padding.Left
offsetY += parent.Border.Top.Width + parent.Padding.Top
}
return offsetX, offsetY
}
type BoxSizing struct {
Width float32
Height float32
}
func FindBounds(n Node, style map[string]string, state *map[string]State) (BoxSizing, BoxSpacing, BoxSpacing) {
s := *state
self := s[n.Properties.Id]
var parent State
fs := self.EM
p := n.Parent()
var pwh BoxSizing
if p != nil {
parent = s[p.Properties.Id]
pwh = BoxSizing{
Width: parent.Width,
Height: parent.Height,
}
} else {
pwh = BoxSizing{}
if width, exists := style["width"]; exists {
if f, err := strconv.ParseFloat(strings.TrimSuffix(width, "px"), 32); err == nil {
pwh.Width = float32(f)
}
}
if height, exists := style["height"]; exists {
if f, err := strconv.ParseFloat(strings.TrimSuffix(height, "px"), 32); err == nil {
pwh.Height = float32(f)
}
}
}
wStyle := style["width"]
if wStyle == "" && style["display"] != "inline" {
wStyle = "100%"
}
width := ConvertToPixels(wStyle, fs, pwh.Width)
height := ConvertToPixels(style["height"], fs, pwh.Height)
if minWidth, exists := style["min-width"]; exists {
width = Max(width, ConvertToPixels(minWidth, fs, pwh.Width))
}
if maxWidth, exists := style["max-width"]; exists {
width = Min(width, ConvertToPixels(maxWidth, fs, pwh.Width))
}
if minHeight, exists := style["min-height"]; exists {
height = Max(height, ConvertToPixels(minHeight, fs, pwh.Height))
}
if maxHeight, exists := style["max-height"]; exists {
height = Min(height, ConvertToPixels(maxHeight, fs, pwh.Height))
}
wh := BoxSizing{
Width: width,
Height: height,
}
m := getMP(n, style, wh, state, "margin")
padding := getMP(n, style, wh, state, "padding")
if p != nil {
wh.Width += padding.Left + padding.Right
wh.Height += padding.Top + padding.Bottom
}
if wStyle == "100%" && style["position"] != "absolute" {
wh.Width -= (m.Right + m.Left + self.Border.Left.Width + self.Border.Right.Width + parent.Padding.Left + parent.Padding.Right + padding.Left + padding.Right)
}
if style["height"] == "100%" {
if style["position"] == "absolute" {
wh.Height -= (m.Top + m.Bottom)
} else {
wh.Height -= (m.Top + m.Bottom + parent.Padding.Top + parent.Padding.Bottom)
}
}
return wh, m, padding
}
func getMP(n Node, style map[string]string, wh BoxSizing, state *map[string]State, t string) BoxSpacing {
s := *state
self := s[n.Properties.Id]
fs := self.EM
m := BoxSpacing{}
// Cache style properties
leftKey, rightKey, topKey, bottomKey := t+"-left", t+"-right", t+"-top", t+"-bottom"
leftStyle := style[leftKey]
rightStyle := style[rightKey]
topStyle := style[topKey]
bottomStyle := style[bottomKey]
// Convert left and right properties
if leftStyle != "" || rightStyle != "" {
m.Left = ConvertToPixels(leftStyle, fs, wh.Width)
m.Right = ConvertToPixels(rightStyle, fs, wh.Width)
}
// Convert top and bottom properties
if topStyle != "" || bottomStyle != "" {
m.Top = ConvertToPixels(topStyle, fs, wh.Height)
m.Bottom = ConvertToPixels(bottomStyle, fs, wh.Height)
}
p := n.Parent()
if t == "margin" {
siblingMargin := float32(0)
firstChild := false
// Margin Collapse
// !ISSUE: Check margin collapse
if p != nil {
sibIndex := -1
for i, v := range p.Children {
if v.Properties.Id == n.Properties.Id {
sibIndex = i - 1
break
}
}
if sibIndex > -1 {
sib := s[p.Children[sibIndex].Properties.Id]
siblingMargin = sib.Margin.Bottom
}
}
// Handle top margin collapse
for i, v := range p.Children {
if v.Properties.Id == n.Properties.Id {
if i == 0 {
firstChild = true
}
break
}
}
if firstChild {
parent := s[p.Properties.Id]
if parent.Margin.Top < m.Top {
parent.Margin.Top = m.Top
(*state)[p.Properties.Id] = parent
}
m.Top = 0
} else {
if m.Top != 0 {
if m.Top < 0 {
m.Top += siblingMargin
} else {
m.Top = Max(m.Top-siblingMargin, 0)
}
}
}
// Handle auto margins
if leftStyle == "auto" && rightStyle == "auto" {
parent := s[p.Properties.Id]
pwh := BoxSizing{
Width: parent.Width,
}
m.Left = Max((pwh.Width-wh.Width)/2, 0)
m.Right = m.Left
}
}
return m
}
var unitFactors = map[string]float64{
"px": 1,
"rem": -1, // special handling
"em": -1, // special handling
"pt": 1.33,
"pc": 16.89,
"%": -1, // special handling
"vw": -1, // special handling
"vh": -1, // special handling
"cm": 37.79527559,
"in": 96,
"auto": -1,
}
// ConvertToPixels converts a CSS measurement to pixels.
func ConvertToPixels(value string, em, max float32) float32 {
// Quick check for predefined units
switch value {
case "thick":
return 5
case "medium":
return 3
case "thin":
return 1
}
// Handle calculation expression
if len(value) > 5 && value[:5] == "calc(" {
return evaluateCalcExpression(value[5:len(value)-1], em, max)
}
for unit, factor := range unitFactors {
if strings.HasSuffix(value, unit) {
if unit == "em" && strings.HasSuffix(value, "rem") {
continue
}
cutStr := strings.TrimSuffix(value, unit)
numericValue, err := strconv.ParseFloat(cutStr, 64)
if err != nil && value != "auto" {
return 0
}
// Handle special units like "em", "%" etc.
if factor == -1 {
switch unit {
case "em":
return float32(numericValue) * em
// !ISSUE: REM not properly impleamented
case "rem":
return float32(numericValue) * em
case "%", "vw", "vh":
return float32(numericValue) * (max / 100)
case "pt":
return (float32(numericValue) * 96) / 72
case "auto":
return max
}
}
return float32(numericValue) * float32(factor)
}
}
// Default return if no match
return 0
}
// evaluateCalcExpression recursively evaluates 'calc()' expressions
func evaluateCalcExpression(expression string, em, max float32) float32 {
terms := strings.FieldsFunc(expression, func(c rune) bool {
return c == '+' || c == '-' || c == '*' || c == '/'
})
operators := strings.FieldsFunc(expression, func(c rune) bool {
return c != '+' && c != '-' && c != '*' && c != '/'
})
var result float32
for i, term := range terms {
value := ConvertToPixels(strings.TrimSpace(term), em, max)
if i > 0 {
switch operators[i-1] {
case "+":
result += value
case "-":
result -= value
case "*":
result *= value
case "/":
if value != 0 {
result /= value
} else {
return 0
}
}
} else {
result = value
}
}
return result
}
func Max(a, b float32) float32 {
if a > b {
return a
} else {
return b
}
}
func Min(a, b float32) float32 {
if a < b {
return a
} else {
return b
}
}
func GetInnerText(n *html.Node) string {
var result strings.Builder
var getText func(*html.Node)
getText = func(n *html.Node) {
// Skip processing if the node is a head tag
if n.Type == html.ElementNode && n.Data == "head" {
return
}
// If it's a text node, append its content
if n.Type == html.TextNode {
result.WriteString(n.Data)
}
// Traverse child nodes recursively
for c := n.FirstChild; c != nil; c = c.NextSibling {
getText(c)
}
}
getText(n)
return result.String()
}
func RGBAtoString(c ic.RGBA) string {
return fmt.Sprintf("R%d%d%d%d", c.R, c.G, c.B, c.A)
}
func SplitByComma(input string) []string {
var result []string
var current strings.Builder
// Track nesting level for each bracket type
squareBrackets := 0 // []
curlyBraces := 0 // {}
parentheses := 0 // ()
for _, char := range input {
switch char {
case '[':
squareBrackets++
current.WriteRune(char)
case ']':
squareBrackets--
current.WriteRune(char)
case '{':
curlyBraces++
current.WriteRune(char)
case '}':
curlyBraces--
current.WriteRune(char)
case '(':
parentheses++
current.WriteRune(char)
case ')':
parentheses--
current.WriteRune(char)
case ',':
// Only split on comma if we're not inside any brackets
if squareBrackets == 0 && curlyBraces == 0 && parentheses == 0 {
result = append(result, strings.TrimSpace(current.String()))
current.Reset()
} else {
current.WriteRune(char)
}
default:
current.WriteRune(char)
}
}
// Add the last segment if there's anything left
if current.Len() > 0 {
result = append(result, strings.TrimSpace(current.String()))
}
return result
}