Skip to main content

Crate cel_euclid

Crate cel_euclid 

Source
Expand description

Opt-in f64 / [euclid::UnknownUnit] geometry for CEL.

Install once on a fresh lookup; no parser or runtime built-ins change. Named operations support ordinary calls and receiver calls (including zero-argument property syntax). Operators require exact operand types.

use cel_parser::{CELParser, OpLookup};
let mut lookup = OpLookup::new();
cel_euclid::install(&mut lookup);
let mut parser = CELParser::new(lookup);
let mut segment = parser.parse_str(
    "(Point2D { y: 2.0, x: 1.0 } + Vector2D { x: 3.0, y: 4.0 }).x"
).unwrap();
assert_eq!(segment.call0::<f64>().unwrap(), 4.0);

Geometry arithmetic, bounds and containment retain Euclid semantics, including non-finite arithmetic and half-open containment. Three operations are checked: normalize rejects zero/non-finite lengths and non-finite results; inverse rejects non-finite coefficients, zero/non-finite determinants, non-finite inverses and zero inverse coefficients with nonzero exact cofactors; outer_transformed_rect rejects invalid or unrepresentable bounds. Ordinary point/vector mapping, constructors and composition remain unchecked. Checked inversion permits exact-zero cofactors and nonzero subnormal results. Translation cofactor checks compare exact products, so underflow or rounding before determinant scaling cannot disguise a lost nonzero coefficient. Other rounding and conditioning errors retain Euclid semantics: checked inversion does not guarantee exact-real arithmetic or numerical stability.

degrees and radians construct angles from f64 and extract the respective scalar from angles. Matrix accessors use Euclid’s six coefficient names. a.then(b) applies a first, then b; vectors ignore translation.

use cel_parser::{CELParser, OpLookup};
let mut lookup = OpLookup::new();
cel_euclid::install(&mut lookup);
let mut parser = CELParser::new(lookup);
let mut segment = parser.parse_str(
    "translation(Vector2D { x: 10.0, y: 0.0 }).then(scale(2.0, 2.0))\
     .transform_point(Point2D { x: 1.0, y: 0.0 }).x"
).unwrap();
assert_eq!(segment.call0::<f64>().unwrap(), 22.0);

Checked outer bounds enclose all four Euclid-mapped corners, not just a diagonal, for arbitrary affine rotation, shear and reflection. They are not an interior-containment test. Inputs must have finite matrix coefficients, finite source origins/extents/endpoints and nonnegative source extents. Positive source extents must advance their endpoints. Projected edges must not overflow or completely underflow, and positive projected spans must not collapse after mapping. Mapped corners and resulting bounds must be finite. Zero-size rectangles and singular transforms are allowed. If reconstructing a bound’s maximum rounds inward, its extent advances one ULP to enclose that maximum; unrepresentable extents/endpoints return evaluation errors. The adam feature only adds optional type registration; it is disabled by default.

§Optional Adam installation

Hosts explicitly install both cell types and operations. adam-lang itself has no geometry dependency. Its registration-aware checker consumes the same registry and lookup as compilation; unresolved call results remain best-effort. Bare CEL hosts that use registered closure annotations must also configure their [cel_parser::TypeResolver]; installing record operations alone does not replace the parser’s annotation resolver.

let mut types = adam_lang::TypeRegistry::new();
cel_euclid::register_adam_types(&mut types);
let mut lookup = cel_parser::OpLookup::new();
cel_euclid::install(&mut lookup);
let mut parser = adam_lang::AdamParser::new(types, lookup);
assert!(parser.parse_str("sheet s { cell p: Point2D; }").is_ok());

Run cargo run -p cel-euclid --features adam --example adam_geometry for source-preserving crop/straighten and pan/zoom sheets. Crop fitting, its eight-epsilon inward guard, and precision/coverage requirements live in CEL, not in library operations. The example’s Rust boundary validates inputs only.

§Supported geometry signatures

Every geometry value uses f64 components and euclid::UnknownUnit. CEL exposes concrete names, not Euclid’s generic parameters. These records require each field exactly once; callers may write fields in any order.

CEL / Rust aliasRequired fields (schema order)
Point2Dx: f64, y: f64
Vector2Dx: f64, y: f64
Size2Dwidth: f64, height: f64
Rectorigin: Point2D, size: Size2D
Box2Dmin: Point2D, max: Point2D
Angleradians: f64
Transform2Dm11: f64, m12: f64, m21: f64, m22: f64, m31: f64, m32: f64

Use 0.0, 0f64, or 0 as f64, not an unsuffixed integer where a field or operand needs f64. There are no implicit numeric casts, scalar-left geometry products, generic geometry constructors, or return-type-only overloads.

The following table is the complete set of 98 exact signatures. T is documentation shorthand, not language syntax. A slash in an operand or result column lists corresponding alternatives, not additional cross-product overloads. Every named operation with a receiver accepts both name(receiver, args...) and receiver.name(args...). Unary named operations also accept receiver.name. identity_transform() has no receiver. Operators use their normal CEL spelling.

