prawn-core 0.6.3 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Rakefile +1 -1
  2. data/examples/general/context_sensitive_headers.rb +37 -0
  3. data/examples/general/float.rb +11 -0
  4. data/examples/general/repeaters.rb +43 -0
  5. data/examples/m17n/chinese_text_wrapping.rb +1 -3
  6. data/examples/text/font_calculations.rb +6 -6
  7. data/examples/text/text_box.rb +80 -17
  8. data/lib/prawn/core.rb +3 -1
  9. data/lib/prawn/document/bounding_box.rb +9 -0
  10. data/lib/prawn/document/column_box.rb +13 -2
  11. data/lib/prawn/document/internals.rb +21 -3
  12. data/lib/prawn/document/snapshot.rb +7 -2
  13. data/lib/prawn/document/span.rb +3 -3
  14. data/lib/prawn/document.rb +78 -19
  15. data/lib/prawn/font/afm.rb +10 -7
  16. data/lib/prawn/font/ttf.rb +6 -4
  17. data/lib/prawn/font.rb +34 -24
  18. data/lib/prawn/graphics/cap_style.rb +5 -2
  19. data/lib/prawn/graphics/color.rb +117 -57
  20. data/lib/prawn/graphics/dash.rb +4 -2
  21. data/lib/prawn/graphics/join_style.rb +6 -3
  22. data/lib/prawn/graphics/transparency.rb +65 -18
  23. data/lib/prawn/images/jpg.rb +1 -1
  24. data/lib/prawn/images/png.rb +1 -1
  25. data/lib/prawn/object_store.rb +30 -1
  26. data/lib/prawn/reference.rb +25 -3
  27. data/lib/prawn/repeater.rb +117 -0
  28. data/lib/prawn/stamp.rb +102 -40
  29. data/lib/prawn/text/box.rb +344 -0
  30. data/lib/prawn/text.rb +255 -0
  31. data/spec/document_spec.rb +125 -4
  32. data/spec/object_store_spec.rb +33 -0
  33. data/spec/repeater_spec.rb +79 -0
  34. data/spec/stamp_spec.rb +8 -0
  35. data/spec/text_box_spec.rb +282 -69
  36. data/spec/text_spec.rb +49 -29
  37. data/spec/transparency_spec.rb +14 -0
  38. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +2 -2
  39. metadata +158 -155
  40. data/examples/general/measurement_units.pdf +0 -4667
  41. data/lib/prawn/document/text/box.rb +0 -90
  42. data/lib/prawn/document/text/wrapping.rb +0 -62
  43. data/lib/prawn/document/text.rb +0 -184
@@ -20,9 +20,9 @@ module Prawn
20
20
  end
21
21
  end
22
22
 
23
- attr_reader :attributes
23
+ attr_reader :attributes #:nodoc:
24
24
 
25
- def initialize(document, name, options={})
25
+ def initialize(document, name, options={}) #:nodoc:
26
26
  unless BUILT_INS.include?(name)
27
27
  raise Prawn::Errors::UnknownFont, "#{name} is not a known font."
28
28
  end
@@ -45,15 +45,14 @@ module Prawn
45
45
  @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
46
46
  end
47
47
 
48
+ # The font bbox, as an array of integers
49
+ #
48
50
  def bbox
49
51
  @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
50
52
  end
51
53
 
52
- # calculates the width of the supplied string.
53
- #
54
- # String *must* be encoded as WinAnsi
55
- #
56
- def compute_width_of(string, options={})
54
+ # NOTE: String *must* be encoded as WinAnsi
55
+ def compute_width_of(string, options={}) #:nodoc:
57
56
  scale = (options[:size] || size) / 1000.0
58
57
 
59
58
  if options[:kerning]
@@ -65,6 +64,8 @@ module Prawn
65
64
  end
66
65
  end
67
66
 
67
+ # Returns true if the font has kerning data, false otherwise
68
+ #
68
69
  def has_kerning_data?
69
70
  @kern_pairs.any?
70
71
  end
@@ -72,6 +73,7 @@ module Prawn
72
73
  # built-in fonts only work with winansi encoding, so translate the
73
74
  # string. Changes the encoding in-place, so the argument itself
74
75
  # is replaced with a string in WinAnsi encoding.
76
+ #
75
77
  def normalize_encoding(text)
76
78
  enc = Prawn::Encoding::WinAnsi.new
