File: stretchable.rb

package info (click to toggle)
ruby-rmagick 6.0.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,232 kB
  • sloc: cpp: 19,563; ruby: 17,147; sh: 88; javascript: 36; makefile: 13
file content (159 lines) | stat: -rw-r--r-- 5,155 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#--
# $Id: stretchable.rb,v 1.7 2009/02/28 23:52:28 rmagick Exp $
# Copyright (C) 2009 Timothy P. Hunter
#++
module Magick
  class RVG
    module PreserveAspectRatio
      #--
      #   Included in Stretchable module and Image class
      #++
      # Specifies how the image within a viewport should be scaled.
      # [+align+] a combination of 'xMin', 'xMid', or 'xMax', followed by
      #           'YMin', 'YMid', or 'YMax'
      # [+meet_or_slice+] one of 'meet' or 'slice'
      def preserve_aspect_ratio(align, meet_or_slice = 'meet')
        @align = align.to_s
        if @align != 'none'
          m = /\A(xMin|xMid|xMax)(YMin|YMid|YMax)\z/.match(@align)
          raise(ArgumentError, "unknown alignment specifier: #{@align}") unless m
        end

        if meet_or_slice
          meet_or_slice = meet_or_slice.to_s.downcase
          raise(ArgumentError, "specifier must be `meet' or `slice' (got #{meet_or_slice})") unless %w[meet slice].include?(meet_or_slice)

          @meet_or_slice = meet_or_slice
        end
        yield(self) if block_given?
        self
      end
    end # module PreserveAspectRatio

    # The methods in this module describe the user-coordinate space.
    # RVG and Pattern objects are stretchable.
    module Stretchable
      private

      # Scale to fit
      def set_viewbox_none(width, height)
        sx = 1.0
        sy = 1.0

        sx = width / @vbx_width if @vbx_width
        sy = height / @vbx_height if @vbx_height

        [sx, sy]
      end

      # Use align attribute to compute x- and y-offset from viewport's upper-left corner.
      def align_to_viewport(width, height, sx, sy)
        tx = case @align
             when /\AxMin/
               0
             when NilClass, /\AxMid/
               (width - @vbx_width * sx) / 2.0
             when /\AxMax/
               width - @vbx_width * sx
             end

        ty = case @align
             when /YMin\z/
               0
             when NilClass, /YMid\z/
               (height - @vbx_height * sy) / 2.0
             when /YMax\z/
               height - @vbx_height * sy
             end
        [tx, ty]
      end

      # Scale to smaller viewbox dimension
      def set_viewbox_meet(width, height)
        sx = sy = [width / @vbx_width, height / @vbx_height].min
        [sx, sy]
      end

      # Scale to larger viewbox dimension
      def set_viewbox_slice(width, height)
        sx = sy = [width / @vbx_width, height / @vbx_height].max
        [sx, sy]
      end

      # Establish the viewbox as necessary
      def add_viewbox_primitives(width, height, gc)
        @vbx_width  ||= width
        @vbx_height ||= height
        @vbx_x ||= 0.0
        @vbx_y ||= 0.0

        if @align == 'none'
          sx, sy = set_viewbox_none(width, height)
          tx = 0
          ty = 0
        elsif @meet_or_slice == 'meet'
          sx, sy = set_viewbox_meet(width, height)
          tx, ty = align_to_viewport(width, height, sx, sy)
        else
          sx, sy = set_viewbox_slice(width, height)
          tx, ty = align_to_viewport(width, height, sx, sy)
        end

        # Establish clipping path around the current viewport
        name = __id__.to_s
        gc.define_clip_path(name) do
          gc.path("M0,0 l#{width},0 l0,#{height} l-#{width},0 l0,-#{height}z")
        end

        gc.clip_path(name)
        # Add a non-scaled translation if meet or slice
        gc.translate(tx, ty) if tx.abs > 1.0e-10 || ty.abs > 1.0e-10
        # Scale viewbox as necessary
        gc.scale(sx, sy) if sx != 1.0 || sy != 1.0
        # Add a scaled translation if non-0 origin
        gc.translate(-@vbx_x, -@vbx_y) if @vbx_x.abs != 0.0 || @vbx_y.abs != 0
      end

      def initialize(*_args)
        super()
        @vbx_x, @vbx_y, @vbx_width, @vbx_height = nil
        @meet_or_slice = 'meet'
        @align = nil
      end

      public

      include PreserveAspectRatio

      # Describe a user coordinate system to be imposed on the viewbox.
      # The arguments must be numbers and the +width+ and +height+
      # arguments must be positive.
      def viewbox(x, y, width, height)
        begin
          @vbx_x = Float(x)
          @vbx_y = Float(y)
          @vbx_width = Float(width)
          @vbx_height = Float(height)
        rescue ArgumentError
          raise ArgumentError, "arguments must be convertable to float (got #{x.class}, #{y.class}, #{width.class}, #{height.class})"
        end
        raise(ArgumentError, "viewbox width must be > 0 (#{@vbx_width} given)") unless @vbx_width >= 0
        raise(ArgumentError, "viewbox height must be > 0 (#{@vbx_height} given)") unless @vbx_height >= 0

        # return the user-coordinate space attributes if defined
        class << self
          unless defined? @redefined
            @redefined = true
            define_method(:x) { @vbx_x }
            define_method(:y) { @vbx_y }
            define_method(:width) { @vbx_width }
            define_method(:height) { @vbx_height }
          end
        end

        yield(self) if block_given?
        self
      end
    end # module Stretchable
  end # class RVG
end # module Magick