prawn 1.0.0.rc2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (169) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/COPYING +2 -2
  4. data/Gemfile +8 -15
  5. data/LICENSE +1 -1
  6. data/Rakefile +25 -16
  7. data/data/images/16bit.alpha +0 -0
  8. data/data/images/16bit.color +0 -0
  9. data/data/images/dice.alpha +0 -0
  10. data/data/images/dice.color +0 -0
  11. data/data/images/indexed_color.dat +0 -0
  12. data/data/images/indexed_color.png +0 -0
  13. data/data/images/license.md +8 -0
  14. data/data/images/page_white_text.alpha +0 -0
  15. data/data/images/page_white_text.color +0 -0
  16. data/lib/prawn.rb +85 -23
  17. data/lib/prawn/document.rb +134 -116
  18. data/lib/prawn/document/bounding_box.rb +33 -4
  19. data/lib/prawn/document/column_box.rb +18 -6
  20. data/lib/prawn/document/graphics_state.rb +11 -74
  21. data/lib/prawn/document/internals.rb +24 -23
  22. data/lib/prawn/document/span.rb +12 -10
  23. data/lib/prawn/encoding.rb +8 -9
  24. data/lib/prawn/errors.rb +13 -32
  25. data/lib/prawn/font.rb +137 -105
  26. data/lib/prawn/font/afm.rb +76 -32
  27. data/lib/prawn/font/dfont.rb +4 -3
  28. data/lib/prawn/font/ttf.rb +33 -25
  29. data/lib/prawn/font_metric_cache.rb +47 -0
  30. data/lib/prawn/graphics.rb +177 -57
  31. data/lib/prawn/graphics/cap_style.rb +4 -3
  32. data/lib/prawn/graphics/color.rb +5 -4
  33. data/lib/prawn/graphics/dash.rb +53 -31
  34. data/lib/prawn/graphics/join_style.rb +9 -7
  35. data/lib/prawn/graphics/patterns.rb +4 -15
  36. data/lib/prawn/graphics/transformation.rb +10 -9
  37. data/lib/prawn/graphics/transparency.rb +3 -1
  38. data/lib/prawn/{layout/grid.rb → grid.rb} +72 -54
  39. data/lib/prawn/image_handler.rb +42 -0
  40. data/lib/prawn/images.rb +58 -54
  41. data/lib/prawn/images/image.rb +6 -22
  42. data/lib/prawn/images/jpg.rb +20 -14
  43. data/lib/prawn/images/png.rb +58 -121
  44. data/lib/prawn/layout.rb +12 -15
  45. data/lib/prawn/measurement_extensions.rb +10 -6
  46. data/lib/prawn/measurements.rb +27 -21
  47. data/lib/prawn/outline.rb +108 -147
  48. data/lib/prawn/repeater.rb +10 -8
  49. data/lib/prawn/security.rb +59 -40
  50. data/lib/prawn/security/arcfour.rb +52 -0
  51. data/lib/prawn/soft_mask.rb +4 -4
  52. data/lib/prawn/stamp.rb +5 -3
  53. data/lib/prawn/table.rb +83 -60
  54. data/lib/prawn/table/cell.rb +17 -21
  55. data/lib/prawn/table/cell/image.rb +2 -3
  56. data/lib/prawn/table/cell/in_table.rb +8 -2
  57. data/lib/prawn/table/cell/span_dummy.rb +5 -0
  58. data/lib/prawn/table/cell/subtable.rb +3 -2
  59. data/lib/prawn/table/cell/text.rb +14 -12
  60. data/lib/prawn/table/cells.rb +58 -14
  61. data/lib/prawn/table/column_width_calculator.rb +61 -0
  62. data/lib/prawn/text.rb +27 -26
  63. data/lib/prawn/text/box.rb +12 -6
  64. data/lib/prawn/text/formatted.rb +5 -4
  65. data/lib/prawn/text/formatted/arranger.rb +290 -0
  66. data/lib/prawn/text/formatted/box.rb +85 -57
  67. data/lib/prawn/text/formatted/fragment.rb +11 -11
  68. data/lib/prawn/text/formatted/line_wrap.rb +266 -0
  69. data/lib/prawn/text/formatted/parser.rb +11 -4
  70. data/lib/prawn/text/formatted/wrap.rb +156 -0
  71. data/lib/prawn/utilities.rb +5 -3
  72. data/manual/document_and_page_options/document_and_page_options.rb +2 -1
  73. data/manual/document_and_page_options/metadata.rb +3 -3
  74. data/manual/document_and_page_options/page_size.rb +2 -2
  75. data/manual/document_and_page_options/print_scaling.rb +20 -0
  76. data/manual/example_file.rb +2 -7
  77. data/manual/example_helper.rb +62 -81
  78. data/manual/graphics/common_lines.rb +2 -0
  79. data/manual/graphics/helper.rb +11 -4
  80. data/manual/graphics/stroke_dash.rb +19 -14
  81. data/manual/manual/cover.rb +16 -0
  82. data/manual/manual/manual.rb +1 -5
  83. data/manual/text/fallback_fonts.rb +4 -4
  84. data/manual/text/formatted_text.rb +5 -5
  85. data/manual/text/inline.rb +2 -4
  86. data/manual/text/registering_families.rb +12 -12
  87. data/manual/text/single_usage.rb +4 -4
  88. data/manual/text/text.rb +0 -2
  89. data/prawn.gemspec +21 -13
  90. data/spec/acceptance/png.rb +23 -0
  91. data/spec/annotations_spec.rb +16 -32
  92. data/spec/bounding_box_spec.rb +22 -5
  93. data/spec/cell_spec.rb +49 -5
  94. data/spec/column_box_spec.rb +32 -0
  95. data/spec/destinations_spec.rb +5 -5
  96. data/spec/document_spec.rb +112 -118
  97. data/spec/extensions/encoding_helpers.rb +5 -2
  98. data/spec/font_metric_cache_spec.rb +52 -0
  99. data/spec/font_spec.rb +121 -120
  100. data/spec/formatted_text_arranger_spec.rb +24 -24
  101. data/spec/formatted_text_box_spec.rb +31 -32
  102. data/spec/formatted_text_fragment_spec.rb +2 -2
  103. data/spec/graphics_spec.rb +63 -45
  104. data/spec/grid_spec.rb +24 -13
  105. data/spec/image_handler_spec.rb +54 -0
  106. data/spec/images_spec.rb +34 -21
  107. data/spec/inline_formatted_text_parser_spec.rb +69 -20
  108. data/spec/jpg_spec.rb +3 -3
  109. data/spec/line_wrap_spec.rb +25 -14
  110. data/spec/measurement_units_spec.rb +5 -5
  111. data/spec/outline_spec.rb +68 -64
  112. data/spec/png_spec.rb +15 -18
  113. data/spec/reference_spec.rb +2 -82
  114. data/spec/repeater_spec.rb +1 -1
  115. data/spec/security_spec.rb +41 -9
  116. data/spec/soft_mask_spec.rb +0 -40
  117. data/spec/span_spec.rb +6 -11
  118. data/spec/spec_helper.rb +20 -2
  119. data/spec/stamp_spec.rb +19 -20
  120. data/spec/stroke_styles_spec.rb +31 -13
  121. data/spec/table/span_dummy_spec.rb +17 -0
  122. data/spec/table_spec.rb +268 -43
  123. data/spec/text_at_spec.rb +13 -27
  124. data/spec/text_box_spec.rb +35 -30
  125. data/spec/text_spec.rb +56 -40
  126. data/spec/transparency_spec.rb +5 -5
  127. metadata +214 -217
  128. data/README.md +0 -98
  129. data/data/fonts/Action Man.dfont +0 -0
  130. data/data/fonts/Activa.ttf +0 -0
  131. data/data/fonts/Chalkboard.ttf +0 -0
  132. data/data/fonts/DejaVuSans.ttf +0 -0
  133. data/data/fonts/Dustismo_Roman.ttf +0 -0
  134. data/data/fonts/comicsans.ttf +0 -0
  135. data/data/fonts/gkai00mp.ttf +0 -0
  136. data/data/images/16bit.dat +0 -0
  137. data/data/images/barcode_issue.png +0 -0
  138. data/data/images/dice.dat +0 -0
  139. data/data/images/page_white_text.dat +0 -0
  140. data/data/images/rails.dat +0 -0
  141. data/data/images/rails.png +0 -0
  142. data/lib/prawn/compatibility.rb +0 -87
  143. data/lib/prawn/core.rb +0 -87
  144. data/lib/prawn/core/annotations.rb +0 -61
  145. data/lib/prawn/core/byte_string.rb +0 -9
  146. data/lib/prawn/core/destinations.rb +0 -90
  147. data/lib/prawn/core/document_state.rb +0 -79
  148. data/lib/prawn/core/literal_string.rb +0 -16
  149. data/lib/prawn/core/name_tree.rb +0 -177
  150. data/lib/prawn/core/object_store.rb +0 -320
  151. data/lib/prawn/core/page.rb +0 -212
  152. data/lib/prawn/core/pdf_object.rb +0 -125
  153. data/lib/prawn/core/reference.rb +0 -119
  154. data/lib/prawn/core/text.rb +0 -268
  155. data/lib/prawn/core/text/formatted/arranger.rb +0 -294
  156. data/lib/prawn/core/text/formatted/line_wrap.rb +0 -288
  157. data/lib/prawn/core/text/formatted/wrap.rb +0 -153
  158. data/lib/prawn/document/page_geometry.rb +0 -136
  159. data/lib/prawn/document/snapshot.rb +0 -89
  160. data/manual/manual/foreword.rb +0 -13
  161. data/manual/templates/full_template.rb +0 -23
  162. data/manual/templates/page_template.rb +0 -47
  163. data/manual/templates/templates.rb +0 -26
  164. data/manual/text/group.rb +0 -29
  165. data/spec/name_tree_spec.rb +0 -112
  166. data/spec/object_store_spec.rb +0 -170
  167. data/spec/pdf_object_spec.rb +0 -172
  168. data/spec/snapshot_spec.rb +0 -186
  169. data/spec/template_spec.rb +0 -351
