prawnx 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +2 -0
  3. data/GPLv2 +339 -0
  4. data/GPLv3 +674 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-prawn-svg +21 -0
  7. data/README.md +107 -0
  8. data/data/fonts/Courier-Bold.afm +342 -0
  9. data/data/fonts/Courier-BoldOblique.afm +342 -0
  10. data/data/fonts/Courier-Oblique.afm +342 -0
  11. data/data/fonts/Courier.afm +342 -0
  12. data/data/fonts/Helvetica-Bold.afm +2827 -0
  13. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  14. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  15. data/data/fonts/Helvetica.afm +3051 -0
  16. data/data/fonts/MustRead.html +19 -0
  17. data/data/fonts/Symbol.afm +213 -0
  18. data/data/fonts/Times-Bold.afm +2588 -0
  19. data/data/fonts/Times-BoldItalic.afm +2384 -0
  20. data/data/fonts/Times-Italic.afm +2667 -0
  21. data/data/fonts/Times-Roman.afm +2419 -0
  22. data/data/fonts/ZapfDingbats.afm +225 -0
  23. data/lib/pdf/core.rb +4 -0
  24. data/lib/prawn/measurement_extensions.rb +4 -0
  25. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  26. data/lib/prawn/svg/attributes/mask.rb +16 -0
  27. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  28. data/lib/prawn/svg/attributes/space.rb +10 -0
  29. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  30. data/lib/prawn/svg/attributes/transform.rb +10 -0
  31. data/lib/prawn/svg/attributes.rb +6 -0
  32. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  33. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  34. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  35. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  36. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  37. data/lib/prawn/svg/color.rb +240 -0
  38. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  39. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  40. data/lib/prawn/svg/css/font_parser.rb +46 -0
  41. data/lib/prawn/svg/css/selector_parser.rb +178 -0
  42. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  43. data/lib/prawn/svg/css/values_parser.rb +68 -0
  44. data/lib/prawn/svg/document.rb +170 -0
  45. data/lib/prawn/svg/elements/anchor.rb +10 -0
  46. data/lib/prawn/svg/elements/base.rb +312 -0
  47. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  48. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  49. data/lib/prawn/svg/elements/circle.rb +25 -0
  50. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  51. data/lib/prawn/svg/elements/container.rb +10 -0
  52. data/lib/prawn/svg/elements/direct_render_base.rb +27 -0
  53. data/lib/prawn/svg/elements/ellipse.rb +22 -0
  54. data/lib/prawn/svg/elements/gradient.rb +184 -0
  55. data/lib/prawn/svg/elements/ignored.rb +5 -0
  56. data/lib/prawn/svg/elements/image.rb +177 -0
  57. data/lib/prawn/svg/elements/line.rb +27 -0
  58. data/lib/prawn/svg/elements/marker.rb +72 -0
  59. data/lib/prawn/svg/elements/mask.rb +65 -0
  60. data/lib/prawn/svg/elements/path.rb +294 -0
  61. data/lib/prawn/svg/elements/pattern.rb +150 -0
  62. data/lib/prawn/svg/elements/polygon.rb +25 -0
  63. data/lib/prawn/svg/elements/polyline.rb +23 -0
  64. data/lib/prawn/svg/elements/rect.rb +64 -0
  65. data/lib/prawn/svg/elements/root.rb +22 -0
  66. data/lib/prawn/svg/elements/switch.rb +92 -0
  67. data/lib/prawn/svg/elements/text.rb +99 -0
  68. data/lib/prawn/svg/elements/text_component.rb +346 -0
  69. data/lib/prawn/svg/elements/text_node.rb +308 -0
  70. data/lib/prawn/svg/elements/text_path.rb +153 -0
  71. data/lib/prawn/svg/elements/use.rb +142 -0
  72. data/lib/prawn/svg/elements/viewport.rb +29 -0
  73. data/lib/prawn/svg/elements.rb +46 -0
  74. data/lib/prawn/svg/extension.rb +25 -0
  75. data/lib/prawn/svg/font.rb +20 -0
  76. data/lib/prawn/svg/font_metrics.rb +144 -0
  77. data/lib/prawn/svg/font_registry.rb +315 -0
  78. data/lib/prawn/svg/funciri.rb +14 -0
  79. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  80. data/lib/prawn/svg/gradients.rb +53 -0
  81. data/lib/prawn/svg/interface.rb +72 -0
  82. data/lib/prawn/svg/length.rb +43 -0
  83. data/lib/prawn/svg/link_renderer.rb +40 -0
  84. data/lib/prawn/svg/loaders/data.rb +17 -0
  85. data/lib/prawn/svg/loaders/file.rb +111 -0
  86. data/lib/prawn/svg/loaders/web.rb +24 -0
  87. data/lib/prawn/svg/paint.rb +67 -0
  88. data/lib/prawn/svg/pathable.rb +153 -0
  89. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  90. data/lib/prawn/svg/pdfmatrix.rb +37 -0
  91. data/lib/prawn/svg/percentage.rb +24 -0
  92. data/lib/prawn/svg/properties.rb +329 -0
  93. data/lib/prawn/svg/renderer.rb +249 -0
  94. data/lib/prawn/svg/state.rb +24 -0
  95. data/lib/prawn/svg/transform_parser.rb +78 -0
  96. data/lib/prawn/svg/ttc.rb +35 -0
  97. data/lib/prawn/svg/ttf.rb +95 -0
  98. data/lib/prawn/svg/url_loader.rb +46 -0
  99. data/lib/prawn/svg/version.rb +5 -0
  100. data/lib/prawn/table.rb +4 -0
  101. data/lib/prawn-svg.rb +53 -0
  102. data/lib/prawn.rb +10 -0
  103. data/lib/prawnx/annotations.rb +21 -0
  104. data/lib/prawnx/document/bounding_box.rb +193 -0
  105. data/lib/prawnx/document/column_box.rb +91 -0
  106. data/lib/prawnx/document/internals.rb +144 -0
  107. data/lib/prawnx/document/span.rb +25 -0
  108. data/lib/prawnx/document.rb +253 -0
  109. data/lib/prawnx/encoding.rb +29 -0
  110. data/lib/prawnx/errors.rb +37 -0
  111. data/lib/prawnx/font.rb +214 -0
  112. data/lib/prawnx/fonts/afm.rb +171 -0
  113. data/lib/prawnx/fonts/otf.rb +50 -0
  114. data/lib/prawnx/fonts/ttf.rb +410 -0
  115. data/lib/prawnx/graphic_state.rb +41 -0
  116. data/lib/prawnx/graphics/color.rb +148 -0
  117. data/lib/prawnx/graphics/patterns.rb +100 -0
  118. data/lib/prawnx/graphics/style.rb +171 -0
  119. data/lib/prawnx/graphics.rb +208 -0
  120. data/lib/prawnx/grid.rb +216 -0
  121. data/lib/prawnx/images/image.rb +244 -0
  122. data/lib/prawnx/images/png_decoder.rb +65 -0
  123. data/lib/prawnx/images.rb +115 -0
  124. data/lib/prawnx/measurement_extensions.rb +40 -0
  125. data/lib/prawnx/measurements.rb +62 -0
  126. data/lib/prawnx/outline.rb +146 -0
  127. data/lib/prawnx/page.rb +125 -0
  128. data/lib/prawnx/page_geometry.rb +27 -0
  129. data/lib/prawnx/pdf/outline_node.rb +53 -0
  130. data/lib/prawnx/pdf/reference.rb +31 -0
  131. data/lib/prawnx/pdf/writer.rb +105 -0
  132. data/lib/prawnx/pdf.rb +66 -0
  133. data/lib/prawnx/pdf_a/srgb_profile.rb +61 -0
  134. data/lib/prawnx/pdf_a.rb +130 -0
  135. data/lib/prawnx/pdf_core.rb +25 -0
  136. data/lib/prawnx/repeater.rb +79 -0
  137. data/lib/prawnx/security.rb +108 -0
  138. data/lib/prawnx/stamp.rb +74 -0
  139. data/lib/prawnx/table/cell/kinds.rb +246 -0
  140. data/lib/prawnx/table/cell.rb +331 -0
  141. data/lib/prawnx/table/cells.rb +208 -0
  142. data/lib/prawnx/table.rb +296 -0
  143. data/lib/prawnx/text/box.rb +16 -0
  144. data/lib/prawnx/text/formatted/arranger.rb +217 -0
  145. data/lib/prawnx/text/formatted/box.rb +312 -0
  146. data/lib/prawnx/text/formatted/fragment.rb +205 -0
  147. data/lib/prawnx/text/formatted/line_wrap.rb +204 -0
  148. data/lib/prawnx/text/formatted/parser.rb +149 -0
  149. data/lib/prawnx/text/formatted/wrap.rb +103 -0
  150. data/lib/prawnx/text.rb +281 -0
  151. data/lib/prawnx/transformation_stack.rb +43 -0
  152. data/lib/prawnx/true_type/cff_subset.rb +157 -0
  153. data/lib/prawnx/true_type/file.rb +371 -0
  154. data/lib/prawnx/true_type/open_type_layout.rb +189 -0
  155. data/lib/prawnx/true_type/simple_subsets.rb +94 -0
  156. data/lib/prawnx/true_type/subset.rb +187 -0
  157. data/lib/prawnx/version.rb +7 -0
  158. data/lib/prawnx/view.rb +28 -0
  159. data/lib/prawnx.rb +102 -0
  160. metadata +251 -0
