prawn 0.1.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 (120) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +56 -0
  3. data/README +30 -0
  4. data/Rakefile +83 -0
  5. data/data/fonts/Activa.ttf +0 -0
  6. data/data/fonts/Chalkboard.ttf +0 -0
  7. data/data/fonts/Courier-Bold.afm +342 -0
  8. data/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/data/fonts/Courier-Oblique.afm +342 -0
  10. data/data/fonts/Courier.afm +342 -0
  11. data/data/fonts/DejaVuSans.ttf +0 -0
  12. data/data/fonts/Dustismo_Roman.ttf +0 -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/data/fonts/comicsans.ttf +0 -0
  25. data/data/fonts/gkai00mp.ttf +0 -0
  26. data/data/images/dice.png +0 -0
  27. data/data/images/pigs.jpg +0 -0
  28. data/data/images/ruport.png +0 -0
  29. data/data/images/ruport_data.dat +0 -0
  30. data/data/images/ruport_transparent.png +0 -0
  31. data/data/images/stef.jpg +0 -0
  32. data/data/shift_jis_text.txt +1 -0
  33. data/examples/addressbook.csv +6 -0
  34. data/examples/alignment.rb +16 -0
  35. data/examples/bounding_boxes.pdf +62 -0
  36. data/examples/bounding_boxes.rb +30 -0
  37. data/examples/canvas.pdf +81 -0
  38. data/examples/canvas.rb +12 -0
  39. data/examples/cell.rb +27 -0
  40. data/examples/currency.csv +1834 -0
  41. data/examples/curves.rb +10 -0
  42. data/examples/fancy_table.rb +48 -0
  43. data/examples/font_size.rb +19 -0
  44. data/examples/hexagon.rb +14 -0
  45. data/examples/image.pdf +0 -0
  46. data/examples/image.rb +23 -0
  47. data/examples/image2.rb +13 -0
  48. data/examples/inline_styles.pdf +117 -0
  49. data/examples/kerning.rb +27 -0
  50. data/examples/line.rb +31 -0
  51. data/examples/multi_page_layout.rb +14 -0
  52. data/examples/on_page_start.rb +17 -0
  53. data/examples/page_geometry.rb +28 -0
  54. data/examples/polygons.rb +16 -0
  55. data/examples/ruport_formatter.rb +47 -0
  56. data/examples/ruport_helpers.rb +17 -0
  57. data/examples/russian_boxes.rb +34 -0
  58. data/examples/simple_text.rb +15 -0
  59. data/examples/simple_text_ttf.rb +16 -0
  60. data/examples/sjis.rb +19 -0
  61. data/examples/table.rb +45 -0
  62. data/examples/table_bench.rb +92 -0
  63. data/examples/text_flow.rb +65 -0
  64. data/examples/utf8.rb +12 -0
  65. data/lib/prawn.rb +33 -0
  66. data/lib/prawn/compatibility.rb +33 -0
  67. data/lib/prawn/document.rb +334 -0
  68. data/lib/prawn/document/bounding_box.rb +253 -0
  69. data/lib/prawn/document/page_geometry.rb +78 -0
  70. data/lib/prawn/document/table.rb +253 -0
  71. data/lib/prawn/document/text.rb +346 -0
  72. data/lib/prawn/errors.rb +33 -0
  73. data/lib/prawn/font.rb +5 -0
  74. data/lib/prawn/font/cmap.rb +59 -0
  75. data/lib/prawn/font/metrics.rb +414 -0
  76. data/lib/prawn/font/wrapping.rb +45 -0
  77. data/lib/prawn/graphics.rb +285 -0
  78. data/lib/prawn/graphics/cell.rb +226 -0
  79. data/lib/prawn/images.rb +241 -0
  80. data/lib/prawn/images/jpg.rb +43 -0
  81. data/lib/prawn/images/png.rb +178 -0
  82. data/lib/prawn/pdf_object.rb +64 -0
  83. data/lib/prawn/reference.rb +47 -0
  84. data/spec/bounding_box_spec.rb +120 -0
  85. data/spec/box_calculation_spec.rb +17 -0
  86. data/spec/document_spec.rb +152 -0
  87. data/spec/graphics_spec.rb +250 -0
  88. data/spec/images_spec.rb +42 -0
  89. data/spec/jpg_spec.rb +25 -0
  90. data/spec/metrics_spec.rb +60 -0
  91. data/spec/pdf_object_spec.rb +102 -0
  92. data/spec/png_spec.rb +35 -0
  93. data/spec/reference_spec.rb +29 -0
  94. data/spec/spec_helper.rb +29 -0
  95. data/spec/table_spec.rb +145 -0
  96. data/spec/text_spec.rb +190 -0
  97. data/vendor/font_ttf/ttf.rb +20 -0
  98. data/vendor/font_ttf/ttf/datatypes.rb +189 -0
  99. data/vendor/font_ttf/ttf/encodings.rb +140 -0
  100. data/vendor/font_ttf/ttf/exceptions.rb +28 -0
  101. data/vendor/font_ttf/ttf/file.rb +290 -0
  102. data/vendor/font_ttf/ttf/fontchunk.rb +77 -0
  103. data/vendor/font_ttf/ttf/table/cmap.rb +408 -0
  104. data/vendor/font_ttf/ttf/table/cvt.rb +49 -0
  105. data/vendor/font_ttf/ttf/table/fpgm.rb +48 -0
  106. data/vendor/font_ttf/ttf/table/gasp.rb +88 -0
  107. data/vendor/font_ttf/ttf/table/glyf.rb +452 -0
  108. data/vendor/font_ttf/ttf/table/head.rb +86 -0
  109. data/vendor/font_ttf/ttf/table/hhea.rb +96 -0
  110. data/vendor/font_ttf/ttf/table/hmtx.rb +98 -0
  111. data/vendor/font_ttf/ttf/table/kern.rb +186 -0
  112. data/vendor/font_ttf/ttf/table/loca.rb +75 -0
  113. data/vendor/font_ttf/ttf/table/maxp.rb +81 -0
  114. data/vendor/font_ttf/ttf/table/name.rb +222 -0
  115. data/vendor/font_ttf/ttf/table/os2.rb +172 -0
  116. data/vendor/font_ttf/ttf/table/post.rb +120 -0
  117. data/vendor/font_ttf/ttf/table/prep.rb +27 -0
  118. data/vendor/font_ttf/ttf/table/vhea.rb +45 -0
  119. data/vendor/font_ttf/ttf/table/vmtx.rb +36 -0
  120. metadata +180 -0
