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