@@ -0,0 +1,42 @@
1
+ # ImageHandler provides a way to register image processors with Prawn
2
+ #
3
+ # Contributed by Evan Sharp in November 2013.
4
+ #
5
+ # This is free software. Please see the LICENSE and COPYING files for details.
6
+
7
+ module Prawn
8
+ # @group Extension API
9
+
10
+ def self.image_handler
11
+ @image_handler ||= ImageHandler.new
12
+ end
13
+
14
+ class ImageHandler
15
+ def initialize
16
+ @handlers = []
17
+ end
18
+
19
+ def register(handler)
20
+ @handlers.delete(handler)
21
+ @handlers.push handler
22
+ end
23
+
24
+ def register!(handler)
25
+ @handlers.delete(handler)
26
+ @handlers.unshift handler
27
+ end
28
+
29
+ def unregister(handler)
30
+ @handlers.reject!{ |h| h == handler }
31
+ end
32
+
33
+ def find(image_blob)
34
+ handler = @handlers.find{ |h| h.can_render? image_blob }
35
+
36
+ return handler if handler
37
+
38
+ raise Prawn::Errors::UnsupportedImageType,
39
+ "image file is an unrecognised format"
40
+ end
41
+ end
42
+ end
@@ -6,42 +6,39 @@
6
6
  # This is free software. Please see the LICENSE and COPYING files for details.
