prawn-core 0.5.0.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 (186) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +56 -0
  3. data/README +121 -0
  4. data/Rakefile +74 -0
  5. data/data/encodings/win_ansi.txt +29 -0
  6. data/data/fonts/Action Man.dfont +0 -0
  7. data/data/fonts/Activa.ttf +0 -0
  8. data/data/fonts/Chalkboard.ttf +0 -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/DejaVuSans.ttf +0 -0
  14. data/data/fonts/Dustismo_Roman.ttf +0 -0
  15. data/data/fonts/Helvetica-Bold.afm +2827 -0
  16. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  17. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  18. data/data/fonts/Helvetica.afm +3051 -0
  19. data/data/fonts/MustRead.html +19 -0
  20. data/data/fonts/Symbol.afm +213 -0
  21. data/data/fonts/Times-Bold.afm +2588 -0
  22. data/data/fonts/Times-BoldItalic.afm +2384 -0
  23. data/data/fonts/Times-Italic.afm +2667 -0
  24. data/data/fonts/Times-Roman.afm +2419 -0
  25. data/data/fonts/ZapfDingbats.afm +225 -0
  26. data/data/fonts/comicsans.ttf +0 -0
  27. data/data/fonts/gkai00mp.ttf +0 -0
  28. data/data/images/16bit.alpha +0 -0
  29. data/data/images/16bit.dat +0 -0
  30. data/data/images/16bit.png +0 -0
  31. data/data/images/arrow.png +0 -0
  32. data/data/images/arrow2.png +0 -0
  33. data/data/images/barcode_issue.png +0 -0
  34. data/data/images/dice.alpha +0 -0
  35. data/data/images/dice.dat +0 -0
  36. data/data/images/dice.png +0 -0
  37. data/data/images/dice_interlaced.png +0 -0
  38. data/data/images/fractal.jpg +0 -0
  39. data/data/images/letterhead.jpg +0 -0
  40. data/data/images/page_white_text.alpha +0 -0
  41. data/data/images/page_white_text.dat +0 -0
  42. data/data/images/page_white_text.png +0 -0
  43. data/data/images/pigs.jpg +0 -0
  44. data/data/images/rails.dat +0 -0
  45. data/data/images/rails.png +0 -0
  46. data/data/images/ruport.png +0 -0
  47. data/data/images/ruport_data.dat +0 -0
  48. data/data/images/ruport_transparent.png +0 -0
  49. data/data/images/ruport_type0.png +0 -0
  50. data/data/images/stef.jpg +0 -0
  51. data/data/images/tru256.bmp +0 -0
  52. data/data/images/web-links.dat +1 -0
  53. data/data/images/web-links.png +0 -0
  54. data/data/shift_jis_text.txt +1 -0
  55. data/examples/bounding_box/bounding_boxes.rb +44 -0
  56. data/examples/bounding_box/russian_boxes.rb +37 -0
  57. data/examples/column_box/column_box_example.rb +44 -0
  58. data/examples/general/background.rb +20 -0
  59. data/examples/general/canvas.rb +16 -0
  60. data/examples/general/measurement_units.rb +52 -0
  61. data/examples/general/metadata-info.rb +17 -0
  62. data/examples/general/multi_page_layout.rb +17 -0
  63. data/examples/general/page_geometry.rb +32 -0
  64. data/examples/graphics/basic_images.rb +24 -0
  65. data/examples/graphics/cmyk.rb +13 -0
  66. data/examples/graphics/curves.rb +12 -0
  67. data/examples/graphics/hexagon.rb +14 -0
  68. data/examples/graphics/image_fit.rb +16 -0
  69. data/examples/graphics/image_flow.rb +38 -0
  70. data/examples/graphics/image_position.rb +18 -0
  71. data/examples/graphics/line.rb +33 -0
  72. data/examples/graphics/png_types.rb +23 -0
  73. data/examples/graphics/polygons.rb +17 -0
  74. data/examples/graphics/remote_images.rb +12 -0
  75. data/examples/graphics/ruport_style_helpers.rb +20 -0
  76. data/examples/graphics/stroke_bounds.rb +21 -0
  77. data/examples/m17n/chinese_text_wrapping.rb +20 -0
  78. data/examples/m17n/euro.rb +16 -0
  79. data/examples/m17n/sjis.rb +29 -0
  80. data/examples/m17n/utf8.rb +14 -0
  81. data/examples/m17n/win_ansi_charset.rb +55 -0
  82. data/examples/text/alignment.rb +19 -0
  83. data/examples/text/dfont.rb +49 -0
  84. data/examples/text/family_based_styling.rb +25 -0
  85. data/examples/text/font_calculations.rb +92 -0
  86. data/examples/text/font_size.rb +34 -0
  87. data/examples/text/kerning.rb +31 -0
  88. data/examples/text/simple_text.rb +18 -0
  89. data/examples/text/simple_text_ttf.rb +18 -0
  90. data/examples/text/span.rb +30 -0
  91. data/examples/text/text_box.rb +26 -0
  92. data/examples/text/text_flow.rb +68 -0
  93. data/lib/prawn/compatibility.rb +38 -0
  94. data/lib/prawn/core.rb +79 -0
  95. data/lib/prawn/document.rb +399 -0
  96. data/lib/prawn/document/annotations.rb +63 -0
  97. data/lib/prawn/document/bounding_box.rb +377 -0
  98. data/lib/prawn/document/column_box.rb +89 -0
  99. data/lib/prawn/document/destinations.rb +81 -0
  100. data/lib/prawn/document/internals.rb +133 -0
  101. data/lib/prawn/document/page_geometry.rb +149 -0
  102. data/lib/prawn/document/span.rb +55 -0
  103. data/lib/prawn/document/text.rb +186 -0
  104. data/lib/prawn/document/text/box.rb +83 -0
  105. data/lib/prawn/document/text/wrapping.rb +59 -0
  106. data/lib/prawn/encoding.rb +121 -0
  107. data/lib/prawn/errors.rb +49 -0
  108. data/lib/prawn/font.rb +292 -0
  109. data/lib/prawn/font/afm.rb +202 -0
  110. data/lib/prawn/font/dfont.rb +31 -0
  111. data/lib/prawn/font/ttf.rb +327 -0
  112. data/lib/prawn/graphics.rb +257 -0
  113. data/lib/prawn/graphics/color.rb +141 -0
  114. data/lib/prawn/images.rb +339 -0
  115. data/lib/prawn/images/jpg.rb +45 -0
  116. data/lib/prawn/images/png.rb +217 -0
  117. data/lib/prawn/literal_string.rb +14 -0
  118. data/lib/prawn/measurement_extensions.rb +46 -0
  119. data/lib/prawn/measurements.rb +71 -0
  120. data/lib/prawn/name_tree.rb +165 -0
  121. data/lib/prawn/pdf_object.rb +77 -0
  122. data/lib/prawn/reference.rb +59 -0
  123. data/spec/annotations_spec.rb +90 -0
  124. data/spec/bounding_box_spec.rb +141 -0
  125. data/spec/destinations_spec.rb +15 -0
  126. data/spec/document_spec.rb +178 -0
  127. data/spec/font_spec.rb +274 -0
  128. data/spec/graphics_spec.rb +209 -0
  129. data/spec/images_spec.rb +79 -0
  130. data/spec/jpg_spec.rb +25 -0
  131. data/spec/measurement_units_spec.rb +23 -0
  132. data/spec/name_tree_spec.rb +103 -0
  133. data/spec/pdf_object_spec.rb +117 -0
  134. data/spec/png_spec.rb +236 -0
  135. data/spec/reference_spec.rb +42 -0
  136. data/spec/span_spec.rb +45 -0
  137. data/spec/spec_helper.rb +23 -0
  138. data/spec/text_box_spec.rb +83 -0
  139. data/spec/text_spec.rb +178 -0
  140. data/vendor/pdf-inspector/README +18 -0
  141. data/vendor/pdf-inspector/lib/pdf/inspector.rb +25 -0
  142. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +80 -0
  143. data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +16 -0
  144. data/vendor/pdf-inspector/lib/pdf/inspector/text.rb +31 -0
  145. data/vendor/pdf-inspector/lib/pdf/inspector/xobject.rb +19 -0
  146. data/vendor/ttfunk/data/fonts/DejaVuSans.ttf +0 -0
  147. data/vendor/ttfunk/data/fonts/comicsans.ttf +0 -0
  148. data/vendor/ttfunk/example.rb +45 -0
  149. data/vendor/ttfunk/lib/ttfunk.rb +102 -0
  150. data/vendor/ttfunk/lib/ttfunk/directory.rb +17 -0
  151. data/vendor/ttfunk/lib/ttfunk/encoding/mac_roman.rb +88 -0
  152. data/vendor/ttfunk/lib/ttfunk/encoding/windows_1252.rb +69 -0
  153. data/vendor/ttfunk/lib/ttfunk/reader.rb +44 -0
  154. data/vendor/ttfunk/lib/ttfunk/resource_file.rb +78 -0
  155. data/vendor/ttfunk/lib/ttfunk/subset.rb +18 -0
  156. data/vendor/ttfunk/lib/ttfunk/subset/base.rb +141 -0
  157. data/vendor/ttfunk/lib/ttfunk/subset/mac_roman.rb +46 -0
  158. data/vendor/ttfunk/lib/ttfunk/subset/unicode.rb +48 -0
  159. data/vendor/ttfunk/lib/ttfunk/subset/unicode_8bit.rb +63 -0
  160. data/vendor/ttfunk/lib/ttfunk/subset/windows_1252.rb +51 -0
  161. data/vendor/ttfunk/lib/ttfunk/subset_collection.rb +72 -0
  162. data/vendor/ttfunk/lib/ttfunk/table.rb +46 -0
  163. data/vendor/ttfunk/lib/ttfunk/table/cmap.rb +34 -0
  164. data/vendor/ttfunk/lib/ttfunk/table/cmap/format00.rb +54 -0
  165. data/vendor/ttfunk/lib/ttfunk/table/cmap/format04.rb +126 -0
  166. data/vendor/ttfunk/lib/ttfunk/table/cmap/subtable.rb +79 -0
  167. data/vendor/ttfunk/lib/ttfunk/table/glyf.rb +64 -0
  168. data/vendor/ttfunk/lib/ttfunk/table/glyf/compound.rb +81 -0
  169. data/vendor/ttfunk/lib/ttfunk/table/glyf/simple.rb +37 -0
  170. data/vendor/ttfunk/lib/ttfunk/table/head.rb +44 -0
  171. data/vendor/ttfunk/lib/ttfunk/table/hhea.rb +41 -0
  172. data/vendor/ttfunk/lib/ttfunk/table/hmtx.rb +47 -0
  173. data/vendor/ttfunk/lib/ttfunk/table/kern.rb +79 -0
  174. data/vendor/ttfunk/lib/ttfunk/table/kern/format0.rb +62 -0
  175. data/vendor/ttfunk/lib/ttfunk/table/loca.rb +43 -0
  176. data/vendor/ttfunk/lib/ttfunk/table/maxp.rb +40 -0
  177. data/vendor/ttfunk/lib/ttfunk/table/name.rb +119 -0
  178. data/vendor/ttfunk/lib/ttfunk/table/os2.rb +78 -0
  179. data/vendor/ttfunk/lib/ttfunk/table/post.rb +91 -0
  180. data/vendor/ttfunk/lib/ttfunk/table/post/format10.rb +43 -0
  181. data/vendor/ttfunk/lib/ttfunk/table/post/format20.rb +35 -0
  182. data/vendor/ttfunk/lib/ttfunk/table/post/format25.rb +23 -0
  183. data/vendor/ttfunk/lib/ttfunk/table/post/format30.rb +17 -0
  184. data/vendor/ttfunk/lib/ttfunk/table/post/format40.rb +17 -0
  185. data/vendor/ttfunk/lib/ttfunk/table/simple.rb +14 -0
  186. metadata +245 -0