77
79
  text.unpack("U*").collect { |i| enc[i] }.pack("C*")
@@ -88,6 +90,7 @@ module Prawn
88
90
  # the string itself (or an array, if kerning is performed).
89
91
  #
90
92
  # The +text+ parameter must be in WinAnsi encoding (cp1252).
93
+ #
91
94
  def encode_text(text, options={})
92
95
  [[0, options[:kerning] ? kern(text) : text]]
93
96
  end
@@ -22,9 +22,8 @@ module Prawn
22
22
  @line_gap = Integer(@ttf.line_gap * scale_factor)
23
23
  end
24
24
 
25
- # +string+ must be UTF8-encoded.
26
- #
27
- def compute_width_of(string, options={})
25
+ # NOTE: +string+ must be UTF8-encoded.
26
+ def compute_width_of(string, options={}) #:nodoc:
28
27
  scale = (options[:size] || size) / 1000.0
29
28
  if options[:kerning]
30
29
  kern(string).inject(0) do |s,r|
@@ -40,11 +39,14 @@ module Prawn
40
39
  end * scale
41
40
  end
42
41
  end
43
-
42
+
43
+ # The font bbox, as an array of integers
44
+ #
44
45
  def bbox
45
46
  @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
46
47
  end
47
48
 
49
+ # Returns true if the font has kerning data, false otherwise
48
50
  def has_kerning_data?
49
51
  @has_kerning_data
50
52
  end
data/lib/prawn/font.rb CHANGED
@@ -113,21 +113,18 @@ module Prawn
113
113
  # font will be embedded twice. Since we do font subsetting, this double
114
114
  # embedding won't be catastrophic, just annoying.
115
115
  # ++
116
- #
117
- def find_font(name, options={}) #:nodoc:
118
- if font_families.key?(name)
119
- family, name = name, font_families[name][options[:style] || :normal]
120
-
121
- if name.is_a?(Hash)
122
- options = options.merge(name)
123
- name = options[:file]
124
- end
125
- end
126
-
127
- key = "#{name}:#{options[:font] || 0}"
128
- font_registry[key] ||= Font.load(self, name, options.merge(:family => family))
129
- end
130
-
116
+ def find_font(name, options={}) #:nodoc:
117
+ if font_families.key?(name)
118
+ family, name = name, font_families[name][options[:style] || :normal]
119
+ if name.is_a?(Hash)
120
+ options = options.merge(name)
121
+ name = options[:file]
122
+ end
123
+ end
124
+ key = "#{name}:#{options[:font] || 0}"
125
+ font_registry[key] ||= Font.load(self, name, options.merge(:family => family))
126
+ end
127
+
131
128
  # Hash of Font objects keyed by names
132
129
  #
133
130
  def font_registry #:nodoc:
@@ -210,6 +207,10 @@ module Prawn
210
207
  # The options hash used to initialize the font
211
208
  attr_reader :options
212
209
 
210
+ # Shortcut interface for constructing a font object. Filenames of the form
211
+ # *.ttf will call Font::TTF.new, *.dfont Font::DFont.new, and anything else
212
+ # will be passed through to Font::AFM.new()
213
+ #
213
214
  def self.load(document,name,options={})
214
215
  case name
215
216
  when /\.ttf$/ then TTF.new(document, name, options)
@@ -231,26 +232,24 @@ module Prawn
231
232
  @references = {}
232
233
  end
233
234
 
235
+ # The size of the font ascender in PDF points
236
+ #
234
237
  def ascender
235
238
  @ascender / 1000.0 * size
236
239
  end
237
240
 
241
+ # The size of the font descender in PDF points
242
+ #
238
243
  def descender
239
- @descender / 1000.0 * size
244
+ -@descender / 1000.0 * size
240
245
  end
241
246
 
247
+ # The size of the recommended gap between lines of text in PDF points
248
+ #
242
249
  def line_gap
243
250
  @line_gap / 1000.0 * size
244
251
  end
245
252
 
246
- def identifier_for(subset)
247
- "#{@identifier}.#{subset}"
248
- end
249
-
250
- def inspect
251
- "#{self.class.name}< #{name}: #{size} >"
252
- end
253
-
254
253
  # Normalizes the encoding of the string to an encoding supported by the
255
254
  # font. The string is expected to be UTF-8 going in. It will be re-encoded
256
255
  # and the new string will be returned. For an in-place (destructive)