7
7
 
8
8
  require 'digest/sha1'
9
+ require 'pathname'
9
10
 
10
11
  module Prawn
11
12
 
12
13
  module Images
14
+ # @group Stable API
13
15
 
14
16
  # Add the image at filename to the current page. Currently only
15
- # JPG and PNG files are supported.
16
- #
17
- # NOTE: Prawn is very slow at rendering PNGs with alpha channels, and this
18
- # uses a lot of RAM. The workaround for those who don't mind installing
19
- # RMagick is to use:
20
- #
21
- # http://github.com/amberbit/prawn-fast-png
17
+ # JPG and PNG files are supported. (Note that processing PNG
18
+ # images with alpha channels can be processor and memory intensive.)
22
19
  #
23
20
  # Arguments:
24
- # <tt>file</tt>:: path to file or an object that responds to #read
21
+ # <tt>file</tt>:: path to file or an object that responds to #read and #rewind
25
22
  #
26
23
  # Options:
27
24
  # <tt>:at</tt>:: an array [x,y] with the location of the top left corner of the image.
28
25
  # <tt>:position</tt>:: One of (:left, :center, :right) or an x-offset
29
- # <tt>:vposition</tt>:: One of (:top, :center, :center) or an y-offset
26
+ # <tt>:vposition</tt>:: One of (:top, :center, :center) or an y-offset
30
27
  # <tt>:height</tt>:: the height of the image [actual height of the image]
