// Copyright 2025-2026 肖其顿 (XIAO QI DUN) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ofdgo import ( "fmt" "math" "slices" "strconv" "strings" ) // Box 矩形区域 type Box struct { X, Y, W, H float64 } // Point 平面坐标点 type Point struct { X, Y float64 } // ParseBox 解析Box字符串 // 入参: s 字符串 // 返回: Box 矩形对象, error 错误信息 func ParseBox(s string) (Box, error) { var values [4]float64 i := 0 for part := range strings.FieldsSeq(s) { if i == len(values) { return Box{}, fmt.Errorf("invalid box: %s", s) } value, err := strconv.ParseFloat(part, 64) if err != nil { return Box{}, fmt.Errorf("invalid box: %s", s) } values[i] = value i++ } if i != len(values) { return Box{}, fmt.Errorf("invalid box: %s", s) } return Box{X: values[0], Y: values[1], W: values[2], H: values[3]}, nil } // Matrix 2D仿射变换矩阵 type Matrix struct { a, b, c, d, e, f float64 } // IdentityMatrix 单位矩阵 var IdentityMatrix = Matrix{1, 0, 0, 1, 0, 0} // NewMatrix 解析CTM字符串 // 入参: s 字符串 // 返回: Matrix 矩阵对象 func NewMatrix(s string) Matrix { floats := parseFloats(s) if len(floats) != 6 { return IdentityMatrix } return Matrix{ a: floats[0], b: floats[1], c: floats[2], d: floats[3], e: floats[4], f: floats[5], } } // Multiply 矩阵乘法 (m * o) // 入参: o 右侧矩阵 // 返回: Matrix 结果矩阵 func (m Matrix) Multiply(o Matrix) Matrix { return Matrix{ a: m.a*o.a + m.c*o.b, b: m.b*o.a + m.d*o.b, c: m.a*o.c + m.c*o.d, d: m.b*o.c + m.d*o.d, e: m.a*o.e + m.c*o.f + m.e, f: m.b*o.e + m.d*o.f + m.f, } } // Transform 应用变换矩阵 // 入参: x X坐标, y Y坐标 // 返回: float64 变换后X, float64 变换后Y func (m Matrix) Transform(x, y float64) (float64, float64) { nx := m.a*x + m.c*y + m.e ny := m.b*x + m.d*y + m.f return nx, ny } // TransformBox 获取矩形经仿射变换后的轴对齐边界 // 入参: box 原始矩形范围 // 返回: Box 变换后的轴对齐边界 func (m Matrix) TransformBox(box Box) Box { x, y := m.Transform(box.X, box.Y) left, top, right, bottom := x, y, x, y for _, point := range [][2]float64{{box.X + box.W, box.Y}, {box.X, box.Y + box.H}, {box.X + box.W, box.Y + box.H}} { x, y := m.Transform(point[0], point[1]) left, top, right, bottom = math.Min(left, x), math.Min(top, y), math.Max(right, x), math.Max(bottom, y) } return Box{X: left, Y: top, W: right - left, H: bottom - top} } // String 返回标准CTM属性值 // 返回: string 六个空格分隔的矩阵分量 func (m Matrix) String() string { return fmt.Sprintf("%s %s %s %s %s %s", ofdNumber(m.a), ofdNumber(m.b), ofdNumber(m.c), ofdNumber(m.d), ofdNumber(m.e), ofdNumber(m.f)) } // axisAlignedMatrix 判断变换是否保持坐标轴平行,含直角旋转与镜像 // 入参: m 仿射变换矩阵 // 返回: bool 是否为非退化的轴对齐变换 func axisAlignedMatrix(m Matrix) bool { return m.b == 0 && m.c == 0 && m.a != 0 && m.d != 0 || m.a == 0 && m.d == 0 && m.b != 0 && m.c != 0 } // matrixVector 变换位移,不包含平移分量 // 入参: m 仿射变换矩阵, x、y 原始位移 // 返回: float64 变换后的X位移, float64 变换后的Y位移 func matrixVector(m Matrix, x, y float64) (float64, float64) { return m.a*x + m.c*y, m.b*x + m.d*y } // Invert 求逆矩阵 // 返回: Matrix 逆矩阵, bool 是否可逆 func (m Matrix) Invert() (Matrix, bool) { det := m.a*m.d - m.b*m.c if math.Abs(det) < 1e-9 { return IdentityMatrix, false } return Matrix{ a: m.d / det, b: -m.b / det, c: -m.c / det, d: m.a / det, e: (m.c*m.f - m.d*m.e) / det, f: (m.b*m.e - m.a*m.f) / det, }, true } // TranslationMatrix 构造平移矩阵 // 入参: x X轴偏移, y Y轴偏移 // 返回: Matrix 平移矩阵 func TranslationMatrix(x, y float64) Matrix { return Matrix{a: 1, d: 1, e: x, f: y} } // YScale 获取Y轴缩放比例 // 返回: float64 缩放比例 func (m Matrix) YScale() float64 { return math.Sqrt(m.c*m.c + m.d*m.d) } // parseFloats 解析浮点数数组 // 入参: s 字符串 // 返回: []float64 浮点数数组 func parseFloats(s string) []float64 { if s == "" { return nil } if strings.Contains(s, "g") { return parseFloatsWithG(s) } s = strings.ReplaceAll(s, ",", " ") parts := strings.Fields(s) result := make([]float64, 0, len(parts)) for _, p := range parts { if v, err := strconv.ParseFloat(p, 64); err == nil { result = append(result, v) } } return result } // parseFloatsWithG 解析带g的压缩浮点数数组 // 入参: s 字符串 // 返回: []float64 浮点数数组 func parseFloatsWithG(s string) []float64 { var result []float64 gFlag := false gCount := 0 for p := range strings.FieldsSeq(s) { if p == "g" { gFlag = true continue } if gFlag { var err error gCount, err = strconv.Atoi(p) if err != nil { gCount = 0 } gFlag = false continue } if gCount > 0 { v, _ := strconv.ParseFloat(p, 64) result = slices.Grow(result, gCount) for j := 0; j < gCount; j++ { result = append(result, v) } gCount = 0 } else { if v, err := strconv.ParseFloat(p, 64); err == nil { result = append(result, v) } } } return result } // parseInts 解析整数数组 // 入参: s 字符串 // 返回: []int 整数数组 func parseInts(s string) []int { if s == "" { return nil } s = strings.ReplaceAll(s, ",", " ") parts := strings.Fields(s) result := make([]int, 0, len(parts)) for _, p := range parts { if v, err := strconv.Atoi(p); err == nil { result = append(result, v) } } return result }