@@ -261,10 +260,13 @@ module Prawn
261
260
 
262
261
  # Destructive version of normalize_encoding; normalizes the encoding of a
263
262
  # string in place.
263
+ #
264
264
  def normalize_encoding!(str)
265
265
  str.replace(normalize_encoding(str))
266
266
  end
267
267
 
268
+ # Gets height of current font in PDF points at the given font size
269
+ #
268
270
  def height_at(size)
269
271
  @normalized_height ||= (@ascender - @descender + @line_gap) / 1000.0
270
272
  @normalized_height * size
@@ -285,6 +287,14 @@ module Prawn
285
287
  @document.page_fonts.merge!(identifier_for(subset) => @references[subset])
286
288
  end
287
289
 
290
+ def identifier_for(subset) #:nodoc:
291
+ "#{@identifier}.#{subset}"
292
+ end
293
+
294
+ def inspect #:nodoc:
295
+ "#{self.class.name}< #{name}: #{size} >"
296
+ end
297
+
288
298
  private
289
299
 
290
300
  def size
@@ -9,12 +9,15 @@
9
9
  module Prawn
10
10
  module Graphics
11
11
  module CapStyle
12
- # Sets the cap_style for stroked lines and curves
13
- #
14
12
 
15
13
  CAP_STYLES = { :butt => 0, :round => 1, :projecting_square => 2 }
16
14
 
15
+ # Sets the cap style for stroked lines and curves
16
+ #
17
17
  # style is one of :butt, :round, or :projecting_square
18
+ #
19
+ # NOTE: If this method is never called, :butt will be used by default.
20
+ #
18
21
  def cap_style(style=nil)
19
22
  return @cap_style || :butt if style.nil?
20
23
 
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # encoding: utf-8
2
2
 
3
3
  # color.rb : Implements color handling
4
4
  #
@@ -8,41 +8,41 @@
8
8
  #
9
9
  module Prawn
10
10
  module Graphics
11
- module Color
12
-
13
- # Sets the fill color.
11
+ module Color
12
+
13
+ # Sets the fill color.
14
14
  #
15
- # If a single argument is provided, it should be a 6 digit HTML color
15
+ # If a single argument is provided, it should be a 6 digit HTML color
16
16
  # code.
17
- #
17
+ #
18
18
  # pdf.fill_color "f0ffc1"
19
19
  #
20
20
  # If 4 arguments are provided, the color is assumed to be a CMYK value
21
21
  # Values range from 0 - 100.
22
- #
22
+ #
23
23
  # pdf.fill_color 0, 99, 95, 0
24
24
  #
25
- def fill_color(*color)
26
- return @fill_color if color.empty?
27
- @fill_color = process_color(*color)
25
+ def fill_color(*color)
26
+ return @fill_color if color.empty?
27
+ @fill_color = process_color(*color)
28
28
  set_fill_color
29
- end
29
+ end
30
30
 
31
- alias_method :fill_color=, :fill_color
31
+ alias_method :fill_color=, :fill_color
32
32
 
33
33
  # Sets the line stroking color. 6 digit HTML color codes are used.
34
34
  #
35
- # If a single argument is provided, it should be a 6 digit HTML color
35
+ # If a single argument is provided, it should be a 6 digit HTML color
36
36
  # code.
37
- #
37
+ #
38
38
  # pdf.stroke_color "f0ffc1"
39
39
  #
40
40
  # If 4 arguments are provided, the color is assumed to be a CMYK value
41
41
  # Values range from 0 - 100.
42
- #
42
+ #
43
43
  # pdf.stroke_color 0, 99, 95, 0
44
44
  #
45
- def stroke_color(*color)
45
+ def stroke_color(*color)
46
46
  return @stroke_color if color.empty?
47
47
  @stroke_color = process_color(*color)
48
48
  set_stroke_color
@@ -57,30 +57,30 @@ module Prawn
57
57
  # fill_and_stroke_some_method(*args) #=> some_method(*args); fill_and_stroke
58
58
  #
59
59
  def method_missing(id,*args,&block)
60
- case(id.to_s)
60
+ case(id.to_s)
61
61
  when /^fill_and_stroke_(.*)/
62
62
  send($1,*args,&block); fill_and_stroke
63
63
  when /^stroke_(.*)/
64
- send($1,*args,&block); stroke
64
+ send($1,*args,&block); stroke
65
65
  when /^fill_(.*)/