@@ -0,0 +1,241 @@
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>filename</tt>:: the path to the file to be embedded
19
+ #
20
+ # Options:
21
+ # <tt>:at</tt>:: the location of the top left corner of the image [current position]
22
+ # <tt>:height</tt>:: the height of the image [actual height of the image]
23
+ # <tt>:width</tt>:: the width of the image [actual width of the image]
24
+ # <tt>:scale</tt>:: scale the dimensions of the image proportionally
25
+ #
26
+ # Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do
27
+ # pigs = "#{Prawn::BASEDIR}/data/images/pigs.jpg"
28
+ # image pigs, :at => [50,450], :width => 450
29
+ #
30
+ # dice = "#{Prawn::BASEDIR}/data/images/dice.png"
31
+ # image dice, :at => [50, 450], :scale => 0.75
32
+ # end
33
+ #
34
+ # If only one of :width / :height are provided, the image will be scaled
35
+ # proportionally. When both are provided, the image will be stretched to
36
+ # fit the dimensions without maintaining the aspect ratio.
37
+ #
38
+ def image(filename, options={})
39
+ raise ArgumentError, "#{filename} not found" unless File.file?(filename)
40
+
41
+
42
+ read_mode = ruby_18 { "rb" } || ruby_19 { "rb:ASCII-8BIT" }
43
+ image_content = File.open(filename, read_mode) { |f| f.read }
44
+
45
+ image_sha1 = Digest::SHA1.hexdigest(image_content)
46
+
47
+ # register the fact that the current page uses images
48
+ proc_set :ImageC
49
+
50
+ # if this image has already been embedded, just reuse it
51
+ image_obj = image_registry[image_sha1]
52
+
53
+ if image_registry[image_sha1]
54
+ info = image_registry[image_sha1][:info]
55
+ image_obj = image_registry[image_sha1][:obj]
56
+ else
57
+ # build the image object and embed the raw data
58
+ image_obj = case detect_image_format(image_content)
59
+ when :jpg then
60
+ info = Prawn::Images::JPG.new(image_content)
61
+ build_jpg_object(image_content, info)
62
+ when :png then
63
+ info = Prawn::Images::PNG.new(image_content)
64
+ build_png_object(image_content, info)
65
+ end
66
+ image_registry[image_sha1] = {:obj => image_obj, :info => info}
67
+ end
68
+
69
+ # find where the image will be placed and how big it will be
70
+ x,y = translate(options[:at])
71
+ w,h = calc_image_dimensions(info, options)
72
+
73
+ # add a reference to the image object to the current page
74
+ # resource list and give it a label
75
+ label = "I#{next_image_id}"
76
+ page_xobjects.merge!( label => image_obj )
77
+
78
+ # add the image to the current page
79
+ instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ"
80
+ add_content instruct % [ w, h, x, y - h, label ]
81
+ end
82
+
83
+ private
84
+
85
+ def build_jpg_object(data, jpg)
86
+ color_space = case jpg.channels
87
+ when 1
88
+ :DeviceGray
89
+ when 4
90
+ :DeviceCMYK
91
+ else
92
+ :DeviceRGB
93
+ end
94
+ obj = ref(:Type => :XObject,
95
+ :Subtype => :Image,
96
+ :Filter => :DCTDecode,
97
+ :ColorSpace => color_space,
98
+ :BitsPerComponent => jpg.bits,
99
+ :Width => jpg.width,
100
+ :Height => jpg.height,
101
+ :Length => data.size )
102
+ obj << data
103
+ return obj
104
+ end
105
+
106
+ def build_png_object(data, png)
107
+
108
+ if png.compression_method != 0
109
+ raise ArgumentError, 'PNG uses an unsupported compression method'
110
+ end
111
+
112
+ if png.filter_method != 0
113
+ raise ArgumentError, 'PNG uses an unsupported filter method'
114
+ end
115
+
116
+ if png.interlace_method != 0
117
+ raise ArgumentError, 'PNG uses unsupported interlace method'
118
+ end
119
+
120
+ if png.bits > 8
121
+ raise ArgumentError, 'PNG uses more than 8 bits'
122
+ end
123
+
124
+ case png.color_type
125
+ when 0
126
+ ncolor = 1
127
+ color = :DeviceGray
128
+ when 2
129
+ ncolor = 3
130
+ color = :DeviceRGB
131
+ when 3
132
+ ncolor = 1
133
+ color = :DeviceRGB
134
+ when 6
135
+ ncolor = 3
136
+ color = :DeviceRGB
137
+ else
138
+ raise ArgumentError, "PNG has unsupported color type"
139
+ end
140
+
141
+ # build the image dict
142
+ obj = ref(:Type => :XObject,
143
+ :Subtype => :Image,
144
+ :Height => png.height,
145
+ :Width => png.width,
146
+ :BitsPerComponent => png.bits,
147
+ :Length => png.img_data.size,
148
+ :Filter => :FlateDecode
149
+
150
+ )
151
+
152
+ unless png.alpha_channel
153
+ obj.data[:DecodeParms] = {:Predictor => 15,
154
+ :Colors => ncolor,
155
+ :Columns => png.width}
156
+ end
157
+
158
+ # append the actual image data to the object as a stream
159
+ obj << png.img_data
160
+
161
+ # sort out the colours of the image
162
+ if png.palette.empty?
163
+ obj.data[:ColorSpace] = color
164
+ else
165
+ # embed the colour palette in the PDF as a object stream
166
+ palette_obj = ref(:Length => png.palette.size)
167
+ palette_obj << png.palette
168
+
169
+ # build the color space array for the image
170
+ obj.data[:ColorSpace] = [:Indexed,
171
+ :DeviceRGB,
172
+ (png.palette.size / 3) -1,
173
+ palette_obj]
174
+
175
+ # add transparency data if necessary
176
+ #if png.transparency && png.transparency[:type] == 'indexed'
177
+ # obj.data[:Mask] = png.transparency[:data]
178
+ #end
179
+ end
180
+
181
+ if png.alpha_channel
182
+ smask_obj = ref(:Type => :XObject,
183
+ :Subtype => :Image,
184
+ :Height => png.height,
185
+ :Width => png.width,
186
+ :BitsPerComponent => 8,
187
+ :Length => png.alpha_channel.size,
188
+ :Filter => :FlateDecode,
189
+ :ColorSpace => :DeviceGray,
190
+ :Decode => [0, 1]
191
+ )
192
+ smask_obj << png.alpha_channel
193
+ obj.data[:SMask] = smask_obj
194
+ end
195
+
196
+ return obj
197
+ end
198
+
199
+ def calc_image_dimensions(info, options)
200
+ # TODO: allow the image to be aligned in a box
201
+ w = options[:width] || info.width
202
+ h = options[:height] || info.height
203
+
204
+ if options[:width] && !options[:height]
205
+ wp = w / info.width.to_f
206
+ w = info.width * wp
207
+ h = info.height * wp
208
+ elsif options[:height] && !options[:width]
209
+ hp = h / info.height.to_f
210
+ w = info.width * hp
211
+ h = info.height * hp
212
+ elsif options[:scale]
213
+ w = info.width * options[:scale]
214
+ h = info.height * options[:scale]
215
+ end
216
+
217
+ [w,h]
218
+ end
219
+
220
+ def detect_image_format(content)
221
+ top = content[0,128]
222
+
223
+ if top[0, 3] == "\xff\xd8\xff"
224
+ return :jpg
225
+ elsif top[0, 8] == "\x89PNG\x0d\x0a\x1a\x0a"
226
+ return :png
227
+ else
228
+ raise ArgumentError, "Unsupported Image Type"
229
+ end
230
+ end
231
+
232
+ def image_registry
233
+ @image_registry ||= {}
234
+ end
235
+
236
+ def next_image_id
237
+ @image_counter ||= 0
238
+ @image_counter += 1
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: ASCII-8BIT
2
+
3
+ # jpg.rb : Extracts the data from a JPG that is needed for embedding
4
+ #
5
+ # Copyright April 2008, James Healy. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require 'stringio'
10
+
11
+ module Prawn
12
+ module Images
13
+ # A convenience class that wraps the logic for extracting the parts
14
+ # of a PNG image that we need to embed them in a PDF
15
+ class JPG #:nodoc:
16
+ attr_reader :width, :height, :bits, :channels
17
+
18
+ JPEG_SOF_BLOCKS = %W(\xc0 \xc1 \xc2 \xc3 \xc5 \xc6 \xc7 \xc9 \xca \xcb \xcd \xce \xcf)
19
+ JPEG_APP_BLOCKS = %W(\xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef)
20
+
21
+ # Process a new JPG image
22
+ #
23
+ # <tt>:data</tt>:: A string containing a full PNG file
24
+ def initialize(data)
25
+ data = StringIO.new(data.dup)
26
+
27
+ c_marker = "\xff" # Section marker.
28
+ data.read(2) # Skip the first two bytes of JPEG identifier.
29
+ loop do
30
+ marker, code, length = data.read(4).unpack('aan')
31
+ raise "JPEG marker not found!" if marker != c_marker
32
+
33
+ if JPEG_SOF_BLOCKS.include?(code)
34
+ @bits, @height, @width, @channels = data.read(6).unpack("CnnC")
35
+ break
36
+ end
37
+
38
+ buffer = data.read(length - 2)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,178 @@
1
+ # encoding: utf-8
2
+
3
+ # png.rb : Extracts the data from a PNG that is needed for embedding
4
+ #
5
+ # Based on some similar code in PDF::Writer by Austin Ziegler
6
+ #
7
+ # Copyright April 2008, James Healy. All Rights Reserved.
8
+ #
9
+ # This is free software. Please see the LICENSE and COPYING files for details.
10
+
11
+ require 'stringio'
12
+
13
+ module Prawn
14
+ module Images
15
+ # A convenience class that wraps the logic for extracting the parts
16
+ # of a PNG image that we need to embed them in a PDF
17
+ class PNG #:nodoc:
18
+ attr_reader :palette, :img_data, :transparency
19
+ attr_reader :width, :height, :bits
20
+ attr_reader :color_type, :compression_method, :filter_method
21
+ attr_reader :interlace_method, :alpha_channel
22
+
23
+ # Process a new PNG image
24
+ #
25
+ # <tt>:data</tt>:: A string containing a full PNG file
26
+ def initialize(data)
27
+ data = StringIO.new(data.dup)
28
+
29
+ data.read(8) # Skip the default header
30
+
31
+ @palette = ""
32
+ @img_data = ""
33
+
34
+ loop do
35
+ chunk_size = data.read(4).unpack("N")[0]
36
+ section = data.read(4)
37
+ case section
38
+ when 'IHDR'
39
+ # we can grab other interesting values from here (like width,
40
+ # height, etc)
41
+ values = data.read(chunk_size).unpack("NNCCCCC")
42
+
43
+ @width = values[0]
44
+ @height = values[1]
45
+ @bits = values[2]
46
+ @color_type = values[3]
47
+ @compression_method = values[4]
48
+ @filter_method = values[5]
49
+ @interlace_method = values[6]
50
+ when 'PLTE'
51
+ @palette << data.read(chunk_size)
52
+ when 'IDAT'
53
+ @img_data << data.read(chunk_size)
54
+ when 'tRNS'
55
+ # This chunk can only occur once and it must occur after the
56
+ # PLTE chunk and before the IDAT chunk
57
+ @transparency = {}
58
+ case @color_type
59
+ when 3
60
+ # Indexed colour, RGB. Each byte in this chunk is an alpha for
61
+ # the palette index in the PLTE ("palette") chunk up until the
62
+ # last non-opaque entry. Set up an array, stretching over all
63
+ # palette entries which will be 0 (opaque) or 1 (transparent).
64
+ @transparency[:type] = 'indexed'
65
+ @transparency[:data] = data.read(chunk_size).unpack("C*")
66
+ when 0
67
+ # Greyscale. Corresponding to entries in the PLTE chunk.
68
+ # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1
69
+ @transparency[:grayscale] = data.read(2).unpack("n")
70
+ @transparency[:type] = 'indexed'
71
+ when 2
72
+ # True colour with proper alpha channel.
73
+ @transparency[:rgb] = data.read(6).unpack("nnn")
74
+ end
75
+ when 'IEND'
76
+ # we've got everything we need, exit the loop
77
+ break
78
+ else
79
+ # unknown (or un-important) section, skip over it
80
+ data.seek(data.pos + chunk_size)
81
+ end
82
+
83
+ data.read(4) # Skip the CRC
84
+ end
85
+
86
+ # if our img_data contains alpha channel data, split it out
87
+ unfilter_image_data if @color_type == 6
88
+ end
89
+
90
+ private
91
+
92
+ def paeth(a, b, c) # left, above, upper left
93
+ p = a + b - c
94
+ pa = (p - a).abs
95
+ pb = (p - b).abs
96
+ pc = (p - c).abs
97
+
98
+ return a if pa <= pb && pa <= pc
99
+ return b if pb <= pc
100
+ c
101
+ end
102
+
103
+ def unfilter_image_data
104
+ data = Zlib::Inflate.inflate(@img_data).unpack 'C*'
105
+ @img_data = ""
106
+ @alpha_channel = ""
107
+ scanline_length = 4 * @width + 1 # for filter
108
+ row = 0
109
+ pixels = []
110
+ until data.empty? do
111
+ row_data = data.slice! 0, scanline_length
112
+ filter = row_data.shift
113
+ case filter
114
+ when 0 then # None
115
+ when 1 then # Sub
116
+ row_data.each_with_index do |byte, index|
117
+ left = index < 4 ? 0 : row_data[index - 4]
118
+ row_data[index] = (byte + left) % 256
119
+ #p [byte, left, row_data[index]]
120
+ end
121
+ when 2 then # Up
122
+ row_data.each_with_index do |byte, index|
123
+ col = index / 4
124
+ upper = row == 0 ? 0 : pixels[row-1][col][index % 4]
125
+ row_data[index] = (upper + byte) % 256
126
+ end
127
+ when 3 then # Average
128
+ row_data.each_with_index do |byte, index|
129
+ col = index / 4
130
+ upper = row == 0 ? 0 : pixels[row-1][col][index % 4]
131
+ left = index < 4 ? 0 : row_data[index - 4]
132
+
133
+ row_data[index] = (byte + ((left + upper)/2).floor) % 256
134
+ end
135
+ when 4 then # Paeth
136
+ left = upper = upper_left = nil
137
+ row_data.each_with_index do |byte, index|
138
+ col = index / 4
139
+
140
+ left = index < 4 ? 0 : row_data[index - 4]
141
+ if row == 0 then
142
+ upper = upper_left = 0
143
+ else
144
+ upper = pixels[row-1][col][index % 4]
145
+ upper_left = col == 0 ? 0 :
146
+ pixels[row-1][col-1][index % 4]
147
+ end
148
+
149
+ paeth = paeth left, upper, upper_left
150
+ row_data[index] = (byte + paeth) % 256
151
+ #p [byte, paeth, row_data[index]]
152
+ end
153
+ else
154
+ raise ArgumentError, "Invalid filter algorithm #{filter}"
155
+ end
156
+
157
+ pixels << []
158
+ row_data.each_slice 4 do |slice|
159
+ pixels.last << slice
160
+ end
161
+ row += 1
162
+ end
163
+
164
+ # convert the pixel data to seperate strings for colours and alpha
165
+ pixels.each do |row|
166
+ row.each do |pixel|
167
+ @img_data << pixel[0,3].pack("C*")
168
+ @alpha_channel << pixel[3]
169
+ end
170
+ end
171
+
172
+ # compress the data
173
+ @img_data = Zlib::Deflate.deflate(@img_data)
174
+ @alpha_channel = Zlib::Deflate.deflate(@alpha_channel)
175
+ end
176
+ end
177
+ end
178
+ end