prawn 0.1.2 → 0.2.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 (53) hide show
  1. data/README +16 -2
  2. data/Rakefile +3 -3
  3. data/data/images/arrow.png +0 -0
  4. data/data/images/arrow2.png +0 -0
  5. data/data/images/barcode_issue.png +0 -0
  6. data/data/images/ruport_type0.png +0 -0
  7. data/examples/cell.rb +14 -3
  8. data/examples/chinese_text_wrapping.rb +17 -0
  9. data/examples/family_based_styling.rb +21 -0
  10. data/examples/fancy_table.rb +4 -4
  11. data/examples/flowing_text_with_header_and_footer.rb +72 -0
  12. data/examples/font_size.rb +2 -2
  13. data/examples/lazy_bounding_boxes.rb +19 -0
  14. data/examples/table.rb +13 -11
  15. data/examples/text_flow.rb +1 -1
  16. data/lib/prawn.rb +44 -15
  17. data/lib/prawn/compatibility.rb +20 -7
  18. data/lib/prawn/document.rb +72 -122
  19. data/lib/prawn/document/bounding_box.rb +124 -24
  20. data/lib/prawn/document/internals.rb +107 -0
  21. data/lib/prawn/document/table.rb +99 -70
  22. data/lib/prawn/document/text.rb +92 -314
  23. data/lib/prawn/errors.rb +13 -2
  24. data/lib/prawn/font.rb +312 -1
  25. data/lib/prawn/font/cmap.rb +1 -1
  26. data/lib/prawn/font/metrics.rb +52 -49
  27. data/lib/prawn/font/wrapping.rb +14 -12
  28. data/lib/prawn/graphics.rb +23 -74
  29. data/lib/prawn/graphics/cell.rb +30 -25
  30. data/lib/prawn/graphics/color.rb +132 -0
  31. data/lib/prawn/images.rb +37 -16
  32. data/lib/prawn/images/png.rb +29 -24
  33. data/lib/prawn/pdf_object.rb +3 -1
  34. data/spec/bounding_box_spec.rb +12 -3
  35. data/spec/document_spec.rb +40 -72
  36. data/spec/font_spec.rb +97 -0
  37. data/spec/graphics_spec.rb +46 -99
  38. data/spec/images_spec.rb +4 -21
  39. data/spec/pdf_object_spec.rb +8 -8
  40. data/spec/png_spec.rb +47 -12
  41. data/spec/spec_helper.rb +5 -24
  42. data/spec/table_spec.rb +53 -59
  43. data/spec/text_spec.rb +28 -93
  44. data/vendor/pdf-inspector/README +18 -0
  45. data/vendor/pdf-inspector/lib/pdf/inspector.rb +25 -0
  46. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +80 -0
  47. data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +16 -0
  48. data/vendor/pdf-inspector/lib/pdf/inspector/text.rb +31 -0
  49. data/vendor/pdf-inspector/lib/pdf/inspector/xobject.rb +19 -0
  50. metadata +63 -38
  51. data/examples/on_page_start.rb +0 -17
  52. data/examples/table_bench.rb +0 -92
  53. data/spec/box_calculation_spec.rb +0 -17