66
66
  send($1,*args,&block); fill
67
67
  else
68
68
  super
69
69
  end
70
- end
71
-
70
+ end
71
+
72
72
  module_function
73
-
74
- # Converts RGB value array to hex string suitable for use with fill_color
75
- # and stroke_color
73
+
74
+ # Converts RGB value array to hex string suitable for use with fill_color
75
+ # and stroke_color
76
76
  #
77
77
  # >> Prawn::Graphics::Color.rgb2hex([255,120,8])
78
- # => "ff7808"
78
+ # => "ff7808"
79
79
  #
80
80
  def rgb2hex(rgb)
81
81
  rgb.map { |e| "%02x" % e }.join
82
- end
83
-
82
+ end
83
+
84
84
  # Converts hex string into RGB value array:
85
85
  #
86
86
  # >> Prawn::Graphics::Color.hex2rgb("ff7808")
@@ -89,53 +89,113 @@ module Prawn
89
89
  def hex2rgb(hex)
90
90
  r,g,b = hex[0..1], hex[2..3], hex[4..5]
91
91
  [r,g,b].map { |e| e.to_i(16) }
92
- end
93
-
92
+ end
93
+
94
94
  private
95
-
96
- def process_color(*color)
95
+
96
+ def process_color(*color)
97
97
  case(color.size)
98
- when 1
99
- color[0]
98
+ when 1
99
+ color[0]
100
100
  when 4
101
101
  color
102
102
  else
103
- raise ArgumentError, 'wrong number of arguments supplied'
104
- end
105
- end
106
-
107
- def set_fill_color
108
- case @fill_color
109
- when String
110
- r,g,b = hex2rgb(@fill_color)
111
- add_content "%.3f %.3f %.3f rg" %
112
- [r / 255.0, g / 255.0, b / 255.0]
113
- when Array
114
- c,m,y,k = *@fill_color
115
- add_content "%.3f %.3f %.3f %.3f k" %
116
- [c / 100.0, m / 100.0, y / 100.0, k / 100.0]
103
+ raise ArgumentError, 'wrong number of arguments supplied'
117
104
  end
118
105
  end
119
106
 
120
- def set_stroke_color
121
- case @stroke_color
107
+ def color_type(color)
108
+ case color
122
109
  when String
123
- r,g,b = hex2rgb(@stroke_color)
124
- add_content "%.3f %.3f %.3f RG" %
125
- [r / 255.0, g / 255.0, b / 255.0]
110
+ :RGB
126
111
  when Array
127
- c,m,y,k = *@stroke_color
128
- add_content "%.3f %.3f %.3f %.3f K" %
129
- [c / 100.0, m / 100.0, y / 100.0, k / 100.0]
112
+ :CMYK
113
+ end
114
+ end
115
+
116
+ def normalize_color(color)
117
+ case color_type(color)
118
+ when :RGB
119
+ r,g,b = hex2rgb(color)
120
+ [r / 255.0, g / 255.0, b / 255.0]
121
+ when :CMYK
122
+ c,m,y,k = *color
123
+ [c / 100.0, m / 100.0, y / 100.0, k / 100.0]
130
124
  end
131
- end
125
+ end
126
+
127
+ def color_to_s(color)
128
+ normalize_color(color).map { |c| '%.3f' % c }.join(' ')
129
+ end
132
130
 
133
- def update_colors
131
+ def color_space(color)
132
+ case color_type(color)
133
+ when :RGB
134
+ :DeviceRGB
135
+ when :CMYK
136
+ :DeviceCMYK
137
+ end
138
+ end
139
+
140
+ COLOR_SPACES = [:DeviceRGB, :DeviceCMYK, :Pattern]
141
+
142
+ def set_color_space(type, color_space)
143
+ # don't set the same color space again
144
+ return if @color_space[type] == color_space
145
+ @color_space[type] = color_space
146
+
147
+ unless COLOR_SPACES.include?(color_space)
148
+ raise ArgumentError, "unknown color space: '#{color_space}'"
149
+ end
150
+
151
+ operator = case type
152
+ when :fill
153
+ 'cs'
154
+ when :stroke
155
+ 'CS'
156
+ else
157
+ raise ArgumentError, "unknown type '#{type}'"
158
+ end
159
+
160
+ add_content "/#{color_space} #{operator}"
161
+ end
162
+
163
+ def set_color(type, color, options = {})
164
+ operator = case type
165
+ when :fill
166
+ 'scn'
167
+ when :stroke
168
+ 'SCN'
169
+ else
170
+ raise ArgumentError, "unknown type '#{type}'"
171
+ end
172
+
173
+ if options[:pattern]
174
+ set_color_space type, :Pattern
175
+ add_content "/#{color} #{operator}"
176
+ else
177
+ set_color_space type, color_space(color)
178
+ color = color_to_s(color)
179
+ add_content "#{color} #{operator}"
180
+ end
181
+ end
182
+
183
+ def set_fill_color
184
+ set_color :fill, @fill_color
185
+ end
186
+
187
+ def set_stroke_color
188
+ set_color :stroke, @stroke_color
189
+ end
190
+
191
+ def update_colors
192
+ @color_space = {}
134
193
  @fill_color ||= "000000"
