prawn 0.13.0 → 0.14.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +9 -0
  3. data/Gemfile +7 -14
  4. data/Rakefile +5 -10
  5. data/data/images/16bit.alpha +0 -0
  6. data/data/images/16bit.color +0 -0
  7. data/data/images/dice.alpha +0 -0
  8. data/data/images/dice.color +0 -0
  9. data/data/images/page_white_text.alpha +0 -0
  10. data/data/images/page_white_text.color +0 -0
  11. data/lib/pdf/core/document_state.rb +3 -2
  12. data/lib/pdf/core/graphics_state.rb +29 -8
  13. data/lib/pdf/core/object_store.rb +6 -15
  14. data/lib/pdf/core/pdf_object.rb +8 -33
  15. data/lib/prawn/document/bounding_box.rb +11 -0
  16. data/lib/prawn/document/column_box.rb +12 -4
  17. data/lib/prawn/document.rb +30 -42
  18. data/lib/prawn/encoding.rb +1 -2
  19. data/lib/prawn/font/afm.rb +71 -30
  20. data/lib/prawn/font/dfont.rb +1 -1
  21. data/lib/prawn/font/ttf.rb +10 -2
  22. data/lib/prawn/font.rb +7 -8
  23. data/lib/prawn/graphics.rb +8 -7
  24. data/lib/prawn/image_handler.rb +4 -0
  25. data/lib/prawn/images/jpg.rb +9 -10
  26. data/lib/prawn/images/png.rb +46 -118
  27. data/lib/prawn/images.rb +2 -7
  28. data/lib/prawn/layout.rb +2 -2
  29. data/lib/prawn/measurement_extensions.rb +1 -1
  30. data/lib/prawn/table/cells.rb +6 -2
  31. data/lib/prawn/table/column_width_calculator.rb +55 -0
  32. data/lib/prawn/table.rb +9 -22
  33. data/lib/prawn/templates.rb +75 -0
  34. data/lib/prawn/text/formatted/arranger.rb +1 -5
  35. data/lib/prawn/text/formatted/box.rb +1 -1
  36. data/lib/prawn/text/formatted/fragment.rb +5 -11
  37. data/lib/prawn/text/formatted/line_wrap.rb +4 -25
  38. data/lib/prawn/text/formatted/wrap.rb +1 -4
  39. data/lib/prawn.rb +1 -2
  40. data/manual/document_and_page_options/document_and_page_options.rb +2 -1
  41. data/manual/document_and_page_options/metadata.rb +3 -3
  42. data/manual/document_and_page_options/print_scaling.rb +20 -0
  43. data/manual/example_file.rb +2 -7
  44. data/manual/manual/cover.rb +3 -2
  45. data/manual/manual/manual.rb +1 -2
  46. data/prawn.gemspec +10 -6
  47. data/spec/bounding_box_spec.rb +12 -0
  48. data/spec/column_box_spec.rb +32 -0
  49. data/spec/document_spec.rb +5 -7
  50. data/spec/extensions/encoding_helpers.rb +2 -3
  51. data/spec/filters_spec.rb +1 -1
  52. data/spec/font_spec.rb +3 -2
  53. data/spec/formatted_text_box_spec.rb +14 -25
  54. data/spec/graphics_spec.rb +18 -0
  55. data/spec/image_handler_spec.rb +12 -0
  56. data/spec/images_spec.rb +2 -6
  57. data/spec/line_wrap_spec.rb +2 -2
  58. data/spec/object_store_spec.rb +6 -0
  59. data/spec/outline_spec.rb +10 -10
  60. data/spec/png_spec.rb +9 -12
  61. data/spec/table_spec.rb +55 -2
  62. data/spec/{template_spec.rb → template_spec_obsolete.rb} +2 -1
  63. data/spec/text_at_spec.rb +11 -26
  64. data/spec/text_box_spec.rb +6 -2
  65. data/spec/text_spec.rb +39 -27
  66. metadata +58 -38
  67. data/README.md +0 -109
  68. data/data/images/16bit.dat +0 -0
  69. data/data/images/dice.dat +0 -0
  70. data/data/images/page_white_text.dat +0 -0
  71. data/lib/prawn/compatibility.rb +0 -91
  72. data/manual/templates/full_template.rb +0 -25
  73. data/manual/templates/page_template.rb +0 -48
  74. data/manual/templates/templates.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad2fdbe5f0c7d9075d0a6467217a996304aa4983
