prawn 1.3.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/Gemfile +1 -1
  4. data/Rakefile +2 -2
  5. data/data/images/pal_bk.png +0 -0
  6. data/lib/prawn.rb +0 -1
  7. data/lib/prawn/document.rb +2 -2
  8. data/lib/prawn/document/internals.rb +2 -1
  9. data/lib/prawn/encoding.rb +0 -33
  10. data/lib/prawn/font/afm.rb +20 -11
  11. data/lib/prawn/font/ttf.rb +4 -0
  12. data/lib/prawn/font_metric_cache.rb +1 -2
  13. data/lib/prawn/graphics.rb +11 -8
  14. data/lib/prawn/graphics/dash.rb +5 -0
  15. data/lib/prawn/images.rb +2 -3
  16. data/lib/prawn/images/png.rb +3 -12
  17. data/lib/prawn/text.rb +15 -7
  18. data/lib/prawn/text/box.rb +1 -9
  19. data/lib/prawn/text/formatted/box.rb +9 -11
  20. data/lib/prawn/text/formatted/line_wrap.rb +5 -0
  21. data/lib/prawn/version.rb +1 -1
  22. data/manual/basic_concepts/measurement.rb +1 -1
  23. data/manual/bounding_box/bounds.rb +1 -1
  24. data/manual/cover.rb +1 -4
  25. data/manual/example_helper.rb +1 -0
  26. data/manual/how_to_read_this_manual.rb +2 -2
  27. data/manual/repeatable_content/alternate_page_numbering.rb +32 -0
  28. data/manual/repeatable_content/repeatable_content.rb +1 -0
  29. data/manual/security/security.rb +1 -1
  30. data/manual/text/paragraph_indentation.rb +8 -0
  31. data/manual/text/positioned_text.rb +1 -1
  32. data/manual/text/right_to_left_text.rb +4 -0
  33. data/manual/text/text.rb +1 -1
  34. data/manual/text/text_box_overflow.rb +1 -1
  35. data/manual/text/utf8.rb +4 -4
  36. data/manual/text/win_ansi_charset.rb +2 -1
  37. data/prawn.gemspec +4 -19
  38. data/spec/document_spec.rb +24 -1
  39. data/spec/font_spec.rb +25 -0
  40. data/spec/formatted_text_box_spec.rb +77 -33
  41. data/spec/graphics_spec.rb +26 -12
  42. data/spec/line_wrap_spec.rb +26 -10
  43. data/spec/png_spec.rb +9 -1
  44. data/spec/soft_mask_spec.rb +1 -1
  45. data/spec/spec_helper.rb +2 -2
  46. data/spec/text_at_spec.rb +29 -1
  47. data/spec/text_box_spec.rb +2 -21
  48. data/spec/text_spacing_spec.rb +2 -2
  49. data/spec/text_spec.rb +102 -1
  50. metadata +12 -24
  51. data/data/encodings/win_ansi.txt +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 287dd21a281ca025939e562e2d9d0dae14373d21
4
- data.tar.gz: 0c08643d45840aad66a59661f7593bc3abb89302
3
+ metadata.gz: b75f193e883ce03850e0850a8085380b11418d4f
4
+ data.tar.gz: 7ebda9fc8a346132f6cb3a56c5b55bd1894af9c8
5
5
  SHA512:
6
- metadata.gz: 013f40d6fa76d1cf34ed85578e8ce5700c3ba47bbf6b373b9856ae2a34fe42421d83da002604f1acb5bdfd7fdcf0acc85a1f58d9397c89d46254c2e01da5764a
7
- data.tar.gz: b478644390db0dcd837063a79c126cf0d8d70a53746755dae3797bda11553b8520dd33d3ca983301a7627a76b74d824337d3497abba5966683eedd81efb5a2d5
6
+ metadata.gz: 9ad5b0b7ec71ec616f1bd77ac06b8a74188b1a1bddd8849255235e31be7bfd447720a1b48da2fef71a727771d6820c028a32f1f1b3b9f58c4e8ac58ff4772e52
7
+ data.tar.gz: f0a2d18ef07e0103c55274004b0f11c2f2029af8280ba77f5eb07bdbee2627aa053d6b5bf20a6958e79c86b780299b4da485be4b795571813823b7dc99d0b900
data/.yardopts CHANGED
@@ -7,3 +7,4 @@ CONTRIBUTING.md
7
7
  COPYING
