rui_orig/sizeFunc.go

374 lines
9.0 KiB
Go

package rui
import (
"fmt"
"strconv"
"strings"
)
// SizeFunc describes a function that calculates the SizeUnit size.
// Used as the value of the SizeUnit properties.
// "min", "max", "clamp", "sum", "sub", "mul", and "div" functions are available.
type SizeFunc interface {
fmt.Stringer
// Name() returns the function name: "min", "max", "clamp", "sum", "sub", "mul", or "div"
Name() string
// Args() returns a list of function arguments
Args() []any
cssString(session Session) string
writeCSS(topFunc string, buffer *strings.Builder, session Session)
writeString(topFunc string, buffer *strings.Builder)
}
type sizeFuncData struct {
tag string
args []any
}
func parseSizeFunc(text string) SizeFunc {
text = strings.Trim(text, " ")
for _, tag := range []string{"min", "max", "sum", "sub", "mul", "div", "clamp"} {
if strings.HasPrefix(text, tag) {
text = strings.Trim(strings.TrimPrefix(text, tag), " ")
last := len(text) - 1
if text[0] == '(' && text[last] == ')' {
text = text[1:last]
bracket := 0
start := 0
args := []any{}
for i, ch := range text {
switch ch {
case ',':
if bracket == 0 {
args = append(args, text[start:i])
start = i + 1
}
case '(':
bracket++
case ')':
bracket--
}
}
if bracket != 0 {
ErrorLogF(`Invalid "%s" function`, tag)
return nil
}
args = append(args, text[start:])
switch tag {
case "sub", "mul", "div":
if len(args) != 2 {
ErrorLogF(`"%s" function needs 2 arguments`, tag)
return nil
}
case "clamp":
if len(args) != 3 {
ErrorLog(`"clamp" function needs 3 arguments`)
return nil
}
}
data := new(sizeFuncData)
data.tag = tag
if data.parseArgs(args, tag == "mul" || tag == "div") {
return data
}
}
ErrorLogF(`Invalid "%s" function`, tag)
return nil
}
}
return nil
}
func (data *sizeFuncData) parseArgs(args []any, allowNumber bool) bool {
data.args = []any{}
numberArg := func(index int, value float64) bool {
if allowNumber {
if index == 1 {
if value == 0 && data.tag == "div" {
ErrorLog(`Division by 0 in div function`)
return false
}
data.args = append(data.args, value)
return true
} else {
ErrorLogF(`Only the second %s function argument can be a number`, data.tag)
return false
}
}
ErrorLogF(`The %s function argument cann't be a number`, data.tag)
return false
}
for i, arg := range args {
switch arg := arg.(type) {
case string:
if arg = strings.Trim(arg, " \t\n"); arg == "" {
ErrorLogF(`Unsupported %s function argument #%d: ""`, data.tag, i)
return false
}
if arg[0] == '@' {
data.args = append(data.args, arg)
} else if val, err := strconv.ParseFloat(arg, 64); err == nil {
return numberArg(i, val)
} else if fn := parseSizeFunc(arg); fn != nil {
data.args = append(data.args, fn)
} else if size, err := stringToSizeUnit(arg); err == nil {
data.args = append(data.args, size)
} else {
ErrorLogF(`Unsupported %s function argument #%d: "%s"`, data.tag, i, arg)
return false
}
case SizeFunc:
data.args = append(data.args, arg)
case SizeUnit:
if arg.Type == Auto {
ErrorLogF(`Unsupported %s function argument #%d: "auto"`, data.tag, i)
}
data.args = append(data.args, arg)
case float64:
return numberArg(i, arg)
case float32:
return numberArg(i, float64(arg))
default:
if n, ok := isInt(arg); ok {
return numberArg(i, float64(n))
}
ErrorLogF(`Unsupported %s function argument #%d: %v`, data.tag, i, arg)
return false
}
}
return true
}
func (data *sizeFuncData) String() string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
data.writeString("", buffer)
return buffer.String()
}
func (data *sizeFuncData) Name() string {
return data.tag
}
func (data *sizeFuncData) Args() []any {
args := make([]any, len(data.args))
copy(args, data.args)
return args
}
func (data *sizeFuncData) writeString(topFunc string, buffer *strings.Builder) {
buffer.WriteString(data.tag)
buffer.WriteRune('(')
for i, arg := range data.args {
if i > 0 {
buffer.WriteString(", ")
}
switch arg := arg.(type) {
case string:
buffer.WriteString(arg)
case SizeFunc:
arg.writeString(data.tag, buffer)
case SizeUnit:
buffer.WriteString(arg.String())
case fmt.Stringer:
buffer.WriteString(arg.String())
case float64:
buffer.WriteString(fmt.Sprintf("%g", arg))
}
}
buffer.WriteRune(')')
}
func (data *sizeFuncData) cssString(session Session) string {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
data.writeCSS("", buffer, session)
return buffer.String()
}
func (data *sizeFuncData) writeCSS(topFunc string, buffer *strings.Builder, session Session) {
bracket := true
sep := ", "
mathFunc := func(s string) {
sep = s
switch topFunc {
case "":
buffer.WriteString("calc(")
case "min", "max", "clamp":
bracket = false
default:
buffer.WriteRune('(')
}
}
switch data.tag {
case "min", "max", "clamp":
buffer.WriteString(data.tag)
buffer.WriteRune('(')
case "sum":
mathFunc(" + ")
case "sub":
mathFunc(" - ")
case "mul":
mathFunc(" * ")
case "div":
mathFunc(" / ")
default:
return
}
for i, arg := range data.args {
if i > 0 {
buffer.WriteString(sep)
}
switch arg := arg.(type) {
case string:
if arg, ok := session.resolveConstants(arg); ok {
if fn := parseSizeFunc(arg); fn != nil {
fn.writeCSS(data.tag, buffer, session)
} else if size, err := stringToSizeUnit(arg); err == nil {
buffer.WriteString(size.cssString("0", session))
} else {
buffer.WriteString("0")
}
} else {
buffer.WriteString("0")
}
case SizeFunc:
arg.writeCSS(data.tag, buffer, session)
case SizeUnit:
buffer.WriteString(arg.cssString("0", session))
case fmt.Stringer:
buffer.WriteString(arg.String())
case float64:
buffer.WriteString(fmt.Sprintf("%g", arg))
}
}
if bracket {
buffer.WriteRune(')')
}
}
// MaxSize creates a SizeUnit function that calculates the maximum argument.
// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc
func MaxSize(arg0, arg1 any, args ...any) SizeFunc {
data := new(sizeFuncData)
data.tag = "max"
if !data.parseArgs(append([]any{arg0, arg1}, args...), false) {
return nil
}
return data
}
// MinSize creates a SizeUnit function that calculates the minimum argument.
// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc.
func MinSize(arg0, arg1 any, args ...any) SizeFunc {
data := new(sizeFuncData)
data.tag = "min"
if !data.parseArgs(append([]any{arg0, arg1}, args...), false) {
return nil
}
return data
}
// SumSize creates a SizeUnit function that calculates the sum of arguments.
// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc.
func SumSize(arg0, arg1 any, args ...any) SizeFunc {
data := new(sizeFuncData)
data.tag = "sum"
if !data.parseArgs(append([]any{arg0, arg1}, args...), false) {
return nil
}
return data
}
// SumSize creates a SizeUnit function that calculates the result of subtracting the arguments (arg1 - arg2).
// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc.
func SubSize(arg0, arg1 any) SizeFunc {
data := new(sizeFuncData)
data.tag = "sub"
if !data.parseArgs([]any{arg0, arg1}, false) {
return nil
}
return data
}
// MulSize creates a SizeUnit function that calculates the result of multiplying the arguments (arg1 * arg2).
// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc.
// The second argument can also be a number (float32, float32, int, int8...int64, uint, uint8...unit64)
// or a string which is a text representation of a number.
func MulSize(arg0, arg1 any) SizeFunc {
data := new(sizeFuncData)
data.tag = "mul"
if !data.parseArgs([]any{arg0, arg1}, true) {
return nil
}
return data
}
// DivSize creates a SizeUnit function that calculates the result of dividing the arguments (arg1 / arg2).
// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc.
// The second argument can also be a number (float32, float32, int, int8...int64, uint, uint8...unit64)
// or a string which is a text representation of a number.
func DivSize(arg0, arg1 any) SizeFunc {
data := new(sizeFuncData)
data.tag = "div"
if !data.parseArgs([]any{arg0, arg1}, true) {
return nil
}
return data
}
// ClampSize creates a SizeUnit function whose the result is calculated as follows:
//
// min ≤ value ≤ max -> value;
// value < min -> min;
// max < value -> max;
//
// Valid argument types are SizeUnit, SizeFunc and a string which is a text description of SizeUnit or SizeFunc.
func ClampSize(min, value, max any) SizeFunc {
data := new(sizeFuncData)
data.tag = "clamp"
if !data.parseArgs([]any{min, value, max}, false) {
return nil
}
return data
}