prawn 2.0.2 → 2.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/data/images/blend_modes_bottom_layer.jpg +0 -0
  3. data/data/images/blend_modes_top_layer.jpg +0 -0
  4. data/data/images/indexed_transparency.png +0 -0
  5. data/data/images/indexed_transparency_alpha.dat +0 -0
  6. data/data/images/indexed_transparency_color.dat +0 -0
  7. data/lib/prawn.rb +2 -1
  8. data/lib/prawn/document.rb +1 -0
  9. data/lib/prawn/document/internals.rb +10 -2
  10. data/lib/prawn/font.rb +14 -1
  11. data/lib/prawn/graphics.rb +2 -0
  12. data/lib/prawn/graphics/blend_mode.rb +64 -0
  13. data/lib/prawn/graphics/patterns.rb +52 -16
  14. data/lib/prawn/graphics/transformation.rb +3 -0
  15. data/lib/prawn/images/png.rb +43 -5
  16. data/lib/prawn/text/formatted/arranger.rb +25 -17
  17. data/lib/prawn/text/formatted/line_wrap.rb +2 -3
  18. data/lib/prawn/transformation_stack.rb +42 -0
  19. data/lib/prawn/version.rb +1 -1
  20. data/manual/graphics/blend_mode.rb +49 -0
  21. data/manual/graphics/graphics.rb +1 -0
  22. data/manual/graphics/soft_masks.rb +1 -1
  23. data/prawn.gemspec +4 -5
  24. data/spec/acceptance/png_spec.rb +35 -0
  25. data/spec/blend_mode_spec.rb +71 -0
  26. data/spec/document_spec.rb +72 -76
  27. data/spec/font_spec.rb +11 -11
  28. data/spec/formatted_text_arranger_spec.rb +178 -149
  29. data/spec/formatted_text_box_spec.rb +23 -23
  30. data/spec/graphics_spec.rb +67 -28
  31. data/spec/image_handler_spec.rb +7 -7
  32. data/spec/images_spec.rb +1 -1
  33. data/spec/png_spec.rb +26 -4
  34. data/spec/repeater_spec.rb +9 -9
  35. data/spec/spec_helper.rb +1 -4
  36. data/spec/text_at_spec.rb +2 -2
  37. data/spec/text_box_spec.rb +20 -16
  38. data/spec/text_spec.rb +8 -14
  39. data/spec/transformation_stack_spec.rb +63 -0
  40. data/spec/view_spec.rb +10 -10
  41. metadata +27 -31
  42. data/data/images/pal_bk.png +0 -0
  43. data/spec/acceptance/png.rb +0 -24
  44. data/spec/extensions/mocha.rb +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b05cc94288d323f650bc4a44529992282d42b69f
4
- data.tar.gz: 71cb2515eb6667207083899502a96487807f8667
3
+ metadata.gz: e103cb901b7728d21af3dfe412f3072063a86835
4
+ data.tar.gz: 132ff9e3bf2aeeeb1e9b574e5b84115192e576dc
5
5
  SHA512:
6
- metadata.gz: 4081995850066454b2ec29d3532ea5470812d8af92606a7e2d65d9514105242d3e4061927559e8280c180732c8223d11f37debc7423382ffeada0820dbfc8c8c
7
- data.tar.gz: f1ef9c7346cddd535db2acde3a3522bdc4a128dcd8da2eb45439278203ca982e22543de1667a0d7f6435824884f73eb8ed046f6dbf128fa9ad3d721792bc4add
6
+ metadata.gz: 0cad684b3b096da6cdceb69e47b78c5629251687d28c8a3ab4f824fc64b842daa11e2f62122033d9b27f1cd4afe809bfad1c3cc2c150e909cae09f5480a442ec
7
+ data.tar.gz: 5ea7ffa1b819a00d71897150c87afd0a7b7cc08e1afc8c4b41a425fd262f5276c961e969ea0e6e0178dde6f74ded16842076674abe39afbc5ca9bfaf513d5aab
@@ -23,7 +23,7 @@ module Prawn
23
23
 
24
24
  FLOAT_PRECISION = 1.0e-9
25
25
 
26
- # Whe set to true, Prawn will verify hash options to ensure only valid keys
26
+ # When set to true, Prawn will verify hash options to ensure only valid keys
27
27
  # are used. Off by default.
28
28
  #
29
29
  # Example:
@@ -76,6 +76,7 @@ require_relative "prawn/images/png"
76
76
  require_relative "prawn/stamp"
77
77
  require_relative "prawn/soft_mask"
78
78
  require_relative "prawn/security"
79
+ require_relative "prawn/transformation_stack"
79
80
  require_relative "prawn/document"
80
81
  require_relative "prawn/font"
81
82
  require_relative "prawn/measurements"
@@ -57,6 +57,7 @@ module Prawn
57
57
  include Prawn::Images
58
58
  include Prawn::Stamp
59
59
  include Prawn::SoftMask
60
+ include Prawn::TransformationStack
60
61
 
61
62
  # @group Extension API
62
63
 
@@ -24,10 +24,18 @@ module Prawn
24
24
  # Perhaps they will become part of the extension API?
25
25
  # Anyway, for now it's not clear what we should do w. them.
26
26
  delegate [ :graphic_state,
27
- :save_graphics_state,
28
- :restore_graphics_state,
29
27
  :on_page_create ] => :renderer
30
28
 
29
+ def save_graphics_state(state = nil, &block)
30
+ save_transformation_stack
31
+ renderer.save_graphics_state(state, &block)
32
+ end
33
+
34
+ def restore_graphics_state
35
+ restore_transformation_stack
36
+ renderer.restore_graphics_state
37
+ end
38
+
31
39
  # FIXME: This is a circular reference, because in theory Prawn should
32
40
  # be passing instances of renderer to PDF::Core::Page, but it's
33
41
  # passing Prawn::Document objects instead.
@@ -396,7 +396,20 @@ module Prawn
396
396
  # generate a font identifier that hasn't been used on the current page yet
397
397
  #
398
398
  def generate_unique_id
399
- :"F#{@document.font_registry.size + 1}"
399
+ key = nil
400
+ font_count = @document.font_registry.size + 1
401
+ loop do
402
+ key = :"F#{font_count}"
403
+ break if key_is_unique?(key)
404
+ font_count += 1
405
+ end
406
+ key
407
+ end
408
+
409
+ def key_is_unique?(test_key)
410
+ !@document.state.page.fonts.keys.any? do |key|
411
+ key.to_s.start_with?("#{test_key}.")
412
+ end
400
413
  end
401
414
 
402
415
  def size
@@ -6,6 +6,7 @@
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
+ require_relative "graphics/blend_mode"
9
10
  require_relative "graphics/color"
10
11
  require_relative "graphics/dash"
11
12
  require_relative "graphics/cap_style"
@@ -22,6 +23,7 @@ module Prawn
22
23
  # ruby-pdf.rubyforge.org
23
24
  #
24
25
  module Graphics
26
+ include BlendMode
25
27
  include Color
26
28
  include Dash
27
29
  include CapStyle
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ #
3
+ # blend_mode.rb : Implements blend modes
4
+ #
5
+ # Contributed by John Ford. October, 2015
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ #
9
+
10
+ module Prawn
11
+ module Graphics
12
+ # The Prawn::BlendMode module is used to change the way
13
+ # two layers are blended together.
14
+ #
15
+ # Passing an array of blend modes is allowed. PDF viewers should
16
+ # blend layers based on the first recognized blend mode.
17
+ #
18
+ # Valid blend modes in v1.4 of the PDF spec include :Normal, :Multiply, :Screen,
19
+ # :Overlay, :Darken, :Lighten, :ColorDodge, :ColorBurn, :HardLight, :SoftLight,
20
+ # :Difference, :Exclusion, :Hue, :Saturation, :Color, and :Luminosity.
21
+ #
22
+ # Example:
23
+ # pdf.fill_color('0000ff')
24
+ # pdf.fill_rectangle([x, y+25], 50, 50)
25
+ # pdf.blend_mode(:Multiply) do
26
+ # pdf.fill_color('ff0000')
27
+ # pdf.fill_circle([x, y], 25)
28
+ # end
29
+ #
30
+ module BlendMode
31
+ # @group Stable API
32
+
33
+ def blend_mode(blend_mode = :Normal)
34
+ renderer.min_version(1.4)
35
+
36
+ save_graphics_state if block_given?
37
+ renderer.add_content "/#{blend_mode_dictionary_name(blend_mode)} gs"
38
+ if block_given?
39
+ yield
40
+ restore_graphics_state
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def blend_mode_dictionary_registry
47
+ @blend_mode_dictionary_registry ||= {}
48
+ end
49
+
50
+ def blend_mode_dictionary_name(blend_mode)
51
+ key = Array(blend_mode).join('')
52
+ dictionary_name = "BM#{key}"
53
+
54
+ dictionary = blend_mode_dictionary_registry[dictionary_name] ||= ref!(
55
+ :Type => :ExtGState,
56
+ :BM => blend_mode
57
+ )
58
+
59
+ page.ext_gstates.merge!(dictionary_name => dictionary)
60
+ dictionary_name
61
+ end
62
+ end
63
+ end
64
+ end
@@ -14,16 +14,30 @@ module Prawn
14
14
 