@@ -0,0 +1,107 @@
1
+ module Prawn
2
+ class Document
3
+
4
+ # This module exposes a few low-level PDF features for those who want
5
+ # to extend Prawn's core functionality. If you are not comfortable with
6
+ # low level PDF functionality as defined by Adobe's specification, chances
7
+ # are you won't need anything you find here.
8
+ #
9
+ module Internals
10
+
11
+ # Creates a new Prawn::Reference and adds it to the Document's object
12
+ # list. The +data+ argument is anything that Prawn::PdfObject() can convert.
13
+ def ref(data)
14
+ @objects.push(Prawn::Reference.new(@objects.size + 1, data)).last
15
+ end
16
+
17
+ # Appends a raw string to the current page content.
18
+ #
19
+ # # Raw line drawing example:
20
+ # x1,y1,x2,y2 = 100,500,300,550
21
+ # pdf.add_content("%.3f %.3f m" % [ x1, y1 ]) # move
22
+ # pdf.add_content("%.3f %.3f l" % [ x2, y2 ]) # draw path
23
+ # pdf.add_content("S") # stroke
24
+ #
25
+ def add_content(str)
26
+ @page_content << str << "\n"
27
+ end
28
+
29
+ # Add a new type to the current pages ProcSet
30
+ #
31
+ def proc_set(*types)
32
+ @current_page.data[:ProcSet] ||= ref([])
33
+ @current_page.data[:ProcSet].data |= types
34
+ end
35
+
36
+ # The Resources dictionary for the current page
37
+ #
38
+ def page_resources
39
+ @current_page.data[:Resources] ||= {}
40
+ end
41
+
42
+ # The Font dictionary for the current page
43
+ #
44
+ def page_fonts
45
+ page_resources[:Font] ||= {}
46
+ end
47
+
48
+ # The XObject dictionary for the current page
49
+ def page_xobjects
50
+ page_resources[:XObject] ||= {}
51
+ end
52
+
53
+ private
54
+
55
+ def finish_page_content
56
+ @header.draw if @header
57
+ @footer.draw if @footer
58
+ add_content "Q"
59
+ @page_content.compress_stream if compression_enabled?
60
+ @page_content.data[:Length] = @page_content.stream.size
61
+ end
62
+
63
+ # Write out the PDF Header, as per spec 3.4.1
64
+ def render_header(output)
65
+ # pdf version
66
+ output << "%PDF-1.3\n"
67
+
68
+ # 4 binary chars, as recommended by the spec
69
+ output << "\xFF\xFF\xFF\xFF\n"
70
+ end
71
+
72
+ # Write out the PDF Body, as per spec 3.4.2
73
+ def render_body(output)
74
+ @objects.each do |ref|
75
+ ref.offset = output.size
76
+ output << ref.object
77
+ end
78
+ end
79
+
80
+ # Write out the PDF Cross Reference Table, as per spec 3.4.3
81
+ def render_xref(output)
82
+ @xref_offset = output.size
83
+ output << "xref\n"
84
+ output << "0 #{@objects.size + 1}\n"
85
+ output << "0000000000 65535 f \n"
86
+ @objects.each do |ref|
87
+ output.printf("%010d", ref.offset)
88
+ output << " 00000 n \n"
89
+ end
90
+ end
91
+
92
+ # Write out the PDF Body, as per spec 3.4.4
93
+ def render_trailer(output)
94
+ trailer_hash = {:Size => @objects.size + 1,
95
+ :Root => @root,
96
+ :Info => @info}
97
+
98
+ output << "trailer\n"
99
+ output << Prawn::PdfObject(trailer_hash) << "\n"
100
+ output << "startxref\n"
101
+ output << @xref_offset << "\n"
102
+ output << "%%EOF"
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -7,8 +7,8 @@
7
7
  # This is free software. Please see the LICENSE and COPYING files for details.
8
8
 
9
9
  module Prawn
10
- class Document
11
-
10
+ class Document
11
+
12
12
  # Builds and renders a Document::Table object from raw data.
13
13
  # For details on the options that can be passed, see
14
14
  # Document::Table.new
@@ -37,7 +37,7 @@ module Prawn
37
37
  #
38
38
  # end
39
39
  #
40
- def table(data,options={})
40
+ def table(data,options={})
41
41
  Prawn::Document::Table.new(data,self,options).draw
42
42
  end
43
43
 
@@ -53,6 +53,7 @@ module Prawn
53
53
  # * Automated page-breaking as needed
54
54
  # * Column widths can be calculated automatically or defined explictly on a
55
55
  # column by column basis
56
+ # * Text alignment can be set for the whole table or by column
56
57
  #
57
58
  # The current implementation is a bit barebones, but covers most of the
58
59
  # basic needs for PDF table generation. If you have feature requests,
@@ -61,9 +62,13 @@ module Prawn
61
62
  # Tables will be revisited before the end of the Ruby Mendicant project and
62
63
  # the most commonly needed functionality will likely be added.
63
64
  #
64
- class Table
65
+ class Table
66
+
67
+ include Prawn::Configurable
65
68
 
66
- attr_reader :col_widths # :nodoc:
69
+ attr_reader :col_widths # :nodoc:
70
+
71
+ NUMBER_PATTERN = /^-?(?:0|[1-9]\d*)(?:\.\d+(?:[eE][+-]?\d+)?)?$/ #:nodoc:
67
72
 
68
73
  # Creates a new Document::Table object. This is generally called
69
74
  # indirectly through Document#table but can also be used explictly.
@@ -79,12 +84,13 @@ module Prawn
79
84
  # <tt>:horizontal_padding</tt>:: The horizontal cell padding in PDF points [5]
80
85
  # <tt>:vertical_padding</tt>:: The vertical cell padding in PDF points [5]
81
86
  # <tt>:padding</tt>:: Horizontal and vertical cell padding (overrides both)
