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.
- checksums.yaml +4 -4
- data/data/images/blend_modes_bottom_layer.jpg +0 -0
- data/data/images/blend_modes_top_layer.jpg +0 -0
- data/data/images/indexed_transparency.png +0 -0
- data/data/images/indexed_transparency_alpha.dat +0 -0
- data/data/images/indexed_transparency_color.dat +0 -0
- data/lib/prawn.rb +2 -1
- data/lib/prawn/document.rb +1 -0
- data/lib/prawn/document/internals.rb +10 -2
- data/lib/prawn/font.rb +14 -1
- data/lib/prawn/graphics.rb +2 -0
- data/lib/prawn/graphics/blend_mode.rb +64 -0
- data/lib/prawn/graphics/patterns.rb +52 -16
- data/lib/prawn/graphics/transformation.rb +3 -0
- data/lib/prawn/images/png.rb +43 -5
- data/lib/prawn/text/formatted/arranger.rb +25 -17
- data/lib/prawn/text/formatted/line_wrap.rb +2 -3
- data/lib/prawn/transformation_stack.rb +42 -0
- data/lib/prawn/version.rb +1 -1
- data/manual/graphics/blend_mode.rb +49 -0
- data/manual/graphics/graphics.rb +1 -0
- data/manual/graphics/soft_masks.rb +1 -1
- data/prawn.gemspec +4 -5
- data/spec/acceptance/png_spec.rb +35 -0
- data/spec/blend_mode_spec.rb +71 -0
- data/spec/document_spec.rb +72 -76
- data/spec/font_spec.rb +11 -11
- data/spec/formatted_text_arranger_spec.rb +178 -149
- data/spec/formatted_text_box_spec.rb +23 -23
- data/spec/graphics_spec.rb +67 -28
- data/spec/image_handler_spec.rb +7 -7
- data/spec/images_spec.rb +1 -1
- data/spec/png_spec.rb +26 -4
- data/spec/repeater_spec.rb +9 -9
- data/spec/spec_helper.rb +1 -4
- data/spec/text_at_spec.rb +2 -2
- data/spec/text_box_spec.rb +20 -16
- data/spec/text_spec.rb +8 -14
- data/spec/transformation_stack_spec.rb +63 -0
- data/spec/view_spec.rb +10 -10
- metadata +27 -31
- data/data/images/pal_bk.png +0 -0
- data/spec/acceptance/png.rb +0 -24
- data/spec/extensions/mocha.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e103cb901b7728d21af3dfe412f3072063a86835
|
4
|
+
data.tar.gz: 132ff9e3bf2aeeeb1e9b574e5b84115192e576dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0cad684b3b096da6cdceb69e47b78c5629251687d28c8a3ab4f824fc64b842daa11e2f62122033d9b27f1cd4afe809bfad1c3cc2c150e909cae09f5480a442ec
|
7
|
+
data.tar.gz: 5ea7ffa1b819a00d71897150c87afd0a7b7cc08e1afc8c4b41a425fd262f5276c961e969ea0e6e0178dde6f74ded16842076674abe39afbc5ca9bfaf513d5aab
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/prawn.rb
CHANGED
@@ -23,7 +23,7 @@ module Prawn
|
|
23
23
|
|
24
24
|
FLOAT_PRECISION = 1.0e-9
|
25
25
|
|
26
|
-
#
|
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"
|
data/lib/prawn/document.rb
CHANGED
@@ -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.
|
data/lib/prawn/font.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/prawn/graphics.rb
CHANGED
@@ -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(
|
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
|
-
|
66
|
-
|
83
|
+
transformation,
|
84
|
+
x2, y2,
|
67
85
|
gradient[2], gradient[3]
|
68
86
|
]
|
69
87
|
else # radial
|
70
88
|
[
|
71
|
-
|
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(
|
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,
|
132
|
+
coords = [0, 0, x2 - x1, y2 - y1]
|
109
133
|
else
|
110
|
-
coords = [0, 0, args[1],
|
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 =>
|
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
|
data/lib/prawn/images/png.rb
CHANGED
@@ -68,9 +68,7 @@ module Prawn
|
|
68
68
|
@transparency = {}
|
69
69
|
case @color_type
|
70
70
|
when 3
|
71
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
107
|
+
if finalized
|
103
108
|
fail "Lines must not be finalized when calling #next_string"
|
104
109
|
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
@consumed <<
|
110
|
-
@current_format_state =
|
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
|
-
|
117
|
+
|
118
|
+
next_unconsumed_hash[:text]
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|
116
122
|
def preview_next_string
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
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
|
|