15
15
  # Sets the fill gradient from color1 to color2.
16
16
  # old arguments: point, width, height, color1, color2, options = {}
17
- # new arguments: from, to, color1, color1
18
- # or from, r1, to, r2, color1, color2
17
+ # new arguments: from, to, color1, color1, options = {}
18
+ # or from, r1, to, r2, color1, color2, options = {}
19
+ #
20
+ # Option :apply_transformations, if set true, will transform the
21
+ # gradient's co-ordinate space so it matches the current co-ordinate
22
+ # space of the document. This option will be the default from Prawn v3.
23
+ # The current default, false, will mean if you (for example) scale your
24
+ # document by 2 and put a gradient inside, you will have to manually
25
+ # multiply your co-ordinates by 2 so the gradient is correctly positioned.
19
26
  def fill_gradient(*args)
20
27
  set_gradient(:fill, *args)
21
28
  end
22
29
 
23
30
  # Sets the stroke gradient from color1 to color2.
24
31
  # old arguments: point, width, height, color1, color2, options = {}
25
- # new arguments: from, to, color1, color2
26
- # or from, r1, to, r2, color1, color2
32
+ # new arguments: from, to, color1, color2, options = {}
33
+ # or from, r1, to, r2, color1, color2, options = {}
34
+ #
35
+ # Option :apply_transformations, if set true, will transform the
36
+ # gradient's co-ordinate space so it matches the current co-ordinate
37
+ # space of the document. This option will be the default from Prawn v3.
38
+ # The current default, false, will mean if you (for example) scale your
39
+ # document by 2 and put a gradient inside, you will have to manually
40
+ # multiply your co-ordinates by 2 so the gradient is correctly positioned.
27
41
  def stroke_gradient(*args)
28
42
  set_gradient(:stroke, *args)
29
43
  end
@@ -31,15 +45,17 @@ module Prawn
31
45
  private
32
46
 
33
47
  def set_gradient(type, *grad)
48
+ opts = grad.last.is_a?(Hash) ? grad.pop : {}
49
+
34
50
  patterns = page.resources[:Pattern] ||= {}
35
51
 
36
- registry_key = gradient_registry_key grad
52
+ registry_key = gradient_registry_key grad, opts
37
53
 
38
54
  if patterns["SP#{registry_key}"]
39
55
  shading = patterns["SP#{registry_key}"]
40
56
  else
41
57
  unless shading = gradient_registry[registry_key]
42
- shading = gradient(*grad)
58
+ shading = gradient(grad, opts)
43
59
  gradient_registry[registry_key] = shading
44
60
  end
45
61
 
@@ -59,18 +75,20 @@ module Prawn
59
75
  renderer.add_content "/SP#{registry_key} #{operator}"
60
76
  end
61
77
 
62
- def gradient_registry_key(gradient)
78
+ def gradient_registry_key(gradient, opts)
79
+ _x1, _y1, x2, y2, transformation = gradient_coordinates(gradient, opts)
80
+
63
81
  if gradient[1].is_a?(Array) # axial
64
82
  [
65
- map_to_absolute(gradient[0]),
66
- map_to_absolute(gradient[1]),
83
+ transformation,
84
+ x2, y2,
67
85
  gradient[2], gradient[3]
68
86
  ]
69
87
  else # radial
70
88
  [
71
- map_to_absolute(gradient[0]),
89
+ transformation,
90
+ x2, y2,
72
91
  gradient[1],
73
- map_to_absolute(gradient[2]),
74
92
  gradient[3],
75
93
  gradient[4], gradient[5]
76
94
  ]
@@ -81,11 +99,15 @@ module Prawn
81
99
  @gradient_registry ||= {}
82
100
  end
83
101
 
