Class: Idml::Render::GradientResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/idml/render/gradient_resolver.rb

Overview

Resolves IDML gradient references to discrete PDF gradient fills. A gradient is rendered as N thin colored rectangles that approximate the smooth gradient transition. This is not a true PDF shading pattern (which requires Function dictionaries), but produces a visually correct gradient without complex PDF structure.

Constant Summary collapse

SEGMENTS =
32

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(table) ⇒ GradientResolver

Returns a new instance of GradientResolver.



23
24
25
26
# File 'lib/idml/render/gradient_resolver.rb', line 23

def initialize(table)
  @table = table
  @color_resolver = nil
end

Class Method Details

.build(graphic) ⇒ Object



13
14
15
16
17
18
19
20
21
# File 'lib/idml/render/gradient_resolver.rb', line 13

def self.build(graphic)
  table = {}
  return new(table) unless graphic&.gradient

  graphic.gradient.each do |gradient|
    table[gradient.self_attr] = gradient
  end
  new(table)
end

.gradient?(name) ⇒ Boolean

Detect if a color reference is a gradient.

Returns:

  • (Boolean)


29
30
31
# File 'lib/idml/render/gradient_resolver.rb', line 29

def self.gradient?(name)
  name.is_a?(String) && name.start_with?("Gradient/")
end

Instance Method Details

#blend(color_a, color_b, t) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/idml/render/gradient_resolver.rb', line 91

def blend(color_a, color_b, t)
  case color_a[:model]
  when :rgb
    {
      model: :rgb,
      r: color_a[:r] + ((color_b[:r] - color_a[:r]) * t),
      g: color_a[:g] + ((color_b[:g] - color_a[:g]) * t),
      b: color_a[:b] + ((color_b[:b] - color_a[:b]) * t),
    }
  when :cmyk
    {
      model: :cmyk,
      c: color_a[:c] + ((color_b[:c] - color_a[:c]) * t),
      m: color_a[:m] + ((color_b[:m] - color_a[:m]) * t),
      y: color_a[:y] + ((color_b[:y] - color_a[:y]) * t),
      k: color_a[:k] + ((color_b[:k] - color_a[:k]) * t),
    }
  end
end

#blend_between(prev_stop, next_stop, offset) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/idml/render/gradient_resolver.rb', line 72

def blend_between(prev_stop, next_stop, offset)
  range = (next_stop.location - prev_stop.location)
  range = 1.0 if range.zero?
  local = (offset - prev_stop.location) / range

  prev_color = resolve_color(prev_stop.stop_color)
  next_color = resolve_color(next_stop.stop_color)
  return prev_color unless prev_color && next_color
  return next_color if local >= 1.0

  blend(prev_color, next_color, local)
end

#interpolate(stops, offset) ⇒ Object



64
65
66
67
68
69
70
# File 'lib/idml/render/gradient_resolver.rb', line 64

def interpolate(stops, offset)
  next_stop = stops.find { |s| s.location >= offset } || stops.last
  prev_stop = stops.reverse.find { |s| s.location <= offset } || stops.first
  return resolve_color(next_stop.stop_color) if next_stop == prev_stop

  blend_between(prev_stop, next_stop, offset)
end

#render_gradient(name, x:, y:, width:, height:, color_resolver:) ⇒ Object

Render a gradient fill as PDF operators. Returns nil if the color is not a gradient. The returned operators emit colored rectangles from y2 down to y1 (matching PDF Y-up coords).



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/idml/render/gradient_resolver.rb', line 36

def render_gradient(name, x:, y:, width:, height:, color_resolver:)
  @color_resolver = color_resolver
  gradient = @table[name]
  return nil unless gradient

  stops = gradient.gradient_stop
  return nil if stops.length < 2

  segment_height = height.to_f / SEGMENTS
  ops = []
  SEGMENTS.downto(1) do |i|
    offset = (i - 1) / SEGMENTS.to_f
    color = interpolate(stops, offset)
    next unless color

    ops << "q"
    ops << Idml::Render::ColorHelper.to_canvas(color)
    ops << format("%<x>.2f %<y>.2f %<w>.2f %<h>.2f re",
                  x: x,
                  y: y + ((SEGMENTS - i) * segment_height),
                  w: width,
                  h: segment_height + 0.1)
    ops << "f"
    ops << "Q"
  end
  ops.join("\n")
end

#resolve_color(name) ⇒ Object



85
86
87
88
89
# File 'lib/idml/render/gradient_resolver.rb', line 85

def resolve_color(name)
  return nil unless @color_resolver && name

  @color_resolver.resolve(name)
end