kavu-prawn-core 0.4.99

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +56 -0
  3. data/README +40 -0
  4. data/Rakefile +73 -0
  5. data/data/encodings/win_ansi.txt +29 -0
  6. data/data/fonts/Action Man.dfont +0 -0
  7. data/data/fonts/Activa.ttf +0 -0
  8. data/data/fonts/Chalkboard.ttf +0 -0
  9. data/data/fonts/Courier-Bold.afm +342 -0
  10. data/data/fonts/Courier-BoldOblique.afm +342 -0
  11. data/data/fonts/Courier-Oblique.afm +342 -0
  12. data/data/fonts/Courier.afm +342 -0
  13. data/data/fonts/DejaVuSans.ttf +0 -0
  14. data/data/fonts/Dustismo_Roman.ttf +0 -0
  15. data/data/fonts/Helvetica-Bold.afm +2827 -0
  16. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  17. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  18. data/data/fonts/Helvetica.afm +3051 -0
  19. data/data/fonts/MustRead.html +19 -0
  20. data/data/fonts/Symbol.afm +213 -0
  21. data/data/fonts/Times-Bold.afm +2588 -0
  22. data/data/fonts/Times-BoldItalic.afm +2384 -0
  23. data/data/fonts/Times-Italic.afm +2667 -0
  24. data/data/fonts/Times-Roman.afm +2419 -0
  25. data/data/fonts/ZapfDingbats.afm +225 -0
  26. data/data/fonts/comicsans.ttf +0 -0
  27. data/data/fonts/gkai00mp.ttf +0 -0
  28. data/data/images/arrow.png +0 -0
  29. data/data/images/arrow2.png +0 -0
  30. data/data/images/barcode_issue.png +0 -0
  31. data/data/images/dice.alpha +0 -0
  32. data/data/images/dice.dat +0 -0
  33. data/data/images/dice.png +0 -0
  34. data/data/images/fractal.jpg +0 -0
  35. data/data/images/letterhead.jpg +0 -0
  36. data/data/images/page_white_text.alpha +0 -0
  37. data/data/images/page_white_text.dat +0 -0
  38. data/data/images/page_white_text.png +0 -0
  39. data/data/images/pigs.jpg +0 -0
  40. data/data/images/rails.dat +0 -0
  41. data/data/images/rails.png +0 -0
  42. data/data/images/ruport.png +0 -0
  43. data/data/images/ruport_data.dat +0 -0
  44. data/data/images/ruport_transparent.png +0 -0
  45. data/data/images/ruport_type0.png +0 -0
  46. data/data/images/stef.jpg +0 -0
  47. data/data/images/web-links.dat +1 -0
  48. data/data/images/web-links.png +0 -0
  49. data/data/shift_jis_text.txt +1 -0
  50. data/examples/bounding_box/bounding_boxes.rb +44 -0
  51. data/examples/bounding_box/russian_boxes.rb +37 -0
  52. data/examples/general/background.rb +20 -0
  53. data/examples/general/canvas.rb +16 -0
  54. data/examples/general/measurement_units.rb +52 -0
  55. data/examples/general/multi_page_layout.rb +17 -0
  56. data/examples/general/page_geometry.rb +32 -0
  57. data/examples/graphics/basic_images.rb +27 -0
  58. data/examples/graphics/cmyk.rb +13 -0
  59. data/examples/graphics/curves.rb +12 -0
  60. data/examples/graphics/hexagon.rb +14 -0
  61. data/examples/graphics/image_fit.rb +16 -0
  62. data/examples/graphics/image_flow.rb +38 -0
  63. data/examples/graphics/image_position.rb +18 -0
  64. data/examples/graphics/line.rb +33 -0
  65. data/examples/graphics/png_types.rb +23 -0
  66. data/examples/graphics/polygons.rb +17 -0
  67. data/examples/graphics/remote_images.rb +12 -0
  68. data/examples/graphics/ruport_style_helpers.rb +20 -0
  69. data/examples/graphics/stroke_bounds.rb +21 -0
  70. data/examples/m17n/chinese_text_wrapping.rb +20 -0
  71. data/examples/m17n/euro.rb +16 -0
  72. data/examples/m17n/sjis.rb +29 -0
  73. data/examples/m17n/utf8.rb +14 -0
  74. data/examples/m17n/win_ansi_charset.rb +55 -0
  75. data/examples/text/alignment.rb +19 -0
  76. data/examples/text/dfont.rb +49 -0
  77. data/examples/text/family_based_styling.rb +25 -0
  78. data/examples/text/font_calculations.rb +92 -0
  79. data/examples/text/font_size.rb +34 -0
  80. data/examples/text/kerning.rb +31 -0
  81. data/examples/text/simple_text.rb +18 -0
  82. data/examples/text/simple_text_ttf.rb +18 -0
  83. data/examples/text/span.rb +30 -0
  84. data/examples/text/text_box.rb +26 -0
  85. data/examples/text/text_flow.rb +68 -0
  86. data/lib/prawn/compatibility.rb +38 -0
  87. data/lib/prawn/document/annotations.rb +63 -0
  88. data/lib/prawn/document/bounding_box.rb +263 -0
  89. data/lib/prawn/document/destinations.rb +81 -0
  90. data/lib/prawn/document/internals.rb +126 -0
  91. data/lib/prawn/document/page_geometry.rb +79 -0
  92. data/lib/prawn/document/span.rb +55 -0
  93. data/lib/prawn/document/text/box.rb +76 -0
  94. data/lib/prawn/document/text/wrapping.rb +59 -0
  95. data/lib/prawn/document/text.rb +185 -0
  96. data/lib/prawn/document.rb +309 -0
  97. data/lib/prawn/encoding.rb +121 -0
  98. data/lib/prawn/errors.rb +45 -0
  99. data/lib/prawn/font/afm.rb +202 -0
  100. data/lib/prawn/font/dfont.rb +31 -0
  101. data/lib/prawn/font/ttf.rb +326 -0
  102. data/lib/prawn/font.rb +288 -0
  103. data/lib/prawn/graphics/color.rb +141 -0
  104. data/lib/prawn/graphics.rb +257 -0
  105. data/lib/prawn/images/jpg.rb +45 -0
  106. data/lib/prawn/images/png.rb +199 -0
  107. data/lib/prawn/images.rb +336 -0
  108. data/lib/prawn/literal_string.rb +14 -0
  109. data/lib/prawn/measurement_extensions.rb +46 -0
  110. data/lib/prawn/measurements.rb +71 -0
  111. data/lib/prawn/name_tree.rb +165 -0
  112. data/lib/prawn/pdf_object.rb +73 -0
  113. data/lib/prawn/reference.rb +59 -0
  114. data/lib/prawn.rb +70 -0
  115. data/spec/annotations_spec.rb +90 -0
  116. data/spec/bounding_box_spec.rb +141 -0
  117. data/spec/destinations_spec.rb +15 -0
  118. data/spec/document_spec.rb +163 -0
  119. data/spec/font_spec.rb +241 -0
  120. data/spec/graphics_spec.rb +209 -0
  121. data/spec/images_spec.rb +68 -0
  122. data/spec/jpg_spec.rb +25 -0
  123. data/spec/measurement_units_spec.rb +23 -0
  124. data/spec/name_tree_spec.rb +103 -0
  125. data/spec/pdf_object_spec.rb +112 -0
  126. data/spec/png_spec.rb +196 -0
  127. data/spec/reference_spec.rb +42 -0
  128. data/spec/spec_helper.rb +23 -0
  129. data/spec/text_spec.rb +178 -0
  130. metadata +259 -0