84
- def gradient(*args)
102
+ def gradient(args, opts)
85
103
  if args.length != 4 && args.length != 6
86
104
  fail ArgumentError, "Unknown type of gradient: #{args.inspect}"
87
105
  end
88
106
 
107
+ if opts[:apply_transformations].nil? && current_transformation_matrix_with_translation(0, 0) != [1, 0, 0, 1, 0, 0]
108
+ warn "Gradients in Prawn 2.x and lower are not correctly positioned when a transformation has been made to the document. Pass 'apply_transformations: true' to correctly transform the gradient, or see https://github.com/prawnpdf/prawn/wiki/Gradient-Transformations for more information."
109
+ end
110
+
89
111
  color1 = normalize_color(args[-2]).dup.freeze
90
112
  color2 = normalize_color(args[-1]).dup.freeze
91
113
 
@@ -104,10 +126,12 @@ module Prawn
104
126
  :N => 1.0
105
127
  )
106
128
 
129
+ x1, y1, x2, y2, transformation = gradient_coordinates(args, opts)
130
+
107
131
  if args.length == 4
108
- coords = [0, 0, args[1].first - args[0].first, args[1].last - args[0].last]
132
+ coords = [0, 0, x2 - x1, y2 - y1]
109
133
  else
110
- coords = [0, 0, args[1], args[2].first - args[0].first, args[2].last - args[0].last, args[3]]
134
+ coords = [0, 0, args[1], x2 - x1, y2 - y1, args[3]]
111
135
  end
112
136
 
