prawn-git 2.0.1

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 (252) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/COPYING +2 -0
  4. data/GPLv2 +340 -0
  5. data/GPLv3 +674 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +56 -0
  8. data/Rakefile +55 -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/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/data/images/16bit.alpha +0 -0
  25. data/data/images/16bit.color +0 -0
  26. data/data/images/16bit.png +0 -0
  27. data/data/images/arrow.png +0 -0
  28. data/data/images/arrow2.png +0 -0
  29. data/data/images/dice.alpha +0 -0
  30. data/data/images/dice.color +0 -0
  31. data/data/images/dice.png +0 -0
  32. data/data/images/dice_interlaced.png +0 -0
  33. data/data/images/fractal.jpg +0 -0
  34. data/data/images/indexed_color.dat +0 -0
  35. data/data/images/indexed_color.png +0 -0
  36. data/data/images/letterhead.jpg +0 -0
  37. data/data/images/license.md +8 -0
  38. data/data/images/page_white_text.alpha +0 -0
  39. data/data/images/page_white_text.color +0 -0
  40. data/data/images/page_white_text.png +0 -0
  41. data/data/images/pal_bk.png +0 -0
  42. data/data/images/pigs.jpg +0 -0
  43. data/data/images/prawn.png +0 -0
  44. data/data/images/ruport.png +0 -0
  45. data/data/images/ruport_data.dat +0 -0
  46. data/data/images/ruport_transparent.png +0 -0
  47. data/data/images/ruport_type0.png +0 -0
  48. data/data/images/stef.jpg +0 -0
  49. data/data/images/tru256.bmp +0 -0
  50. data/data/images/web-links.dat +1 -0
  51. data/data/images/web-links.png +0 -0
  52. data/data/pdfs/complex_template.pdf +0 -0
  53. data/data/pdfs/contains_ttf_font.pdf +0 -0
  54. data/data/pdfs/encrypted.pdf +0 -0
  55. data/data/pdfs/form.pdf +820 -0
  56. data/data/pdfs/hexagon.pdf +61 -0
  57. data/data/pdfs/indirect_reference.pdf +86 -0
  58. data/data/pdfs/multipage_template.pdf +127 -0
  59. data/data/pdfs/nested_pages.pdf +118 -0
  60. data/data/pdfs/page_without_mediabox.pdf +193 -0
  61. data/data/pdfs/resources_as_indirect_object.pdf +83 -0
  62. data/data/pdfs/two_hexagons.pdf +90 -0
  63. data/data/pdfs/version_1_6.pdf +61 -0
  64. data/data/shift_jis_text.txt +1 -0
  65. data/lib/prawn.rb +89 -0
  66. data/lib/prawn/document.rb +706 -0
  67. data/lib/prawn/document/bounding_box.rb +539 -0
  68. data/lib/prawn/document/column_box.rb +144 -0
  69. data/lib/prawn/document/internals.rb +58 -0
  70. data/lib/prawn/document/span.rb +57 -0
  71. data/lib/prawn/encoding.rb +87 -0
  72. data/lib/prawn/errors.rb +80 -0
  73. data/lib/prawn/font.rb +413 -0
  74. data/lib/prawn/font/afm.rb +256 -0
  75. data/lib/prawn/font/dfont.rb +43 -0
  76. data/lib/prawn/font/ttf.rb +355 -0
  77. data/lib/prawn/font_metric_cache.rb +46 -0
  78. data/lib/prawn/graphics.rb +646 -0
  79. data/lib/prawn/graphics/cap_style.rb +47 -0
  80. data/lib/prawn/graphics/color.rb +232 -0
  81. data/lib/prawn/graphics/dash.rb +109 -0
  82. data/lib/prawn/graphics/join_style.rb +49 -0
  83. data/lib/prawn/graphics/patterns.rb +126 -0
  84. data/lib/prawn/graphics/transformation.rb +157 -0
  85. data/lib/prawn/graphics/transparency.rb +101 -0
  86. data/lib/prawn/grid.rb +279 -0
  87. data/lib/prawn/image_handler.rb +44 -0
  88. data/lib/prawn/images.rb +199 -0
  89. data/lib/prawn/images/image.rb +49 -0
  90. data/lib/prawn/images/jpg.rb +91 -0
  91. data/lib/prawn/images/png.rb +290 -0
  92. data/lib/prawn/measurement_extensions.rb +50 -0
  93. data/lib/prawn/measurements.rb +77 -0
  94. data/lib/prawn/outline.rb +289 -0
  95. data/lib/prawn/repeater.rb +124 -0
  96. data/lib/prawn/security.rb +288 -0
  97. data/lib/prawn/security/arcfour.rb +54 -0
  98. data/lib/prawn/soft_mask.rb +94 -0
  99. data/lib/prawn/stamp.rb +136 -0
  100. data/lib/prawn/text.rb +437 -0
  101. data/lib/prawn/text/box.rb +141 -0
  102. data/lib/prawn/text/formatted.rb +7 -0
  103. data/lib/prawn/text/formatted/arranger.rb +290 -0
  104. data/lib/prawn/text/formatted/box.rb +614 -0
  105. data/lib/prawn/text/formatted/fragment.rb +264 -0
  106. data/lib/prawn/text/formatted/line_wrap.rb +277 -0
  107. data/lib/prawn/text/formatted/parser.rb +224 -0
  108. data/lib/prawn/text/formatted/wrap.rb +160 -0
  109. data/lib/prawn/utilities.rb +46 -0
  110. data/lib/prawn/version.rb +5 -0
  111. data/lib/prawn/view.rb +91 -0
  112. data/manual/absolute_position.pdf +0 -0
  113. data/manual/basic_concepts/adding_pages.rb +27 -0
  114. data/manual/basic_concepts/basic_concepts.rb +36 -0
  115. data/manual/basic_concepts/creation.rb +39 -0
  116. data/manual/basic_concepts/cursor.rb +33 -0
  117. data/manual/basic_concepts/measurement.rb +25 -0
  118. data/manual/basic_concepts/origin.rb +38 -0
  119. data/manual/basic_concepts/other_cursor_helpers.rb +40 -0
  120. data/manual/basic_concepts/view.rb +42 -0
  121. data/manual/bounding_box/bounding_box.rb +39 -0
  122. data/manual/bounding_box/bounds.rb +49 -0
  123. data/manual/bounding_box/canvas.rb +24 -0
  124. data/manual/bounding_box/creation.rb +23 -0
  125. data/manual/bounding_box/indentation.rb +46 -0
  126. data/manual/bounding_box/nesting.rb +45 -0
  127. data/manual/bounding_box/russian_boxes.rb +40 -0
  128. data/manual/bounding_box/stretchy.rb +31 -0
  129. data/manual/contents.rb +29 -0
  130. data/manual/cover.rb +39 -0
  131. data/manual/document_and_page_options/background.rb +27 -0
  132. data/manual/document_and_page_options/document_and_page_options.rb +32 -0
  133. data/manual/document_and_page_options/metadata.rb +23 -0
  134. data/manual/document_and_page_options/page_margins.rb +38 -0
  135. data/manual/document_and_page_options/page_size.rb +34 -0
  136. data/manual/document_and_page_options/print_scaling.rb +20 -0
  137. data/manual/example_helper.rb +7 -0
  138. data/manual/graphics/circle_and_ellipse.rb +22 -0
  139. data/manual/graphics/color.rb +24 -0
  140. data/manual/graphics/common_lines.rb +30 -0
  141. data/manual/graphics/fill_and_stroke.rb +42 -0
  142. data/manual/graphics/fill_rules.rb +37 -0
  143. data/manual/graphics/gradients.rb +37 -0
  144. data/manual/graphics/graphics.rb +58 -0
  145. data/manual/graphics/helper.rb +24 -0
  146. data/manual/graphics/line_width.rb +35 -0
  147. data/manual/graphics/lines_and_curves.rb +41 -0
  148. data/manual/graphics/polygon.rb +29 -0
  149. data/manual/graphics/rectangle.rb +21 -0
  150. data/manual/graphics/rotate.rb +28 -0
  151. data/manual/graphics/scale.rb +41 -0
  152. data/manual/graphics/soft_masks.rb +46 -0
  153. data/manual/graphics/stroke_cap.rb +31 -0
  154. data/manual/graphics/stroke_dash.rb +48 -0
  155. data/manual/graphics/stroke_join.rb +30 -0
  156. data/manual/graphics/translate.rb +29 -0
  157. data/manual/graphics/transparency.rb +35 -0
  158. data/manual/how_to_read_this_manual.rb +40 -0
  159. data/manual/images/absolute_position.rb +23 -0
  160. data/manual/images/fit.rb +21 -0
  161. data/manual/images/horizontal.rb +25 -0
  162. data/manual/images/images.rb +40 -0
  163. data/manual/images/plain_image.rb +18 -0
  164. data/manual/images/scale.rb +22 -0
  165. data/manual/images/vertical.rb +28 -0
  166. data/manual/images/width_and_height.rb +25 -0
  167. data/manual/layout/boxes.rb +27 -0
  168. data/manual/layout/content.rb +25 -0
  169. data/manual/layout/layout.rb +28 -0
  170. data/manual/layout/simple_grid.rb +23 -0
  171. data/manual/outline/add_subsection_to.rb +61 -0
  172. data/manual/outline/insert_section_after.rb +47 -0
  173. data/manual/outline/outline.rb +32 -0
  174. data/manual/outline/sections_and_pages.rb +67 -0
  175. data/manual/repeatable_content/alternate_page_numbering.rb +32 -0
  176. data/manual/repeatable_content/page_numbering.rb +54 -0
  177. data/manual/repeatable_content/repeatable_content.rb +32 -0
  178. data/manual/repeatable_content/repeater.rb +55 -0
  179. data/manual/repeatable_content/stamp.rb +41 -0
  180. data/manual/security/encryption.rb +31 -0
  181. data/manual/security/permissions.rb +38 -0
  182. data/manual/security/security.rb +28 -0
  183. data/manual/table.rb +16 -0
  184. data/manual/text/alignment.rb +44 -0
  185. data/manual/text/color.rb +24 -0
  186. data/manual/text/column_box.rb +32 -0
  187. data/manual/text/fallback_fonts.rb +37 -0
  188. data/manual/text/font.rb +41 -0
  189. data/manual/text/font_size.rb +45 -0
  190. data/manual/text/font_style.rb +23 -0
  191. data/manual/text/formatted_callbacks.rb +60 -0
  192. data/manual/text/formatted_text.rb +50 -0
  193. data/manual/text/free_flowing_text.rb +51 -0
  194. data/manual/text/inline.rb +41 -0
  195. data/manual/text/kerning_and_character_spacing.rb +39 -0
  196. data/manual/text/leading.rb +25 -0
  197. data/manual/text/line_wrapping.rb +41 -0
  198. data/manual/text/paragraph_indentation.rb +34 -0
  199. data/manual/text/positioned_text.rb +38 -0
  200. data/manual/text/registering_families.rb +48 -0
  201. data/manual/text/rendering_and_color.rb +37 -0
  202. data/manual/text/right_to_left_text.rb +47 -0
  203. data/manual/text/rotation.rb +43 -0
  204. data/manual/text/single_usage.rb +37 -0
  205. data/manual/text/text.rb +73 -0
  206. data/manual/text/text_box_excess.rb +32 -0
  207. data/manual/text/text_box_extensions.rb +45 -0
  208. data/manual/text/text_box_overflow.rb +48 -0
  209. data/manual/text/utf8.rb +28 -0
  210. data/manual/text/win_ansi_charset.rb +60 -0
  211. data/prawn.gemspec +45 -0
  212. data/spec/acceptance/png.rb +25 -0
  213. data/spec/annotations_spec.rb +74 -0
  214. data/spec/bounding_box_spec.rb +510 -0
  215. data/spec/column_box_spec.rb +65 -0
  216. data/spec/data/curves.pdf +66 -0
  217. data/spec/destinations_spec.rb +15 -0
  218. data/spec/document_spec.rb +748 -0
  219. data/spec/extensions/encoding_helpers.rb +11 -0
  220. data/spec/extensions/mocha.rb +46 -0
  221. data/spec/font_metric_cache_spec.rb +52 -0
  222. data/spec/font_spec.rb +474 -0
  223. data/spec/formatted_text_arranger_spec.rb +421 -0
  224. data/spec/formatted_text_box_spec.rb +705 -0
  225. data/spec/formatted_text_fragment_spec.rb +298 -0
  226. data/spec/graphics_spec.rb +683 -0
  227. data/spec/grid_spec.rb +96 -0
  228. data/spec/image_handler_spec.rb +54 -0
  229. data/spec/images_spec.rb +153 -0
  230. data/spec/inline_formatted_text_parser_spec.rb +564 -0
  231. data/spec/jpg_spec.rb +25 -0
  232. data/spec/line_wrap_spec.rb +367 -0
  233. data/spec/measurement_units_spec.rb +25 -0
  234. data/spec/outline_spec.rb +430 -0
  235. data/spec/png_spec.rb +245 -0
  236. data/spec/reference_spec.rb +25 -0
  237. data/spec/repeater_spec.rb +160 -0
  238. data/spec/security_spec.rb +158 -0
  239. data/spec/soft_mask_spec.rb +79 -0
  240. data/spec/span_spec.rb +44 -0
  241. data/spec/spec_helper.rb +54 -0
  242. data/spec/stamp_spec.rb +160 -0
  243. data/spec/stroke_styles_spec.rb +211 -0
  244. data/spec/text_at_spec.rb +143 -0
  245. data/spec/text_box_spec.rb +1043 -0
  246. data/spec/text_rendering_mode_spec.rb +45 -0
  247. data/spec/text_spacing_spec.rb +93 -0
  248. data/spec/text_spec.rb +557 -0
  249. data/spec/text_with_inline_formatting_spec.rb +35 -0
  250. data/spec/transparency_spec.rb +91 -0
  251. data/spec/view_spec.rb +43 -0
  252. metadata +509 -0