82
- # <tt>:border</tt>:: With of border lines in PDF points [1]
87
+ # <tt>:border_width</tt>:: With of border lines in PDF points [1]
83
88
  # <tt>:border_style</tt>:: If set to :grid, fills in all borders. Otherwise, borders are drawn on columns only, not rows
84
89
  # <tt>:position</tt>:: One of <tt>:left</tt>, <tt>:center</tt> or <tt>n</tt>, where <tt>n</tt> is an x-offset from the left edge of the current bounding box
85
90
  # <tt>:widths:</tt> A hash of indices and widths in PDF points. E.g. <tt>{ 0 => 50, 1 => 100 }</tt>
86
91
  # <tt>:row_colors</tt>:: An array of row background colors which are used cyclicly.
87
- # <tt>:align</tt>:: Alignment of text in columns [:left]
92
+ # <tt>:align</tt>:: Alignment of text in columns, for entire table (<tt>:center</tt>) or by column (<tt>{ 0 => :left, 1 => :center}</tt>)
93
+ # <tt>:align_headers</tt>:: Alignment of header text.
88
94
  #
89
95
  # Row colors are specified as html encoded values, e.g.
90
96
  # ["ffffff","aaaaaa","ccaaff"]. You can also specify
@@ -94,31 +100,37 @@ module Prawn
94
100
  # See Document#table for typical usage, as directly using this class is
95
101
  # not recommended unless you know why you want to do it.
96
102
  #
97
- def initialize(data, document,options={})
98
- @data = data
99
- @document = document
100
- @font_size = options[:font_size] || 12
101
- @border_style = options[:border_style]
102
- @border = options[:border] || 1
103
- @position = options[:position] || :left
104
- @headers = options[:headers]
105
- @row_colors = options[:row_colors]
106
- @align = options[:align]
107
-
108
- @horizontal_padding = options[:horizontal_padding] || 5
109
- @vertical_padding = options[:vertical_padding] || 5
110
-
111
- if options[:padding]
112
- @horizontal_padding = @vertical_padding = options[:padding]
103
+ def initialize(data, document,options={})
104
+ unless data.all? { |e| Array === e }
105
+ raise Prawn::Errors::InvalidTableData,
106
+ "data must be a two dimensional array of Prawn::Cells or strings"
113
107
  end
114
-
115
108
 
116
- @row_colors = ["ffffff","cccccc"] if @row_colors == :pdf_writer
109
+ @data = data
110
+ @document = document
111
+
112
+ Prawn.verify_options [:font_size,:border_style, :border_width,
113
+ :position, :headers, :row_colors, :align, :align_headers,
114
+ :horizontal_padding, :vertical_padding, :padding, :widths ], options
115
+
116
+ configuration.update(options)
117
117
 
118
- @original_row_colors = @row_colors.dup if @row_colors
118
+ if padding = options[:padding]
119
+ C(:horizontal_padding => padding, :vertical_padding => padding)
120
+ end
121
+
122
+ if options[:row_colors] == :pdf_writer
123
+ C(:row_colors => ["ffffff","cccccc"])
124
+ end
119
125
 
126
+ if options[:row_colors]
127
+ C(:original_row_colors => C(:row_colors))
128
+ end
129
+
120
130
  calculate_column_widths(options[:widths])
121
- end
131
+ end
132
+
133
+ attr_reader :col_widths #:nodoc:
122
134
 
123
135
  # Width of the table in PDF points
124
136
  #
@@ -128,85 +140,98 @@ module Prawn
128
140
 
129
141
  # Draws the table onto the PDF document
130
142
  #
131
- def draw
132
- case(@position)
143
+ def draw
144
+ @parent_bounds = @document.bounds
145
+ case C(:position)
133
146
  when :center
134
147
  x = (@document.bounds.width - width) / 2.0
135
- y = @document.y - @document.bounds.absolute_bottom
136
- @document.bounding_box [x, y], :width => width do
148
+ dy = @document.bounds.absolute_top - @document.y
149
+ @document.bounding_box [x, @parent_bounds.top], :width => width do
150
+ @document.move_down(dy)
137
151
  generate_table
138
152
  end
139
153
  when Numeric
140
- x = @position
141
- y = @document.y - @document.bounds.absolute_bottom
142
- @document.bounding_box [x,y], :width => width do
143
- generate_table
144
- end
154
+ x, y = C(:position), @document.y - @document.bounds.absolute_bottom
155
+ @document.bounding_box([x,y], :width => width) { generate_table }
145
156
  else
146
157
  generate_table
147
158
  end
148
159
  end
149
160
 
150
161
  private