31
28
  # <tt>:width</tt>:: the width of the image [actual width of the image]
32
29
  # <tt>:scale</tt>:: scale the dimensions of the image proportionally
33
30
  # <tt>:fit</tt>:: scale the dimensions of the image proportionally to fit inside [width,height]
34
- #
35
- # Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do
36
- # pigs = "#{Prawn::DATADIR}/images/pigs.jpg"
37
- # image pigs, :at => [50,450], :width => 450
31
+ #
32
+ # Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do
33
+ # pigs = "#{Prawn::DATADIR}/images/pigs.jpg"
34
+ # image pigs, :at => [50,450], :width => 450
38
35
  #
39
36
  # dice = "#{Prawn::DATADIR}/images/dice.png"
40
- # image dice, :at => [50, 450], :scale => 0.75
41
- # end
37
+ # image dice, :at => [50, 450], :scale => 0.75
38
+ # end
42
39
  #
43
40
  # If only one of :width / :height are provided, the image will be scaled
44
- # proportionally. When both are provided, the image will be stretched to
41
+ # proportionally. When both are provided, the image will be stretched to
45
42
  # fit the dimensions without maintaining the aspect ratio.
46
43
  #
47
44
  #
@@ -55,16 +52,16 @@ module Prawn
55
52
  #
56
53
  # require "open-uri"
57
54
  #
58
- # Prawn::Document.generate("remote_images.pdf") do
55
+ # Prawn::Document.generate("remote_images.pdf") do
59
56
  # image open("http://prawn.majesticseacreature.com/media/prawn_logo.png")
60
57
  # end
61
58
  #
62
59
  # This method returns an image info object which can be used to check the
63
- # dimensions of an image object if needed.
60
+ # dimensions of an image object if needed.
64
61
  # (See also: Prawn::Images::PNG , Prawn::Images::JPG)
65
- #
62
+ #
66
63
  def image(file, options={})