4
- data.tar.gz: 6db5219bdb341f2c8d36c3cc0aca48720adbdccc
3
+ metadata.gz: d83c112e79d7d99ade30524585cf7d7cb7d4de52
4
+ data.tar.gz: a0143b1b1ebf8db3776daafe04dbceeedb62db6b
5
5
  SHA512:
6
- metadata.gz: c2795eaa023f044fdf99b91f79a4e56f181c8dcb13fd73112dcf181156234230117e17221521634c728e60c92a7f05a607f16cec47cd72eff9a77995e91f750f
7
- data.tar.gz: cdf2c886e7bea9946289a11fa1f33c4574884ca65d2eebbaec6967016bc31aee008a9048fccecb73756a4950a6b4e686c838ddbf5e3c3c258bd1dd2ed6fea379
6
+ metadata.gz: 6f9ac40edd92c31df1f2a63e73684f983069edcb8f1dd15ffb26f9bcc85256ac4adc0f476aa6ef5b4fa2248318515c56a39dc68e5f2352711e30dd4863aa4989
7
+ data.tar.gz: 7ecbae3e281cae29a8a7234fc32c0148eaae4d27dabc35cecba01073d28319c5480c50dcc1cce2f1bdb5b1c99b4a11e80168d62b8c7c94faa8a1a1f7995f0043
data/.yardopts ADDED
@@ -0,0 +1,9 @@
1
+ --charset UTF-8
2
+ --main README.md
3
+ --title 'Prawn Documentation'
4
+ lib/**/*.rb
5
+ -
6
+ CONTRIBUTING.md
7
+ COPYING
8
+ LICENSE
9
+ README.md
data/Gemfile CHANGED
@@ -1,18 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "ttfunk", "~>1.0.3"
4
- gem "pdf-reader", "~> 1.2"
5
- gem "ruby-rc4"
6
- gem "afm"
3
+ gemspec
7
4
 
8
- group :development do
9
- gem "coderay", "~> 1.0.7"
10
- gem "rdoc"
11
- end
12
-
13
- group :test do
14
- gem "pdf-inspector", "~> 1.1.0", :require => "pdf/inspector"
15
- gem "rspec"
16
- gem "mocha", :require => false
17
- gem "rake"
5
+ if ENV["CI"]
6
+ platforms :rbx do
7
+ gem "rubysl-singleton", "~> 2.0"
8
+ gem "rubysl-digest", "~> 2.0"
9
+ gem "rubysl-enumerator", "~> 2.0"
10
+ end
18
11
  end
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ Bundler.setup
3
3
 
4
4
  require 'rake'
5
5
  require 'rspec/core/rake_task'
6
- require 'rdoc/task'
6
+ require 'yard'
7
7
  require 'rubygems/package_task'
8
8
 
9
9
  task :default => [:spec]
@@ -21,16 +21,11 @@ task :stats do
21
21
  ["Specs", "spec"] ).to_s
22
22
  end
23
23
 
24
- desc "genrates documentation"
25
- RDoc::Task.new do |rdoc|
26
- rdoc.rdoc_files.include( "README.md",
27
- "COPYING",
28
- "LICENSE",
29
- "lib/" )
30
- rdoc.main = "README.md"
31
- rdoc.rdoc_dir = "doc/html"
32
- rdoc.title = "Prawn Documentation"
24
+ YARD::Rake::YardocTask.new do |t|
25
+ t.options = ['--output-dir', 'doc/html']
33
26
  end