162
+
163
+ def default_configuration
164
+ { :font_size => 12,
165
+ :border_width => 1,
166
+ :position => :left,
167
+ :horizontal_padding => 5,
168
+ :vertical_padding => 5 }
169
+ end
151
170
 
152
171
  def calculate_column_widths(manual_widths=nil)
153
172
  @col_widths = [0] * @data[0].length
154
173
  renderable_data.each do |row|
155
174
  row.each_with_index do |cell,i|
156
175
  length = cell.to_s.lines.map { |e|
157
- @document.font_metrics.string_width(e,@font_size) }.max.to_f +
158
- 2*@horizontal_padding
159
- @col_widths[i] = length if length > @col_widths[i]
176
+ @document.font.metrics.string_width(e,C(:font_size)) }.max.to_f +
177
+ 2*C(:horizontal_padding)
178
+ @col_widths[i] = length.ceil if length > @col_widths[i]
160
179
  end
161
180
  end
162
181
 
163
- # TODO: Could optimize here
164
182
  manual_widths.each { |k,v| @col_widths[k] = v } if manual_widths
165
183
  end
166
184
 
167
185
  def renderable_data
168
- if @headers
169
- [@headers] + @data
170
- else
171
- @data
172
- end
186
+ C(:headers) ? [C(:headers)] + @data : @data
173
187
  end
174
188
 
175
- def generate_table
189
+ def generate_table
176
190
  page_contents = []
177
- y_pos = @document.y
191
+ y_pos = @document.y
178
192
 
179
- @document.font_size(@font_size) do
193
+ @document.font.size C(:font_size) do
180
194
  renderable_data.each_with_index do |row,index|
181
195
  c = Prawn::Graphics::CellBlock.new(@document)
182
- row.each_with_index do |e,i|
183
- case(e)
196
+ row.each_with_index do |e,i|
197
+ case C(:align)
198
+ when Hash
199
+ align = C(:align)[i]
200
+ else
201
+ align = C(:align)
202
+ end
203
+
204
+
205
+ align ||= e.to_s =~ NUMBER_PATTERN ? :right : :left
206
+
207
+ case e
184
208
  when Prawn::Graphics::Cell
185
209
  e.document = @document
186
210
  e.width = @col_widths[i]
187
- e.horizontal_padding = @horizontal_padding
188
- e.vertical_padding = @vertical_padding
189
- e.border = @border
211
+ e.horizontal_padding = C(:horizontal_padding)
212
+ e.vertical_padding = C(:vertical_padding)
213
+ e.border_width = C(:border_width)
190
214
  e.border_style = :sides
191
- e.align = @align
215
+ e.align = align
192
216
  c << e
193
217
  else
194
218
  c << Prawn::Graphics::Cell.new(
195
219
  :document => @document,
196
220
  :text => e.to_s,
197
221
  :width => @col_widths[i],
198
- :horizontal_padding => @horizontal_padding,
199
- :vertical_padding => @vertical_padding,
200
- :border => @border,
201
- :border_style => :sides,
202
- :align => @align )
222
+ :horizontal_padding => C(:horizontal_padding),
223
+ :vertical_padding => C(:vertical_padding),
224
+ :border_width => C(:border_width),
225
+ :border_style => :sides,
226
+ :align => align )
203
227
  end
204
228
  end
205
-
206
- if c.height > y_pos - @document.margin_box.absolute_bottom
229
+
230
+ bbox = @parent_bounds.stretchy? ? @document.margin_box : @parent_bounds
231
+ if c.height > y_pos - bbox.absolute_bottom
207
232
  draw_page(page_contents)
208
233
  @document.start_new_page
209
- if @headers
234
+ if C(:headers)
210
235
  page_contents = [page_contents[0]]
211
236
  y_pos = @document.y - page_contents[0].height
212
237
  else
@@ -224,22 +249,26 @@ module Prawn
224
249
  end
225
250
 
226
251
  end
227
- @document.y -= @vertical_padding
228
252
  end
229
253
  end
230
254
 
231
255
  def draw_page(contents)
232
256
  return if contents.empty?
233
257
 
234
- if @border_style == :grid || contents.length == 1
258
+ if C(:border_style) == :grid || contents.length == 1
235
259
  contents.each { |e| e.border_style = :all }
236
- else
237
- contents.first.border_style = @headers ? :all : :no_bottom
260
+ else
261
+ if C(:headers)
262
+ contents.first.border_style = :all
263
+ contents.first.align = C(:align_headers) || :left
264
+ else
265
+ contents.first.border_style = :no_bottom
266
+ end
238
267
  contents.last.border_style = :no_top