@@ -0,0 +1,257 @@
1
+ # encoding: utf-8
2
+
3
+ # graphics.rb : Implements PDF drawing primitives
4
+ #
5
+ # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require "enumerator"
10
+ require "prawn/graphics/color"
11
+
12
+ module Prawn
13
+
14
+ # Implements the drawing facilities for Prawn::Document.
15
+ # Use this to draw the most beautiful imaginable things.
16
+ #
17
+ # This file lifts and modifies several of PDF::Writer's graphics functions
18
+ # ruby-pdf.rubyforge.org
19
+ #
20
+ module Graphics
21
+
22
+ include Color
23
+
24
+ #######################################################################
25
+ # Low level drawing operations must translate to absolute coords! #
26
+ #######################################################################
27
+
28
+ # Moves the drawing position to a given point. The point can be
29
+ # specified as a tuple or a flattened argument list
30
+ #
31
+ # pdf.move_to [100,50]
32
+ # pdf.move_to(100,50)
33
+ #
34
+ def move_to(*point)
35
+ x,y = translate(point)
36
+ add_content("%.3f %.3f m" % [ x, y ])
37
+ end
38
+
39
+ # Draws a line from the current drawing position to the specified point.
40
+ # The destination may be described as a tuple or a flattened list:
41
+ #
42
+ # pdf.line_to [50,50]
43
+ # pdf.line_to(50,50)
44
+ #
45
+ def line_to(*point)
46
+ x,y = translate(point)
47
+ add_content("%.3f %.3f l" % [ x, y ])
48
+ end
49
+
50
+ # Draws a Bezier curve from the current drawing position to the
51
+ # specified point, bounded by two additional points.
52
+ #
53
+ # pdf.curve_to [100,100], :bounds => [[90,90],[75,75]]
54
+ #
55
+ def curve_to(dest,options={})
56
+ options[:bounds] or raise Prawn::Errors::InvalidGraphicsPath,
57
+ "Bounding points for bezier curve must be specified "+
58
+ "as :bounds => [[x1,y1],[x2,y2]]"
59
+
60
+ curve_points = (options[:bounds] << dest).map { |e| translate(e) }
61
+ add_content("%.3f %.3f %.3f %.3f %.3f %.3f c" %
62
+ curve_points.flatten )
63
+ end
64
+
65
+ # Draws a rectangle given <tt>point</tt>, <tt>width</tt> and
66
+ # <tt>height</tt>. The rectangle is bounded by its upper-left corner.
67
+ #
68
+ # pdf.rectangle [300,300], 100, 200
69
+ #
70
+ def rectangle(point,width,height)
71
+ x,y = translate(point)
72
+ add_content("%.3f %.3f %.3f %.3f re" % [ x, y - height, width, height ])
73
+ end
74
+
75
+ ###########################################################
76
+ # Higher level functions: May use relative coords #
77
+ ###########################################################
78
+
79
+ # Sets line thickness to the <tt>width</tt> specified.
80
+ #
81
+ def line_width=(width)
82
+ @line_width = width
83
+ add_content("#{width} w")
84
+ end
85
+
86
+ # When called without an argument, returns the current line thickness.
87
+ # When called with an argument, sets the line thickness to the specified
88
+ # value (in PDF points)
89
+ #
90
+ # pdf.line_width #=> 1
91
+ # pdf.line_width(5)
92
+ # pdf.line_width #=> 5
93
+ #
94
+ def line_width(width=nil)
95
+ if width
96
+ self.line_width = width
97
+ else
98
+ @line_width || 1
99
+ end
100
+ end
101
+
102
+ # Draws a line from one point to another. Points may be specified as
103
+ # tuples or flattened argument list:
104
+ #
105
+ # pdf.line [100,100], [200,250]
106
+ # pdf.line(100,100,200,250)
107
+ #
108
+ def line(*points)
109
+ x0,y0,x1,y1 = points.flatten
110
+ move_to(x0, y0)
111
+ line_to(x1, y1)
112
+ end
113
+
114
+ # Draws a horizontal line from <tt>x1</tt> to <tt>x2</tt> at the
115
+ # current <tt>y</tt> position, or the position specified by the :at option.
116
+ #
117
+ # # draw a line from [25, 75] to [100, 75]
118
+ # horizontal_line 25, 100, :at => 75
119
+ #
120
+ def horizontal_line(x1,x2,options={})
121
+ if options[:at]
122
+ y1 = options[:at]
123
+ else
124
+ y1 = y - bounds.absolute_bottom
125
+ end
126
+
127
+ line(x1,y1,x2,y1)
128
+ end
129
+
130
+ # Draws a horizontal line from the left border to the right border of the
131
+ # bounding box at the current <tt>y</tt> position.
132
+ #
133
+ def horizontal_rule
134
+ horizontal_line(bounds.left, bounds.right)
135
+ end
136
+
137
+ # Draws a vertical line at the x cooordinate given by :at from y1 to y2.
138
+ #
139
+ # # draw a line from [25, 100] to [25, 300]
140
+ # vertical_line 100, 300, :at => 25
141
+ #
142
+ def vertical_line(y1,y2,params)
143
+ line(params[:at],y1,params[:at],y2)
144
+ end
145
+
146
+ # Draws a Bezier curve between two points, bounded by two additional
147
+ # points
148
+ #
149
+ # pdf.curve [50,100], [100,100], :bounds => [[90,90],[75,75]]
150
+ #
151
+ def curve(origin,dest, options={})
152
+ move_to(*origin)
153
+ curve_to(dest,options)
154
+ end
155
+
156
+ # This constant is used to approximate a symmetrical arc using a cubic
157
+ # Bezier curve.
158
+ #
159
+ KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0)
160
+
161
+ # Draws a circle of radius <tt>:radius</tt> with the centre-point at <tt>point</tt>
162
+ # as a complete subpath. The drawing point will be moved to the
163
+ # centre-point upon completion of the drawing the circle.
164
+ #
165
+ # pdf.circle_at [100,100], :radius => 25
166
+ #
167
+ def circle_at(point, options)
168
+ x,y = point
169
+ ellipse_at [x, y], options[:radius]
170
+ end
171
+
172
+ # Draws an ellipse of +x+ radius <tt>r1</tt> and +y+ radius <tt>r2</tt>
173
+ # with the centre-point at <tt>point</tt> as a complete subpath. The
174
+ # drawing point will be moved to the centre-point upon completion of the
175
+ # drawing the ellipse.
176
+ #
177
+ # # draws an ellipse with x-radius 25 and y-radius 50
178
+ # pdf.ellipse_at [100,100], 25, 50
179
+ #
180
+ def ellipse_at(point, r1, r2 = r1)
181
+ x, y = point
182
+ l1 = r1 * KAPPA
183
+ l2 = r2 * KAPPA
184
+
185
+ move_to(x + r1, y)
186
+
187
+ # Upper right hand corner
188
+ curve_to [x, y + r2],
189
+ :bounds => [[x + r1, y + l1], [x + l2, y + r2]]
190
+
191
+ # Upper left hand corner
192
+ curve_to [x - r1, y],
193
+ :bounds => [[x - l2, y + r2], [x - r1, y + l1]]
194
+
195
+ # Lower left hand corner
196
+ curve_to [x, y - r2],
197
+ :bounds => [[x - r1, y - l1], [x - l2, y - r2]]
198
+
199
+ # Lower right hand corner
200
+ curve_to [x + r1, y],
201
+ :bounds => [[x + l2, y - r2], [x + r1, y - l1]]
202
+
203
+ move_to(x, y)
204
+ end
205
+
206
+ # Draws a polygon from the specified points.
207
+ #
208
+ # # draws a snazzy triangle
209
+ # pdf.polygon [100,100], [100,200], [200,200]
210
+ #
211
+ def polygon(*points)
212
+ move_to points[0]
213
+ (points[1..-1] << points[0]).each do |point|
214
+ line_to(*point)
215
+ end
216
+ end
217
+
218
+ # Strokes and closes the current path. See Graphic::Color for color details
219
+ #
220
+ def stroke
221
+ yield if block_given?
222
+ add_content "S"
223
+ end
224
+
225
+ # Draws and strokes a rectangle represented by the current bounding box
226
+ #
227
+ def stroke_bounds
228
+ stroke_rectangle bounds.top_left, bounds.width, bounds.height
229
+ end
230
+
231
+ # Fills and closes the current path. See Graphic::Color for color details
232
+ #
233
+ def fill
234
+ yield if block_given?
235
+ add_content "f"
236
+ end
237
+
238
+ # Fills, strokes, and closes the current path. See Graphic::Color for color details
239
+ #
240
+ def fill_and_stroke
241
+ yield if block_given?
242
+ add_content "b"
243
+ end
244
+
245
+ private
246
+
247
+ def translate(*point)
248
+ x,y = point.flatten
249
+ [@bounding_box.absolute_left + x, @bounding_box.absolute_bottom + y]
250
+ end
251
+
252
+ def translate!(point)
253
+ point.replace(translate(point))
254
+ end
255
+
256
+ end
257
+ end
@@ -0,0 +1,141 @@
1
+ # encoding: utf-8
2
+
3
+ # color.rb : Implements color handling
4
+ #
5
+ # Copyright June 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ #
9
+ module Prawn
10
+ module Graphics
11
+ module Color
12
+
13
+ # Sets the fill color.
14
+ #
15
+ # If a single argument is provided, it should be a 6 digit HTML color
16
+ # code.
17
+ #
18
+ # pdf.fill_color "f0ffc1"
19
+ #
20
+ # If 4 arguments are provided, the color is assumed to be a CMYK value
21
+ # Values range from 0 - 100.
22
+ #
23
+ # pdf.fill_color 0, 99, 95, 0
24
+ #
25
+ def fill_color(*color)
26
+ return @fill_color if color.empty?
27
+ @fill_color = process_color(*color)
28
+ set_fill_color
29
+ end
30
+
31
+ alias_method :fill_color=, :fill_color
32
+
33
+ # Sets the line stroking color. 6 digit HTML color codes are used.
34
+ #
35
+ # If a single argument is provided, it should be a 6 digit HTML color
36
+ # code.
37
+ #
38
+ # pdf.stroke_color "f0ffc1"
39
+ #
40
+ # If 4 arguments are provided, the color is assumed to be a CMYK value
41
+ # Values range from 0 - 100.
42
+ #
43
+ # pdf.stroke_color 0, 99, 95, 0
44
+ #
45
+ def stroke_color(*color)
46
+ return @stroke_color if color.empty?
47
+ @stroke_color = process_color(*color)
48
+ set_stroke_color
49
+ end
50
+
51
+ alias_method :stroke_color=, :stroke_color
52
+
53
+ # Provides the following shortcuts:
54
+ #
55
+ # stroke_some_method(*args) #=> some_method(*args); stroke
56
+ # fill_some_method(*args) #=> some_method(*args); fill
57
+ # fill_and_stroke_some_method(*args) #=> some_method(*args); fill_and_stroke
58
+ #
59
+ def method_missing(id,*args,&block)
60
+ case(id.to_s)
61
+ when /^fill_and_stroke_(.*)/
62
+ send($1,*args,&block); fill_and_stroke
63
+ when /^stroke_(.*)/
64
+ send($1,*args,&block); stroke
65
+ when /^fill_(.*)/
66
+ send($1,*args,&block); fill
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ module_function
73
+
74
+ # Converts RGB value array to hex string suitable for use with fill_color
75
+ # and stroke_color
76
+ #
77
+ # >> Prawn::Graphics::Color.rgb2hex([255,120,8])
78
+ # => "ff7808"
79
+ #
80
+ def rgb2hex(rgb)
81
+ rgb.map { |e| "%02x" % e }.join
82
+ end
83
+
84
+ # Converts hex string into RGB value array:
85
+ #
86
+ # >> Prawn::Graphics::Color.hex2rgb("ff7808")
87
+ # => [255, 120, 8]
88
+ #
89
+ def hex2rgb(hex)
90
+ r,g,b = hex[0..1], hex[2..3], hex[4..5]
91
+ [r,g,b].map { |e| e.to_i(16) }
92
+ end
93
+
94
+ private
95
+
96
+ def process_color(*color)
97
+ case(color.size)
98
+ when 1
99
+ color[0]
100
+ when 4
101
+ color
102
+ else
103
+ raise ArgumentError, 'wrong number of arguments supplied'
104
+ end
105
+ end
106
+
107
+ def set_fill_color
108
+ case @fill_color
109
+ when String
110
+ r,g,b = hex2rgb(@fill_color)
111
+ add_content "%.3f %.3f %.3f rg" %
112
+ [r / 255.0, g / 255.0, b / 255.0]
113
+ when Array
114
+ c,m,y,k = *@fill_color
115
+ add_content "%.3f %.3f %.3f %.3f k" %
116
+ [c / 100.0, m / 100.0, y / 100.0, k / 100.0]
117
+ end
118
+ end
119
+
120
+ def set_stroke_color
121
+ case @stroke_color
122
+ when String
123
+ r,g,b = hex2rgb(@stroke_color)
124
+ add_content "%.3f %.3f %.3f RG" %
125
+ [r / 255.0, g / 255.0, b / 255.0]
126
+ when Array
127
+ c,m,y,k = *@stroke_color
128
+ add_content "%.3f %.3f %.3f %.3f K" %
129
+ [c / 100.0, m / 100.0, y / 100.0, k / 100.0]
130
+ end
131
+ end
132
+
133
+ def update_colors
134
+ @fill_color ||= "000000"
135
+ @stroke_color ||= "000000"
136
+ set_fill_color
137
+ set_stroke_color
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,339 @@
1
+ # encoding: ASCII-8BIT
2
+ # images.rb : Implements PDF image embedding
3
+ #
4
+ # Copyright April 2008, James Healy, Gregory Brown. All Rights Reserved.
5
+ #
6
+ # This is free software. Please see the LICENSE and COPYING files for details.
7
+
8
+ require 'digest/sha1'
9
+
10
+ module Prawn
11
+
12
+ module Images
13
+
14
+ # Add the image at filename to the current page. Currently only
15
+ # JPG and PNG files are supported.
16
+ #
17
+ # Arguments:
18
+ # <tt>file</tt>:: path to file or an object that responds to #read
19
+ #
20
+ # Options:
21
+ # <tt>:at</tt>:: an array [x,y] with the location of the top left corner of the image.
22
+ # <tt>:position</tt>:: One of (:left, :center, :right) or an x-offset
23
+ # <tt>:vposition</tt>:: One of (:top, :center, :center) or an y-offset
24
+ # <tt>:height</tt>:: the height of the image [actual height of the image]
25
+ # <tt>:width</tt>:: the width of the image [actual width of the image]
26
+ # <tt>:scale</tt>:: scale the dimensions of the image proportionally
27
+ # <tt>:fit</tt>:: scale the dimensions of the image proportionally to fit inside [width,height]
28
+ #
29
+ # Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do
30
+ # pigs = "#{Prawn::BASEDIR}/data/images/pigs.jpg"
31
+ # image pigs, :at => [50,450], :width => 450
32
+ #
33
+ # dice = "#{Prawn::BASEDIR}/data/images/dice.png"
34
+ # image dice, :at => [50, 450], :scale => 0.75
35
+ # end
36
+ #
37
+ # If only one of :width / :height are provided, the image will be scaled
38
+ # proportionally. When both are provided, the image will be stretched to
39
+ # fit the dimensions without maintaining the aspect ratio.
40
+ #
41
+ #
42
+ # If :at is provided, the image will be place in the current page but
43
+ # the text position will not be changed.
44
+ #
45
+ #
46
+ # If instead of an explicit filename, an object with a read method is
47
+ # passed as +file+, you can embed images from IO objects and things
48
+ # that act like them (including Tempfiles and open-uri objects).
49
+ #
50
+ # require "open-uri"
51
+ #
52
+ # Prawn::Document.generate("remote_images.pdf") do
53
+ # image open("http://prawn.majesticseacreature.com/media/prawn_logo.png")
54
+ # end
55
+ #
56
+ # This method returns an image info object which can be used to check the
57
+ # dimensions of an image object if needed.
58
+ # (See also: Prawn::Images::PNG , Prawn::Images::JPG)
59
+ #
60
+ def image(file, options={})
61
+ Prawn.verify_options [:at, :position, :vposition, :height,
62
+ :width, :scale, :fit], options
63
+
64
+ if file.respond_to?(:read)
65
+ image_content = file.read
66
+ else
67
+ raise ArgumentError, "#{file} not found" unless File.file?(file)
68
+ image_content = File.binread(file)
69
+ end
70
+
71
+ image_sha1 = Digest::SHA1.hexdigest(image_content)
72
+
73
+ # register the fact that the current page uses images
74
+ proc_set :ImageC
75
+
76
+ # if this image has already been embedded, just reuse it
77
+ if image_registry[image_sha1]
78
+ info = image_registry[image_sha1][:info]
79
+ image_obj = image_registry[image_sha1][:obj]
80
+ else
81
+ # build the image object and embed the raw data
82
+ image_obj = case detect_image_format(image_content)
83
+ when :jpg then
84
+ info = Prawn::Images::JPG.new(image_content)
85
+ build_jpg_object(image_content, info)
86
+ when :png then
87
+ info = Prawn::Images::PNG.new(image_content)
88
+ build_png_object(image_content, info)
89
+ end
90
+ image_registry[image_sha1] = {:obj => image_obj, :info => info}
91
+ end
92
+
93
+ # find where the image will be placed and how big it will be
94
+ w,h = calc_image_dimensions(info, options)
95
+
96
+ if options[:at]
97
+ x,y = translate(options[:at])
98
+ else
99
+ x,y = image_position(w,h,options)
100
+ move_text_position h
101
+ end
102
+
103
+ # add a reference to the image object to the current page
104
+ # resource list and give it a label
105
+ label = "I#{next_image_id}"
106
+ page_xobjects.merge!( label => image_obj )
107
+
108
+ # add the image to the current page
109
+ instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ"
110
+ add_content instruct % [ w, h, x, y - h, label ]
111
+
112
+ return info
113
+ end
114
+
115
+ private
116
+
117
+ def image_position(w,h,options)
118
+ options[:position] ||= :left
119
+
120
+ x = case options[:position]
121
+ when :left
122
+ bounds.absolute_left
123
+ when :center
124
+ bounds.absolute_left + (bounds.width - w) / 2.0
125
+ when :right
126
+ bounds.absolute_right - w
127
+ when Numeric
128
+ options[:position] + bounds.absolute_left
129
+ end
130
+
131
+ y = case options[:vposition]
132
+ when :top
133
+ bounds.absolute_top
134
+ when :center
135
+ bounds.absolute_top - (bounds.height - h) / 2.0
136
+ when :bottom
137
+ bounds.absolute_bottom + h
138
+ when Numeric
139
+ bounds.absolute_top - options[:vposition]
140
+ else
141
+ self.y
142
+ end
143
+ return [x,y]
144
+ end
145
+
146
+ def build_jpg_object(data, jpg)
147
+ color_space = case jpg.channels
148
+ when 1
149
+ :DeviceGray
150
+ when 3
151
+ :DeviceRGB
152
+ when 4
153
+ :DeviceCMYK
154
+ else
155
+ raise ArgumentError, 'JPG uses an unsupported number of channels'
156
+ end
157
+ obj = ref(:Type => :XObject,
158
+ :Subtype => :Image,
159
+ :Filter => :DCTDecode,
160
+ :ColorSpace => color_space,
161
+ :BitsPerComponent => jpg.bits,
162
+ :Width => jpg.width,
163
+ :Height => jpg.height,
164
+ :Length => data.size )
165
+
166
+ # add extra decode params for CMYK images. By swapping the
167
+ # min and max values from the default, we invert the colours. See
168
+ # section 4.8.4 of the spec.
169
+ if color_space == :DeviceCMYK
170
+ obj.data[:Decode] = [ 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0 ]
171
+ end
172
+
173
+ obj << data
174
+ return obj
175
+ end
176
+
177
+ def build_png_object(data, png)
178
+
179
+ if png.compression_method != 0
180
+ raise Errors::UnsupportedImageType, 'PNG uses an unsupported compression method'
181
+ end
182
+
183
+ if png.filter_method != 0
184
+ raise Errors::UnsupportedImageType, 'PNG uses an unsupported filter method'
185
+ end
186
+
187
+ if png.interlace_method != 0
188
+ raise Errors::UnsupportedImageType, 'PNG uses unsupported interlace method'
189
+ end
190
+
191
+ # some PNG types store the colour and alpha channel data together,
192
+ # which the PDF spec doesn't like, so split it out.
193
+ png.split_alpha_channel!
194
+
195
+ case png.colors
196
+ when 1
197
+ color = :DeviceGray
198
+ when 3
199
+ color = :DeviceRGB
200
+ else
201
+ raise Errors::UnsupportedImageType, "PNG uses an unsupported number of colors (#{png.colors})"
202
+ end
203
+
204
+ # build the image dict
205
+ obj = ref(:Type => :XObject,
206
+ :Subtype => :Image,
207
+ :Height => png.height,
208
+ :Width => png.width,
209
+ :BitsPerComponent => png.bits,
210
+ :Length => png.img_data.size,
211
+ :Filter => :FlateDecode
212
+ )
213
+
214
+ unless png.alpha_channel
215
+ obj.data[:DecodeParms] = {:Predictor => 15,
216
+ :Colors => png.colors,
217
+ :BitsPerComponent => png.bits,
218
+ :Columns => png.width}
219
+ end
220
+
221
+ # append the actual image data to the object as a stream
222
+ obj << png.img_data
223
+
224
+ # sort out the colours of the image
225
+ if png.palette.empty?
226
+ obj.data[:ColorSpace] = color
227
+ else
228
+ # embed the colour palette in the PDF as a object stream
229
+ palette_obj = ref(:Length => png.palette.size)
230
+ palette_obj << png.palette
231
+
232
+ # build the color space array for the image
233
+ obj.data[:ColorSpace] = [:Indexed,
234
+ :DeviceRGB,
235
+ (png.palette.size / 3) -1,
236
+ palette_obj]
237
+ end
238
+
239
+ # *************************************
240
+ # add transparency data if necessary
241
+ # *************************************
242
+
243
+ # For PNG color types 0, 2 and 3, the transparency data is stored in
244
+ # a dedicated PNG chunk, and is exposed via the transparency attribute
245
+ # of the PNG class.
246
+ if png.transparency[:grayscale]
247
+ # Use Color Key Masking (spec section 4.8.5)
248
+ # - An array with N elements, where N is two times the number of color
249
+ # components.
250
+ val = png.transparency[:grayscale]
251
+ obj.data[:Mask] = [val, val]
252
+ elsif png.transparency[:rgb]
253
+ # Use Color Key Masking (spec section 4.8.5)
254
+ # - An array with N elements, where N is two times the number of color
255
+ # components.
256
+ rgb = png.transparency[:rgb]
257
+ obj.data[:Mask] = rgb.collect { |val| [val,val] }.flatten
258
+ elsif png.transparency[:indexed]
259
+ # TODO: broken. I was attempting to us Color Key Masking, but I think
260
+ # we need to construct an SMask i think. Maybe do it inside
261
+ # the PNG class, and store it in alpha_channel
262
+ #obj.data[:Mask] = png.transparency[:indexed]
263
+ end
264
+
265
+ # For PNG color types 4 and 6, the transparency data is stored as a alpha
266
+ # channel mixed in with the main image data. The PNG class seperates
267
+ # it out for us and makes it available via the alpha_channel attribute
268
+ if png.alpha_channel
269
+ smask_obj = ref(:Type => :XObject,
270
+ :Subtype => :Image,
271
+ :Height => png.height,
272
+ :Width => png.width,
273
+ :BitsPerComponent => png.bits,
274
+ :Length => png.alpha_channel.size,
275
+ :Filter => :FlateDecode,
276
+ :ColorSpace => :DeviceGray,
277
+ :Decode => [0, 1]
278
+ )
279
+ smask_obj << png.alpha_channel
280
+ obj.data[:SMask] = smask_obj
281
+ end
282
+
283
+ return obj
284
+ end
285
+
286
+ def calc_image_dimensions(info, options)
287
+ w = options[:width] || info.width
288
+ h = options[:height] || info.height
289
+
290
+ if options[:width] && !options[:height]
291
+ wp = w / info.width.to_f
292
+ w = info.width * wp
293
+ h = info.height * wp
294
+ elsif options[:height] && !options[:width]
295
+ hp = h / info.height.to_f
296
+ w = info.width * hp
297
+ h = info.height * hp
298
+ elsif options[:scale]
299
+ w = info.width * options[:scale]
300
+ h = info.height * options[:scale]
301
+ elsif options[:fit]
302
+ bw, bh = options[:fit]
303
+ bp = bw / bh.to_f
304
+ ip = info.width / info.height.to_f
305
+ if ip > bp
306
+ w = bw
307
+ h = bw / ip
308
+ else
309
+ h = bh
310
+ w = bh * ip
311
+ end
312
+ end
313
+ info.scaled_width = w
314
+ info.scaled_height = h
315
+ [w,h]
316
+ end
317
+
318
+ def detect_image_format(content)
319
+ top = content[0,128]
320
+
321
+ if top[0, 3] == "\xff\xd8\xff"
322
+ return :jpg
323
+ elsif top[0, 8] == "\x89PNG\x0d\x0a\x1a\x0a"
324
+ return :png
325
+ else
326
+ raise Errors::UnsupportedImageType, "image file is an unrecognised format"
327
+ end
328
+ end
329
+
330
+ def image_registry
331
+ @image_registry ||= {}
332
+ end
333
+
334
+ def next_image_id
335
+ @image_counter ||= 0
336
+ @image_counter += 1
337
+ end
338
+ end
339
+ end