27
+ task :docs => :yard
28
+
34
29
 
35
30
  desc "Generate the 'Prawn by Example' manual"
36
31
  task :manual do
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -5,10 +5,11 @@ module PDF
5
5
  normalize_metadata(options)
6
6
 
7
7
  if options[:template]
8
- @store = PDF::Core::ObjectStore.new(:template => options[:template])
8
+ @store = PDF::Core::ObjectStore.new(:template => options[:template],
9
+ :print_scaling => options[:print_scaling])
9
10
  @store.info.data.merge!(options[:info]) if options[:info]
10
11
  else
11
- @store = PDF::Core::ObjectStore.new(:info => options[:info])
12
+ @store = PDF::Core::ObjectStore.new(:info => options[:info], :print_scaling => options[:print_scaling])
12
13
  end
13
14
 
14
15
  @version = 1.3
@@ -43,17 +43,23 @@ module PDF
43
43
 
44
44
  end
45
45
 
46
+ # NOTE: This class may be a good candidate for a copy-on-write hash.
46
47
  class GraphicState
47
- attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width, :fill_color, :stroke_color
48
+ attr_accessor :color_space, :dash, :cap_style, :join_style, :line_width,
49
+ :fill_color, :stroke_color
48
50
 
49
51
  def initialize(previous_state = nil)
50
- @color_space = previous_state ? previous_state.color_space.dup : {}
51
- @fill_color = previous_state ? previous_state.fill_color : "000000"
52
- @stroke_color = previous_state ? previous_state.stroke_color : "000000"
53
- @dash = previous_state ? previous_state.dash : { :dash => nil, :space => nil, :phase => 0 }
54
- @cap_style = previous_state ? previous_state.cap_style : :butt
55
- @join_style = previous_state ? previous_state.join_style : :miter
56
- @line_width = previous_state ? previous_state.line_width : 1
52
+ if previous_state
53
+ initialize_copy(previous_state)
54
+ else
55
+ @color_space = {}
56
+ @fill_color = "000000"
57
+ @stroke_color = "000000"
58
+ @dash = { :dash => nil, :space => nil, :phase => 0 }
59
+ @cap_style = :butt
60
+ @join_style = :miter
61
+ @line_width = 1
62
+ end
57
63
  end
58
64
 
59
65
  def dash_setting
@@ -63,6 +69,21 @@ module PDF
63
69
  "[#{@dash[:dash]} #{@dash[:space]}] #{@dash[:phase]} d"
64
70
  end
65
71
  end
72
+
73
+ private
74
+
75
+ def initialize_copy(other)
76
+ # mutable state
77
+ @color_space = other.color_space.dup
78
+ @fill_color = other.fill_color.dup
79
+ @stroke_color = other.stroke_color.dup
80
+ @dash = other.dash.dup
81
+
82
+ # immutable state that doesn't need to be duped
83
+ @cap_style = other.cap_style
84
+ @join_style = other.join_style
85
+ @line_width = other.line_width
86
+ end
66
87
  end
67
88
  end
68
89
  end
@@ -26,6 +26,9 @@ module PDF
26
26
 
27
27
  @info ||= ref(opts[:info] || {}).identifier
28
28
  @root ||= ref(:Type => :Catalog).identifier
29
+ if opts[:print_scaling] == :none
30
+ root.data[:ViewerPreferences] = {:PrintScaling => :None}
31
+ end
29
32
  if pages.nil?
30
33
  root.data[:Pages] = ref(:Type => :Pages, :Count => 0, :Kids => [])
31
34
  end
@@ -299,21 +302,9 @@ module PDF
299
302
  end
300
303
  end
301
304
 