135
194
  @stroke_color ||= "000000"
136
195
  set_fill_color
137
196
  set_stroke_color
138
- end
197
+ end
139
198
  end
140
199
  end
141
200
  end
201
+
@@ -40,13 +40,15 @@ module Prawn
40
40
 
41
41
  alias_method :dash=, :dash
42
42
 
43
- # Restores solid stroking
43
+ # Stops dashing, restoring solid stroked lines and curves
44
+ #
44
45
  def undash
45
46
  @dash = undash_hash
46
47
  write_stroke_dash
47
48
  end
48
49
 
49
- # Returns true iff the stroke is dashed
50
+ # Returns when stroke is dashed, false otherwise
51
+ #
50
52
  def dashed?
51
53
  dash != undash_hash
52
54
  end
@@ -9,12 +9,15 @@
9
9
  module Prawn
10
10
  module Graphics
11
11
  module JoinStyle
12
- # Sets the join_style for stroked lines and curves
13
- #
14
-
15
12
  JOIN_STYLES = { :miter => 0, :round => 1, :bevel => 2 }
16
13
 
14
+ # Sets the join style for stroked lines and curves
15
+ #
17
16
  # style is one of :miter, :round, or :bevel
17
+ #
18
+ # NOTE: if this method is never called, :miter will be used for join style
19
+ # throughout the document
20
+ #
18
21
  def join_style(style=nil)
19
22
  return @join_style || :miter if style.nil?
20
23
 
@@ -9,35 +9,61 @@
9
9
 
10
10
  module Prawn
11
11
  module Graphics
12
+
13
+ # The Prawn::Transparency module is used to place transparent
14
+ # content on the page. It has the capacity for separate
15
+ # transparency values for stroked content and all other content.
16
+ #
17
+ # Example:
18
+ # # both the fill and stroke will be at 50% opacity
19
+ # pdf.transparent(0.5) do
20
+ # pdf.text("hello world")
21
+ # pdf.fill_and_stroke_circle_at([x, y], :radius => 25)
22
+ # end
23
+ #
24
+ # # the fill will be at 50% opacity, but the stroke will
25
+ # # be at 75% opacity
26
+ # pdf.transparent(0.5, 0.75) do
27
+ # pdf.text("hello world")
28
+ # pdf.fill_and_stroke_circle_at([x, y], :radius => 25)
29
+ # end
30
+ #
12
31
  module Transparency
13
32
 
33
+ # Sets the <tt>opacity</tt> and <tt>stroke_opacity</tt> for all
34
+ # the content within the <tt>block</tt>
35
+ # If <tt>stroke_opacity</tt> is not provided, then it takes on
36
+ # the same value as <tt>opacity</tt>
37
+ #
38
+ # Valid ranges for both paramters are 0.0 to 1.0
39
+ #
40
+ # Example:
41
+ # # both the fill and stroke will be at 50% opacity
42
+ # pdf.transparent(0.5) do
43
+ # pdf.text("hello world")
44
+ # pdf.fill_and_stroke_circle_at([x, y], :radius => 25)
45
+ # end
46
+ #
47
+ # # the fill will be at 50% opacity, but the stroke will
48
+ # # be at 75% opacity
49
+ # pdf.transparent(0.5, 0.75) do
50
+ # pdf.text("hello world")
51
+ # pdf.fill_and_stroke_circle_at([x, y], :radius => 25)
52
+ # end
53
+ #
14
54
  def transparent(opacity, stroke_opacity=opacity, &block)
15
55
  min_version(1.4)