@@ -0,0 +1,256 @@
1
+ # encoding: utf-8
2
+
3
+ # prawn/font/afm.rb : Implements AFM font support for Prawn
4
+ #
5
+ # Copyright May 2008, Gregory Brown / James Healy. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require_relative "../encoding"
10
+
11
+ module Prawn
12
+ class Font
13
+
14
+ # @private
15
+
16
+ class AFM < Font
17
+ class << self
18
+ attr_accessor :hide_m17n_warning
19
+ end
20
+
21
+ self.hide_m17n_warning = false
22
+
23
+ BUILT_INS = %w[ Courier Helvetica Times-Roman Symbol ZapfDingbats
24
+ Courier-Bold Courier-Oblique Courier-BoldOblique
25
+ Times-Bold Times-Italic Times-BoldItalic
26
+ Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique ]
27
+
28
+ def unicode?
29
+ false
30
+ end
31
+
32
+ def self.metrics_path
33
+ if m = ENV['METRICS']
34
+ @metrics_path ||= m.split(':')
35
+ else
36
+ @metrics_path ||= [
37
+ ".", "/usr/lib/afm",
38
+ "/usr/local/lib/afm",
39
+ "/usr/openwin/lib/fonts/afm",
40
+ Prawn::DATADIR+'/fonts']
41
+ end
42
+ end
43
+
44
+ attr_reader :attributes #:nodoc:
45
+
46
+ def initialize(document, name, options={}) #:nodoc:
47
+ unless BUILT_INS.include?(name)
48
+ raise Prawn::Errors::UnknownFont, "#{name} is not a known font."
49
+ end
50
+
51
+ super
52
+
53
+ @@font_data ||= SynchronizedCache.new # parse each ATM font file once only
54
+
55
+ file_name = @name.dup
56
+ file_name << ".afm" unless file_name =~ /\.afm$/
57
+ file_name = file_name[0] == ?/ ? file_name : find_font(file_name)
58
+
59
+ font_data = @@font_data[file_name] ||= parse_afm(file_name)
60
+ @glyph_widths = font_data[:glyph_widths]
61
+ @glyph_table = font_data[:glyph_table]
62
+ @bounding_boxes = font_data[:bounding_boxes]
63
+ @kern_pairs = font_data[:kern_pairs]
64
+ @kern_pair_table = font_data[:kern_pair_table]
65
+ @attributes = font_data[:attributes]
66
+
67
+ @ascender = @attributes["ascender"].to_i
68
+ @descender = @attributes["descender"].to_i
69
+ @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
70
+ end
71
+
72
+ # The font bbox, as an array of integers
73
+ #
74
+ def bbox
75
+ @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
76
+ end
77
+
78
+ # NOTE: String *must* be encoded as WinAnsi
79
+ def compute_width_of(string, options={}) #:nodoc:
80
+ scale = (options[:size] || size) / 1000.0
81
+
82
+ if options[:kerning]
83
+ strings, numbers = kern(string).partition { |e| e.is_a?(String) }
84
+ total_kerning_offset = numbers.inject(0.0) { |s,r| s + r }
85
+ (unscaled_width_of(strings.join) - total_kerning_offset) * scale
86
+ else
87
+ unscaled_width_of(string) * scale
88
+ end
89
+ end
90
+
91
+ # Returns true if the font has kerning data, false otherwise
92
+ #
93
+ def has_kerning_data?
94
+ @kern_pairs.any?
95
+ end
96
+
97
+ # built-in fonts only work with winansi encoding, so translate the
98
+ # string. Changes the encoding in-place, so the argument itself
99
+ # is replaced with a string in WinAnsi encoding.
100
+ #
101
+ def normalize_encoding(text)
102
+ text.encode("windows-1252")
103
+ rescue ::Encoding::InvalidByteSequenceError,
104
+ ::Encoding::UndefinedConversionError
105
+
106
+ raise Prawn::Errors::IncompatibleStringEncoding,
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")
113
+ end
114
+
115
+ # Returns the number of characters in +str+ (a WinAnsi-encoded string).
116
+ #
117
+ def character_count(str)
118
+ str.length
119
+ end
120
+
121
+ # Perform any changes to the string that need to happen
122
+ # before it is rendered to the canvas. Returns an array of
123
+ # subset "chunks", where each chunk is an array of two elements.
124
+ # The first element is the font subset number, and the second
125
+ # is either a string or an array (for kerned text).
126
+ #
127
+ # For Adobe fonts, there is only ever a single subset, so
128
+ # the first element of the array is "0", and the second is
129
+ # the string itself (or an array, if kerning is performed).
130
+ #
131
+ # The +text+ parameter must be in WinAnsi encoding (cp1252).
132
+ #
133
+ def encode_text(text, options={})
134
+ [[0, options[:kerning] ? kern(text) : text]]
135
+ end
136
+
137
+ def glyph_present?(char)
138
+ !!normalize_encoding(char)
139
+ rescue Prawn::Errors::IncompatibleStringEncoding
140
+ false
141
+ end
142
+
143
+ private
144
+
145
+ def register(subset)
146
+ font_dict = {:Type => :Font,
147
+ :Subtype => :Type1,
148
+ :BaseFont => name.to_sym}
149
+
150
+ # Symbolic AFM fonts (Symbol, ZapfDingbats) have their own encodings
151
+ font_dict.merge!(:Encoding => :WinAnsiEncoding) unless symbolic?
152
+
153
+ @document.ref!(font_dict)
154
+ end
155
+
156
+ def symbolic?
157
+ attributes["characterset"] == "Special"
158
+ end
159
+
160
+ def find_font(file)
161
+ self.class.metrics_path.find { |f| File.exist? "#{f}/#{file}" } + "/#{file}"
162
+ rescue NoMethodError
163
+ raise Prawn::Errors::UnknownFont,
164
+ "Couldn't find the font: #{file} in any of:\n" +
165
+ self.class.metrics_path.join("\n")
166
+ end
167
+
168
+ def parse_afm(file_name)
169
+ data = {:glyph_widths => {}, :bounding_boxes => {}, :kern_pairs => {}, :attributes => {}}
170
+ section = []
171
+
172
+ File.foreach(file_name) do |line|
173
+ case line
174
+ when /^Start(\w+)/
175
+ section.push $1
176
+ next
177
+ when /^End(\w+)/
178
+ section.pop
179
+ next
180
+ end
181
+
182
+ case section
183
+ when ["FontMetrics", "CharMetrics"]
184
+ next unless line =~ /^CH?\s/
185
+
186
+ name = line[/\bN\s+(\.?\w+)\s*;/, 1]
187
+ data[:glyph_widths][name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
188
+ data[:bounding_boxes][name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
189
+ when ["FontMetrics", "KernData", "KernPairs"]
190
+ next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
191
+ data[:kern_pairs][[$1, $2]] = $3.to_i
192
+ when ["FontMetrics", "KernData", "TrackKern"],
193
+ ["FontMetrics", "Composites"]
194
+ next
195
+ else
196
+ parse_generic_afm_attribute(line, data)
197
+ end
198
+ end
199
+
200
+ # process data parsed from AFM file to build tables which
201
+ # will be used when measuring and kerning text
202
+ data[:glyph_table] = (0..255).map do |i|
203
+ data[:glyph_widths][Encoding::WinAnsi::CHARACTERS[i]].to_i
204
+ end
205
+
206
+ character_hash = Hash[Encoding::WinAnsi::CHARACTERS.zip((0..Encoding::WinAnsi::CHARACTERS.size).to_a)]
207
+ data[:kern_pair_table] = data[:kern_pairs].inject({}) do |h,p|
208
+ h[p[0].map { |n| character_hash[n] }] = p[1]
209
+ h
210
+ end
211
+
212
+ data.each_value { |hash| hash.freeze }
213
+ data.freeze
214
+ end
215
+
216
+ def parse_generic_afm_attribute(line, hash)
217
+ line =~ /(^\w+)\s+(.*)/
218
+ key, value = $1.to_s.downcase, $2
219
+
220
+ hash[:attributes][key] = hash[:attributes][key] ? Array(hash[:attributes][key]) << value : value
221
+ end
222
+
223
+ # converts a string into an array with spacing offsets
224
+ # bewteen characters that need to be kerned
225
+ #
226
+ # String *must* be encoded as WinAnsi
227
+ #
228
+ def kern(string)
229
+ kerned = [[]]
230
+ last_byte = nil
231
+
232
+ string.each_byte do |byte|
233
+ if k = last_byte && @kern_pair_table[[last_byte, byte]]
234
+ kerned << -k << [byte]
235
+ else
236
+ kerned.last << byte
237
+ end
238
+ last_byte = byte
239
+ end
240
+
241
+ kerned.map { |e|
242
+ e = (Array === e ? e.pack("C*") : e)
243
+ e.respond_to?(:force_encoding) ? e.force_encoding(::Encoding::Windows_1252) : e
244
+ }
245
+ end
246
+
247
+ private
248
+
249
+ def unscaled_width_of(string)
250
+ string.bytes.inject(0) do |s,r|
251
+ s + @glyph_table[r]
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ #
3
+ # font.rb : The Prawn font class
4
+ #
5
+ # Copyright November 2008, Jamis Buck. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ #
9
+ require_relative 'ttf'
10
+
11
+ module Prawn
12
+ class Font
13
+ # @private
14
+ class DFont < TTF
15
+
16
+ # Returns a list of the names of all named fonts in the given dfont file.
17
+ # Note that fonts are not required to be named in a dfont file, so the
18
+ # list may be empty even if the file does contain fonts. Also, note that
19
+ # the list is returned in no particular order, so the first font in the
20
+ # list is not necessarily the font at index 0 in the file.
21
+ #
22
+ def self.named_fonts(file)
23
+ TTFunk::ResourceFile.open(file) do |f|
24
+ return f.resources_for("sfnt")
25
+ end
26
+ end
27
+
28
+ # Returns the number of fonts contained in the dfont file.
29
+ #
30
+ def self.font_count(file)
31
+ TTFunk::ResourceFile.open(file) do |f|
32
+ return f.map["sfnt"][:list].length
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def read_ttf_file
39
+ TTFunk::File.from_dfont(@name, @options[:font] || 0)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,355 @@
1
+ # encoding: utf-8
2
+
3
+ # prawn/font/ttf.rb : Implements AFM font support for Prawn
4
+ #
5
+ # Copyright May 2008, Gregory Brown / James Healy / Jamis Buck
6
+ # All Rights Reserved.
7
+ #
8
+ # This is free software. Please see the LICENSE and COPYING files for details.
9
+
10
+ require 'ttfunk'
11
+ require 'ttfunk/subset_collection'
12
+
13
+ module Prawn
14
+ class Font
15
+
16
+ # @private
17
+ class TTF < Font
18
+ attr_reader :ttf, :subsets
19
+
20
+ def unicode?
21
+ true
22
+ end
23
+
24
+ def initialize(document, name, options={})
25
+ super
26
+
27
+ @ttf = read_ttf_file
28
+ @subsets = TTFunk::SubsetCollection.new(@ttf)
29
+
30
+ @attributes = {}
31
+ @bounding_boxes = {}
32
+ @char_widths = {}
33
+ @has_kerning_data = @ttf.kerning.exists? && @ttf.kerning.tables.any?
34
+
35
+ @ascender = Integer(@ttf.ascent * scale_factor)
36
+ @descender = Integer(@ttf.descent * scale_factor)
37
+ @line_gap = Integer(@ttf.line_gap * scale_factor)
38
+ end
39
+
40
+ # NOTE: +string+ must be UTF8-encoded.
41
+ def compute_width_of(string, options={}) #:nodoc:
42
+ scale = (options[:size] || size) / 1000.0
43
+ if options[:kerning]
44
+ kern(string).inject(0) do |s,r|
45
+ if r.is_a?(Numeric)
46
+ s - r
47
+ else
48
+ r.inject(s) { |s2, u| s2 + character_width_by_code(u) }
49
+ end
50
+ end * scale
51
+ else
52
+ string.codepoints.inject(0) do |s,r|
53
+ s + character_width_by_code(r)
54
+ end * scale
55
+ end
56
+ end
57
+
58
+ # The font bbox, as an array of integers
59
+ #
60
+ def bbox
61
+ @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
62
+ end
63
+
64
+ # Returns true if the font has kerning data, false otherwise
65
+ def has_kerning_data?
66
+ @has_kerning_data
67
+ end
68
+
69
+ # Perform any changes to the string that need to happen
70
+ # before it is rendered to the canvas. Returns an array of
71
+ # subset "chunks", where the even-numbered indices are the
72
+ # font subset number, and the following entry element is
73
+ # either a string or an array (for kerned text).
74
+ #
75
+ # The +text+ parameter must be UTF8-encoded.
76
+ #
77
+ def encode_text(text,options={})
78
+ text = text.chomp
79
+
80
+ if options[:kerning]
81
+ last_subset = nil
82
+ kern(text).inject([]) do |result, element|
83
+ if element.is_a?(Numeric)
84
+ result.last[1] = [result.last[1]] unless result.last[1].is_a?(Array)
85
+ result.last[1] << element
86
+ result
87
+ else
88
+ encoded = @subsets.encode(element)
89
+
90
+ if encoded.first[0] == last_subset
91
+ result.last[1] << encoded.first[1]
92
+ encoded.shift
93
+ end
94
+
95
+ if encoded.any?
96
+ last_subset = encoded.last[0]
97
+ result + encoded
98
+ else
99
+ result
100
+ end
101
+ end
102
+ end
103
+ else
104
+ @subsets.encode(text.unpack("U*"))
105
+ end
106
+ end
107
+
108
+ def basename
109
+ @basename ||= @ttf.name.postscript_name
110
+ end
111
+
112
+ # not sure how to compute this for true-type fonts...
113
+ def stemV
114
+ 0
115
+ end
116
+
117
+ def italic_angle
118
+ @italic_angle ||= if @ttf.postscript.exists?
119
+ raw = @ttf.postscript.italic_angle
120
+ hi, low = raw >> 16, raw & 0xFF
121
+ hi = -((hi ^ 0xFFFF) + 1) if hi & 0x8000 != 0
122
+ "#{hi}.#{low}".to_f
123
+ else
124
+ 0
125
+ end
126
+ end
127
+
128
+ def cap_height
129
+ @cap_height ||= begin
130
+ height = @ttf.os2.exists? && @ttf.os2.cap_height || 0
131
+ height == 0 ? @ascender : height
132
+ end
133
+ end
134
+
135
+ def x_height
136
+ # FIXME: seems like if os2 table doesn't exist, we could
137
+ # just find the height of the lower-case 'x' glyph?
138
+ @ttf.os2.exists? && @ttf.os2.x_height || 0
139
+ end
140
+
141
+ def family_class
142
+ @family_class ||= (@ttf.os2.exists? && @ttf.os2.family_class || 0) >> 8
143
+ end
144
+
145
+ def serif?
146
+ @serif ||= [1,2,3,4,5,7].include?(family_class)
147
+ end
148
+
149
+ def script?
150
+ @script ||= family_class == 10
151
+ end
152
+
153
+ def pdf_flags
154
+ @flags ||= begin
155
+ flags = 0
156
+ flags |= 0x0001 if @ttf.postscript.fixed_pitch?
157
+ flags |= 0x0002 if serif?
158
+ flags |= 0x0008 if script?
159
+ flags |= 0x0040 if italic_angle != 0
160
+ flags |= 0x0004 # assume the font contains at least some non-latin characters
161
+ end
162
+ end
163
+
164
+ def normalize_encoding(text)
165
+ begin
166
+ text.encode(::Encoding::UTF_8)
167
+ rescue => e
168
+ puts e
169
+ raise Prawn::Errors::IncompatibleStringEncoding, "Encoding " +
170
+ "#{text.encoding} can not be transparently converted to UTF-8. " +
171
+ "Please ensure the encoding of the string you are attempting " +
172
+ "to use is set correctly"
173
+ end
174
+ end
175
+
176
+ def to_utf8(text)
177
+ text.encode("UTF-8")
178
+ end
179
+
180
+ def glyph_present?(char)
181
+ code = char.codepoints.first
182
+ cmap[code] > 0
183
+ end
184
+
185
+ # Returns the number of characters in +str+ (a UTF-8-encoded string).
186
+ #
187
+ def character_count(str)
188
+ str.length
189
+ end
190
+
191
+ private
192
+
193
+ def cmap
194
+ @cmap ||= @ttf.cmap.unicode.first or raise("no unicode cmap for font")
195
+ end
196
+
197
+ # +string+ must be UTF8-encoded.
198
+ #
199
+ # Returns an array. If an element is a numeric, it represents the
200
+ # kern amount to inject at that position. Otherwise, the element
201
+ # is an array of UTF-16 characters.
202
+ def kern(string)
203
+ a = []
204
+
205
+ string.each_codepoint do |r|
206
+ if a.empty?
207
+ a << [r]
208
+ elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]])
209
+ kern *= scale_factor
210
+ a << -kern << [r]
211
+ else
212
+ a.last << r
213
+ end
214
+ end
215
+
216
+ a
217
+ end
218
+
219
+ def kern_pairs_table
220
+ @kerning_data ||= has_kerning_data? ? @ttf.kerning.tables.first.pairs : {}
221
+ end
222
+
223
+ def cid_to_gid_map
224
+ max = cmap.code_map.keys.max
225
+ (0..max).map { |cid| cmap[cid] }.pack("n*")
226
+ end
227
+
228
+ def hmtx
229
+ @hmtx ||= @ttf.horizontal_metrics
230
+ end
231
+
232
+ def character_width_by_code(code)
233
+ return 0 unless cmap[code]
234
+
235
+ # Some TTF fonts have nonzero widths for \n (UTF-8 / ASCII code: 10).
236
+ # Patch around this as we'll never be drawing a newline with a width.
237
+ return 0.0 if code == 10
238
+
239
+ @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor)
240
+ end
241
+
242
+ def scale_factor
243
+ @scale ||= 1000.0 / @ttf.header.units_per_em
244
+ end
245
+
246
+ def register(subset)
247
+ temp_name = @ttf.name.postscript_name.gsub("\0","").to_sym
248
+ ref = @document.ref!(:Type => :Font, :BaseFont => temp_name)
249
+
250
+ # Embed the font metrics in the document after everything has been
251
+ # drawn, just before the document is emitted.
252
+ @document.renderer.before_render { |doc| embed(ref, subset) }
253
+
254
+ ref
255
+ end
256
+
257
+ def embed(reference, subset)
258
+ font_content = @subsets[subset].encode
259
+
260
+ # FIXME: we need postscript_name and glyph widths from the font
261
+ # subset. Perhaps this could be done by querying the subset,
262
+ # rather than by parsing the font that the subset produces?
263
+ font = TTFunk::File.new(font_content)
264
+
265
+ # empirically, it looks like Adobe Reader will not display fonts
266
+ # if their font name is more than 33 bytes long. Strange. But true.
267
+ basename = font.name.postscript_name[0, 33].gsub("\0","")
268
+
269
+ raise "Can't detect a postscript name for #{file}" if basename.nil?
270
+
271
+ fontfile = @document.ref!(:Length1 => font_content.size)
272
+ fontfile.stream << font_content
273
+ fontfile.stream.compress!
274
+
275
+ descriptor = @document.ref!(:Type => :FontDescriptor,
276
+ :FontName => basename.to_sym,
277
+ :FontFile2 => fontfile,
278
+ :FontBBox => bbox,
279
+ :Flags => pdf_flags,
280
+ :StemV => stemV,
281
+ :ItalicAngle => italic_angle,
282
+ :Ascent => @ascender,
283
+ :Descent => @descender,
284
+ :CapHeight => cap_height,
285
+ :XHeight => x_height)
286
+
287
+ hmtx = font.horizontal_metrics
288
+ widths = font.cmap.tables.first.code_map.map { |gid|
289
+ Integer(hmtx.widths[gid] * scale_factor) }[32..-1]
290
+
291
+ # It would be nice to have Encoding set for the macroman subsets,
292
+ # and only do a ToUnicode cmap for non-encoded unicode subsets.
293
+ # However, apparently Adobe Reader won't render MacRoman encoded
294
+ # subsets if original font contains unicode characters. (It has to
295
+ # be some flag or something that ttfunk is simply copying over...
296
+ # but I can't figure out which flag that is.)
297
+ #
298
+ # For now, it's simplest to just create a unicode cmap for every font.
299
+ # It offends my inner purist, but it'll do.
300
+
301
+ map = @subsets[subset].to_unicode_map
302
+
303
+ ranges = [[]]
304
+ map.keys.sort.inject("") do |s, code|
305
+ ranges << [] if ranges.last.length >= 100
306
+ unicode = map[code]
307
+ ranges.last << "<%02x><%04x>" % [code, unicode]
308
+ end
309
+
310
+ range_blocks = ranges.inject("") do |s, list|
311
+ s << "%d beginbfchar\n%s\nendbfchar\n" % [list.length, list.join("\n")]
312
+ end
313
+
314
+ to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip
315
+
316
+ cmap = @document.ref!({})
317
+ cmap << to_unicode_cmap
318
+ cmap.stream.compress!
319
+
320
+ reference.data.update(:Subtype => :TrueType,
321
+ :BaseFont => basename.to_sym,
322
+ :FontDescriptor => descriptor,
323
+ :FirstChar => 32,
324
+ :LastChar => 255,
325
+ :Widths => @document.ref!(widths),
326
+ :ToUnicode => cmap)
327
+ end
328
+
329
+ UNICODE_CMAP_TEMPLATE = <<-STR.strip.gsub(/^\s*/, "")
330
+ /CIDInit /ProcSet findresource begin
331
+ 12 dict begin
332
+ begincmap
333
+ /CIDSystemInfo <<
334
+ /Registry (Adobe)
335
+ /Ordering (UCS)
336
+ /Supplement 0
337
+ >> def
338
+ /CMapName /Adobe-Identity-UCS def
339
+ /CMapType 2 def
340
+ 1 begincodespacerange
341
+ <00><ff>
342
+ endcodespacerange
343
+ %s
344
+ endcmap
345
+ CMapName currentdict /CMap defineresource pop
346
+ end
347
+ end
348
+ STR
349
+
350
+ def read_ttf_file
351
+ TTFunk::File.open(@name)
352
+ end
353
+ end
354
+ end
355
+ end