302
- ruby_18 do
303
- def is_utf8?(str)
304
- begin
305
- str.unpack("U*")
306
- true
307
- rescue
308
- false
309
- end
310
- end
311
- end
312
- ruby_19 do
313
- def is_utf8?(str)
314
- str.force_encoding("utf-8")
315
- str.valid_encoding?
316
- end
305
+ def is_utf8?(str)
306
+ str.force_encoding(::Encoding::UTF_8)
307
+ str.valid_encoding?
317
308
  end
318
309
  end
319
310
  end
@@ -12,40 +12,15 @@ module PDF
12
12
  module Core
13
13
  module_function
14
14
 
15
- if "".respond_to?(:encode)
16
- # Ruby 1.9+
17
- def utf8_to_utf16(str)
18
- "\xFE\xFF".force_encoding("UTF-16BE") + str.encode("UTF-16BE")
19
- end
20
-
21
- # encodes any string into a hex representation. The result is a string
22
- # with only 0-9 and a-f characters. That result is valid ASCII so tag
23
- # it as such to account for behaviour of different ruby VMs
24
- def string_to_hex(str)
25
- str.unpack("H*").first.force_encoding("ascii")
26
- end
27
- else
28
- # Ruby 1.8
29
- def utf8_to_utf16(str)
30
- utf16 = "\xFE\xFF"
31
-
32
- str.codepoints do |cp|
33
- if cp < 0x10000 # Basic Multilingual Plane
34
- utf16 << [cp].pack("n")
35
- else
36
- # pull out high/low 10 bits
37
- hi, lo = (cp - 0x10000).divmod(2**10)
38
- # encode a surrogate pair
39
- utf16 << [0xD800 + hi, 0xDC00 + lo].pack("n*")
40
- end
41
- end
42
-
43
- utf16
44
- end
15
+ def utf8_to_utf16(str)
16
+ "\xFE\xFF".force_encoding(::Encoding::UTF_16BE) + str.encode(::Encoding::UTF_16BE)
17
+ end
45
18
 
46
- def string_to_hex(str)
47
- str.unpack("H*").first
48
- end
19
+ # encodes any string into a hex representation. The result is a string
20
+ # with only 0-9 and a-f characters. That result is valid ASCII so tag
21
+ # it as such to account for behaviour of different ruby VMs
22
+ def string_to_hex(str)
23
+ str.unpack("H*").first.force_encoding(::Encoding::US_ASCII)
49
24
  end
50
25
 
51
26
  # Serializes Ruby objects to their PDF equivalents. Most primitive objects
@@ -188,10 +188,21 @@ module Prawn
188
188
 
189
189
  parent_box = @bounding_box
190
190
 
191
+ original_ypos = y
192
+
191
193
  init_block.call(parent_box)
192
194
 
193
195
  self.y = @bounding_box.absolute_top
194
196
  user_block.call
197
+
198
+ # If the user actions did not modify the y position
199
+ # restore the original y position before the bounding
200
+ # box was created.
201
+
202
+ if y == @bounding_box.absolute_top
203
+ self.y = original_ypos
204
+ end
205
+
195
206
  unless options[:hold_position] || @bounding_box.stretchy?
196
207
  self.y = @bounding_box.absolute_bottom
197
208
  end
@@ -4,7 +4,8 @@
4
4
  #
5
5
  # Author Paul Ostazeski.
6
6
 
7
- require "prawn/document/bounding_box"
7
+ require_relative "bounding_box"
8
+
8
9
  module Prawn
9
10
  class Document
10
11
 
@@ -14,10 +15,13 @@ module Prawn
14
15
  # filled.
15
16
  #
16
17
  # column_box accepts the same parameters as bounding_box, as well as the
17
- # number of :columns and a :spacer (in points) between columns.
18
- #
19
- # Defaults are :columns = 3 and :spacer = font_size
18
+ # number of :columns and a :spacer (in points) between columns. If resetting
19
+ # the top margin is desired on a new page (e.g. to allow for initial page
20
+ # wide column titles) the option :reflow_margins => true can be set.
20
21
  #