239
268
  end
240
269
 
241
270
  contents.each do |x|
242
- x.background_color = next_row_color if @row_colors
271
+ x.background_color = next_row_color if C(:row_colors)
243
272
  x.draw
244
273
  end
245
274
 
@@ -247,11 +276,11 @@ module Prawn
247
276
  end
248
277
 
249
278
  def next_row_color
250
- @row_colors.unshift(@row_colors.pop).last
279
+ C(:row_colors).unshift(C(:row_colors).pop).last
251
280
  end
252
281
 
253
- def reset_row_colors
254
- @row_colors = @original_row_colors.dup if @row_colors
282
+ def reset_row_colors
283
+ C(:row_colors => C(:original_row_colors).dup) if C(:row_colors)
255
284
  end
256
285
 
257
286
  end
@@ -10,34 +10,34 @@ require "zlib"
10
10
  module Prawn
11
11
  class Document
12
12
  module Text
13
- DEFAULT_FONT_SIZE = 12
14
-
15
- # The built in fonts specified by the Adobe PDF spec.
16
- BUILT_INS = %w[ Courier Courier-Bold Courier-Oblique Courier-BoldOblique
17
- Helvetica Helvetica-Bold Helvetica-Oblique
18
- Helvetica-BoldOblique Times-Roman Times-Bold Times-Italic
19
- Times-BoldItalic Symbol ZapfDingbats ]
20
-
21
- # Draws text on the page. If a point is specified via the <tt>:at</tt>
13
+ # Draws text on the page. If a point is specified via the +:at+
22
14
  # option the text will begin exactly at that point, and the string is
23
15
  # assumed to be pre-formatted to properly fit the page.
16
+ #
17
+ # pdf.text "Hello World", :at => [100,100]
18
+ # pdf.text "Goodbye World", :at => [50,50], :size => 16
24
19
  #
25
- # When <tt>:at</tt> is not specified, Prawn attempts to wrap the text to
26
- # fit within your current bounding box (or margin box if no bounding box
20
+ # When +:at+ is not specified, Prawn attempts to wrap the text to
21
+ # fit within your current bounding box (or margin_box if no bounding box
27
22
  # is being used ). Text will flow onto the next page when it reaches
28
- # the bottom of the margin_box. Text wrap in Prawn does not re-flow
23
+ # the bottom of the bounding box. Text wrap in Prawn does not re-flow
29
24
  # linebreaks, so if you want fully automated text wrapping, be sure to
30
25
  # remove newlines before attempting to draw your string.
31
26
  #
32
- # pdf.text "Hello World", :at => [100,100]
33
- # pdf.text "Goodbye World", :at => [50,50], :size => 16
34
27
  # pdf.text "Will be wrapped when it hits the edge of your bounding box"
28
+ # pdf.text "This will be centered", :align => :center
29
+ # pdf.text "This will be right aligned", :align => :right
30
+ #
31
+ # Wrapping is done by splitting words by spaces by default. If your text
32
+ # does not contain spaces, you can wrap based on characters instead:
33
+ #
34
+ # pdf.text "This will be wrapped by character", :wrap => :character
35
35
  #
36
36
  # If your font contains kerning pairs data that Prawn can parse, the
37
37
  # text will be kerned by default. You can disable this feature by passing
38
38
  # <tt>:kerning => false</tt>.
39
39
  #
40
- # == Encoding
40
+ # === Character Encoding Details:
41
41
  #
42
42
  # Note that strings passed to this function should be encoded as UTF-8.
43
43
  # If you get unexpected characters appearing in your rendered document,
@@ -45,162 +45,82 @@ module Prawn
45
45
  #
46
46
  # If the current font is a built-in one, although the string must be
47
47
  # encoded as UTF-8, only characters that are available in ISO-8859-1
48
- # are allowed.
48
+ # are allowed (transliteration will be attempted).
49
49
  #
50
50
  # If an empty box is rendered to your PDF instead of the character you
51
51
  # wanted it usually means the current font doesn't include that character.
52
52
  #
53
- def text(text,options={})
54
- # ensure a valid font is selected
55
- font "Helvetica" unless fonts[@font]
56
-
53
+ def text(text,options={})
57
54
  # we'll be messing with the strings encoding, don't change the users
58
55
  # original string