113
137
  shading = ref!(
@@ -121,10 +145,22 @@ module Prawn
121
145
  ref!(
122
146
  :PatternType => 2, # shading pattern
123
147
  :Shading => shading,
124
- :Matrix => [1, 0,
125
- 0, 1] + map_to_absolute(args[0])
148
+ :Matrix => transformation
126
149
  )
127
150
  end
151
+
152
+ def gradient_coordinates(args, opts)
153
+ x1, y1 = map_to_absolute(args[0])
154
+ x2, y2 = map_to_absolute(args[args.length == 4 ? 1 : 2])
155
+
156
+ transformation = if opts[:apply_transformations]
157
+ current_transformation_matrix_with_translation(x1, y1)
158
+ else
159
+ [1, 0, 0, 1, x1, y1]
160
+ end
161
+
162
+ [x1, y1, x2, y2, transformation]
163
+ end
128
164
  end
129
165
  end
130
166
  end
@@ -145,6 +145,9 @@ module Prawn
145
145
  def transformation_matrix(a, b, c, d, e, f)
146
146
  values = [a, b, c, d, e, f].map { |x| "%.5f" % x }.join(" ")
147
147
  save_graphics_state if block_given?
148
+
149
+ add_to_transformation_stack(a, b, c, d, e, f)
150
+
148
151
  renderer.add_content "#{values} cm"
149
152
  if block_given?
150
153
  yield
@@ -68,9 +68,7 @@ module Prawn
68
68
  @transparency = {}
69
69
  case @color_type
70
70
  when 3
71
- fail Errors::UnsupportedImageType,
72
- "Pallete-based transparency in PNG is not currently supported.\n" \
73
- "See https://github.com/prawnpdf/prawn/issues/783"
71
+ @transparency[:palette] = data.read(chunk_size).unpack('C*')
74
72
  when 0
75
73
  # Greyscale. Corresponding to entries in the PLTE chunk.
76
74
  # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1
@@ -109,11 +107,20 @@ module Prawn
109
107
  # where it's required.
110
108
  #
111
109
  def split_alpha_channel!
112
- split_image_data if alpha_channel?
110
+ if alpha_channel?
111
+ if color_type == 3
112
+ generate_alpha_channel
113
+ else
114
+ split_image_data
115
+ end
116
+ end
113
117
  end
114
118
 
115
119
  def alpha_channel?
116
- @color_type == 4 || @color_type == 6
120
+ return true if color_type == 4 || color_type == 6
121
+ return @transparency.any? if color_type == 3
122
+
123
+ false
117
124
  end
118
125
 
119
126
  # Build a PDF object representing this image in +document+, and return
@@ -285,6 +292,37 @@ module Prawn
285
292
 
286
293
  @img_data = color_data
287
294
  end
295
+
296
+ def generate_alpha_channel
297
+ alpha_palette = Hash.new(0xff)
298
+ 0.upto(palette.bytesize / 3) do |n|
299
+ alpha_palette[n] = @transparency[:palette][n] || 0xff
300
+ end
301
+
302
+ scanline_length = width + 1
303
+ scanlines = @img_data.bytesize / scanline_length
304
+ pixels = width * height
305
+
306
+ data = StringIO.new(@img_data)
307
+ data.binmode
308
+
309
+ @alpha_channel = [0x00].pack('C') * (pixels + scanlines)
310
+ alpha = StringIO.new(@alpha_channel)
311
+ alpha.binmode
312
+
313
+ scanlines.times do |line|
314
+ data.seek(line * scanline_length)
315
+
316
+ filter = data.getbyte
317
+
318
+ alpha.putc filter
319
+
320
+ width.times do
321
+ color = data.read(1).unpack('C').first
322
+ alpha.putc alpha_palette[color]
323
+ end
324
+ end
325
+ end
288
326
  end
289
327
  end
290
328
  end
@@ -16,6 +16,7 @@ module Prawn
16
16
  attr_reader :max_line_height
17
17
  attr_reader :max_descender
18
18
  attr_reader :max_ascender
19
+ attr_reader :finalized
19
20
  attr_accessor :consumed
20
21
 
21
22
  # The following present only for testing purposes
@@ -31,34 +32,38 @@ module Prawn
31
32
  end
32
33
 
33
34
  def space_count
34
- if @unfinalized_line
35
+ unless finalized
35
36
  fail "Lines must be finalized before calling #space_count"
36
37
  end
38
+
37
39
  @fragments.inject(0) do |sum, fragment|
38
40
  sum + fragment.space_count
39
41
  end
40
42
  end
41
43
 
42
44
  def line_width
43
- if @unfinalized_line
45
+ unless finalized
44
46
  fail "Lines must be finalized before calling #line_width"
45
47
  end
48
+
46
49
  @fragments.inject(0) do |sum, fragment|
47
50
  sum + fragment.width
48
51
  end
49
52
  end
50
53
 
51
54
  def line
52
- if @unfinalized_line
55
+ unless finalized
53
56
  fail "Lines must be finalized before calling #line"
54
57
  end
58
+
55
59
  @fragments.collect do |fragment|
56
60
  fragment.text.dup.force_encoding(::Encoding::UTF_8)
57
61
  end.join
58
62
  end
59
63
 
60
64
  def finalize_line
61
- @unfinalized_line = false
65
+ @finalized = true
66
+
62
67
  omit_trailing_whitespace_from_line_width
63
68
  @fragments = []
64
69
  @consumed.each do |hash|
@@ -85,7 +90,7 @@ module Prawn
85
90
  end
86
91
 
87
92
  def initialize_line
88
- @unfinalized_line = true
93
+ @finalized = false
89
94
  @max_line_height = 0
90
95
  @max_descender = 0
91
96
  @max_ascender = 0
@@ -99,24 +104,26 @@ module Prawn
99
104
  end
100
105
 
101
106
  def next_string
102
- unless @unfinalized_line
107
+ if finalized
103
108
  fail "Lines must not be finalized when calling #next_string"
104
109
  end
105
- hash = @unconsumed.shift
106
- if hash.nil?
107
- nil
108
- else
109
- @consumed << hash.dup
110
- @current_format_state = hash.dup
110
+
111
+ next_unconsumed_hash = @unconsumed.shift
112
+
113
+ if next_unconsumed_hash
114
+ @consumed << next_unconsumed_hash.dup
115
+ @current_format_state = next_unconsumed_hash.dup
111
116
  @current_format_state.delete(:text)
112
- hash[:text]
117
+
118
+ next_unconsumed_hash[:text]
113
119
  end
114
120
  end
115
121
 
116
122
  def preview_next_string
117
- hash = @unconsumed.first
118
- if hash.nil? then nil
119
- else hash[:text]
123
+ next_unconsumed_hash = @unconsumed.first
124
+
125
+ if next_unconsumed_hash
126
+ next_unconsumed_hash[:text]
120
127
  end
121
128
  end
122
129
 
@@ -181,9 +188,10 @@ module Prawn
181
188
  end
182
189
 
183
190
  def retrieve_fragment
184
- if @unfinalized_line
191
+ unless finalized
185
192
  fail "Lines must be finalized before fragments can be retrieved"
186
193
  end
194
+
187
195
  @fragments.shift
188
196
  end
189
197