22
+ # Defaults are :columns = 3, :spacer = font_size, and
23
+ # :reflow_margins => false
24
+ #
21
25
  # Under PDF::Writer, "spacer" was known as "gutter"
22
26
  #
23
27
  def column_box(*args, &block)
@@ -51,6 +55,7 @@ module Prawn
51
55
  @columns = options[:columns] || 3
52
56
  @spacer = options[:spacer] || @document.font_size
53
57
  @current_column = 0
58
+ @reflow_margins = options[:reflow_margins]
54
59
  end
55
60
 
56
61
  # The column width, not the width of the whole box,
@@ -103,6 +108,9 @@ module Prawn
103
108
  @current_column = (@current_column + 1) % @columns
104
109
  @document.y = @y
105
110
  if 0 == @current_column
111
+ if @reflow_margins
112
+ @y = @parent.absolute_top
113
+ end
106
114
  @document.start_new_page
107
115
  end
108
116
  end
@@ -7,12 +7,12 @@
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
9
  require "stringio"
10
- require "prawn/document/bounding_box"
11
- require "prawn/document/column_box"
12
- require "prawn/document/internals"
13
- require "prawn/document/span"
14
- require "prawn/document/snapshot"
15
- require "prawn/document/graphics_state"
10
+ require_relative "document/bounding_box"
11
+ require_relative "document/column_box"
12
+ require_relative "document/internals"
13
+ require_relative "document/span"
14
+ require_relative "document/snapshot"
15
+ require_relative "document/graphics_state"
16
16
 
17
17
  module Prawn
18
18
 
@@ -62,6 +62,14 @@ module Prawn
62
62
  include Prawn::Stamp
63
63
  include Prawn::SoftMask
64
64
 
65
+ # NOTE: We probably need to rethink the options validation system, but this
66
+ # constant temporarily allows for extensions to modify the list.
67
+
68
+ VALID_OPTIONS = [:page_size, :page_layout, :margin, :left_margin,
69
+ :right_margin, :top_margin, :bottom_margin, :skip_page_creation,
70
+ :compress, :skip_encoding, :background, :info,
71
+ :optimize_objects, :text_formatter, :print_scaling]
72
+
65
73
  # Any module added to this array will be included into instances of
66
74
  # Prawn::Document at the per-object level. These will also be inherited by
67
75
  # any subclasses.
@@ -138,7 +146,6 @@ module Prawn
138
146
  # <tt>:background</tt>:: An image path to be used as background on all pages [nil]
139
147
  # <tt>:background_scale</tt>:: Backgound image scale [1] [nil]
140
148
  # <tt>:info</tt>:: Generic hash allowing for custom metadata properties [nil]
141
- # <tt>:template</tt>:: The path to an existing PDF file to use as a template [nil]
142
149
  # <tt>:text_formatter</tt>: The text formatter to use for <tt>:inline_format</tt>ted text [Prawn::Text::Formatted::Parser]
143
150
  #
144
151
  # Setting e.g. the :margin to 100 points and the :left_margin to 50 will result in margins
@@ -173,10 +180,7 @@ module Prawn
173
180
  def initialize(options={},&block)
174
181
  options = options.dup
175
182
 
176
- Prawn.verify_options [:page_size, :page_layout, :margin, :left_margin,
177
- :right_margin, :top_margin, :bottom_margin, :skip_page_creation,
178
- :compress, :skip_encoding, :background, :info,
179
- :optimize_objects, :template, :text_formatter], options
183
+ Prawn.verify_options VALID_OPTIONS, options
180
184
 
181
185
  # need to fix, as the refactoring breaks this
182
186
  # raise NotImplementedError if options[:skip_page_creation]
@@ -186,6 +190,8 @@ module Prawn
186
190
  @internal_state.populate_pages_from_store(self)
187
191
  min_version(state.store.min_version) if state.store.min_version
188
192
 
