// Copyright (c) 2010, 2015 Kiel University and others.
// Copyright (c) 2010, 2017 Kiel University and others.
// Copyright (c) 2014, 2017 Kiel University and others.
// Copyright (c) 2016 Kiel University and others.
// Copyright (c) 2026 The elk-go Authors.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0

// Package geometry implements ELK's core two-dimensional data types.
package geometry import ( ) // Point is the Go counterpart of ELK's KVector. type Point struct { X float64 Y float64 } func (, float64) Point { return Point{X: , Y: } } // Between returns the vector pointing from start to end. func (, Point) Point { return Point{X: .X - .X, Y: .Y - .Y} } // FromAngle returns a unit vector at the given angle in radians. func ( float64) Point { return Point{X: math.Cos(), Y: math.Sin()} } func ( Point) () float64 { return .X*.X + .Y*.Y } func ( Point) () float64 { return math.Sqrt(.LengthSquared()) } func ( Point) ( Point) float64 { return Between(, ).Length() } func ( Point) ( Point) float64 { return .X*.X + .Y*.Y } func ( Point) ( Point) float64 { return .X*.Y - .Y*.X } func ( Point) ( Point) Point { return Point{X: .X + .X, Y: .Y + .Y} } func ( Point) ( Point) Point { return Point{X: .X - .X, Y: .Y - .Y} } func ( Point) ( float64) Point { return Point{X: .X * , Y: .Y * } } func ( Point) (, float64) Point { return Point{X: .X * , Y: .Y * } } func ( Point) () Point { return Point{X: -.X, Y: -.Y} } func ( Point) () Point { := .Length() if == 0 { return } return .Scale(1 / ) } func ( Point) ( float64) Point { return .Normalize().Scale() } // Radians returns an angle in [0, 2*pi), matching KVector.toRadians. func ( Point) () float64 { := .Length() if == 0 { return math.NaN() } := math.Asin(.Y / ) if .X >= 0 && .Y >= 0 { return } if .X < 0 { return math.Pi - } return 2*math.Pi + } func ( Point) () float64 { return .Radians() * 180 / math.Pi } func ( Point) ( float64) Point { , := math.Sincos() return Point{X: .X* - .Y*, Y: .X* + .Y*} } func ( Point) ( Point) float64 { return math.Acos(.Dot() / (.Length() * .Length())) } func ( Point) (, , , float64) Point { if < || < { panic("high bounds must not be smaller than low bounds") } .X = math.Max(, math.Min(, .X)) .Y = math.Max(, math.Min(, .Y)) return } func ( Point) () bool { return math.IsNaN(.X) || math.IsNaN(.Y) } func ( Point) () bool { return math.IsInf(.X, 0) || math.IsInf(.Y, 0) } func ( Point) ( Point, float64) bool { return math.Abs(.X-.X) <= && math.Abs(.Y-.Y) <= } func ( string) (Point, error) { := strings.Trim(, "()[]{}\"' \t\r\n") := strings.FieldsFunc(, func( rune) bool { return == ',' || == ';' || == '\r' || == '\n' }) if len() != 2 { return Point{}, fmt.Errorf("exactly two numbers are expected, %d were found", len()) } , := strconv.ParseFloat(strings.TrimSpace([0]), 64) if != nil { return Point{}, } , := strconv.ParseFloat(strings.TrimSpace([1]), 64) if != nil { return Point{}, } return Point{X: , Y: }, nil } func ( Point) () string { return "(" + formatDouble(.X) + "," + formatDouble(.Y) + ")" } // Chain is ELK's ordered KVectorChain. type Chain []Point func ( Chain) () Chain { return append(Chain(nil), ...) } func ( Chain) ( Point) Chain { for := range { [] = [].Add() } return } func ( Chain) ( float64) Chain { for := range { [] = [].Scale() } return } func ( Chain) (, float64) Chain { for := range { [] = [].ScaleXY(, ) } return } func ( Chain) () float64 { var float64 for := 1; < len(); ++ { += [-1].Distance([]) } return } func ( Chain) () bool { for , := range { if .IsNaN() { return true } } return false } func ( Chain) () bool { for , := range { if .IsInfinite() { return true } } return false } // PointAt returns a point at distance from the start, or from the end when // distance is negative. func ( Chain) ( float64) Point { if len() == 0 { panic("cannot determine a point on an empty chain") } if len() == 1 { return [0] } := >= 0 = math.Abs() := func( int) int { if { return } return len() - 1 - } var float64 for := 0; < len()-1; ++ { , := [()], [(+1)] := .Distance() if == 0 { continue } if + >= { return .Add(.Sub().Scale(( - ) / )) } += } return [(len()-1)] } func ( Chain) () Chain { := make(Chain, len()) for := range { [len()-1-] = [] } return } func ( string) (Chain, error) { := strings.FieldsFunc(, func( rune) bool { return strings.ContainsRune(",;()[]{}", ) || unicode.IsSpace() }) if len()%2 != 0 { return nil, fmt.Errorf("vector chain requires coordinate pairs") } := make(Chain, 0, len()/2) for := 0; < len(); += 2 { , := strconv.ParseFloat([], 64) if != nil { return nil, } , := strconv.ParseFloat([+1], 64) if != nil { return nil, } = append(, Point{X: , Y: }) } return , nil } func ( Chain) () string { := make([]string, len()) for , := range { [] = formatDouble(.X) + "," + formatDouble(.Y) } return "(" + strings.Join(, "; ") + ")" } // Spacing is used for both ELK padding and margin values. type Spacing struct{ Top, Right, Bottom, Left float64 } func ( float64) Spacing { return Spacing{Top: , Right: , Bottom: , Left: } } func (, float64) Spacing { return Spacing{Top: , Right: , Bottom: , Left: } } func ( Spacing) () float64 { return .Left + .Right } func ( Spacing) () float64 { return .Top + .Bottom } func ( string) (Spacing, error) { := strings.Trim(, "()[]{}\"' \t\r\n") var Spacing if == "" { return , nil } for , := range strings.FieldsFunc(, func( rune) bool { return == ',' || == ';' }) { := strings.Split(, "=") if len() != 2 { return Spacing{}, fmt.Errorf("expecting a list of key-value pairs") } , := strconv.ParseFloat(strings.TrimSpace([1]), 64) if != nil { return Spacing{}, fmt.Errorf("spacing contains a value that is not a number: %w", ) } switch strings.TrimSpace([0]) { case "top": .Top = case "right": .Right = case "bottom": .Bottom = case "left": .Left = } } return , nil } func ( Spacing) () string { return "[top=" + formatDouble(.Top) + ",left=" + formatDouble(.Left) + ",bottom=" + formatDouble(.Bottom) + ",right=" + formatDouble(.Right) + "]" } // Rectangle is an axis-aligned rectangle. type Rectangle struct{ X, Y, Width, Height float64 } func ( Rectangle) () Point { return Point{X: .X, Y: .Y} } func ( Rectangle) () Point { return Point{X: .X + .Width, Y: .Y} } func ( Rectangle) () Point { return Point{X: .X, Y: .Y + .Height} } func ( Rectangle) () Point { return Point{X: .X + .Width, Y: .Y + .Height} } func ( Rectangle) () Point { return Point{X: .X + .Width/2, Y: .Y + .Height/2} } func ( Rectangle) () float64 { return .X + .Width } func ( Rectangle) () float64 { return .Y + .Height } func ( Rectangle) ( Point) Rectangle { .X += .X; .Y += .Y; return } func ( Rectangle) ( Rectangle) Rectangle { := math.Min(.X, .X) := math.Min(.Y, .Y) := math.Max(.MaxX(), .MaxX()) := math.Max(.MaxY(), .MaxY()) return Rectangle{X: , Y: , Width: - , Height: - } } func ( Rectangle) ( Rectangle) bool { return .X < .MaxX() && .MaxX() > .X && .MaxY() > .Y && .Y < .MaxY() } func formatDouble( float64) string { if math.IsInf(, 1) { return "Infinity" } if math.IsInf(, -1) { return "-Infinity" } if math.IsNaN() { return "NaN" } return strconv.FormatFloat(, 'g', -1, 64) }