67
- Prawn.verify_options [:at, :position, :vposition, :height,
64
+ Prawn.verify_options [:at, :position, :vposition, :height,
68
65
  :width, :scale, :fit], options
69
66
 
70
67
  pdf_obj, info = build_image_object(file)
@@ -73,23 +70,14 @@ module Prawn
73
70
  info
74
71
  end
75
72
 
73
+
76
74
  # Builds an info object (Prawn::Images::*) and a PDF reference representing
77
75
  # the given image. Return a pair: [pdf_obj, info].
78
76
  #
77
+ # @private
79
78
  def build_image_object(file)
80
- # Rewind if the object we're passed is an IO, so that multiple embeds of
81
- # the same IO object will work
82
- file.rewind if file.respond_to?(:rewind)
83
- # read the file as binary so the size is calculated correctly
84
- file.binmode if file.respond_to?(:binmode)
85
-
86
- if file.respond_to?(:read)
87
- image_content = file.read
88
- else
89
- raise ArgumentError, "#{file} not found" unless File.file?(file)
90
- image_content = File.binread(file)
91
- end
92
-
79
+ io = verify_and_open_image(file)
80
+ image_content = io.read
93
81
  image_sha1 = Digest::SHA1.hexdigest(image_content)
94
82
 
95
83
  # if this image has already been embedded, just reuse it
@@ -98,11 +86,7 @@ module Prawn
98
86
  image_obj = image_registry[image_sha1][:obj]
99
87
  else
100
88
  # Build the image object
101
- klass = case Image.detect_image_format(image_content)
102
- when :jpg then Prawn::Images::JPG
103
- when :png then Prawn::Images::PNG
104
- end
105
- info = klass.new(image_content)
89
+ info = Prawn.image_handler.find(image_content).new(image_content)
106
90
 
107
91
  # Bump PDF version if the image requires it
108
92
  min_version(info.min_pdf_version) if info.respond_to?(:min_pdf_version)
@@ -120,15 +104,16 @@ module Prawn
120
104
  # build_image_object), embed the image according to the <tt>options</tt>
121
105
  # given.
122
106
  #
107
+ # @private
123
108
  def embed_image(pdf_obj, info, options)
124
- # find where the image will be placed and how big it will be
109
+ # find where the image will be placed and how big it will be
125
110
  w,h = info.calc_image_dimensions(options)
126
111
 
127
- if options[:at]
128
- x,y = map_to_absolute(options[:at])
129
- else
130
- x,y = image_position(w,h,options)
131
- move_text_position h
112
+ if options[:at]
113
+ x,y = map_to_absolute(options[:at])
114
+ else
115
+ x,y = image_position(w,h,options)
116
+ move_text_position h
132
117
  end
133
118
 
134
119
  # add a reference to the image object to the current page
@@ -140,12 +125,31 @@ module Prawn
140
125
  instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ"
141
126
  add_content instruct % [ w, h, x, y - h, label ]
142
127
  end
143
-
144
- private
128
+
129
+ private
130
+
131
+ def verify_and_open_image(io_or_path)
132
+ # File or IO
133
+ if io_or_path.respond_to?(:rewind)
134
+ io = io_or_path
135
+ # Rewind if the object we're passed is an IO, so that multiple embeds of
136
+ # the same IO object will work
137
+ io.rewind
138
+ # read the file as binary so the size is calculated correctly
139
+ # guard binmode because some objects acting io-like don't implement it
140
+ io.binmode if io.respond_to?(:binmode)
141
+ return io
142
+ end
143
+ # String or Pathname
144
+ io_or_path = Pathname.new(io_or_path)
145
+ raise ArgumentError, "#{io_or_path} not found" unless io_or_path.file?
146
+ io = io_or_path.open('rb')
147
+ io
148
+ end
145
149
 
146
150
  def image_position(w,h,options)
147
151
  options[:position] ||= :left
148
-
152
+
149
153
  y = case options[:vposition]
150
154
  when :top
151
155
  bounds.absolute_top
@@ -159,11 +163,11 @@ module Prawn
159
163
  determine_y_with_page_flow(h)
160
164
  end
161
165
 
162
- x = case options[:position]
166
+ x = case options[:position]
163
167
  when :left
164
168
  bounds.left_side
165
169
  when :center
166
- bounds.left_side + (bounds.width - w) / 2.0
170
+ bounds.left_side + (bounds.width - w) / 2.0
167
171
  when :right
168
172
  bounds.right_side - w
169
173
  when Numeric
@@ -171,17 +175,17 @@ module Prawn
171
175
  end
172
176
 
173
177
  return [x,y]
174
- end
175
-
178
+ end
179
+
176
180
  def determine_y_with_page_flow(h)
177
181
  if overruns_page?(h)
178
182
  bounds.move_past_bottom
179
183
  end
180
184
  self.y
181
- end
182
-
185
+ end
186
+
183
187
  def overruns_page?(h)
184
- (self.y - h) < reference_bounds.absolute_bottom
188
+ (self.y - h) < reference_bounds.absolute_bottom
185
189
  end
186
190
 
187
191
  def image_registry
@@ -10,23 +10,24 @@ require 'digest/sha1'
10
10
  module Prawn
11
11
  module Images
12
12
  class Image
13
+ # @group Extension API
13
14
 
14
15
  def calc_image_dimensions(options)
15
16
  w = options[:width] || width
16
17
  h = options[:height] || height
17
18
 
18
19
  if options[:width] && !options[:height]
19
- wp = w / width.to_f
20
+ wp = w / width.to_f
20
21
  w = width * wp
21
22
  h = height * wp
22
- elsif options[:height] && !options[:width]
23
+ elsif options[:height] && !options[:width]
23
24
  hp = h / height.to_f
24
25
  w = width * hp
25
- h = height * hp
26
- elsif options[:scale]
26
+ h = height * hp
27
+ elsif options[:scale]
27
28
  w = width * options[:scale]
28
29
  h = height * options[:scale]
29
- elsif options[:fit]
30
+ elsif options[:fit]
30
31
  bw, bh = options[:fit]
31
32
  bp = bw / bh.to_f
32
33
  ip = width / height.to_f
@@ -43,23 +44,6 @@ module Prawn
43
44
 
44
45
  [w,h]
45
46
  end
46
-
47
- def self.detect_image_format(content)
48
- top = content[0,128]
49
-
50
- # Unpack before comparing for JPG header, so as to avoid having to worry
51
- # about the source string encoding. We just want a byte-by-byte compare.
52
- if top[0, 3].unpack("C*") == [255, 216, 255]
53
- return :jpg
54
- elsif top[0, 8].unpack("C*") == [137, 80, 78, 71, 13, 10, 26, 10]
55
- return :png
56
- else
57
- raise Errors::UnsupportedImageType, "image file is an unrecognised format"
58
- end
59
- end
60
-
61
-
62
47
  end
63
48
  end
64
49
  end
65
-
@@ -10,37 +10,43 @@ require 'stringio'
10
10
 
11
11
  module Prawn
12
12
  module Images
13
+
13
14
  # A convenience class that wraps the logic for extracting the parts
14
15
  # of a JPG image that we need to embed them in a PDF
15
16
  #
16
17
  class JPG < Image
18
+ # @group Extension API
19
+
17
20
  attr_reader :width, :height, :bits, :channels
18
21
  attr_accessor :scaled_width, :scaled_height
19
-
20
- JPEG_SOF_BLOCKS = %W(\xc0 \xc1 \xc2 \xc3 \xc5 \xc6 \xc7 \xc9 \xca \xcb \xcd \xce \xcf)
21
- JPEG_APP_BLOCKS = %W(\xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef)
22
+
23
+ JPEG_SOF_BLOCKS = [0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF]
24
+
25
+ def self.can_render?(image_blob)
26
+ image_blob[0, 3].unpack("C*") == [255, 216, 255]
27
+ end
22
28
 
23
29
  # Process a new JPG image
24
30
  #
25
31
  # <tt>:data</tt>:: A binary string of JPEG data
26
32
  #
27
33
  def initialize(data)
28
- @data = data.dup
29
- ruby_19 { data.force_encoding("binary") }
30
- data = StringIO.new(data)
34
+ @data = data
35
+ d = StringIO.new(@data)
36
+ d.binmode
31
37
 
32
- c_marker = "\xff" # Section marker.
33
- data.read(2) # Skip the first two bytes of JPEG identifier.
38
+ c_marker = 0xff # Section marker.
39
+ d.seek(2) # Skip the first two bytes of JPEG identifier.
34
40
  loop do
35
- marker, code, length = data.read(4).unpack('aan')
41
+ marker, code, length = d.read(4).unpack('CCn')
36
42
  raise "JPEG marker not found!" if marker != c_marker
37
43
 
38
44
  if JPEG_SOF_BLOCKS.include?(code)
39
- @bits, @height, @width, @channels = data.read(6).unpack("CnnC")
45
+ @bits, @height, @width, @channels = d.read(6).unpack("CnnC")
40
46
  break
41
47
  end
42
48
 
43
- buffer = data.read(length - 2)
49
+ d.seek(length - 2, IO::SEEK_CUR)
44
50
  end
45
51
  end
46
52
 
@@ -62,12 +68,11 @@ module Prawn
62
68
  obj = document.ref!(
63
69
  :Type => :XObject,
64
70
  :Subtype => :Image,
65
- :Filter => :DCTDecode,
66
71
  :ColorSpace => color_space,
67
72
  :BitsPerComponent => bits,
68
73
  :Width => width,
69
74
  :Height => height
70
- )
75
+ )
71
76
 