193
+ min_version(1.6) if options[:print_scaling] == :none
194
+
189
195
  @background = options[:background]
190
196
  @background_scale = options[:background_scale] || 1
191
197
  @font_size = 12
@@ -200,16 +206,7 @@ module Prawn
200
206
  options[:size] = options.delete(:page_size)
201
207
  options[:layout] = options.delete(:page_layout)
202
208
 
203
- if options[:template]
204
- fresh_content_streams(options)
205
- go_to_page(1)
206
- else
207
- if options[:skip_page_creation] || options[:template]
208
- start_new_page(options.merge(:orphan => true))
209
- else
210
- start_new_page(options)
211
- end
212
- end
209
+ initialize_first_page(options)
213
210
 
214
211
  @bounding_box = @margin_box
215
212
 
@@ -232,6 +229,14 @@ module Prawn
232
229
  state.page
233
230
  end
234
231
 
232
+ def initialize_first_page(options)
233
+ if options[:skip_page_creation]
234
+ start_new_page(options.merge(:orphan => true))
235
+ else
236
+ start_new_page(options)
237
+ end
238
+ end
239
+
235
240
  # Creates and advances to a new page in the document.
236
241
  #
237
242
  # Page size, margins, and layout can also be set when generating a
@@ -242,14 +247,6 @@ module Prawn
242
247
  # pdf.start_new_page(:left_margin => 50, :right_margin => 50)
243
248
  # pdf.start_new_page(:margin => 100)
244
249
  #
245
- # A template for a page can be specified by pointing to the path of and existing pdf.
246
- # One can also specify which page of the template which defaults otherwise to 1.
247
- #
248
- # pdf.start_new_page(:template => multipage_template.pdf, :template_page => 2)
249
- #
250
- # Note: templates get indexed by either the object_id of the filename or stream
251
- # entered so that if you reuse the same template multiple times be sure to use the
252
- # same instance for more efficient use of resources and smaller rendered pdfs.
253
250
  def start_new_page(options = {})
254
251
  if last_page = state.page
255
252
  last_page_size = last_page.size
@@ -266,7 +263,6 @@ module Prawn
266
263
  new_graphic_state.color_space = {} if new_graphic_state
267
264
  page_options.merge!(:graphic_state => new_graphic_state)
268
265
  end
269
- merge_template_options(page_options, options) if options[:template]
270
266
 
271
267
  state.page = PDF::Core::Page.new(self, page_options)
272
268
 
@@ -279,9 +275,7 @@ module Prawn
279
275
  @bounding_box = @margin_box
280
276
  end
281
277
 
282
- state.page.new_content_stream if options[:template]
283
- use_graphic_settings(options[:template])
284
- forget_text_rendering_mode! if options[:template]
278
+ use_graphic_settings
285
279
 
286
280
  unless options[:orphan]
287
281
  state.insert_page(state.page, @page_number)
@@ -370,7 +364,7 @@ module Prawn
370
364
  render_trailer(output)
371
365
  if output.instance_of?(StringIO)
372
366
  str = output.string
373
- str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
367
+ str.force_encoding(::Encoding::ASCII_8BIT)
374
368
  return str
375
369
  else
376
370
  return nil
@@ -382,8 +376,7 @@ module Prawn
382
376
  # pdf.render_file "foo.pdf"
383
377
  #
384
378
  def render_file(filename)
385
- Kernel.const_defined?("Encoding") ? mode = "wb:ASCII-8BIT" : mode = "wb"
386
- File.open(filename,mode) { |f| render(f) }
379
+ File.open(filename, "wb") { |f| render(f) }
387
380
  end
388
381
 
389
382
  # The bounds method returns the current bounding box you are currently in,
@@ -652,13 +645,8 @@ module Prawn
652
645
 
653
646
  private
654
647
 
