prawn 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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