prawn 1.3.0 → 2.0.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 (50) 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/right_to_left_text.rb +4 -0
  32. data/manual/text/text.rb +1 -1
  33. data/manual/text/text_box_overflow.rb +1 -1
  34. data/manual/text/utf8.rb +4 -4
  35. data/manual/text/win_ansi_charset.rb +2 -1
  36. data/prawn.gemspec +4 -19
  37. data/spec/document_spec.rb +24 -1
  38. data/spec/font_spec.rb +25 -0
  39. data/spec/formatted_text_box_spec.rb +77 -33
  40. data/spec/graphics_spec.rb +26 -12
  41. data/spec/line_wrap_spec.rb +26 -10
  42. data/spec/png_spec.rb +9 -1
  43. data/spec/soft_mask_spec.rb +1 -1
  44. data/spec/spec_helper.rb +2 -2
  45. data/spec/text_at_spec.rb +0 -1
  46. data/spec/text_box_spec.rb +2 -21
  47. data/spec/text_spacing_spec.rb +2 -2
  48. data/spec/text_spec.rb +102 -1
  49. metadata +12 -24
  50. 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: 98b52bd4123515071703b076cf87b787cd33ebff
4
+ data.tar.gz: 7d8c0d44d9a17975823452b1c95d81f81ace8d20
5
5
  SHA512:
6
- metadata.gz: 013f40d6fa76d1cf34ed85578e8ce5700c3ba47bbf6b373b9856ae2a34fe42421d83da002604f1acb5bdfd7fdcf0acc85a1f58d9397c89d46254c2e01da5764a
7
- data.tar.gz: b478644390db0dcd837063a79c126cf0d8d70a53746755dae3797bda11553b8520dd33d3ca983301a7627a76b74d824337d3497abba5966683eedd81efb5a2d5
6
+ metadata.gz: 20656b10430797d190dac2bebb0140e178bf1f35c6c98e92d1d9f1929ef5ec101a0fb41be5873a2b0b3b2a972c6c7932503ad619bc71588943573efac2453fa1
7
+ data.tar.gz: 511baa72ae16f53839675590c957ce49494b7180ab997bab2e189686f46a70a7ed4c761ca9de495f913f2b45fc74f013431da0fbb64517ce2ee4e3e4ec70a2d0
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