655
- def merge_template_options(page_options, options)
656
- object_id = state.store.import_page(options[:template], options[:template_page] || 1)
657
- page_options.merge!(:object_id => object_id, :page_template => true)
658
- end
659
-
660
648
  # setting override_settings to true ensures that a new graphic state does not end up using
661
- # previous settings especially from imported template streams
649
+ # previous settings.
662
650
  def use_graphic_settings(override_settings = false)
663
651
  set_fill_color if current_fill_color != "000000" || override_settings
664
652
  set_stroke_color if current_stroke_color != "000000" || override_settings
@@ -108,8 +108,7 @@ module Prawn
108
108
  private
109
109
 
110
110
  def load_mapping
111
- RUBY_VERSION >= "1.9" ? mode = "r:BINARY" : mode = "r"
112
- File.open(@mapping_file, mode) do |f|
111
+ File.open(@mapping_file, "r:BINARY") do |f|
113
112
  f.each do |l|
114
113
  _, single_byte, unicode = *l.match(/([0-9A-Za-z]+);([0-9A-F]{4})/)
115
114
  self.class.mapping["0x#{unicode}".hex] = "0x#{single_byte}".hex if single_byte
@@ -6,8 +6,7 @@
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
- require 'prawn/encoding'
10
- require 'afm'
9
+ require_relative '../../prawn/encoding'
11
10
 
12
11
  module Prawn
13
12
  class Font
@@ -49,21 +48,23 @@ module Prawn
49
48
  file_name << ".afm" unless file_name =~ /\.afm$/
50
49
  file_name = file_name[0] == ?/ ? file_name : find_font(file_name)
51
50
 
52
- font_data = @@font_data[file_name] ||= ::AFM::Font.new(file_name)
53
- @glyph_table = build_glyph_table(font_data)
54
- @kern_pairs = font_data.kern_pairs
55
- @kern_pair_table = build_kern_pair_table(@kern_pairs)
56
- @attributes = font_data.metadata
51
+ font_data = @@font_data[file_name] ||= parse_afm(file_name)
52
+ @glyph_widths = font_data[:glyph_widths]
53
+ @glyph_table = font_data[:glyph_table]
54
+ @bounding_boxes = font_data[:bounding_boxes]
55
+ @kern_pairs = font_data[:kern_pairs]
56
+ @kern_pair_table = font_data[:kern_pair_table]
57
+ @attributes = font_data[:attributes]
57
58
 
58
- @ascender = @attributes["Ascender"].to_i
59
- @descender = @attributes["Descender"].to_i
59
+ @ascender = @attributes["ascender"].to_i
60
+ @descender = @attributes["descender"].to_i
60
61
  @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
61
62
  end
62
63
 
63
64
  # The font bbox, as an array of integers
64
65
  #
65
66
  def bbox
66
- @bbox ||= @attributes['FontBBox'].split(/\s+/).map { |e| Integer(e) }
67
+ @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
67
68
  end
68
69
 
69
70
  # NOTE: String *must* be encoded as WinAnsi
@@ -141,7 +142,7 @@ module Prawn
141
142
  end
142
143
 
143
144
  def symbolic?
144
- attributes["CharacterSet"] == "Special"
145
+ attributes["characterset"] == "Special"
145
146
  end
146
147
 
147
148
  def find_font(file)
@@ -152,6 +153,61 @@ module Prawn
152
153
  self.class.metrics_path.join("\n")
153
154
  end
154
155
 