59
- text = text.dup
60
-
61
- # check the string is encoded sanely
62
- # - UTF-8 for TTF fonts
63
- # - ISO-8859-1 for Built-In fonts
64
- if using_builtin_font?
65
- normalize_builtin_encoding(text)
66
- else
67
- normalize_ttf_encoding(text)
68
- end
69
-
70
- if options.key?(:kerning)
71
- options[:kerning] = false unless font_metrics.has_kerning_data?
72
- else
73
- options[:kerning] = true if font_metrics.has_kerning_data?
74
- end
75
-
76
- return wrapped_text(text,options) unless options[:at]
56
+ text = text.to_s.dup
77
57
 
78
- x,y = translate(options[:at])
79
- font_size(options[:size] || current_font_size) do
80
- font_name = font_registry[fonts[@font]]
81
-
82
- text = @font_metrics.convert_text(text,options)
58
+ # we might also mess with the font
59
+ original_font = font.name
60
+
61
+ options = text_options.merge(options)
62
+ process_text_options(options)
63
+
64
+ font.normalize_encoding(text) unless @skip_encoding
65
+
66
+ if options[:at]
67
+ x,y = translate(options[:at])
68
+ font.size(options[:size]) { add_text_content(text,x,y,options) }
69
+ else
70
+ wrapped_text(text,options)
71
+ end
83
72
 
84
- add_content %Q{
85
- BT
86
- /#{font_name} #{current_font_size} Tf
87
- #{x} #{y} Td
88
- }
89
-
90
- add_content Prawn::PdfObject(text, true) <<
91
- " #{options[:kerning] ? 'TJ' : 'Tj'}\n"
92
-
93
- add_content %Q{
94
- ET
95
- }
96
- end
97
- end
73
+ font(original_font)
74
+ end
75
+
76
+ # A hash of configuration options, to be used globally by text().
77
+ #
78
+ # pdf.text_options.update(:size => 16, :align => :right)
79
+ # pdf.text "Hello World" #=> Size 16 w. right alignment
80
+ #
81
+ def text_options
82
+ @text_options ||= {}
83
+ end
98
84
 
99
- # Access to low-level font metrics data. This is only necessary for those
100
- # who require direct access to font attributes, and can be safely ignored
101
- # otherwise.
102
- #
103
- def font_metrics
104
- @font_metrics ||= Prawn::Font::Metrics["Helvetica"]
105
- end
106
-
107
- # Sets the current font.
108
- #
109
- # The single parameter must be a string. It can be one of the 14 built-in
110
- # fonts supported by PDF, or the location of a TTF file. The BUILT_INS
111
- # array specifies the valid built in font values.
112
- #
113
- # pdf.font "Times-Roman"
114
- # pdf.font "Chalkboard.ttf"
115
- #
116
- # If a ttf font is specified, the full file will be embedded in the
117
- # rendered PDF. This should be your preferred option in most cases.
118
- # It will increase the size of the resulting file, but also make it
119
- # more portable.
120
- #
121
- def font(name)
122
- proc_set :PDF, :Text
123
- @font_metrics = Prawn::Font::Metrics[name]
124
- case(name)
125
- when /\.ttf$/
126
- @font = embed_ttf_font(name)
127
- else
128
- @font = register_builtin_font(name)
129
- end
130
- set_current_font
131
- end
132
-
133
- # Sets the default font size for use within a block. Individual overrides
134
- # can be used as desired. The previous font size will be restored after the
135
- # block.
136
- #
137
- # Prawn::Document.generate("font_size.pdf") do
138
- # font_size!(16)
139
- # text "At size 16"
140
- #
141
- # font_size(10) do
142
- # text "At size 10"
143
- # text "At size 6", :size => 6
144
- # text "At size 10"
145
- # end
146
- #
147
- # text "At size 16"
148
- # end
149
- #
150
- # When called without an argument, this method returns the current font
151
- # size.
152
- #
153
- def font_size(size=nil)
154
- return current_font_size unless size
155
- font_size_before_block = @font_size || DEFAULT_FONT_SIZE
156
- font_size!(size)
157
- yield
158
- font_size!(font_size_before_block)
159
- end
160
-
161
- # Sets the default font size. See example in font_size
162
- #
163
- def font_size!(size)
164
- @font_size = size unless size == nil
165
- end
85
+ private
166
86
 
167
- alias_method :font_size=, :font_size!
87
+ def process_text_options(options)
88
+ Prawn.verify_options [:style, :kerning, :size, :at, :wrap,
89
+ :spacing, :align ], options
90
+
91
+ if options[:style]
92
+ raise "Bad font family" unless font.family
93
+ font(font.family,:style => options[:style])
94
+ end
168
95
 
169
- private
96
+ unless options.key?(:kerning)
97
+ options[:kerning] = font.metrics.has_kerning_data?
98
+ end
170
99
 