72
77
  # add extra decode params for CMYK images. By swapping the
73
78
  # min and max values from the default, we invert the colours. See
@@ -76,7 +81,8 @@ module Prawn
76
81
  obj.data[:Decode] = [ 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0 ]
77
82
  end
78
83
 
79
- obj << @data
84
+ obj.stream << @data
85
+ obj.stream.filters << :DCTDecode
80
86
  obj
81
87
  end
82
88
 
@@ -17,12 +17,18 @@ module Prawn
17
17
  # of a PNG image that we need to embed them in a PDF
18
18
  #
19
19
  class PNG < Image
20
+ # @group Extension API
21
+
20
22
  attr_reader :palette, :img_data, :transparency
21
23
  attr_reader :width, :height, :bits
22
24
  attr_reader :color_type, :compression_method, :filter_method
23
25
  attr_reader :interlace_method, :alpha_channel
24
26
  attr_accessor :scaled_width, :scaled_height
25
27
 
28
+ def self.can_render?(image_blob)
29
+ image_blob[0, 8].unpack("C*") == [137, 80, 78, 71, 13, 10, 26, 10]
30
+ end
31
+
26
32
  # Process a new PNG image
27
33
  #
28
34
  # <tt>data</tt>:: A binary string of PNG data
@@ -88,6 +94,8 @@ module Prawn
88
94
 