@@ -0,0 +1,67 @@
1
+ module Prawn::SVG
2
+ Paint = Struct.new(:color, :url) do
3
+ class << self
4
+ def none
5
+ new(:none, nil)
6
+ end
7
+
8
+ def black
9
+ new(Color.black, nil)
10
+ end
11
+
12
+ def parse(value)
13
+ case CSS::ValuesParser.parse(value)
14
+ in [['url', [url]]]
15
+ # If there is no fallback color, and the URL is unresolvable, the spec says that the document is in error.
16
+ # Chrome appears to treat this the same as an explicit 'none', so we do the same.
17
+ new(:none, url)
18
+ in [['url', [url]], keyword_or_color]
19
+ parse_keyword_or_color(keyword_or_color, url)
20
+ in [['url', [url]], keyword_or_color, ['icc-color', _args]] # rubocop:disable Lint/DuplicateBranch
21
+ parse_keyword_or_color(keyword_or_color, url)
22
+ in [keyword_or_color, ['icc-color', _args]]
23
+ parse_keyword_or_color(keyword_or_color, nil)
24
+ in [keyword_or_color] # rubocop:disable Lint/DuplicateBranch
25
+ parse_keyword_or_color(keyword_or_color, nil)
26
+ else
27
+ nil
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def parse_keyword_or_color(value, url)
34
+ if value.is_a?(String)
35
+ keyword = value.downcase
36
+ return new(keyword.to_sym, url) if ['none', 'currentcolor'].include?(keyword)
37
+ end
38
+
39
+ color = Color.parse(value)
40
+ new(color, url) if color
41
+ end
42
+ end
43
+
44
+ def none?
45
+ color == :none && (url.nil? || !!@unresolved_url)
46
+ end
47
+
48
+ def resolve(gradients, current_color, color_mode)
49
+ if url
50
+ if url[0] == '#' && gradients && (gradient = gradients[url[1..]])
51
+ return gradient
52
+ else
53
+ @unresolved_url = true
54
+ end
55
+ end
56
+
57
+ case color
58
+ when :currentcolor
59
+ current_color
60
+ when :none
61
+ nil
62
+ else
63
+ color_mode == :cmyk ? color.to_cmyk : color
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,153 @@
1
+ module Prawn::SVG::Pathable
2
+ Move = Struct.new(:destination)
3
+ Close = Struct.new(:destination)
4
+ Line = Struct.new(:destination)
5
+ Curve = Struct.new(:destination, :point1, :point2)
6
+
7
+ def bounding_box
8
+ points = commands.map { |command| translate(command.destination) }
9
+ x1, x2 = points.map(&:first).minmax
10
+ y2, y1 = points.map(&:last).minmax
11
+
12
+ [x1, y1, x2, y2]
13
+ end
14
+
15
+ protected
16
+
17
+ def apply_commands
18
+ commands.each do |command|
19
+ case command
20
+ when Move
21
+ add_call 'move_to', translate(command.destination)
22
+ when Close
23
+ add_call 'close_path'
24
+ when Line
25
+ add_call 'line_to', translate(command.destination)
26
+ when Curve
27
+ add_call 'curve_to', translate(command.destination),
28
+ bounds: [translate(command.point1), translate(command.point2)]
29
+ else
30
+ raise NotImplementedError, 'Unknown path command type'
31
+ end
32
+ end
33
+ end
34
+
35
+ def apply_markers
36
+ if (marker = extract_element_from_url_id_reference(properties.marker_start, 'marker'))
37
+ marker.apply_marker(self, point: commands.first.destination, angle: angles.first)
38
+ end
39
+
40
+ if (marker = extract_element_from_url_id_reference(properties.marker_mid, 'marker'))
41
+ (1..commands.length - 2).each do |index|
42
+ marker.apply_marker(self, point: commands[index].destination, angle: angles[index])
43
+ end
44
+ end
45
+
46
+ if (marker = extract_element_from_url_id_reference(properties.marker_end, 'marker'))
47
+ marker.apply_marker(self, point: commands.last.destination, angle: angles.last)
48
+ end
49
+ end
50
+
51
+ def angles
52
+ return @angles if @angles
53
+
54
+ last_point = nil
55
+
56
+ destination_angles = commands.map do |command|
57
+ angles =
58
+ case command
59
+ when Move
60
+ [nil, nil]
61
+ when Close, Line
62
+ angle = Math.atan2(command.destination[1] - last_point[1],
63
+ command.destination[0] - last_point[0]) * 180.0 / Math::PI
64
+ [angle, angle]
65
+ when Curve
66
+ point = select_non_equal_point(last_point, command.point1, command.point2, command.destination)
67
+ start = Math.atan2(point[1] - last_point[1], point[0] - last_point[0]) * 180.0 / Math::PI
68
+
69
+ point = select_non_equal_point(command.destination, command.point2, command.point1, last_point)
70
+ stop = Math.atan2(command.destination[1] - point[1],
71
+ command.destination[0] - point[0]) * 180.0 / Math::PI
72
+
73
+ [start, stop]
74
+ else
75
+ raise NotImplementedError, 'Unknown path command type'
76
+ end
77
+
78
+ last_point = command.destination
79
+ angles
80
+ end
81
+
82
+ angles = destination_angles.each_cons(2).map do |first_angles, second_angles|
83
+ if first_angles.first.nil?
84
+ second_angles.first || 0
85
+ elsif second_angles.first.nil?
86
+ first_angles.last
87
+ else
88
+ first = first_angles.last
89
+ second = second_angles.last
90
+ bisect = (first + second) / 2.0
91
+
92
+ if (first - second).abs > 180
93
+ bisect >= 0 ? bisect - 180 : bisect + 180
94
+ else
95
+ bisect
96
+ end
97
+ end
98
+ end
99
+
100
+ if commands.last.is_a?(Close)
101
+ first = destination_angles.last.last || 0
102
+ second = angles.first
103
+ bisect = (first + second) / 2.0
104
+
105
+ closing_angle = if (first - second).abs > 180
106
+ bisect >= 0 ? bisect - 180 : bisect + 180
107
+ else
108
+ bisect
109
+ end
110
+
111
+ angles[0] = closing_angle
112
+ angles << closing_angle
113
+ else
114
+ angles << destination_angles.last.last
115
+ end
116
+
117
+ @angles = angles
118
+ end
119
+
120
+ def parse_points(points_string)
121
+ values = points_string
122
+ .to_s
123
+ .strip
124
+ .gsub(/(\d)-(\d)/, '\1 -\2')
125
+ .split(Prawn::SVG::Elements::COMMA_WSP_REGEXP)
126
+ .map(&:to_f)
127
+
128
+ if values.length.odd?
129
+ document.warnings << 'points attribute has an odd number of points; ignoring the last one'
130
+ values.pop
131
+ end
132
+
133
+ raise Prawn::SVG::Elements::Base::SkipElementQuietly if values.empty?
134
+
135
+ values.each_slice(2).to_a
136
+ end
137
+
138
+ def translate(point)
139
+ [point[0].to_f, document.sizing.output_height - point[1].to_f]
140
+ end
141
+
142
+ private
143
+
144
+ def select_non_equal_point(base, point_a, point_b, point_c)
145
+ if point_a != base
146
+ point_a
147
+ elsif point_b != base
148
+ point_b
149
+ else
150
+ point_c
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,112 @@
1
+ class Prawn::SVG::PatternRenderer
2
+ include Prawn::SVG::PDFMatrix
3
+
4
+ def initialize(prawn, draw_type, renderer, tile_x:, tile_y:, tile_width:, tile_height:, transform:, calls:)
5
+ @prawn = prawn
6
+ @draw_type = draw_type
7
+ @renderer = renderer
8
+ @tile_x = tile_x
9
+ @tile_y = tile_y
10
+ @tile_width = tile_width
11
+ @tile_height = tile_height
12
+ @transform = transform
13
+ @calls = calls
14
+ end
15
+
16
+ def draw
17
+ key = Prawn::SVG::GradientRenderer.next_key
18
+
19
+ pattern_ref = create_tiling_pattern(key)
20
+
21
+ prawn.page.resources[:Pattern] ||= {}
22
+ prawn.page.resources[:Pattern]["PSVG-Pattern-#{key}"] = pattern_ref
23
+
24
+ prawn.send(:set_color_space, draw_type, :Pattern)
25
+ draw_operator = draw_type == :fill ? 'scn' : 'SCN'
26
+ prawn.renderer.add_content("/PSVG-Pattern-#{key} #{draw_operator}")
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :prawn, :draw_type, :renderer, :tile_x, :tile_y, :tile_width, :tile_height, :transform, :calls
32
+
33
+ def create_tiling_pattern(key)
34
+ stamp_name = "PSVG-PatternContent-#{key}"
35
+
36
+ prawn.create_stamp(stamp_name) do
37
+ renderer.render_calls(prawn, calls)
38
+ end
39
+
40
+ registry = prawn.instance_variable_get(:@stamp_dictionary_registry)
41
+ stamp_entry = registry[stamp_name]
42
+ stamp_dict = stamp_entry[:stamp_dictionary]
43
+
44
+ # The content shift uses the base matrix (without patternTransform) so that
45
+ # content fills the tile correctly. The pattern Matrix includes the transform
46
+ # so the tiling grid is rotated/scaled, but each tile's content is not.
47
+ base = base_pattern_matrix
48
+ inverse = base.inverse
49
+
50
+ pattern_ref = prawn.ref!(
51
+ PatternType: 1,
52
+ PaintType: 1,
53
+ TilingType: 1,
54
+ BBox: [0, 0, tile_width, tile_height],
55
+ XStep: tile_width,
56
+ YStep: tile_height,
57
+ Matrix: matrix_for_pdf(full_pattern_matrix),
58
+ Resources: stamp_dict.data[:Resources] || {}
59
+ )
60
+
61
+ inv_pdf = matrix_for_pdf(inverse)
62
+ cm_str = inv_pdf.map { |v| v.is_a?(Float) ? format('%.6f', v) : v.to_s }.join(' ')
63
+ pattern_ref.stream << "#{cm_str} cm\n"
64
+ pattern_ref.stream << stamp_dict.stream
65
+
66
+ registry.delete(stamp_name)
67
+
68
+ pattern_ref
69
+ end
70
+
71
+ def svg_to_page_matrix
72
+ bounds_x, bounds_y = prawn.bounds.anchor
73
+ output_height = prawn.bounds.height
74
+
75
+ Matrix[
76
+ [1.0, 0.0, bounds_x.to_f],
77
+ [0.0, -1.0, bounds_y.to_f + output_height],
78
+ [0.0, 0.0, 1.0]
79
+ ]
80
+ end
81
+
82
+ def pattern_to_svg_matrix
83
+ output_height = prawn.bounds.height
84
+ svg_x = tile_x.to_f
85
+ svg_y = (output_height - tile_y - tile_height).to_f
86
+
87
+ Matrix[
88
+ [1.0, 0.0, svg_x],
89
+ [0.0, -1.0, svg_y + tile_height],
90
+ [0.0, 0.0, 1.0]
91
+ ]
92
+ end
93
+
94
+ # Maps pattern space to page space WITHOUT patternTransform.
95
+ # Used to shift stamp content (in page coords) to pattern-local coords.
96
+ def base_pattern_matrix
97
+ svg_to_page_matrix * pattern_to_svg_matrix
98
+ end
99
+
100
+ # Maps pattern space to page space WITH patternTransform and viewport scaling.
101
+ # Used as the pattern's Matrix field so the tiling grid is transformed.
102
+ # Viewport scaling is needed because stamp content is in SVG user units,
103
+ # but shapes on the page are scaled by the document's viewport transform.
104
+ def full_pattern_matrix
105
+ viewport_scale = scale_matrix(sizing.x_scale, sizing.y_scale)
106
+ svg_to_page_matrix * viewport_scale * transform * pattern_to_svg_matrix
107
+ end
108
+
109
+ def sizing
110
+ renderer.document.sizing
111
+ end
112
+ end
@@ -0,0 +1,37 @@
1
+ module Prawn::SVG::PDFMatrix
2
+ def load_matrix(matrix)
3
+ if matrix.is_a?(Matrix) && matrix.row_count == 3 && matrix.column_count == 3
4
+ matrix
5
+ elsif matrix.is_a?(Array) && matrix.length == 6
6
+ Matrix[
7
+ [matrix[0], matrix[2], matrix[4]],
8
+ [matrix[1], matrix[3], matrix[5]],
9
+ [0.0, 0.0, 1.0]
10
+ ]
11
+ else
12
+ raise ArgumentError, 'unexpected matrix format'
13
+ end
14
+ end
15
+
16
+ def matrix_for_pdf(matrix)
17
+ matrix.to_a[0..1].transpose.flatten
18
+ end
19
+
20
+ def translation_matrix(x = 0, y = 0)
21
+ Matrix[[1.0, 0.0, x.to_f], [0.0, 1.0, y.to_f], [0.0, 0.0, 1.0]]
22
+ end
23
+
24
+ def scale_matrix(x = 1, y = x)
25
+ Matrix[[x.to_f, 0.0, 0.0], [0.0, y.to_f, 0.0], [0.0, 0.0, 1.0]]
26
+ end
27
+
28
+ def rotation_matrix(angle, space: :pdf)
29
+ dir = space == :svg ? 1.0 : -1.0
30
+
31
+ Matrix[
32
+ [Math.cos(angle), -dir * Math.sin(angle), 0],
33
+ [dir * Math.sin(angle), Math.cos(angle), 0],
34
+ [0, 0, 1]
35
+ ]
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ Prawn::SVG::Percentage = Struct.new(:value)
2
+
3
+ class Prawn::SVG::Percentage
4
+ REGEXP = /\A([+-]?\d*(?:\.\d+)?)%\z/i.freeze
5
+
6
+ def self.parse(value, positive_only: false)
7
+ if (matches = value.match(REGEXP))
8
+ number = Float(matches[1], exception: false)
9
+ new(number) if number && (!positive_only || number >= 0)
10
+ end
11
+ end
12
+
13
+ def to_f
14
+ value
15
+ end
16
+
17
+ def to_factor
18
+ value / 100.0
19
+ end
20
+
21
+ def to_pixels(axis_length, _font_size)
22
+ value * axis_length / 100.0
23
+ end
24
+ end