156
+ def parse_afm(file_name)
157
+ data = {:glyph_widths => {}, :bounding_boxes => {}, :kern_pairs => {}, :attributes => {}}
158
+ section = []
159
+
160
+ File.foreach(file_name) do |line|
161
+ case line
162
+ when /^Start(\w+)/
163
+ section.push $1
164
+ next
165
+ when /^End(\w+)/
166
+ section.pop
167
+ next
168
+ end
169
+
170
+ case section
171
+ when ["FontMetrics", "CharMetrics"]
172
+ next unless line =~ /^CH?\s/
173
+
174
+ name = line[/\bN\s+(\.?\w+)\s*;/, 1]
175
+ data[:glyph_widths][name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
176
+ data[:bounding_boxes][name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
177
+ when ["FontMetrics", "KernData", "KernPairs"]
178
+ next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
179
+ data[:kern_pairs][[$1, $2]] = $3.to_i
180
+ when ["FontMetrics", "KernData", "TrackKern"],
181
+ ["FontMetrics", "Composites"]
182
+ next
183
+ else
184
+ parse_generic_afm_attribute(line, data)
185
+ end
186
+ end
187
+
188
+ # process data parsed from AFM file to build tables which
189
+ # will be used when measuring and kerning text
190
+ data[:glyph_table] = (0..255).map do |i|
191
+ data[:glyph_widths][Encoding::WinAnsi::CHARACTERS[i]].to_i
192
+ end
193
+
194
+ character_hash = Hash[Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a)]
195
+ data[:kern_pair_table] = data[:kern_pairs].inject({}) do |h,p|
196
+ h[p[0].map { |n| character_hash[n] }] = p[1]
197
+ h
198
+ end
199
+
200
+ data.each_value { |hash| hash.freeze }
201
+ data.freeze
202
+ end
203
+
204
+ def parse_generic_afm_attribute(line, hash)
205
+ line =~ /(^\w+)\s+(.*)/
206
+ key, value = $1.to_s.downcase, $2
207
+
208
+ hash[:attributes][key] = hash[:attributes][key] ? Array(hash[:attributes][key]) << value : value
209
+ end
210
+
155
211
  # converts a string into an array with spacing offsets
156
212
  # bewteen characters that need to be kerned
157
213
  #
@@ -172,27 +228,12 @@ module Prawn
172
228
 
173
229
  kerned.map { |e|
174
230
  e = (Array === e ? e.pack("C*") : e)
175
- e.respond_to?(:force_encoding) ? e.force_encoding("Windows-1252") : e
231
+ e.respond_to?(:force_encoding) ? e.force_encoding(::Encoding::Windows_1252) : e
176
232
  }
177
233
  end
178
-
179
- def build_kern_pair_table(kern_pairs)
180
- character_hash = Hash[Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a)]
181
- kern_pairs.inject({}) do |h,p|
182
- h[
183
- [character_hash[p[0]], character_hash[p[1]]]
184
- ] = p[2]
185
- h
186
- end
187
- end
188
-
189
- def build_glyph_table(font_data)
190
- (0..255).map do |char|
191
- metrics = font_data.metrics_for(char)
192
- metrics ? metrics[:wx] : 0
193
- end
194
- end
195
-
234
+
235
+ private
236
+
196
237
  def unscaled_width_of(string)
197
238
  string.bytes.inject(0) do |s,r|
198
239
  s + @glyph_table[r]
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
  #
9
- require 'prawn/font/ttf'
9
+ require_relative 'ttf'
10
10
 
11
11
  module Prawn
12
12
  class Font
@@ -160,7 +160,15 @@ module Prawn
160
160
  end
161
161
 
162
162
  def normalize_encoding(text)
163
- text.normalize_to_utf8
163
+ begin
164
+ text.encode(::Encoding::UTF_8)
165
+ rescue => e
166
+ puts e
167
+ raise Prawn::Errors::IncompatibleStringEncoding, "Encoding " +
168
+ "#{text.encoding} can not be transparently converted to UTF-8. " +
169
+ "Please ensure the encoding of the string you are attempting " +
170
+ "to use is set correctly"
171
+ end
164
172
  end
165
173
 
166
174
  def glyph_present?(char)
@@ -171,7 +179,7 @@ module Prawn
171
179
  # Returns the number of characters in +str+ (a UTF-8-encoded string).
172
180
  #
173
181
  def character_count(str)
174
- str.unicode_length
182
+ str.length
175
183
  end
176
184
 
177
185
  private