8
8
  LICENSE
9
9
  README.md
10
+ CHANGELOG.md
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- if ENV["CI"]
5
+ if ENV["CI"]
6
6
  platforms :rbx do
7
7
  gem "rubysl-singleton", "~> 2.0"
8
8
  gem "rubysl-digest", "~> 2.0"
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ require 'yard'
7
7
  require 'rubygems/package_task'
8
8
  require 'rubocop/rake_task'
9
9
 
10
- task :default => [:rubocop, :spec]
10
+ task :default => [:spec, :rubocop]
11
11
 
12
12
  desc "Run all rspec files"
13
13
  RSpec::Core::RakeTask.new("spec") do |c|
@@ -52,4 +52,4 @@ task :console do
52
52
  IRB.start
53
53
  end
54
54
 
55
- Rubocop::RakeTask.new
55
+ RuboCop::RakeTask.new
Binary file
@@ -78,7 +78,6 @@ require_relative "prawn/soft_mask"
78
78
  require_relative "prawn/security"
79
79
  require_relative "prawn/document"
80
80
  require_relative "prawn/font"
81
- require_relative "prawn/encoding"
82
81
  require_relative "prawn/measurements"
83
82
  require_relative "prawn/repeater"
84
83
  require_relative "prawn/outline"
@@ -66,7 +66,7 @@ module Prawn
66
66
 
67
67
  VALID_OPTIONS = [:page_size, :page_layout, :margin, :left_margin,
68
68
  :right_margin, :top_margin, :bottom_margin, :skip_page_creation,
69
- :compress, :skip_encoding, :background, :info,
69
+ :compress, :background, :info,
70
70
  :text_formatter, :print_scaling]
71
71
 
72
72
  # Any module added to this array will be included into instances of
@@ -243,7 +243,7 @@ module Prawn
243
243
  if last_page = state.page
244
244
  last_page_size = last_page.size
245
245
  last_page_layout = last_page.layout
246
- last_page_margins = last_page.margins
246
+ last_page_margins = last_page.margins.dup
247
247
  end
248
248
 
249
249
  page_options = {:size => options[:size] || last_page_size,
@@ -26,7 +26,8 @@ module Prawn
26
26
  # Anyway, for now it's not clear what we should do w. them.
27
27
  delegate [ :graphic_state,
28
28
  :save_graphics_state,
29
- :restore_graphics_state ] => :renderer
29
+ :restore_graphics_state,
30
+ :on_page_create ] => :renderer
30
31
 
31
32
  # FIXME: This is a circular reference, because in theory Prawn should
32
33
  # be passing instances of renderer to PDF::Core::Page, but it's
@@ -82,39 +82,6 @@ module Prawn
82
82
  oslash ugrave uacute ucircumflex
83
83
  udieresis yacute thorn ydieresis
84
84
  ]
85
-
86
- def initialize
87
- @mapping_file = "#{Prawn::DATADIR}/encodings/win_ansi.txt"
88
- load_mapping if self.class.mapping.empty?
89
- end
90
-
91
- # Converts a Unicode codepoint into a valid WinAnsi single byte character.
92
- #
93
- # If there is no WinAnsi equivlant for a character, a _ will be substituted.
94
- #
95
- def [](codepoint)
96
- # unicode codepoints < 255 map directly to the single byte value in WinAnsi
97
- return codepoint if codepoint <= 255
98
-
99
- # There are a handful of codepoints > 255 that have equivilants in WinAnsi.
100
- # Replace anything else with an underscore
101
- self.class.mapping[codepoint] || 95
102
- end
103
-
104
- def self.mapping
105
- @mapping ||= {}
106
- end
107
-
108
- private
109
-
110
- def load_mapping
111
- File.open(@mapping_file, "r:BINARY") do |f|
112
- f.each do |l|
113
- _, single_byte, unicode = *l.match(/([0-9A-Za-z]+);([0-9A-F]{4})/)
114
- self.class.mapping["0x#{unicode}".hex] = "0x#{single_byte}".hex if single_byte
115
- end
116
- end
117
- end
118
85
  end
