gamba 2.0.0

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 (163) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/COPYING +2 -0
  4. data/GPLv2 +339 -0
  5. data/GPLv3 +674 -0
  6. data/LICENSE +65 -0
  7. data/LICENSE-prawn-svg +21 -0
  8. data/README.md +148 -0
  9. data/data/fonts/Courier-Bold.afm +342 -0
  10. data/data/fonts/Courier-BoldOblique.afm +342 -0
  11. data/data/fonts/Courier-Oblique.afm +342 -0
  12. data/data/fonts/Courier.afm +342 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/lib/gamba/annotations.rb +21 -0
  25. data/lib/gamba/combine.rb +307 -0
  26. data/lib/gamba/document/bounding_box.rb +193 -0
  27. data/lib/gamba/document/column_box.rb +91 -0
  28. data/lib/gamba/document/internals.rb +264 -0
  29. data/lib/gamba/document/span.rb +25 -0
  30. data/lib/gamba/document.rb +268 -0
  31. data/lib/gamba/encoding.rb +13 -0
  32. data/lib/gamba/errors.rb +39 -0
  33. data/lib/gamba/font.rb +228 -0
  34. data/lib/gamba/fonts/afm.rb +171 -0
  35. data/lib/gamba/fonts/otf.rb +50 -0
  36. data/lib/gamba/fonts/ttf.rb +413 -0
  37. data/lib/gamba/graphic_state.rb +84 -0
  38. data/lib/gamba/graphics/color.rb +174 -0
  39. data/lib/gamba/graphics/patterns.rb +100 -0
  40. data/lib/gamba/graphics/style.rb +159 -0
  41. data/lib/gamba/graphics.rb +212 -0
  42. data/lib/gamba/grid.rb +216 -0
  43. data/lib/gamba/images/image.rb +271 -0
  44. data/lib/gamba/images/png_decoder.rb +65 -0
  45. data/lib/gamba/images.rb +115 -0
  46. data/lib/gamba/measurement_extensions.rb +40 -0
  47. data/lib/gamba/measurements.rb +62 -0
  48. data/lib/gamba/outline.rb +148 -0
  49. data/lib/gamba/page.rb +125 -0
  50. data/lib/gamba/page_geometry.rb +27 -0
  51. data/lib/gamba/pdf/decryption.rb +165 -0
  52. data/lib/gamba/pdf/outline_node.rb +53 -0
  53. data/lib/gamba/pdf/reader.rb +616 -0
  54. data/lib/gamba/pdf/reference.rb +31 -0
  55. data/lib/gamba/pdf/writer.rb +105 -0
  56. data/lib/gamba/pdf.rb +87 -0
  57. data/lib/gamba/pdf_a/srgb_profile.rb +61 -0
  58. data/lib/gamba/pdf_a.rb +132 -0
  59. data/lib/gamba/repeater.rb +79 -0
  60. data/lib/gamba/security.rb +111 -0
  61. data/lib/gamba/stamp.rb +76 -0
  62. data/lib/gamba/table/cell/kinds.rb +246 -0
  63. data/lib/gamba/table/cell.rb +331 -0
  64. data/lib/gamba/table/cells.rb +208 -0
  65. data/lib/gamba/table.rb +298 -0
  66. data/lib/gamba/text/box.rb +16 -0
  67. data/lib/gamba/text/formatted/arranger.rb +217 -0
  68. data/lib/gamba/text/formatted/box.rb +312 -0
  69. data/lib/gamba/text/formatted/fragment.rb +205 -0
  70. data/lib/gamba/text/formatted/line_wrap.rb +205 -0
  71. data/lib/gamba/text/formatted/parser.rb +150 -0
  72. data/lib/gamba/text/formatted/wrap.rb +103 -0
  73. data/lib/gamba/text.rb +310 -0
  74. data/lib/gamba/transformation_stack.rb +43 -0
  75. data/lib/gamba/true_type/cff_subset.rb +157 -0
  76. data/lib/gamba/true_type/file.rb +372 -0
  77. data/lib/gamba/true_type/open_type_layout.rb +189 -0
  78. data/lib/gamba/true_type/simple_subsets.rb +108 -0
  79. data/lib/gamba/true_type/subset.rb +206 -0
  80. data/lib/gamba/version.rb +10 -0
  81. data/lib/gamba/view.rb +28 -0
  82. data/lib/gamba.rb +124 -0
  83. data/lib/pdf/core.rb +4 -0
  84. data/lib/prawn/measurement_extensions.rb +4 -0
  85. data/lib/prawn/svg/attributes/clip_path.rb +46 -0
  86. data/lib/prawn/svg/attributes/mask.rb +16 -0
  87. data/lib/prawn/svg/attributes/opacity.rb +30 -0
  88. data/lib/prawn/svg/attributes/space.rb +10 -0
  89. data/lib/prawn/svg/attributes/stroke.rb +50 -0
  90. data/lib/prawn/svg/attributes/transform.rb +10 -0
  91. data/lib/prawn/svg/attributes.rb +6 -0
  92. data/lib/prawn/svg/calculators/arc_to_bezier_curve.rb +57 -0
  93. data/lib/prawn/svg/calculators/aspect_ratio.rb +60 -0
  94. data/lib/prawn/svg/calculators/document_sizing.rb +103 -0
  95. data/lib/prawn/svg/calculators/path_length.rb +168 -0
  96. data/lib/prawn/svg/calculators/pixels.rb +57 -0
  97. data/lib/prawn/svg/color.rb +240 -0
  98. data/lib/prawn/svg/css/font_face_parser.rb +72 -0
  99. data/lib/prawn/svg/css/font_family_parser.rb +39 -0
  100. data/lib/prawn/svg/css/font_parser.rb +46 -0
  101. data/lib/prawn/svg/css/selector_parser.rb +180 -0
  102. data/lib/prawn/svg/css/stylesheets.rb +233 -0
  103. data/lib/prawn/svg/css/values_parser.rb +68 -0
  104. data/lib/prawn/svg/document.rb +169 -0
  105. data/lib/prawn/svg/elements/anchor.rb +10 -0
  106. data/lib/prawn/svg/elements/base.rb +315 -0
  107. data/lib/prawn/svg/elements/bbox_scaling.rb +78 -0
  108. data/lib/prawn/svg/elements/call_duplicator.rb +37 -0
  109. data/lib/prawn/svg/elements/circle.rb +19 -0
  110. data/lib/prawn/svg/elements/clip_path.rb +26 -0
  111. data/lib/prawn/svg/elements/container.rb +10 -0
  112. data/lib/prawn/svg/elements/direct_render_base.rb +28 -0
  113. data/lib/prawn/svg/elements/ellipse.rb +20 -0
  114. data/lib/prawn/svg/elements/gradient.rb +183 -0
  115. data/lib/prawn/svg/elements/ignored.rb +5 -0
  116. data/lib/prawn/svg/elements/image.rb +178 -0
  117. data/lib/prawn/svg/elements/line.rb +27 -0
  118. data/lib/prawn/svg/elements/marker.rb +72 -0
  119. data/lib/prawn/svg/elements/mask.rb +65 -0
  120. data/lib/prawn/svg/elements/path.rb +294 -0
  121. data/lib/prawn/svg/elements/pattern.rb +150 -0
  122. data/lib/prawn/svg/elements/polygon.rb +25 -0
  123. data/lib/prawn/svg/elements/polyline.rb +23 -0
  124. data/lib/prawn/svg/elements/rect.rb +64 -0
  125. data/lib/prawn/svg/elements/root.rb +22 -0
  126. data/lib/prawn/svg/elements/switch.rb +92 -0
  127. data/lib/prawn/svg/elements/text.rb +99 -0
  128. data/lib/prawn/svg/elements/text_component.rb +352 -0
  129. data/lib/prawn/svg/elements/text_node.rb +308 -0
  130. data/lib/prawn/svg/elements/text_path.rb +153 -0
  131. data/lib/prawn/svg/elements/use.rb +142 -0
  132. data/lib/prawn/svg/elements/viewport.rb +29 -0
  133. data/lib/prawn/svg/elements.rb +46 -0
  134. data/lib/prawn/svg/extension.rb +25 -0
  135. data/lib/prawn/svg/font.rb +20 -0
  136. data/lib/prawn/svg/font_metrics.rb +144 -0
  137. data/lib/prawn/svg/font_registry.rb +315 -0
  138. data/lib/prawn/svg/funciri.rb +14 -0
  139. data/lib/prawn/svg/gradient_renderer.rb +332 -0
  140. data/lib/prawn/svg/gradients.rb +53 -0
  141. data/lib/prawn/svg/interface.rb +72 -0
  142. data/lib/prawn/svg/length.rb +43 -0
  143. data/lib/prawn/svg/link_renderer.rb +40 -0
  144. data/lib/prawn/svg/loaders/data.rb +17 -0
  145. data/lib/prawn/svg/loaders/file.rb +111 -0
  146. data/lib/prawn/svg/loaders/web.rb +24 -0
  147. data/lib/prawn/svg/paint.rb +67 -0
  148. data/lib/prawn/svg/pathable.rb +153 -0
  149. data/lib/prawn/svg/pattern_renderer.rb +112 -0
  150. data/lib/prawn/svg/pdfmatrix.rb +39 -0
  151. data/lib/prawn/svg/percentage.rb +24 -0
  152. data/lib/prawn/svg/properties.rb +329 -0
  153. data/lib/prawn/svg/renderer.rb +249 -0
  154. data/lib/prawn/svg/state.rb +24 -0
  155. data/lib/prawn/svg/transform_parser.rb +78 -0
  156. data/lib/prawn/svg/ttc.rb +35 -0
  157. data/lib/prawn/svg/ttf.rb +95 -0
  158. data/lib/prawn/svg/url_loader.rb +46 -0
  159. data/lib/prawn/svg/version.rb +5 -0
  160. data/lib/prawn/table.rb +4 -0
  161. data/lib/prawn-svg.rb +52 -0
  162. data/lib/prawn.rb +3 -0
  163. metadata +263 -0