89
95
  data.read(4) # Skip the CRC
90
96
  end
97
+
98
+ @img_data = Zlib::Inflate.inflate(@img_data)
91
99
  end
92
100
 
93
101
  # number of color components to each pixel
@@ -101,34 +109,17 @@ module Prawn
101
109
  end
102
110
  end
103
111
 
104
- # number of bits used per pixel
105
- #
106
- def pixel_bitlength
107
- if alpha_channel?
108
- self.bits * (self.colors + 1)
109
- else
110
- self.bits * self.colors
111
- end
112
- end
113
-
114
112
  # split the alpha channel data from the raw image data in images
115
113
  # where it's required.
116
114
  #
117
115
  def split_alpha_channel!
118
- unfilter_image_data if alpha_channel?
116
+ split_image_data if alpha_channel?
119
117
  end
120
118
 
121
119
  def alpha_channel?
122
120
  @color_type == 4 || @color_type == 6
123
121
  end
124
122
 
125
- # Adobe Reader can't handle 16-bit png channels -- chop off the second
126
- # byte (least significant)
127
- #
128
- def alpha_channel_bits
129
- 8
130
- end
131
-
132
123
  # Build a PDF object representing this image in +document+, and return
133
124
  # a Reference to it.
134
125
  #
@@ -168,20 +159,21 @@ module Prawn
168
159
  :Subtype => :Image,
169
160
  :Height => height,
170
161
  :Width => width,
171
- :BitsPerComponent => bits,
172
- :Filter => :FlateDecode
162
+ :BitsPerComponent => bits
173
163
  )
174
164
 
175
- unless alpha_channel
176
- obj.data[:DecodeParms] = {:Predictor => 15,
177
- :Colors => colors,
178
- :BitsPerComponent => bits,
179
- :Columns => width}
180
- end
181
-
182
165
  # append the actual image data to the object as a stream
183
166
  obj << img_data
184
167
 
168
+ obj.stream.filters << {
169
+ :FlateDecode => {
170
+ :Predictor => 15,
171
+ :Colors => colors,
172
+ :BitsPerComponent => bits,
173
+ :Columns => width
174
+ }
175
+ }
176
+
185
177
  # sort out the colours of the image
186
178
  if palette.empty?
187
179
  obj.data[:ColorSpace] = color
@@ -232,12 +224,20 @@ module Prawn
232
224
  :Subtype => :Image,
233
225
  :Height => height,
234
226
  :Width => width,
235
- :BitsPerComponent => alpha_channel_bits,
236
- :Filter => :FlateDecode,
227
+ :BitsPerComponent => bits,
237
228
  :ColorSpace => :DeviceGray,
238
229
  :Decode => [0, 1]
239
230
  )
240
- smask_obj << alpha_channel
231
+ smask_obj.stream << alpha_channel
232
+
233
+ smask_obj.stream.filters << {
234
+ :FlateDecode => {
235
+ :Predictor => 15,
236
+ :Colors => 1,
237
+ :BitsPerComponent => bits,
238
+ :Columns => width
239
+ }
240
+ }
241
241
  obj.data[:SMask] = smask_obj
242
242
  end
243
243
 
@@ -259,103 +259,40 @@ module Prawn
259
259
 
260
260
  private
261
261
 