100
+ options[:size] ||= font.size
101
+ end
171
102
 
172
- # The current font_size being used in the document.
173
- #
174
- def current_font_size
175
- @font_size || DEFAULT_FONT_SIZE
103
+ def move_text_position(dy)
104
+ bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom :
105
+ @bounding_box.absolute_bottom
106
+ start_new_page if (y - dy) < bottom
107
+
108
+ self.y -= dy
176
109
  end
177
110
 
178
- def move_text_position(dy)
179
- (y - dy) < @margin_box.absolute_bottom ? start_new_page : self.y -= dy
180
- end
181
-
182
- def text_width(text,size)
183
- @font_metrics.string_width(text,size)
184
- end
185
-
186
- # TODO: Get kerning working with wrapped text
187
111
  def wrapped_text(text,options)
188
- options[:align] ||= :left
189
- font_size(options[:size] || current_font_size) do
190
- font_name = font_registry[fonts[@font]]
112
+ options[:align] ||= :left
191
113
 
192
- text = @font_metrics.naive_wrap(text, bounds.right, current_font_size,
193
- :kerning => options[:kerning])
114
+ font.size(options[:size]) do
115
+ text = font.metrics.naive_wrap(text, bounds.right, font.size,
116
+ :kerning => options[:kerning], :mode => options[:wrap])
194
117
 
195
118
  lines = text.lines
196
-
197
- lines.each do |e|
198
-
199
- move_text_position(@font_metrics.font_height(current_font_size) +
200
- @font_metrics.descender / 1000.0 * current_font_size)
201
-
119
+
120
+ lines.each do |e|
121
+ move_text_position( font.height + font.descender )
202
122
 
203
- line_width = text_width(e,font_size)
123
+ line_width = font.width_of(e)
204
124
  case(options[:align])
205
125
  when :left
206
126
  x = @bounding_box.absolute_left
@@ -208,173 +128,31 @@ module Prawn
208
128
  x = @bounding_box.absolute_left +
209
129
  (@bounding_box.width - line_width) / 2.0
210
130
  when :right
211
- x = @bounding_box.absolute_right - line_width
131
+ x = @bounding_box.absolute_right - line_width
212
132
  end
213
133
 