@@ -0,0 +1,309 @@
1
+ # encoding: utf-8
2
+
3
+ # document.rb : Implements PDF document generation for Prawn
4
+ #
5
+ # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require "stringio"
10
+ require "prawn/document/page_geometry"
11
+ require "prawn/document/bounding_box"
12
+ require "prawn/document/text"
13
+ require "prawn/document/internals"
14
+ require "prawn/document/span"
15
+ require "prawn/document/annotations"
16
+ require "prawn/document/destinations"
17
+
18
+ module Prawn
19
+ class Document
20
+
21
+ include Text
22
+ include PageGeometry
23
+ include Internals
24
+ include Annotations
25
+ include Destinations
26
+ include Prawn::Graphics
27
+ include Prawn::Images
28
+
29
+ attr_accessor :y, :margin_box
30
+ attr_reader :margins, :page_size, :page_layout
31
+ attr_writer :font_size
32
+
33
+ # Creates and renders a PDF document.
34
+ #
35
+ # When using the implicit block form, Prawn will evaluate the block
36
+ # within an instance of Prawn::Document, simplifying your syntax.
37
+ # However, please note that you will not be able to reference variables
38
+ # from the enclosing scope within this block.
39
+ #
40
+ # # Using implicit block form and rendering to a file
41
+ # Prawn::Document.generate "foo.pdf" do
42
+ # font "Times-Roman"
43
+ # text "Hello World", :at => [200,720], :size => 32
44
+ # end
45
+ #
46
+ # If you need to access your local and instance variables, use the explicit
47
+ # block form shown below. In this case, Prawn yields an instance of
48
+ # PDF::Document and the block is an ordinary closure:
49
+ #
50
+ # # Using explicit block form and rendering to a file
51
+ # content = "Hello World"
52
+ # Prawn::Document.generate "foo.pdf" do |pdf|
53
+ # pdf.font "Times-Roman"
54
+ # pdf.text content, :at => [200,720], :size => 32
55
+ # end
56
+ #
57
+ def self.generate(filename,options={},&block)
58
+ pdf = new(options,&block)
59
+ pdf.render_file(filename)
60
+ end
61
+
62
+ # Creates a new PDF Document. The following options are available:
63
+ #
64
+ # <tt>:page_size</tt>:: One of the Document::PageGeometry::SIZES [LETTER]
65
+ # <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt>
66
+ # <tt>:left_margin</tt>:: Sets the left margin in points [ 0.5 inch]
67
+ # <tt>:right_margin</tt>:: Sets the right margin in points [ 0.5 inch]
68
+ # <tt>:top_margin</tt>:: Sets the top margin in points [ 0.5 inch]
69
+ # <tt>:bottom_margin</tt>:: Sets the bottom margin in points [0.5 inch]
70
+ # <tt>:skip_page_creation</tt>:: Creates a document without starting the first page [false]
71
+ # <tt>:compress</tt>:: Compresses content streams before rendering them [false]
72
+ # <tt>:background</tt>:: An image path to be used as background on all pages [nil]
73
+ #
74
+ # Usage:
75
+ #
76
+ # # New document, US Letter paper, portrait orientation
77
+ # pdf = Prawn::Document.new
78
+ #
79
+ # # New document, A4 paper, landscaped
80
+ # pdf = Prawn::Document.new(:page_size => "A4", :page_layout => :landscape)
81
+ #
82
+ # # New document, with background
83
+ # pdf = Prawn::Document.new(:background => "#{Prawn::BASEDIR}/data/images/pigs.jpg")
84
+ #
85
+ def initialize(options={},&block)
86
+ Prawn.verify_options [:page_size, :page_layout, :left_margin,
87
+ :right_margin, :top_margin, :bottom_margin, :skip_page_creation,
88
+ :compress, :skip_encoding, :text_options, :background ], options
89
+
90
+ @objects = []
91
+ @info = ref(:Creator => "Prawn", :Producer => "Prawn")
92
+ @pages = ref(:Type => :Pages, :Count => 0, :Kids => [])
93
+ @root = ref(:Type => :Catalog, :Pages => @pages)
94
+ @page_size = options[:page_size] || "LETTER"
95
+ @page_layout = options[:page_layout] || :portrait
96
+ @compress = options[:compress] || false
97
+ @skip_encoding = options[:skip_encoding]
98
+ @background = options[:background]
99
+ @font_size = 12
100
+
101
+ text_options.update(options[:text_options] || {})
102
+
103
+ @margins = { :left => options[:left_margin] || 36,
104
+ :right => options[:right_margin] || 36,
105
+ :top => options[:top_margin] || 36,
106
+ :bottom => options[:bottom_margin] || 36 }
107
+
108
+ generate_margin_box
109
+
110
+ @bounding_box = @margin_box
111
+
112
+ start_new_page unless options[:skip_page_creation]
113
+
114
+ if block
115
+ block.arity < 1 ? instance_eval(&block) : block[self]
116
+ end
117
+ end
118
+
119
+ # Creates and advances to a new page in the document.
120
+ #
121
+ # Page size, margins, and layout can also be set when generating a
122
+ # new page. These values will become the new defaults for page creation
123
+ #
124
+ # pdf.start_new_page(:size => "LEGAL", :layout => :landscape)
125
+ # pdf.start_new_page(:left_margin => 50, :right_margin => 50)
126
+ #
127
+ def start_new_page(options = {})
128
+ @page_size = options[:size] if options[:size]
129
+ @page_layout = options[:layout] if options[:layout]
130
+
131
+ [:left,:right,:top,:bottom].each do |side|
132
+ if options[:"#{side}_margin"]
133
+ @margins[side] = options[:"#{side}_margin"]
134
+ end
135
+ end
136
+
137
+ finish_page_content if @page_content
138
+ build_new_page_content
139
+
140
+ @pages.data[:Kids] << @current_page
141
+ @pages.data[:Count] += 1
142
+
143
+ add_content "q"
144
+
145
+ @y = @bounding_box.absolute_top
146
+
147
+ image(@background, :at => [0,@y]) if @background
148
+ end
149
+
150
+ # Returns the number of pages in the document
151
+ #
152
+ # pdf = Prawn::Document.new
153
+ # pdf.page_count #=> 1
154
+ # 3.times { pdf.start_new_page }
155
+ # pdf.page_count #=> 4
156
+ #
157
+ def page_count
158
+ @pages.data[:Count]
159
+ end
160
+
161
+ # The current y drawing position relative to the innermost bounding box,
162
+ # or to the page margins at the top level.
163
+ #
164
+ def cursor
165
+ y - bounds.absolute_bottom
166
+ end
167
+
168
+ # Renders the PDF document to string
169
+ #
170
+ def render
171
+ output = StringIO.new
172
+ finish_page_content
173
+
174
+ render_header(output)
175
+ render_body(output)
176
+ render_xref(output)
177
+ render_trailer(output)
178
+ str = output.string
179
+ str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
180
+ str
181
+ end
182
+
183
+ # Renders the PDF document to file.
184
+ #
185
+ # pdf.render_file "foo.pdf"
186
+ #
187
+ def render_file(filename)
188
+ Kernel.const_defined?("Encoding") ? mode = "wb:ASCII-8BIT" : mode = "wb"
189
+ File.open(filename,mode) { |f| f << render }
190
+ end
191
+
192
+ # Returns the current BoundingBox object, which is by default
193
+ # the box represented by the margin box. When called from within
194
+ # a <tt>bounding_box</tt> block, the box defined by that call will
195
+ # be used.
196
+ #
197
+ def bounds
198
+ @bounding_box
199
+ end
200
+
201
+ # Sets Document#bounds to the BoundingBox provided. If you don't know
202
+ # why you'd need to do this, chances are, you can ignore this feature
203
+ #
204
+ def bounds=(bounding_box)
205
+ @bounding_box = bounding_box
206
+ end
207
+
208
+ # Moves up the document by n points
209
+ #
210
+ def move_up(n)
211
+ self.y += n
212
+ end
213
+
214
+ # Moves down the document by n point
215
+ #
216
+ def move_down(n)
217
+ self.y -= n
218
+ end
219
+
220
+ # Moves down the document and then executes a block.
221
+ #
222
+ # pdf.text "some text"
223
+ # pdf.pad_top(100) do
224
+ # pdf.text "This is 100 points below the previous line of text"
225
+ # end
226
+ # pdf.text "This text appears right below the previous line of text"
227
+ #
228
+ def pad_top(y)
229
+ move_down(y)
230
+ yield
231
+ end
232
+
233
+ # Executes a block then moves down the document
234
+ #
235
+ # pdf.text "some text"
236
+ # pdf.pad_bottom(100) do
237
+ # pdf.text "This text appears right below the previous line of text"
238
+ # end
239
+ # pdf.text "This is 100 points below the previous line of text"
240
+ #
241
+ def pad_bottom(y)
242
+ yield
243
+ move_down(y)
244
+ end
245
+
246
+ # Moves down the document by y, executes a block, then moves down the
247
+ # document by y again.
248
+ #
249
+ # pdf.text "some text"
250
+ # pdf.pad(100) do
251
+ # pdf.text "This is 100 points below the previous line of text"
252
+ # end
253
+ # pdf.text "This is 100 points below the previous line of text"
254
+ #
255
+ def pad(y)
256
+ move_down(y)
257
+ yield
258
+ move_down(y)
259
+ end
260
+
261
+ def mask(*fields) # :nodoc:
262
+ # Stores the current state of the named attributes, executes the block, and
263
+ # then restores the original values after the block has executed.
264
+ # -- I will remove the nodoc if/when this feature is a little less hacky
265
+ stored = {}
266
+ fields.each { |f| stored[f] = send(f) }
267
+ yield
268
+ fields.each { |f| send("#{f}=", stored[f]) }
269
+ end
270
+
271
+ # Returns true if content streams will be compressed before rendering,
272
+ # false otherwise
273
+ #
274
+ def compression_enabled?
275
+ !!@compress
276
+ end
277
+
278
+ private
279
+
280
+ # See Prawn::Document::Internals for low-level PDF functions
281
+
282
+ def build_new_page_content
283
+ generate_margin_box
284
+ @page_content = ref(:Length => 0)
285
+
286
+ @current_page = ref(:Type => :Page,
287
+ :Parent => @pages,
288
+ :MediaBox => page_dimensions,
289
+ :Contents => @page_content)
290
+ update_colors
291
+ end
292
+
293
+ def generate_margin_box
294
+ old_margin_box = @margin_box
295
+ @margin_box = BoundingBox.new(
296
+ self,
297
+ [ @margins[:left], page_dimensions[-1] - @margins[:top] ] ,
298
+ :width => page_dimensions[-2] - (@margins[:left] + @margins[:right]),
299
+ :height => page_dimensions[-1] - (@margins[:top] + @margins[:bottom])
300
+ )
301
+
302
+ # update bounding box if not flowing from the previous page
303
+ # FIXME: This may have a bug where the old margin is restored
304
+ # when the bounding box exits.
305
+ @bounding_box = @margin_box if old_margin_box == @bounding_box
306
+ end
307
+
308
+ end
309
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright September 2008, Gregory Brown, James Healy All Rights Reserved.
4
+ #
5
+ # This is free software. Please see the LICENSE and COPYING files for details.
6
+ #
7
+ module Prawn
8
+ module Encoding
9
+ # Map between unicode and WinAnsiEnoding
10
+ #
11
+ class WinAnsi #:nodoc:
12
+ CHARACTERS = %w[
13
+ .notdef .notdef .notdef .notdef
14
+ .notdef .notdef .notdef .notdef
15
+ .notdef .notdef .notdef .notdef
16
+ .notdef .notdef .notdef .notdef
17
+ .notdef .notdef .notdef .notdef
18
+ .notdef .notdef .notdef .notdef
19
+ .notdef .notdef .notdef .notdef
20
+ .notdef .notdef .notdef .notdef
21
+
22
+ space exclam quotedbl numbersign
23
+ dollar percent ampersand quotesingle
24
+ parenleft parenright asterisk plus
25
+ comma hyphen period slash
26
+ zero one two three
27
+ four five six seven
28
+ eight nine colon semicolon
29
+ less equal greater question
30
+
31
+ at A B C
32
+ D E F G
33
+ H I J K
34
+ L M N O
35
+ P Q R S
36
+ T U V W
37
+ X Y Z bracketleft
38
+ backslash bracketright asciicircum underscore
39
+
40
+ grave a b c
41
+ d e f g
42
+ h i j k
43
+ l m n o
44
+ p q r s
45
+ t u v w
46
+ x y z braceleft
47
+ bar braceright asciitilde .notdef
48
+
49
+ Euro .notdef quotesinglbase florin
50
+ quotedblbase ellipsis dagger daggerdbl
51
+ circumflex perthousand Scaron guilsinglleft
52
+ OE .notdef Zcaron .notdef
53
+ .notdef quoteleft quoteright quotedblleft
54
+ quotedblright bullet endash emdash
55
+ tilde trademark scaron guilsinglright
56
+ oe .notdef zcaron ydieresis
57
+
58
+ space exclamdown cent sterling
59
+ currency yen brokenbar section
60
+ dieresis copyright ordfeminine guillemotleft
61
+ logicalnot hyphen registered macron
62
+ degree plusminus twosuperior threesuperior
63
+ acute mu paragraph periodcentered
64
+ cedilla onesuperior ordmasculine guillemotright
65
+ onequarter onehalf threequarters questiondown
66
+
67
+ Agrave Aacute Acircumflex Atilde
68
+ Adieresis Aring AE Ccedilla
69
+ Egrave Eacute Ecircumflex Edieresis
70
+ Igrave Iacute Icircumflex Idieresis
71
+ Eth Ntilde Ograve Oacute
72
+ Ocircumflex Otilde Odieresis multiply
73
+ Oslash Ugrave Uacute Ucircumflex
74
+ Udieresis Yacute Thorn germandbls
75
+
76
+ agrave aacute acircumflex atilde
77
+ adieresis aring ae ccedilla
78
+ egrave eacute ecircumflex edieresis
79
+ igrave iacute icircumflex idieresis
80
+ eth ntilde ograve oacute
81
+ ocircumflex otilde odieresis divide
82
+ oslash ugrave uacute ucircumflex
83
+ udieresis yacute thorn ydieresis
84
+ ]
85
+
86
+ def initialize
87
+ @mapping_file = "#{Prawn::BASEDIR}/data/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
+ RUBY_VERSION >= "1.9" ? mode = "r:BINARY" : mode = "r"
112
+ File.open(@mapping_file, mode) do |f|
113
+ f.each do |l|
114
+ m, single_byte, unicode = *l.match(/([0-9A-Za-z]+);([0-9A-F]{4})/)
115
+ self.class.mapping["0x#{unicode}".hex] = "0x#{single_byte}".hex if single_byte
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ # errors.rb : Implements custom error classes for Prawn
4
+ #
5
+ # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ module Prawn
10
+ module Errors
11
+
12
+ # This error is raised when Prawn::PdfObject() encounters a Ruby object it
13
+ # cannot convert to PDF
14
+ #
15
+ class FailedObjectConversion < StandardError; end
16
+
17
+ # This error is raised when Document#page_layout is set to anything
18
+ # other than :portrait or :landscape
19
+ #
20
+ class InvalidPageLayout < StandardError; end
21
+
22
+ # This error is raised when a method requiring a current page is called
23
+ # without being on a page.
24
+ #
25
+ class NotOnPage < StandardError; end
26
+
27
+ # This error is raised when Prawn cannot find a specified font
28
+ #
29
+ class UnknownFont < StandardError; end
30
+
31
+ # This error is raised when Prawn is being used on a M17N aware VM,
32
+ # and the user attempts to add text that isn't compatible with UTF-8
33
+ # to their document
34
+ #
35
+ class IncompatibleStringEncoding < StandardError; end
36
+
37
+ # This error is raised when Prawn encounters an unknown key in functions
38
+ # that accept an options hash. This usually means there is a typo in your
39
+ # code or that the option you are trying to use has a different name than
40
+ # what you have specified.
41
+ #
42
+ class UnknownOption < StandardError; end
43
+
44
+ end
45
+ end
@@ -0,0 +1,202 @@
1
+ require 'prawn/encoding'
2
+
3
+ module Prawn
4
+ class Font
5
+ class AFM < Font
6
+ BUILT_INS = %w[ Courier Helvetica Times-Roman Symbol ZapfDingbats
7
+ Courier-Bold Courier-Oblique Courier-BoldOblique
8
+ Times-Bold Times-Italic Times-BoldItalic
9
+ Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique ]
10
+
11
+ def self.metrics_path
12
+ if m = ENV['METRICS']
13
+ @metrics_path ||= m.split(':')
14
+ else
15
+ @metrics_path ||= [
16
+ ".", "/usr/lib/afm",
17
+ "/usr/local/lib/afm",
18
+ "/usr/openwin/lib/fonts/afm/",
19
+ Prawn::BASEDIR+'/data/fonts/']
20
+ end
21
+ end
22
+
23
+ attr_reader :attributes
24
+
25
+ def initialize(document, name, options={})
26
+ unless BUILT_INS.include?(name)
27
+ raise Prawn::Errors::UnknownFont, "#{name} is not a known font."
28
+ end
29
+
30
+ super
31
+
32
+ @attributes = {}
33
+ @glyph_widths = {}
34
+ @bounding_boxes = {}
35
+ @kern_pairs = {}
36
+
37
+ file_name = @name.dup
38
+ file_name << ".afm" unless file_name =~ /\.afm$/
39
+ file_name = file_name[0] == ?/ ? file_name : find_font(file_name)
40
+
41
+ parse_afm(file_name)
42
+
43
+ @ascender = @attributes["ascender"].to_i
44
+ @descender = @attributes["descender"].to_i
45
+ @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
46
+ end
47
+
48
+ def bbox
49
+ @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
50
+ end
51
+
52
+ # calculates the width of the supplied string.
53
+ #
54
+ # String *must* be encoded as WinAnsi
55
+ #
56
+ def compute_width_of(string, options={})
57
+ scale = (options[:size] || size) / 1000.0
58
+
59
+ if options[:kerning]
60
+ strings, numbers = kern(string).partition { |e| e.is_a?(String) }
61
+ total_kerning_offset = numbers.inject(0.0) { |s,r| s + r }
62
+ (unscaled_width_of(strings.join) - total_kerning_offset) * scale
63
+ else
64
+ unscaled_width_of(string) * scale
65
+ end
66
+ end
67
+
68
+ def has_kerning_data?
69
+ @kern_pairs.any?
70
+ end
71
+
72
+ # built-in fonts only work with winansi encoding, so translate the
73
+ # string. Changes the encoding in-place, so the argument itself
74
+ # is replaced with a string in WinAnsi encoding.
75
+ def normalize_encoding(text)
76
+ enc = Prawn::Encoding::WinAnsi.new
77
+ text.replace text.unpack("U*").collect { |i| enc[i] }.pack("C*")
78
+ end
79
+
80
+ # Perform any changes to the string that need to happen
81
+ # before it is rendered to the canvas. Returns an array of
82
+ # subset "chunks", where each chunk is an array of two elements.
83
+ # The first element is the font subset number, and the second
84
+ # is either a string or an array (for kerned text).
85
+ #
86
+ # For Adobe fonts, there is only ever a single subset, so
87
+ # the first element of the array is "0", and the second is
88
+ # the string itself (or an array, if kerning is performed).
89
+ #
90
+ # The +text+ parameter must be in WinAnsi encoding (cp1252).
91
+ def encode_text(text, options={})
92
+ [[0, options[:kerning] ? kern(text) : text]]
93
+ end
94
+
95
+ private
96
+
97
+ def register(subset)
98
+ @document.ref(:Type => :Font,
99
+ :Subtype => :Type1,
100
+ :BaseFont => name.to_sym,
101
+ :Encoding => :WinAnsiEncoding)
102
+ end
103
+
104
+ def find_font(file)
105
+ self.class.metrics_path.find { |f| File.exist? "#{f}/#{file}" } + "/#{file}"
106
+ rescue NoMethodError
107
+ raise Prawn::Errors::UnknownFont,
108
+ "Couldn't find the font: #{file} in any of:\n" +
109
+ self.class.metrics_path.join("\n")
110
+ end
111
+
112
+ def parse_afm(file_name)
113
+ section = []
114
+
115
+ File.foreach(file_name) do |line|
116
+ case line
117
+ when /^Start(\w+)/
118
+ section.push $1
119
+ next
120
+ when /^End(\w+)/
121
+ section.pop
122
+ next
123
+ end
124
+
125
+ case section
126
+ when ["FontMetrics", "CharMetrics"]
127
+ next unless line =~ /^CH?\s/
128
+
129
+ name = line[/\bN\s+(\.?\w+)\s*;/, 1]
130
+ @glyph_widths[name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
131
+ @bounding_boxes[name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
132
+ when ["FontMetrics", "KernData", "KernPairs"]
133
+ next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
134
+ @kern_pairs[[$1, $2]] = $3.to_i
135
+ when ["FontMetrics", "KernData", "TrackKern"],
136
+ ["FontMetrics", "Composites"]
137
+ next
138
+ else
139
+ parse_generic_afm_attribute(line)
140
+ end
141
+ end
142
+ end
143
+
144
+ def parse_generic_afm_attribute(line)
145
+ line =~ /(^\w+)\s+(.*)/
146
+ key, value = $1.to_s.downcase, $2
147
+
148
+ @attributes[key] = @attributes[key] ?
149
+ Array(@attributes[key]) << value : value
150
+ end
151
+
152
+ # converts a string into an array with spacing offsets
153
+ # bewteen characters that need to be kerned
154
+ #
155
+ # String *must* be encoded as WinAnsi
156
+ #
157
+ def kern(string)
158
+ kerned = [[]]
159
+ last_byte = nil
160
+
161
+ kern_pairs = latin_kern_pairs_table
162
+
163
+ string.unpack("C*").each do |byte|
164
+ if k = last_byte && kern_pairs[[last_byte, byte]]
165
+ kerned << -k << [byte]
166
+ else
167
+ kerned.last << byte
168
+ end
169
+ last_byte = byte
170
+ end
171
+
172
+ kerned.map { |e|
173
+ e = (Array === e ? e.pack("C*") : e)
174
+ e.respond_to?(:force_encoding) ? e.force_encoding("Windows-1252") : e
175
+ }
176
+ end
177
+
178
+ def latin_kern_pairs_table
179
+ @kern_pairs_table ||= @kern_pairs.inject({}) do |h,p|
180
+ h[p[0].map { |n| Encoding::WinAnsi::CHARACTERS.index(n) }] = p[1]
181
+ h
182
+ end
183
+ end
184
+
185
+ def latin_glyphs_table
186
+ @glyphs_table ||= (0..255).map do |i|
187
+ @glyph_widths[Encoding::WinAnsi::CHARACTERS[i]].to_i
188
+ end
189
+ end
190
+
191
+ private
192
+
193
+ def unscaled_width_of(string)
194
+ glyph_table = latin_glyphs_table
195
+
196
+ string.unpack("C*").inject(0) do |s,r|
197
+ s + glyph_table[r]
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end