OperationOperands → result
==, !=(T, T) → bool for all seven aliases
is_finite(T) → bool for all seven aliases
+(Point2D, Vector2D) → Point2D; (Vector2D, Vector2D) → Vector2D; (Size2D, Size2D) → Size2D
-(Point2D, Point2D) → Vector2D; (Point2D, Vector2D) → Point2D; (Vector2D, Vector2D) → Vector2D; (Size2D, Size2D) → Size2D
unary -(Vector2D) → Vector2D
*, /(Vector2D, f64) → Vector2D; (Size2D, f64) → Size2D
x, y(Point2D) → f64; (Vector2D) → f64
width, height(Size2D) → f64; (Rect) → f64; (Box2D) → f64
origin(Rect) → Point2D
size(Rect) → Size2D; (Box2D) → Size2D
min, max(Box2D) → Point2D (coexist with cel-std scalar overloads)
min_x, min_y, max_x, max_y(Rect) → f64; (Box2D) → f64
center(Rect) → Point2D; (Box2D) → Point2D
dot, cross(Vector2D, Vector2D) → f64
square_length, length(Vector2D) → f64
normalize(Vector2D) → Vector2D, checked
distance_to(Point2D, Point2D) → f64
translate(Rect, Vector2D) → Rect; (Box2D, Vector2D) → Box2D
inflate(Rect, f64, f64) → Rect; (Box2D, f64, f64) → Box2D
to_box2d(Rect) → Box2D
to_rect(Box2D) → Rect
contains(Rect, Point2D) → bool; (Box2D, Point2D) → bool
contains_rect(Rect, Rect) → bool
contains_box(Box2D, Box2D) → bool
intersects(Rect, Rect) → bool; (Box2D, Box2D) → bool
union(Rect, Rect) → Rect; (Box2D, Box2D) → Box2D
degrees, radians(f64) → Angle; (Angle) → f64
m11, m12, m21, m22, m31, m32(Transform2D) → f64
identity_transform() → Transform2D
translation(Vector2D) → Transform2D
scale(f64, f64) → Transform2D
rotation(Angle) → Transform2D
then(Transform2D, Transform2D) → Transform2D
determinant(Transform2D) → f64
inverse(Transform2D) → Transform2D, checked
transform_point(Transform2D, Point2D) → Point2D
transform_vector(Transform2D, Vector2D) → Vector2D
outer_transformed_rect(Transform2D, Rect) → Rect, checked

a.then(b) applies a first, b second. A translation by (10, 0) followed by scale (2, 2) maps (1, 0) to (22, 0), not (12, 0). Vectors ignore translation. The matrix maps x' = m11*x + m21*y + m31 and y' = m12*x + m22*y + m32.

Ordinary arithmetic retains Euclid’s floating-point semantics, including nonfinite results. Record is_finite checks stored components, not derived endpoints. Rectangles and boxes use Euclid containment; ordinary point containment is half-open at maximum edges. Crop coverage instead permits corners on either image edge and uses explicit closed inequalities.

§Checked-operation errors

  • normalize rejects zero/nonfinite lengths and nonfinite results.
  • inverse rejects nonfinite coefficients, zero/nonfinite determinants, nonfinite inverse coefficients, and zero inverse coefficients with exact nonzero cofactors (including translation-cofactor underflow). Exact-zero cofactors and nonzero subnormals are allowed. Other rounding and conditioning errors retain Euclid semantics; this is not exact-real inversion.
  • outer_transformed_rect rejects nonfinite coefficients, origins, sizes, endpoints or mapped corners; negative sizes; positive sizes whose endpoints do not advance; overflowing/completely underflowing projected edges; collapsed positive projected spans; and unrepresentable bounds. Zero sizes and singular transforms are otherwise allowed. The operation encloses all four mapped corners, advancing an extent one ULP when reconstructing its maximum would round inward. It does not prove that an axis-aligned rectangle fits inside the transformed shape.

All three return evaluation errors with source context. They never substitute an empty shape for an error. A domain-specific sheet can impose stricter requirements, as the crop example does, without changing these general operations.

Functions§

install
Installs seven record schemas and the complete exact geometry signature table.
register_adam_types
Registers the seven exact geometry names with their Euclid defaults.

Type Aliases§

Angle
An angle stored as f64 radians.
Box2D
An axis-aligned box represented by minimum and maximum points in unknown units.
Point2D
A two-dimensional point with f64 coordinates and unknown units.
Rect
A rectangle represented by an origin and size in unknown units.
Size2D
A two-dimensional size with f64 extents and unknown units.
Transform2D
A two-dimensional affine matrix mapping unknown units to unknown units.
Vector2D
A two-dimensional vector with f64 components and unknown units.