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,168 @@
1
+ module Prawn::SVG::Calculators
2
+ class PathLength
3
+ Segment = Struct.new(: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..])
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: url, format: format }
57
+ when 'local'
58
+ name = first[1][0]
59
+ return unless name
60
+
61
+ { type: :local, name: 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
@@ -0,0 +1,46 @@
1
+ module Prawn::SVG::CSS
2
+ class FontParser
3
+ def self.parse(string)
4
+ in_quote = nil
5
+ in_escape = false
6
+ in_list_delimiter = false
7
+ current = nil
8
+ values = []
9
+
10
+ string.each_char do |char|
11
+ if in_escape
12
+ in_escape = false
13
+ if current.nil?
14
+ current = char
15
+ values << current
16
+ else
17
+ current << char
18
+ end
19
+ elsif char == ',' && in_quote.nil? && !in_escape && current
20
+ current << char
21
+ in_list_delimiter = true
22
+ elsif char == '\\'
23
+ in_escape = true
24
+ elsif current.nil?
25
+ if char.match(/\s/).nil?
26
+ in_list_delimiter = false
27
+ current = char
28
+ values << current
29
+ end
30
+ elsif !in_quote && !in_escape && !in_list_delimiter && char.match(/\s/)
31
+ current = nil
32
+ else
33
+ current << char
34
+ end
35
+
36
+ if char == in_quote
37
+ in_quote = nil
38
+ elsif in_quote.nil? && ['"', "'"].include?(char)
39
+ in_quote = char
40
+ end
41
+ end
42
+
43
+ values.map(&:rstrip)
44
+ end
45
+ end
46
+ end