16
56
 
17
- key = "#{opacity}_#{stroke_opacity}"
18
-
19
- if opacity_dictionary_registry[key]
20
- opacity_dictionary = opacity_dictionary_registry[key][:obj]
21
- opacity_dictionary_name = opacity_dictionary_registry[key][:name]
22
- else
23
- opacity_dictionary = ref!(:Type => :ExtGState,
24
- :CA => stroke_opacity,
25
- :ca => opacity
26
- )
27
-
28
- opacity_dictionary_name = "Tr#{next_opacity_dictionary_id}"
29
- opacity_dictionary_registry[key] = { :name => opacity_dictionary_name,
30
- :obj => opacity_dictionary }
31
- end
32
-
33
- page_ext_gstates.merge!(opacity_dictionary_name => opacity_dictionary)
57
+ opacity = [[opacity, 0.0].max, 1.0].min
58
+ stroke_opacity = [[stroke_opacity, 0.0].max, 1.0].min
34
59
 
35
60
  # push a new graphics context onto the graphics context stack
36
61
  add_content "q"
37
- add_content "/#{opacity_dictionary_name} gs"
62
+ add_content "/#{opacity_dictionary_name(opacity, stroke_opacity)} gs"
38
63
 
39
64
  yield if block_given?
40
65
 
66
+ # restore the previous graphics context
41
67
  add_content "Q"
42
68
  end
43
69
 
@@ -51,6 +77,27 @@ module Prawn
51
77
  opacity_dictionary_registry.length + 1
52
78
  end
53
79
 
80
+ def opacity_dictionary_name(opacity, stroke_opacity)
81
+ key = "#{opacity}_#{stroke_opacity}"
82
+
83
+ if opacity_dictionary_registry[key]
84
+ dictionary = opacity_dictionary_registry[key][:obj]
85
+ dictionary_name = opacity_dictionary_registry[key][:name]
86
+ else
87
+ dictionary = ref!(:Type => :ExtGState,
88
+ :CA => stroke_opacity,
89
+ :ca => opacity
90
+ )
91
+
92
+ dictionary_name = "Tr#{next_opacity_dictionary_id}"
93
+ opacity_dictionary_registry[key] = { :name => dictionary_name,
94
+ :obj => dictionary }
95
+ end
96
+
97
+ page_ext_gstates.merge!(dictionary_name => dictionary)
98
+ dictionary_name
99
+ end
100
+
54
101
  end
55
102
  end
56
103
  end
@@ -22,7 +22,7 @@ module Prawn
22
22
 
23
23
  # Process a new JPG image
24
24
  #
25
- # <tt>:data</tt>:: A string containing a full PNG file
25
+ # <tt>:data</tt>:: A binary string of JPEG data
26
26
  #
27
27
  def initialize(data)
28
28
  data = StringIO.new(data.dup)
@@ -25,7 +25,7 @@ module Prawn
25
25
 
26
26
  # Process a new PNG image
27
27
  #
28
- # <tt>data</tt>:: A string containing a full PNG file
28
+ # <tt>data</tt>:: A binary string of PNG data
29
29
  #
30
30
  def initialize(data)
31
31
  data = StringIO.new(data.dup)
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
  module Prawn
9
- class ObjectStore
9
+ class ObjectStore #:nodoc:
10
10
  include Enumerable
11
11
 
12
12
  def initialize(info={})
@@ -59,5 +59,34 @@ module Prawn
59
59
  end
60
60
  alias_method :length, :size
61
61
 
62
+ def compact
63
+ # Clear live markers
64
+ each { |o| o.live = false }
65
+
66
+ # Recursively mark reachable objects live, starting from the roots
67
+ # (the only objects referenced in the trailer)
68
+ root.mark_live
69
+ info.mark_live
70
+
71
+ # Renumber live objects to eliminate gaps (shrink the xref table)
72
+ if @objects.any?{ |_, o| !o.live }
73
+ new_id = 1
74
+ new_objects = {}
75
+ new_identifiers = []
76
+
77
+ each do |obj|
78
+ if obj.live
79
+ obj.identifier = new_id
80
+ new_objects[new_id] = obj
81
+ new_identifiers << new_id
82
+ new_id += 1
83
+ end
84
+ end
85
+
86
+ @objects = new_objects
87
+ @identifiers = new_identifiers
88
+ end
89
+ end
90
+
62
91
  end
63
92
  end