@@ -0,0 +1,103 @@
1
+ module Prawn::SVG::Calculators
2
+ class DocumentSizing
3
+ DEFAULT_ASPECT_RATIO = "xMidYMid meet".freeze
4
+
5
+ attr_writer :document_width, :document_height
6
+ attr_writer :view_box, :preserve_aspect_ratio
7
+
8
+ attr_reader :bounds
9
+ attr_reader :x_offset, :y_offset, :x_scale, :y_scale
10
+ attr_reader :viewport_width, :viewport_height, :viewport_diagonal, :output_width, :output_height
11
+
12
+ def initialize(bounds, attributes = nil)
13
+ @bounds = bounds
14
+ set_from_attributes(attributes) if attributes
15
+ end
16
+
17
+ def set_from_attributes(attributes)
18
+ @document_width = attributes["width"]
19
+ @document_height = attributes["height"]
20
+ @view_box = attributes["viewBox"]
21
+ @preserve_aspect_ratio = attributes["preserveAspectRatio"] || DEFAULT_ASPECT_RATIO
22
+ end
23
+
24
+ def calculate
25
+ @x_offset = @y_offset = 0
26
+ @x_scale = @y_scale = 1
27
+
28
+ container_width = @requested_width || @bounds[0]
29
+ container_height = @requested_height || @bounds[1]
30
+
31
+ @output_width = Pixels::Measurement.to_pixels(@document_width || @requested_width, container_width)
32
+ @output_height = Pixels::Measurement.to_pixels(@document_height || @requested_height, container_height)
33
+
34
+ if @view_box
35
+ values = @view_box.strip.split(Prawn::SVG::Elements::COMMA_WSP_REGEXP)
36
+ @x_offset, @y_offset, @viewport_width, @viewport_height = values.map(&:to_f)
37
+
38
+ if @viewport_width.positive? && @viewport_height.positive?
39
+ # If neither the width nor height was specified, use the entire width and the viewbox ratio
40
+ # to determine the height.
41
+ @output_width = container_width if @output_width.nil? && @output_height.nil?
42
+
43
+ # If one of the output dimensions is missing, calculate it from the other one
44
+ # using the ratio of the viewport width to height.
45
+ @output_width ||= @output_height * @viewport_width / @viewport_height
46
+ @output_height ||= @output_width * @viewport_height / @viewport_width
47
+
48
+ aspect = AspectRatio.new(@preserve_aspect_ratio, [@output_width, @output_height],
49
+ [@viewport_width, @viewport_height])
50
+ @x_scale = aspect.width / @viewport_width
51
+ @y_scale = aspect.height / @viewport_height
52
+ @x_offset -= aspect.x / @x_scale
53
+ @y_offset -= aspect.y / @y_scale
54
+ end
55
+ else
56
+ @output_width ||= container_width
57
+ @output_height ||= container_height
58
+
59
+ @viewport_width = @output_width
60
+ @viewport_height = @output_height
61
+ end
62
+
63
+ return if invalid?
64
+
65
+ # SVG 1.1 section 7.10
66
+ @viewport_diagonal = Math.sqrt((@viewport_width**2) + (@viewport_height**2)) / Math.sqrt(2)
67
+
68
+ if @requested_width
69
+ scale = @requested_width / @output_width
70
+ @output_width = @requested_width
71
+ @output_height *= scale
72
+ @x_scale *= scale
73
+ @y_scale *= scale
74
+
75
+ elsif @requested_height
76
+ scale = @requested_height / @output_height
77
+ @output_height = @requested_height
78
+ @output_width *= scale
79
+ @x_scale *= scale
80
+ @y_scale *= scale
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ def invalid?
87
+ @viewport_width <= 0 ||
88
+ @viewport_height <= 0 ||
89
+ @output_width <= 0 ||
90
+ @output_height <= 0 ||
91
+ (@requested_width && @requested_width <= 0) ||
92
+ (@requested_height && @requested_height <= 0)
93
+ end
94
+
95
+ def requested_width=(value)
96
+ @requested_width = value&.to_f
97
+ end
98
+
99
+ def requested_height=(value)
100
+ @requested_height = value&.to_f
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,168 @@
1
+ module Prawn::SVG::Calculators
2
+ class PathLength
3
+ Segment = Data.define(:start_point, :end_point, :type, :control_points, :segment_length, :cumulative_length, :lookup_table)
4
+
5
+ SUBDIVISION_TOLERANCE = 0.01
6
+ LOOKUP_TABLE_STEPS = 64
7
+
8
+ attr_reader :total_length
9
+
10
+ def initialize(commands)
11
+ @segments = []
12
+ @total_length = 0.0
13
+
14
+ current_point = nil
15
+ subpath_start = nil
16
+
17
+ commands.each do |command|
18
+ case command
19
+ when Prawn::SVG::Pathable::Move
20
+ current_point = command.destination
21
+ subpath_start = current_point
22
+
23
+ when Prawn::SVG::Pathable::Close
24
+ if current_point && subpath_start && current_point != subpath_start
25
+ add_line_segment(current_point, subpath_start)
26
+ current_point = subpath_start
27
+ end
28
+
29
+ when Prawn::SVG::Pathable::Line
30
+ if current_point
31
+ add_line_segment(current_point, command.destination)
32
+ current_point = command.destination
33
+ end
34
+
35
+ when Prawn::SVG::Pathable::Curve
36
+ if current_point
37
+ add_curve_segment(current_point, command.point1, command.point2, command.destination)
38
+ current_point = command.destination
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def point_at(distance)
45
+ return nil if distance.negative? || distance > @total_length || @segments.empty?
46
+
47
+ @segments.each do |segment|
48
+ start_distance = segment.cumulative_length - segment.segment_length
49
+
50
+ next unless distance <= segment.cumulative_length
51
+
52
+ local_distance = distance - start_distance
53
+
54
+ case segment.type
55
+ when :line
56
+ t = segment.segment_length.positive? ? local_distance / segment.segment_length : 0.0
57
+ x = segment.start_point[0] + (t * (segment.end_point[0] - segment.start_point[0]))
58
+ y = segment.start_point[1] + (t * (segment.end_point[1] - segment.start_point[1]))
59
+ dx = segment.end_point[0] - segment.start_point[0]
60
+ dy = segment.end_point[1] - segment.start_point[1]
61
+ angle = Math.atan2(dy, dx) * 180.0 / Math::PI
62
+ return [x, y, angle]
63
+
64
+ when :curve
65
+ t = find_t_for_distance(segment, local_distance)
66
+ p0, p1, p2, p3 = segment.start_point, *segment.control_points, segment.end_point
67
+ x, y = evaluate_cubic(p0, p1, p2, p3, t)
68
+ dx, dy = evaluate_cubic_derivative(p0, p1, p2, p3, t)
69
+ angle = Math.atan2(dy, dx) * 180.0 / Math::PI
70
+ return [x, y, angle]
71
+ end
72
+ end
73
+
74
+ # Exactly at the end
75
+ segment = @segments.last
76
+ [segment.end_point[0], segment.end_point[1], end_angle(segment)]
77
+ end
78
+
79
+ private
80
+
81
+ def add_line_segment(start_point, end_point)
82
+ length = Math.sqrt(((end_point[0] - start_point[0])**2) + ((end_point[1] - start_point[1])**2))
83
+ @total_length += length
84
+ @segments << Segment.new(start_point, end_point, :line, nil, length, @total_length, nil)
85
+ end
86
+
87
+ def add_curve_segment(start_point, control1, control2, end_point)
88
+ lookup_table = build_lookup_table(start_point, control1, control2, end_point)
89
+ length = lookup_table.last
90
+ @total_length += length
91
+ @segments << Segment.new(start_point, end_point, :curve, [control1, control2], length, @total_length, lookup_table)
92
+ end
93
+
94
+ def build_lookup_table(p0, p1, p2, p3)
95
+ table = [0.0]
96
+ prev_point = p0
97
+ cumulative = 0.0
98
+
99
+ 1.upto(LOOKUP_TABLE_STEPS) do |i|
100
+ t = i.to_f / LOOKUP_TABLE_STEPS
101
+ point = evaluate_cubic(p0, p1, p2, p3, t)
102
+ cumulative += Math.sqrt(((point[0] - prev_point[0])**2) + ((point[1] - prev_point[1])**2))
103
+ table << cumulative
104
+ prev_point = point
105
+ end
106
+
107
+ table
108
+ end
109
+
110
+ def find_t_for_distance(segment, target_distance)
111
+ table = segment.lookup_table
112
+ return 0.0 if target_distance <= 0
113
+ return 1.0 if target_distance >= segment.segment_length
114
+
115
+ # Binary search in the lookup table
116
+ low = 0
117
+ high = LOOKUP_TABLE_STEPS
118
+
119
+ while low < high - 1
120
+ mid = (low + high) / 2
121
+ if table[mid] < target_distance
122
+ low = mid
123
+ else
124
+ high = mid
125
+ end
126
+ end
127
+
128
+ # Linear interpolation between low and high
129
+ d_low = table[low]
130
+ d_high = table[high]
131
+ fraction = (d_high > d_low) ? (target_distance - d_low) / (d_high - d_low) : 0.0
132
+ (low + fraction) / LOOKUP_TABLE_STEPS
133
+ end
134
+
135
+ def evaluate_cubic(p0, p1, p2, p3, t)
136
+ mt = 1.0 - t
137
+ mt2 = mt * mt
138
+ mt3 = mt2 * mt
139
+ t2 = t * t
140
+ t3 = t2 * t
141
+
142
+ x = (mt3 * p0[0]) + (3 * mt2 * t * p1[0]) + (3 * mt * t2 * p2[0]) + (t3 * p3[0])
143
+ y = (mt3 * p0[1]) + (3 * mt2 * t * p1[1]) + (3 * mt * t2 * p2[1]) + (t3 * p3[1])
144
+ [x, y]
145
+ end
146
+
147
+ def evaluate_cubic_derivative(p0, p1, p2, p3, t)
148
+ mt = 1.0 - t
149
+
150
+ dx = (3 * mt * mt * (p1[0] - p0[0])) + (6 * mt * t * (p2[0] - p1[0])) + (3 * t * t * (p3[0] - p2[0]))
151
+ dy = (3 * mt * mt * (p1[1] - p0[1])) + (6 * mt * t * (p2[1] - p1[1])) + (3 * t * t * (p3[1] - p2[1]))
152
+ [dx, dy]
153
+ end
154
+
155
+ def end_angle(segment)
156
+ case segment.type
157
+ when :line
158
+ dx = segment.end_point[0] - segment.start_point[0]
159
+ dy = segment.end_point[1] - segment.start_point[1]
160
+ Math.atan2(dy, dx) * 180.0 / Math::PI
161
+ when :curve
162
+ p0, p1, p2, p3 = segment.start_point, *segment.control_points, segment.end_point
163
+ dx, dy = evaluate_cubic_derivative(p0, p1, p2, p3, 1.0)
164
+ Math.atan2(dy, dx) * 180.0 / Math::PI
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,57 @@
1
+ module Prawn::SVG::Calculators::Pixels
2
+ class Measurement
3
+ def self.to_pixels(value, axis_length = nil, font_size: Prawn::SVG::Properties::EM)
4
+ if value.respond_to?(:to_pixels)
5
+ value.to_pixels(axis_length, font_size)
6
+ elsif value.is_a?(String)
7
+ value = value.strip
8
+ value = Prawn::SVG::Length.parse(value) || Prawn::SVG::Percentage.parse(value)
9
+ value&.to_pixels(axis_length, font_size) || 0.0
10
+ elsif value
11
+ value.to_f
12
+ end
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ def x(value)
19
+ x_pixels(value)
20
+ end
21
+
22
+ def y(value)
23
+ # This uses document.sizing, not state.viewport_sizing, because we always
24
+ # want to subtract from the total height of the document.
25
+ document.sizing.output_height - y_pixels(value)
26
+ end
27
+
28
+ def pixels(value)
29
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_diagonal,
30
+ font_size: computed_properties.numeric_font_size)
31
+ end
32
+
33
+ def x_pixels(value)
34
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_width,
35
+ font_size: computed_properties.numeric_font_size)
36
+ end
37
+
38
+ def y_pixels(value)
39
+ value && Measurement.to_pixels(value, state.viewport_sizing.viewport_height,
40
+ font_size: computed_properties.numeric_font_size)
41
+ end
42
+
43
+ def percentage_or_proportion(string, default = 0)
44
+ string = string.to_s.strip
45
+ percentage = false
46
+
47
+ if string[-1] == "%"
48
+ percentage = true
49
+ string = string[0..-2]
50
+ end
51
+
52
+ value = Float(string, exception: false)
53
+ return default unless value
54
+
55
+ percentage ? value / 100.0 : value
56
+ end
57
+ end
@@ -0,0 +1,240 @@
1
+ class Prawn::SVG::Color
2
+ CMYK = Struct.new(:value) do
3
+ def to_cmyk
4
+ self
5
+ end
6
+ end
7
+
8
+ RGB = Struct.new(:value) do
9
+ def to_rgb
10
+ [value[0..1], value[2..3], value[4..5]].map { |h| h.to_i(16) / 255.0 }
11
+ end
12
+
13
+ def to_cmyk
14
+ r, g, b = rgb = to_rgb
15
+ k = 1 - rgb.max
16
+ if k == 1
17
+ CMYK.new([0, 0, 0, 100])
18
+ else
19
+ c = (1 - r - k) / (1 - k)
20
+ m = (1 - g - k) / (1 - k)
21
+ y = (1 - b - k) / (1 - k)
22
+ CMYK.new([c, m, y, k].map { |v| (v * 100).round })
23
+ end
24
+ end
25
+ end
26
+
27
+ RGB_DEFAULT_COLOR = RGB.new("000000")
28
+ CMYK_DEFAULT_COLOR = CMYK.new([0, 0, 0, 100])
29
+
30
+ HTML_COLORS = {
31
+ "aliceblue" => "f0f8ff",
32
+ "antiquewhite" => "faebd7",
33
+ "aqua" => "00ffff",
34
+ "aquamarine" => "7fffd4",
35
+ "azure" => "f0ffff",
36
+ "beige" => "f5f5dc",
37
+ "bisque" => "ffe4c4",
38
+ "black" => "000000",
39
+ "blanchedalmond" => "ffebcd",
40
+ "blue" => "0000ff",
41
+ "blueviolet" => "8a2be2",
42
+ "brown" => "a52a2a",
43
+ "burlywood" => "deb887",
44
+ "cadetblue" => "5f9ea0",
45
+ "chartreuse" => "7fff00",
46
+ "chocolate" => "d2691e",
47
+ "coral" => "ff7f50",
48
+ "cornflowerblue" => "6495ed",
49
+ "cornsilk" => "fff8dc",
50
+ "crimson" => "dc143c",
51
+ "cyan" => "00ffff",
52
+ "darkblue" => "00008b",
53
+ "darkcyan" => "008b8b",
54
+ "darkgoldenrod" => "b8860b",
55
+ "darkgray" => "a9a9a9",
56
+ "darkgreen" => "006400",
57
+ "darkgrey" => "a9a9a9",
58
+ "darkkhaki" => "bdb76b",
59
+ "darkmagenta" => "8b008b",
60
+ "darkolivegreen" => "556b2f",
61
+ "darkorange" => "ff8c00",
62
+ "darkorchid" => "9932cc",
63
+ "darkred" => "8b0000",
64
+ "darksalmon" => "e9967a",
65
+ "darkseagreen" => "8fbc8f",
66
+ "darkslateblue" => "483d8b",
67
+ "darkslategray" => "2f4f4f",
68
+ "darkslategrey" => "2f4f4f",
69
+ "darkturquoise" => "00ced1",
70
+ "darkviolet" => "9400d3",
71
+ "deeppink" => "ff1493",
72
+ "deepskyblue" => "00bfff",
73
+ "dimgray" => "696969",
74
+ "dimgrey" => "696969",
75
+ "dodgerblue" => "1e90ff",
76
+ "firebrick" => "b22222",
77
+ "floralwhite" => "fffaf0",
78
+ "forestgreen" => "228b22",
79
+ "fuchsia" => "ff00ff",
80
+ "gainsboro" => "dcdcdc",
81
+ "ghostwhite" => "f8f8ff",
82
+ "gold" => "ffd700",
83
+ "goldenrod" => "daa520",
84
+ "gray" => "808080",
85
+ "grey" => "808080",
86
+ "green" => "008000",
87
+ "greenyellow" => "adff2f",
88
+ "honeydew" => "f0fff0",
89
+ "hotpink" => "ff69b4",
90
+ "indianred" => "cd5c5c",
91
+ "indigo" => "4b0082",
92
+ "ivory" => "fffff0",
93
+ "khaki" => "f0e68c",
94
+ "lavender" => "e6e6fa",
95
+ "lavenderblush" => "fff0f5",
96
+ "lawngreen" => "7cfc00",
97
+ "lemonchiffon" => "fffacd",
98
+ "lightblue" => "add8e6",
99
+ "lightcoral" => "f08080",
100
+ "lightcyan" => "e0ffff",
101
+ "lightgoldenrodyellow" => "fafad2",
102
+ "lightgray" => "d3d3d3",
103
+ "lightgreen" => "90ee90",
104
+ "lightgrey" => "d3d3d3",
105
+ "lightpink" => "ffb6c1",
106
+ "lightsalmon" => "ffa07a",
107
+ "lightseagreen" => "20b2aa",
108
+ "lightskyblue" => "87cefa",
109
+ "lightslategray" => "778899",
110
+ "lightslategrey" => "778899",
111
+ "lightsteelblue" => "b0c4de",
112
+ "lightyellow" => "ffffe0",
113
+ "lime" => "00ff00",
114
+ "limegreen" => "32cd32",
115
+ "linen" => "faf0e6",
116
+ "magenta" => "ff00ff",
117
+ "maroon" => "800000",
118
+ "mediumaquamarine" => "66cdaa",
119
+ "mediumblue" => "0000cd",
120
+ "mediumorchid" => "ba55d3",
121
+ "mediumpurple" => "9370db",
122
+ "mediumseagreen" => "3cb371",
123
+ "mediumslateblue" => "7b68ee",
124
+ "mediumspringgreen" => "00fa9a",
125
+ "mediumturquoise" => "48d1cc",
126
+ "mediumvioletred" => "c71585",
127
+ "midnightblue" => "191970",
128
+ "mintcream" => "f5fffa",
129
+ "mistyrose" => "ffe4e1",
130
+ "moccasin" => "ffe4b5",
131
+ "navajowhite" => "ffdead",
132
+ "navy" => "000080",
133
+ "oldlace" => "fdf5e6",
134
+ "olive" => "808000",
135
+ "olivedrab" => "6b8e23",
136
+ "orange" => "ffa500",
137
+ "orangered" => "ff4500",
138
+ "orchid" => "da70d6",
139
+ "palegoldenrod" => "eee8aa",
140
+ "palegreen" => "98fb98",
141
+ "paleturquoise" => "afeeee",
142
+ "palevioletred" => "db7093",
143
+ "papayawhip" => "ffefd5",
144
+ "peachpuff" => "ffdab9",
145
+ "peru" => "cd853f",
146
+ "pink" => "ffc0cb",
147
+ "plum" => "dda0dd",
148
+ "powderblue" => "b0e0e6",
149
+ "purple" => "800080",
150
+ "red" => "ff0000",
151
+ "rosybrown" => "bc8f8f",
152
+ "royalblue" => "4169e1",
153
+ "saddlebrown" => "8b4513",
154
+ "salmon" => "fa8072",
155
+ "sandybrown" => "f4a460",
156
+ "seagreen" => "2e8b57",
157
+ "seashell" => "fff5ee",
158
+ "sienna" => "a0522d",
159
+ "silver" => "c0c0c0",
160
+ "skyblue" => "87ceeb",
161
+ "slateblue" => "6a5acd",
162
+ "slategray" => "708090",
163
+ "slategrey" => "708090",
164
+ "snow" => "fffafa",
165
+ "springgreen" => "00ff7f",
166
+ "steelblue" => "4682b4",
167
+ "tan" => "d2b48c",
168
+ "teal" => "008080",
169
+ "thistle" => "d8bfd8",
170
+ "tomato" => "ff6347",
171
+ "turquoise" => "40e0d0",
172
+ "violet" => "ee82ee",
173
+ "wheat" => "f5deb3",
174
+ "white" => "ffffff",
175
+ "whitesmoke" => "f5f5f5",
176
+ "yellow" => "ffff00",
177
+ "yellowgreen" => "9acd32"
178
+ }.freeze
179
+
180
+ class << self
181
+ def black
182
+ RGB_DEFAULT_COLOR
183
+ end
184
+
185
+ def parse(value)
186
+ case value
187
+ in ["rgb", args]
188
+ return unless args.length == 3
189
+
190
+ rgb =
191
+ args.map do |arg|
192
+ number = to_float(arg, 2.55) or break
193
+ format("%02x", number.round.clamp(0, 255))
194
+ end
195
+
196
+ rgb && RGB.new(rgb.join)
197
+
198
+ in ["device-cmyk", args]
199
+ return unless args.length == 4
200
+
201
+ cymk =
202
+ args.map do |arg|
203
+ number = to_float(arg, 0.01) or break
204
+ (number * 100).clamp(0, 100)
205
+ end
206
+
207
+ cymk && CMYK.new(cymk)
208
+
209
+ in /\A#([0-9a-f])([0-9a-f])([0-9a-f])\z/i
210
+ RGB.new("#{$1 * 2}#{$2 * 2}#{$3 * 2}")
211
+
212
+ in /\A#[0-9a-f]{6}\z/i
213
+ RGB.new(value[1..]) # rubocop:disable Performance/ArraySemiInfiniteRangeSlice -- String
214
+
215
+ in String => color
216
+ if (hex = HTML_COLORS[color.downcase])
217
+ RGB.new(hex)
218
+ end
219
+
220
+ else
221
+ nil
222
+ end
223
+ end
224
+
225
+ def default_color(color_mode)
226
+ (color_mode == :cmyk) ? CMYK_DEFAULT_COLOR : RGB_DEFAULT_COLOR
227
+ end
228
+
229
+ private
230
+
231
+ def to_float(string, percentage_multiplier)
232
+ if string[-1] == "%"
233
+ number = Float(string[0..-2], exception: false)
234
+ number && (number * percentage_multiplier)
235
+ else
236
+ Float(string, exception: false)
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,72 @@
1
+ module Prawn::SVG::CSS
2
+ class FontFaceParser
3
+ SUPPORTED_FORMATS = %w[truetype opentype].freeze
4
+
5
+ class << self
6
+ def parse_src(src)
7
+ split_sources(src).filter_map { |entry| parse_source_entry(entry.strip) }
8
+ end
9
+
10
+ private
11
+
12
+ def split_sources(value)
13
+ entries = []
14
+ current = +""
15
+ depth = 0
16
+ in_quote = nil
17
+
18
+ value.each_char do |char|
19
+ if in_quote
20
+ current << char
21
+ in_quote = nil if char == in_quote
22
+ elsif ['"', "'"].include?(char)
23
+ in_quote = char
24
+ current << char
25
+ elsif char == "("
26
+ depth += 1
27
+ current << char
28
+ elsif char == ")"
29
+ depth -= 1
30
+ current << char
31
+ elsif char == "," && depth.zero?
32
+ entries << current
33
+ current = +""
34
+ else
35
+ current << char
36
+ end
37
+ end
38
+
39
+ entries << current unless current.strip.empty?
40
+ entries
41
+ end
42
+
43
+ def parse_source_entry(entry)
44
+ parts = Prawn::SVG::CSS::ValuesParser.parse(entry)
45
+ return if parts.empty?
46
+
47
+ first = parts[0]
48
+ return unless first.is_a?(Array)
49
+
50
+ case first[0]
51
+ when "url"
52
+ url = first[1][0]
53
+ return unless url
54
+
55
+ format = extract_format(parts[1])
56
+ {type: :url, url:, format:}
57
+ when "local"
58
+ name = first[1][0]
59
+ return unless name
60
+
61
+ {type: :local, name:}
62
+ end
63
+ end
64
+
65
+ def extract_format(part)
66
+ return unless part.is_a?(Array) && part[0] == "format"
67
+
68
+ part[1][0]
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,39 @@
1
+ module Prawn::SVG::CSS
2
+ class FontFamilyParser
3
+ def self.parse(string)
4
+ in_quote = nil
5
+ in_escape = false
6
+ current = nil
7
+ fonts = []
8
+
9
+ string.each_char do |char|
10
+ if in_escape
11
+ in_escape = false
12
+ if current.nil?
13
+ current = char
14
+ fonts << current
15
+ else
16
+ current << char
17
+ end
18
+ elsif char == "," && in_quote.nil?
19
+ current = nil
20
+ elsif char == in_quote
21
+ in_quote = nil
22
+ elsif in_quote.nil? && ['"', "'"].include?(char)
23
+ in_quote = char
24
+ elsif char == "\\"
25
+ in_escape = true
26
+ elsif current.nil?
27
+ if char.match(/\s/).nil?
28
+ current = char
29
+ fonts << current
30
+ end
31
+ else
32
+ current << char
33
+ end
34
+ end
35
+
36
+ fonts.map(&:rstrip)
37
+ end
38
+ end
39
+ end