262
- def unfilter_image_data
263
- data = Zlib::Inflate.inflate(@img_data).bytes
264
- @img_data = ""
265
- @alpha_channel = ""
266
-
267
- pixel_bytes = pixel_bitlength / 8
268
- scanline_length = pixel_bytes * self.width + 1
269
- row = 0
270
- pixels = []
271
- row_data = [] # reused for each row of the image
272
- paeth, pa, pb, pc = nil
273
-
274
- data.each do |byte|
275
- # accumulate a whole scanline of bytes, and then process it all at once
276
- # we could do this with Enumerable#each_slice, but it allocates memory,
277
- # and we are trying to avoid that
278
- row_data << byte
279
- next if row_data.length < scanline_length
280
-
281
- filter = row_data.shift
282
- case filter
283
- when 0 # None
284
- when 1 # Sub
285
- row_data.each_with_index do |byte, index|
286
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
287
- row_data[index] = (byte + left) % 256
288
- #p [byte, left, row_data[index]]
289
- end
290
- when 2 # Up
291
- row_data.each_with_index do |byte, index|
292
- col = (index / pixel_bytes).floor
293
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
294
- row_data[index] = (upper + byte) % 256
295
- end
296
- when 3 # Average
297
- row_data.each_with_index do |byte, index|
298
- col = (index / pixel_bytes).floor
299
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
300
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
262
+ def split_image_data
263
+ alpha_bytes = bits / 8
264
+ color_bytes = colors * bits / 8
301
265
 
302
- row_data[index] = (byte + ((left + upper)/2).floor) % 256
303
- end
304
- when 4 # Paeth
305
- left = upper = upper_left = nil
306
- row_data.each_with_index do |byte, index|
307
- col = (index / pixel_bytes).floor
308
-
309
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
310
- if row.zero?
311
- upper = upper_left = 0
312
- else
313
- upper = pixels[row-1][col][index % pixel_bytes]
314
- upper_left = col.zero? ? 0 :
315
- pixels[row-1][col-1][index % pixel_bytes]
316
- end
317
-
318
- p = left + upper - upper_left
319
- pa = (p - left).abs
320
- pb = (p - upper).abs
321
- pc = (p - upper_left).abs
322
-
323
- paeth = if pa <= pb && pa <= pc
324
- left
325
- elsif pb <= pc
326
- upper
327
- else
328
- upper_left
329
- end
330
-
331
- row_data[index] = (byte + paeth) % 256
332
- end
333
- else
334
- raise ArgumentError, "Invalid filter algorithm #{filter}"
335
- end
266
+ scanline_length = (color_bytes + alpha_bytes) * self.width + 1
267
+ scanlines = @img_data.bytesize / scanline_length
268
+ pixels = self.width * self.height
336
269
 
337
- s = []
338
- row_data.each_slice pixel_bytes do |slice|
339
- s << slice
340
- end
341
- pixels << s
342
- row += 1
343
- row_data.clear
344
- end
270
+ data = StringIO.new(@img_data)
271
+ data.binmode
272
+
273
+ color_data = [0x00].pack('C') * (pixels * color_bytes + scanlines)
274
+ color = StringIO.new(color_data)
275
+ color.binmode
276
+
277
+ @alpha_channel = [0x00].pack('C') * (pixels * alpha_bytes + scanlines)
278
+ alpha = StringIO.new(@alpha_channel)
279
+ alpha.binmode
280
+
281
+ scanlines.times do |line|
282
+ data.seek(line * scanline_length)
283
+
284
+ filter = data.getbyte
285
+
286
+ color.putc filter
287
+ alpha.putc filter
345
288
 
346
- # convert the pixel data to separate strings for colours and alpha
347
- color_byte_size = self.colors * self.bits / 8
348
- alpha_byte_size = alpha_channel_bits / 8
349
- pixels.each do |this_row|
350
- this_row.each do |pixel|
351
- @img_data << pixel[0, color_byte_size].pack("C*")
352
- @alpha_channel << pixel[color_byte_size, alpha_byte_size].pack("C*")
289
+ self.width.times do
290
+ color.write data.read(color_bytes)
291
+ alpha.write data.read(alpha_bytes)
353
292
  end
354
293
  end
355
294
 
356
- # compress the data
357
- @img_data = Zlib::Deflate.deflate(@img_data)
358
- @alpha_channel = Zlib::Deflate.deflate(@alpha_channel)
295
+ @img_data = color_data
359
296
  end
360
297
  end
361
298
  end