prawn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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