119
86
  end
120
87
  end
@@ -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_relative '../../prawn/encoding'
9
+ require_relative "../encoding"
10
10
 
11
11
  module Prawn
12
12
  class Font
@@ -14,6 +14,12 @@ module Prawn
14
14
  # @private
15
15
 
16
16
  class AFM < Font
17
+ class << self
18
+ attr_accessor :hide_m17n_warning
19
+ end
20
+
21
+ self.hide_m17n_warning = false
22
+
17
23
  BUILT_INS = %w[ Courier Helvetica Times-Roman Symbol ZapfDingbats
18
24
  Courier-Bold Courier-Oblique Courier-BoldOblique
19
25
  Times-Bold Times-Italic Times-BoldItalic
@@ -44,7 +50,6 @@ module Prawn
44
50
 
45
51
  super
46
52
 
47
- @@winansi ||= Prawn::Encoding::WinAnsi.new # parse data/encodings/win_ansi.txt once only
48
53
  @@font_data ||= SynchronizedCache.new # parse each ATM font file once only
49
54
 
50
55
  file_name = @name.dup
@@ -94,11 +99,17 @@ module Prawn
94
99
  # is replaced with a string in WinAnsi encoding.
95
100
  #
96
101
  def normalize_encoding(text)
97
- enc = @@winansi
98
- text.unpack("U*").collect { |i| enc[i] }.pack("C*")
99
- rescue ArgumentError
102
+ text.encode("windows-1252")
103
+ rescue ::Encoding::InvalidByteSequenceError,
104
+ ::Encoding::UndefinedConversionError
105
+
100
106
  raise Prawn::Errors::IncompatibleStringEncoding,
101
- "Arguments to text methods must be UTF-8 encoded"
107
+ "Your document includes text that's not compatible with the Windows-1252 character set.\n"+
108
+ "If you need full UTF-8 support, use TTF fonts instead of PDF's built-in fonts\n."
109
+ end
110
+
111
+ def to_utf8(text)
112
+ text.encode("UTF-8")
102
113
  end
103
114
 
104
115
  # Returns the number of characters in +str+ (a WinAnsi-encoded string).
@@ -124,11 +135,9 @@ module Prawn
124
135
  end
125
136
 
126
137
  def glyph_present?(char)
127
- if char == "_"
128
- true
129
- else
130
- normalize_encoding(char) != "_"
131
- end
138
+ !!normalize_encoding(char)
139
+ rescue Prawn::Errors::IncompatibleStringEncoding
140
+ false
132
141
  end
133
142
 
134
143
  private
@@ -173,6 +173,10 @@ module Prawn
173
173
  end
174
174
  end
175
175
 
176
+ def to_utf8(text)
177
+ text.encode("UTF-8")
178
+ end
179
+
176
180
  def glyph_present?(char)
177
181
  code = char.codepoints.first
178
182
  cmap[code] > 0
@@ -26,8 +26,7 @@ module Prawn
26
26
  def width_of( string, options )
27
27
  f = if options[:style]
28
28
  # override style with :style => :bold
29
- @document.find_font(@document.font ? @document.font.name : 'Helvetica',
30
- :style => options[:style])
29
+ @document.find_font(@document.font.family, :style => options[:style])
31
30
  else
32
31
  @document.font
33
32
  end
@@ -46,8 +46,8 @@ module Prawn
46
46
  # pdf.move_to(100,50)
47
47
  #
48
48
  def move_to(*point)
49
- x,y = map_to_absolute(point)
50
- renderer.add_content("%.3f %.3f m" % [ x, y ])
49
+ xy = PDF::Core.real_params(map_to_absolute(point))
50
+ renderer.add_content("#{xy} m")
51
51
  end
52
52
 
53
53
  # Draws a line from the current drawing position to the specified point.
@@ -57,8 +57,8 @@ module Prawn
57
57
  # pdf.line_to(50,50)
58
58
  #
59
59
  def line_to(*point)
60
- x,y = map_to_absolute(point)
61
- renderer.add_content("%.3f %.3f l" % [ x, y ])
60
+ xy = PDF::Core.real_params(map_to_absolute(point))
61
+ renderer.add_content("#{xy} l")
62
62
  end
63
63
 
64
64
  # Draws a Bezier curve from the current drawing position to the
@@ -71,9 +71,10 @@ module Prawn
71
71
  "Bounding points for bezier curve must be specified "+
72
72
  "as :bounds => [[x1,y1],[x2,y2]]"
73
73
 
74
- curve_points = (options[:bounds] << dest).map { |e| map_to_absolute(e) }
75
- renderer.add_content("%.3f %.3f %.3f %.3f %.3f %.3f c" %
76
- curve_points.flatten )
74
+ curve_points = PDF::Core.real_params(
75
+ (options[:bounds] << dest).flat_map { |e| map_to_absolute(e) })
76
+
77
+ renderer.add_content("#{curve_points} c")
77
78
  end
78
79
 
79
80
  # Draws a rectangle given <tt>point</tt>, <tt>width</tt> and
@@ -83,7 +84,9 @@ module Prawn
83
84
  #
84
85
  def rectangle(point,width,height)
85
86
  x,y = map_to_absolute(point)
86
- renderer.add_content("%.3f %.3f %.3f %.3f re" % [ x, y - height, width, height ])
87
+ box = PDF::Core.real_params([x, y - height, width, height])
88
+
89
+ renderer.add_content("#{box} re")
87
90
  end
88
91
 
89
92
  # Draws a rounded rectangle given <tt>point</tt>, <tt>width</tt> and
@@ -55,6 +55,11 @@ module Prawn
55
55
  def dash(length=nil, options={})
56
56
  return current_dash_state if length.nil?
57
57
 
58
+ if length == 0 || length.kind_of?(Array) && length.any? { |e| e == 0 }
59
+ raise ArgumentError,
60
+ "Zero length dashes are invalid. Call #undash to disable dashes."
61
+ end
62
+
58
63
  self.current_dash_state = { :dash => length,
59
64
  :space => length.kind_of?(Array) ? nil : options[:space] || length,
60
65
  :phase => options[:phase] || 0 }
@@ -121,9 +121,8 @@ module Prawn
121
121
  label = "I#{next_image_id}"
122
122
  state.page.xobjects.merge!(label => pdf_obj)
123
123
 
124
- # add the image to the current page
125
- instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ"
126
- renderer.add_content instruct % [ w, h, x, y - h, label ]
124
+ cm_params = PDF::Core.real_params([ w, 0, 0, h, x, y - h])
125
+ renderer.add_content("\nq\n#{cm_params} cm\n/#{label} Do\nQ")
127
126
  end
128
127
 
129
128
  private
@@ -68,13 +68,9 @@ module Prawn
68
68
  @transparency = {}
69
69
  case @color_type
70
70
  when 3
71
- # Indexed colour, RGB. Each byte in this chunk is an alpha for
72
- # the palette index in the PLTE ("palette") chunk up until the
73
- # last non-opaque entry. Set up an array, stretching over all
74
- # palette entries which will be 0 (opaque) or 1 (transparent).
75
- @transparency[:indexed] = data.read(chunk_size).unpack("C*")
76
- short = 255 - @transparency[:indexed].size
77
- @transparency[:indexed] += ([255] * short) if short > 0
71
+ raise Errors::UnsupportedImageType,
72
+ "Pallete-based transparency in PNG is not currently supported.\n" +
73
+ "See https://github.com/prawnpdf/prawn/issues/783"
78
74
  when 0
79
75
  # Greyscale. Corresponding to entries in the PLTE chunk.
80
76
  # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1
@@ -208,11 +204,6 @@ module Prawn
208
204
  # components.
209
205
  rgb = transparency[:rgb]
210
206
  obj.data[:Mask] = rgb.collect { |x| [x,x] }.flatten
211
- elsif transparency[:indexed]
212
- # TODO: broken. I was attempting to us Color Key Masking, but I think
213
- # we need to construct an SMask i think. Maybe do it inside
214
- # the PNG class, and store it in alpha_channel
215
- #obj.data[:Mask] = transparency[:indexed]
216
207
  end
217
208
 
218
209
  # For PNG color types 4 and 6, the transparency data is stored as a alpha
@@ -116,7 +116,7 @@ module Prawn
116
116
  # the current font familly. [current style]
117
117
  # <tt>:indent_paragraphs</tt>:: <tt>number</tt>. The amount to indent the
118
118
  # first line of each paragraph. Omit this
119
- # option if you do not want indenting
119
+ # option if you do not want indenting.
120
120
  # <tt>:direction</tt>::
121
121
  # <tt>:ltr</tt>, <tt>:rtl</tt>, Direction of the text (left-to-right
122
122
  # or right-to-left) [value of document.text_direction]
@@ -199,17 +199,13 @@ module Prawn
199
199
 
200
200
  if @indent_paragraphs
201
201
  self.text_formatter.array_paragraphs(array).each do |paragraph|
202
- options[:skip_encoding] = false
203
202
  remaining_text = draw_indented_formatted_line(paragraph, options)
204
- options[:skip_encoding] = true
205
203
 
206
204
  if @no_text_printed
207
205
  # unless this paragraph was an empty line
208
206
  unless @all_text_printed
209
207
  @bounding_box.move_past_bottom
210
- options[:skip_encoding] = false
211
208
  remaining_text = draw_indented_formatted_line(paragraph, options)
212
- options[:skip_encoding] = true
213
209
  end
214
210
  end
215
211
 
@@ -218,7 +214,6 @@ module Prawn
218
214
  end
219
215
  else
220
216
  remaining_text = fill_formatted_text_box(array, options)
221
- options[:skip_encoding] = true
222
217
  draw_remaining_formatted_text_on_new_pages(remaining_text, options)
223
218
  end
224
219
  end
@@ -292,6 +287,16 @@ module Prawn
292
287
  # should already be set
293
288
  #
294
289
  def draw_text!(text, options)
290
+ unless font.unicode? || font.class.hide_m17n_warning || text.ascii_only?
291
+ warn "PDF's built-in fonts have very limited support for "+
292
+ "internationalized text.\nIf you need full UTF-8 support, "+
293
+ "consider using a TTF font instead.\n\nTo disable this "+
294
+ "warning, add the following line to your code:\n"+
295
+ "Prawn::Font::AFM.hide_m17n_warning = true\n"
296
+
297
+ font.class.hide_m17n_warning = true
298
+ end
299
+
295
300
  x,y = map_to_absolute(options[:at])
296
301
  add_text_content(text,x,y,options)
297
302
  end
@@ -354,7 +359,10 @@ module Prawn
354
359
  end
355
360
 
356
361
  def draw_indented_formatted_line(string, options)
357
- indent(@indent_paragraphs) do
362
+ gap = options.fetch(:direction, text_direction) == :ltr ?
363
+ [@indent_paragraphs, 0] : [0, @indent_paragraphs]
364
+
365
+ indent(*gap) do
358
366
  fill_formatted_text_box(string, options.dup.merge(:single_line => true))
359
367
  end
360
368
  end
@@ -72,7 +72,6 @@ module Prawn
72
72
  # <tt>:valign</tt>::
73
73
  # <tt>:top</tt>, <tt>:center</tt>, or <tt>:bottom</tt>. Vertical
74
74
  # alignment within the bounding box [:top]
75
- #
76
75
  # <tt>:rotate</tt>::
77
76
  # <tt>number</tt>. The angle to rotate the text
78
77
  # <tt>:rotate_around</tt>::
@@ -84,8 +83,6 @@ module Prawn
84
83
  # document.default_leading]
85
84
  # <tt>:single_line</tt>::
86
85
  # <tt>boolean</tt>. If true, then only the first line will be drawn [false]
87
- # <tt>:skip_encoding</tt>::
88
- # <tt>boolean</tt> [false]
89
86
  # <tt>:overflow</tt>::
90
87
  # <tt>:truncate</tt>, <tt>:shrink_to_fit</tt>, or <tt>:expand</tt>
91
88
  # This controls the behavior when the amount of text
@@ -99,14 +96,9 @@ module Prawn
99
96
  #
100
97
  # Returns any text that did not print under the current settings.
101
98
  #
102
- # NOTE: if an AFM font is used, then the returned text is encoded in
103
- # WinAnsi. Subsequent calls to text_box that pass this returned text back
104
- # into text box must include a :skip_encoding => true option. This is
105
- # unnecessary when using TTF fonts because those operate on UTF-8 encoding.
106
- #
107
99
  # == Exceptions
108
100
  #
109
- # Raises <tt>Prawn::Errrors::CannotFit</tt> if not wide enough to print
101
+ # Raises <tt>Prawn::Errors::CannotFit</tt> if not wide enough to print
110
102
  # any text
111
103
  #
112
104
  def text_box(string, options={})
@@ -85,7 +85,7 @@ module Prawn
85
85
  #
86
86
  # Raises "Bad font family" if no font family is defined for the current font
87
87
  #
88
- # Raises <tt>Prawn::Errrors::CannotFit</tt> if not wide enough to print
88
+ # Raises <tt>Prawn::Errors::CannotFit</tt> if not wide enough to print
89
89
  # any text
90
90
  #
91
91
  def formatted_text_box(array, options={})
@@ -168,7 +168,6 @@ module Prawn
168
168
  @rotate = options[:rotate] || 0
169
169
  @rotate_around = options[:rotate_around] || :upper_left
170
170
  @single_line = options[:single_line]
171
- @skip_encoding = options[:skip_encoding] || @document.skip_encoding
172
171
  @draw_text_callback = options[:draw_text_callback]
173
172
 
174
173
  # if the text rendering mode is :unknown, force it back to :fill
@@ -229,7 +228,9 @@ module Prawn
229
228
  end
230
229
  end
231
230
 
232
- unprinted_text
231
+ unprinted_text.map do |e|
232
+ e.merge(:text => @document.font.to_utf8(e[:text]))
233
+ end
233
234
  end
234
235
 
235
236
  # The width available at this point in the box
@@ -335,7 +336,6 @@ module Prawn
335
336
  :disable_wrap_by_char,
336
337
  :leading, :character_spacing,
337
338
  :mode, :single_line,
338
- :skip_encoding,
339
339
  :document,
340
340
  :direction,
341
341
  :fallback_fonts,
@@ -345,11 +345,7 @@ module Prawn
345
345
  private
346
346
 
347
347
  def normalized_text(flags)
348
- if @skip_encoding
349
- text = original_text
350
- else
351
- text = normalize_encoding
352
- end
348
+ text = normalize_encoding
353
349
 
354
350
  text.each { |t| t.delete(:color) } if flags[:dry_run]
355
351
 
@@ -484,14 +480,16 @@ module Prawn
484
480
  @vertical_alignment_processed = true
485
481
 
486
482
  return if @vertical_align == :top
483
+
487
484
  wrap(text)
488
485
 
489
486
  case @vertical_align
490
487
  when :center
491
- @at[1] = @at[1] - (@height - height) * 0.5
488
+ @at[1] -= (@height - height + @descender) * 0.5
492
489
  when :bottom
493
- @at[1] = @at[1] - (@height - height) + @descender
490
+ @at[1] -= (@height - height)
494
491
  end
492
+
495
493
  @height = height
496
494
  end
497
495