214
- add_content %Q{
215
- BT
216
- /#{font_name} #{current_font_size} Tf
217
- #{x} #{y} Td
218
- }
219
-
220
- add_content Prawn::PdfObject(@font_metrics.convert_text(e,options), true) <<
221
- " #{options[:kerning] ? 'TJ' : 'Tj'}\n"
222
-
223
- add_content %Q{
224
- ET
225
- }
226
-
227
- ds = -@font_metrics.descender / 1000.0 * current_font_size
228
- move_text_position(options[:spacing] || ds )
229
- end
230
- end
231
- end
232
-
233
- def embed_ttf_font(file) #:nodoc:
234
-
235
- ttf_metrics = Prawn::Font::Metrics::TTF.new(file)
236
-
237
- unless File.file?(file)
238
- raise ArgumentError, "file #{file} does not exist"
239
- end
240
-
241
- basename = @font_metrics.basename
242
-
243
- raise "Can't detect a postscript name for #{file}" if basename.nil?
244
-
245
- enctables[basename] = @font_metrics.enc_table
246
-
247
- if enctables[basename].nil?
248
- raise "#{file} missing the required encoding table"
249
- end
250
-
251
- font_content = File.open(file,"rb") { |f| f.read }
252
- compressed_font = Zlib::Deflate.deflate(font_content)
253
-
254
- fontfile = ref(:Length => compressed_font.size,
255
- :Length1 => font_content.size,
256
- :Filter => :FlateDecode )
257
- fontfile << compressed_font
258
-
259
- # TODO: Not sure what to do about CapHeight, as ttf2afm doesn't
260
- # pick it up. Missing proper StemV and flags
261
- #
262
- descriptor = ref(:Type => :FontDescriptor,
263
- :FontName => basename,
264
- :FontFile2 => fontfile,
265
- :FontBBox => @font_metrics.bbox,
266
- :Flags => 32, # FIXME: additional flags
267
- :StemV => 0,
268
- :ItalicAngle => 0,
269
- :Ascent => @font_metrics.ascender,
270
- :Descent => @font_metrics.descender
271
- )
272
-
273
- descendant = ref(:Type => :Font,
274
- :Subtype => :CIDFontType2, # CID, TTF
275
- :BaseFont => basename,
276
- :CIDSystemInfo => { :Registry => "Adobe",
277
- :Ordering => "Identity",
278
- :Supplement => 0 },
279
- :FontDescriptor => descriptor,
280
- :W => @font_metrics.glyph_widths,
281
- :CIDToGIDMap => :Identity
282
- )
283
-
284
- to_unicode_content = @font_metrics.to_unicode_cmap.to_s
285
- compressed_to_unicode = Zlib::Deflate.deflate(to_unicode_content)
286
- to_unicode = ref(:Length => compressed_to_unicode.size,
287
- :Length1 => to_unicode_content.size,
288
- :Filter => :FlateDecode )
289
- to_unicode << compressed_to_unicode
290
-
291
- # TODO: Needs ToUnicode (at least)
292
- fonts[basename] ||= ref(:Type => :Font,
293
- :Subtype => :Type0,
294
- :BaseFont => basename,
295
- :DescendantFonts => [descendant],
296
- :Encoding => :"Identity-H",
297
- :ToUnicode => to_unicode)
298
- return basename
299
- end
300
-
301
- # built-in fonts only work with latin encoding, so translate the string
302
- def normalize_builtin_encoding(text)
303
- if text.respond_to?(:encode!)
304
- text.encode!("ISO-8859-1")
305
- else
306
- require 'iconv'
307
- text.replace Iconv.conv('ISO-8859-1', 'utf-8', text)
308
- end
309
- rescue
310
- raise Prawn::Errors::IncompatibleStringEncoding, "When using a " +
311
- "builtin font, only characters that exist in " +
312
- "WinAnsi/ISO-8859-1 are allowed."
313
- end
314
-
315
- def normalize_ttf_encoding(text)
316
- # TODO: if the current font is a built in one, we can't use the utf-8
317
- # string provided by the user. We should convert it to WinAnsi or
318
- # MacRoman or some such.
319
- if text.respond_to?(:encode!)
320
- # if we're running under a M17n aware VM, ensure the string provided is
321
- # UTF-8 (by converting it if necessary)
322
- begin
323
- text.encode!("UTF-8")
324
- rescue
325
- raise Prawn::Errors::IncompatibleStringEncoding, "Encoding " +
326
- "#{text.encoding} can not be transparently converted to UTF-8. " +
327
- "Please ensure the encoding of the string you are attempting " +
328
- "to use is set correctly"
329
- end
330
- else
331
- # on a non M17N aware VM, use unpack as a hackish way to verify the
332
- # string is valid utf-8. I thought it was better than loading iconv
333
- # though.
334
- begin
335
- text.unpack("U*")
336
- rescue
337
- raise Prawn::Errors::IncompatibleStringEncoding, "The string you " +
338
- "are attempting to render is not encoded in valid UTF-8."
339
- end
340
- end
341
- end
342
-
343
- def register_builtin_font(name) #:nodoc:
344
- unless BUILT_INS.include?(name)
345
- raise Prawn::Errors::UnknownFont, "#{name} is not a known font."
134
+ add_text_content(e,x,y,options)
135
+ move_text_position(options[:spacing] || -font.descender )
136
+ end
346
137
  end
347
- fonts[name] ||= ref(:Type => :Font,
348
- :Subtype => :Type1,
349
- :BaseFont => name.to_sym,
350
- :Encoding => :WinAnsiEncoding)
351
- return name
352
- end
353
-
354
- def set_current_font #:nodoc:
355
- return if @font.nil?
356
- font_registry[fonts[@font]] ||= :"F#{font_registry.size + 1}"
357
-
358
- page_fonts.merge!(
359
- font_registry[fonts[@font]] => fonts[@font]
360
- )
361
- end
362
-
363
- def enctables #:nodoc
364
- @enctables ||= {}
365
- end
138
+ end
366
139
 
367
- def font_registry #:nodoc:
368
- @font_registry ||= {}
369
- end
370
-
371
- def fonts #:nodoc:
372
- @fonts ||= {}
373
- end
374
-
375
- def using_builtin_font?
376
- fonts[@font].data[:Subtype] == :Type1
377
- end
140
+ def add_text_content(text, x, y, options)
141
+ text = font.metrics.convert_text(text,options)
142
+
143
+ add_content %Q{
144
+ BT
145
+ /#{font.identifier} #{font.size} Tf
146
+ #{x} #{y} Td
147
+ }
148
+
149
+ add_content Prawn::PdfObject(text, true) <<
150
+ " #{options[:kerning] ? 'TJ' : 'Tj'}\n"
151
+
152
+ add_content %Q{
153
+ ET
154
+ }
155
+ end
378
156
  end
379
157
  end
380
158
  end