Module: Idml::Geometry

Defined in:
lib/idml/geometry.rb

Overview

Coordinate-transform helpers for composition operations. The IDML coordinate system has its origin at the bottom-left of the spread, y increasing upward; page item coordinates are spread-relative. When composing two packages, source coordinates must be translated into the destination's coordinate space.

Defined Under Namespace

Classes: Point, Transform

Class Method Summary collapse

Class Method Details

.apply_transform(transform, x, y) ⇒ Object

Apply a Transform to a point [x, y], returning [x', y'].



30
31
32
33
34
35
# File 'lib/idml/geometry.rb', line 30

def apply_transform(transform, x, y)
  return [x, y] unless transform

  [(transform.a * x) + (transform.c * y) + transform.e,
   (transform.b * x) + (transform.d * y) + transform.f]
end

.bounds_to_pdf_rect(bounds, page_height) ⇒ Object

Convert IDML bounds [y1, x1, y2, x2] to a PDF rectangle { x:, y:, width:, height: } with Y-axis flipped.



75
76
77
78
79
80
81
82
83
84
85
# File 'lib/idml/geometry.rb', line 75

def bounds_to_pdf_rect(bounds, page_height)
  return nil unless bounds && bounds.length == 4

  y1, x1, y2, x2 = bounds
  {
    x: x1,
    y: page_height - y2,
    width: (x2 - x1).abs,
    height: (y2 - y1).abs,
  }
end

.combine_transforms(outer, inner) ⇒ Object

Combine (multiply) two transforms: result = outer ∘ inner.



38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/idml/geometry.rb', line 38

def combine_transforms(outer, inner)
  return inner unless outer
  return outer unless inner

  Transform.new(
    (outer.a * inner.a) + (outer.c * inner.b),
    (outer.b * inner.a) + (outer.d * inner.b),
    (outer.a * inner.c) + (outer.c * inner.d),
    (outer.b * inner.c) + (outer.d * inner.d),
    (outer.a * inner.e) + (outer.c * inner.f) + outer.e,
    (outer.b * inner.e) + (outer.d * inner.f) + outer.f,
  )
end

.offset(x:, y:) ⇒ Object



106
107
108
# File 'lib/idml/geometry.rb', line 106

def offset(x:, y:)
  Point.new(x, y)
end

.parse_transform(str) ⇒ Object

Parse an ItemTransform string "a b c d e f" into a Transform.



20
21
22
23
24
25
26
27
# File 'lib/idml/geometry.rb', line 20

def parse_transform(str)
  return nil unless str

  parts = str.split(/\s+/).map(&:to_f)
  return nil unless parts.length == 6

  Transform.new(*parts)
end

.placement_rect(bounds, item_transform_str, page_height) ⇒ Object

Convert IDML bounds + ItemTransform string to a PDF rect with Y-axis flip. Single entry point for all renderers.



67
68
69
70
71
# File 'lib/idml/geometry.rb', line 67

def placement_rect(bounds, item_transform_str, page_height)
  transform = parse_transform(item_transform_str)
  transformed = transform_bounds(bounds, transform)
  bounds_to_pdf_rect(transformed, page_height)
end

.rotate(point, angle_degrees:, around: offset(x: 0, y: 0)) ⇒ Object

Rotate point counterclockwise by angle_degrees around around.



96
97
98
99
100
101
102
103
104
# File 'lib/idml/geometry.rb', line 96

def rotate(point, angle_degrees:, around: offset(x: 0, y: 0))
  rad = angle_degrees * Math::PI / 180
  cos = Math.cos(rad)
  sin = Math.sin(rad)
  dx = point.x - around.x
  dy = point.y - around.y
  Point.new(around.x + ((dx * cos) - (dy * sin)),
            around.y + ((dx * sin) + (dy * cos)))
end

.scale(point, by: offset(x: 1, y: 1)) ⇒ Object



91
92
93
# File 'lib/idml/geometry.rb', line 91

def scale(point, by: offset(x: 1, y: 1))
  Point.new(point.x * by.x, point.y * by.y)
end

.transform_bounds(bounds, transform = nil) ⇒ Object

Transform IDML geometric_bounds [y1, x1, y2, x2] by applying an optional ItemTransform. Returns new [y1, x1, y2, x2].



54
55
56
57
58
59
60
61
62
63
# File 'lib/idml/geometry.rb', line 54

def transform_bounds(bounds, transform = nil)
  return bounds unless transform && bounds

  y1, x1, y2, x2 = bounds
  x1p, y1p = apply_transform(transform, x1, y1)
  x2p, y2p = apply_transform(transform, x2, y2)
  xs = [x1p, x2p]
  ys = [y1p, y2p]
  [ys.min, xs.min, ys.max, xs.max]
end

.translate(point, by: offset(x: 0, y: 0)) ⇒ Object



87
88
89
# File 'lib/idml/geometry.rb', line 87

def translate(point, by: offset(x: 0, y: 0))
  Point.new(point.x + by.x, point.y + by.y)
end