alphasights-prawn 0.10.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 (244) hide show
  1. data/COPYING +340 -0
  2. data/HACKING +50 -0
  3. data/LICENSE +56 -0
  4. data/README +141 -0
  5. data/Rakefile +52 -0
  6. data/data/encodings/win_ansi.txt +29 -0
  7. data/data/fonts/Action Man.dfont +0 -0
  8. data/data/fonts/Activa.ttf +0 -0
  9. data/data/fonts/Chalkboard.ttf +0 -0
  10. data/data/fonts/Courier-Bold.afm +342 -0
  11. data/data/fonts/Courier-BoldOblique.afm +342 -0
  12. data/data/fonts/Courier-Oblique.afm +342 -0
  13. data/data/fonts/Courier.afm +342 -0
  14. data/data/fonts/DejaVuSans.ttf +0 -0
  15. data/data/fonts/Dustismo_Roman.ttf +0 -0
  16. data/data/fonts/Helvetica-Bold.afm +2827 -0
  17. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  18. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  19. data/data/fonts/Helvetica.afm +3051 -0
  20. data/data/fonts/MustRead.html +19 -0
  21. data/data/fonts/Symbol.afm +213 -0
  22. data/data/fonts/Times-Bold.afm +2588 -0
  23. data/data/fonts/Times-BoldItalic.afm +2384 -0
  24. data/data/fonts/Times-Italic.afm +2667 -0
  25. data/data/fonts/Times-Roman.afm +2419 -0
  26. data/data/fonts/ZapfDingbats.afm +225 -0
  27. data/data/fonts/comicsans.ttf +0 -0
  28. data/data/fonts/gkai00mp.ttf +0 -0
  29. data/data/images/16bit.alpha +0 -0
  30. data/data/images/16bit.dat +0 -0
  31. data/data/images/16bit.png +0 -0
  32. data/data/images/arrow.png +0 -0
  33. data/data/images/arrow2.png +0 -0
  34. data/data/images/barcode_issue.png +0 -0
  35. data/data/images/dice.alpha +0 -0
  36. data/data/images/dice.dat +0 -0
  37. data/data/images/dice.png +0 -0
  38. data/data/images/dice_interlaced.png +0 -0
  39. data/data/images/fractal.jpg +0 -0
  40. data/data/images/letterhead.jpg +0 -0
  41. data/data/images/page_white_text.alpha +0 -0
  42. data/data/images/page_white_text.dat +0 -0
  43. data/data/images/page_white_text.png +0 -0
  44. data/data/images/pigs.jpg +0 -0
  45. data/data/images/rails.dat +0 -0
  46. data/data/images/rails.png +0 -0
  47. data/data/images/ruport.png +0 -0
  48. data/data/images/ruport_data.dat +0 -0
  49. data/data/images/ruport_transparent.png +0 -0
  50. data/data/images/ruport_type0.png +0 -0
  51. data/data/images/stef.jpg +0 -0
  52. data/data/images/tru256.bmp +0 -0
  53. data/data/images/web-links.dat +1 -0
  54. data/data/images/web-links.png +0 -0
  55. data/data/pdfs/complex_template.pdf +0 -0
  56. data/data/pdfs/contains_ttf_font.pdf +0 -0
  57. data/data/pdfs/encrypted.pdf +0 -0
  58. data/data/pdfs/hexagon.pdf +61 -0
  59. data/data/pdfs/indirect_reference.pdf +86 -0
  60. data/data/pdfs/nested_pages.pdf +118 -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/examples/bounding_box/bounding_boxes.rb +43 -0
  66. data/examples/bounding_box/indentation.rb +34 -0
  67. data/examples/bounding_box/russian_boxes.rb +36 -0
  68. data/examples/bounding_box/stretched_nesting.rb +67 -0
  69. data/examples/builder/simple.rb +28 -0
  70. data/examples/example_helper.rb +4 -0
  71. data/examples/general/background.rb +23 -0
  72. data/examples/general/canvas.rb +15 -0
  73. data/examples/general/context_sensitive_headers.rb +37 -0
  74. data/examples/general/float.rb +11 -0
  75. data/examples/general/margin.rb +36 -0
  76. data/examples/general/measurement_units.rb +51 -0
  77. data/examples/general/metadata-info.rb +16 -0
  78. data/examples/general/multi_page_layout.rb +18 -0
  79. data/examples/general/outlines.rb +50 -0
  80. data/examples/general/page_geometry.rb +31 -0
  81. data/examples/general/page_numbering.rb +15 -0
  82. data/examples/general/repeaters.rb +47 -0
  83. data/examples/general/stamp.rb +41 -0
  84. data/examples/general/templates.rb +13 -0
  85. data/examples/graphics/basic_images.rb +23 -0
  86. data/examples/graphics/chunkable.rb +38 -0
  87. data/examples/graphics/cmyk.rb +12 -0
  88. data/examples/graphics/curves.rb +11 -0
  89. data/examples/graphics/hexagon.rb +13 -0
  90. data/examples/graphics/image_fit.rb +15 -0
  91. data/examples/graphics/image_flow.rb +37 -0
  92. data/examples/graphics/image_position.rb +17 -0
  93. data/examples/graphics/line.rb +32 -0
  94. data/examples/graphics/png_types.rb +22 -0
  95. data/examples/graphics/polygons.rb +16 -0
  96. data/examples/graphics/remote_images.rb +12 -0
  97. data/examples/graphics/rounded_polygons.rb +19 -0
  98. data/examples/graphics/rounded_rectangle.rb +20 -0
  99. data/examples/graphics/ruport_style_helpers.rb +19 -0
  100. data/examples/graphics/stroke_bounds.rb +20 -0
  101. data/examples/graphics/stroke_cap_and_join.rb +45 -0
  102. data/examples/graphics/stroke_dash.rb +42 -0
  103. data/examples/graphics/transformations.rb +52 -0
  104. data/examples/graphics/transparency.rb +26 -0
  105. data/examples/m17n/chinese_text_wrapping.rb +17 -0
  106. data/examples/m17n/euro.rb +15 -0
  107. data/examples/m17n/sjis.rb +28 -0
  108. data/examples/m17n/utf8.rb +13 -0
  109. data/examples/m17n/win_ansi_charset.rb +54 -0
  110. data/examples/security/hello_foo.rb +8 -0
  111. data/examples/table/bill.rb +53 -0
  112. data/examples/table/cell.rb +12 -0
  113. data/examples/table/checkerboard.rb +22 -0
  114. data/examples/table/header.rb +14 -0
  115. data/examples/table/inline_format_table.rb +12 -0
  116. data/examples/table/multi_page_table.rb +9 -0
  117. data/examples/table/simple_table.rb +24 -0
  118. data/examples/table/subtable.rb +12 -0
  119. data/examples/table/widths.rb +20 -0
  120. data/examples/text/alignment.rb +18 -0
  121. data/examples/text/character_spacing.rb +12 -0
  122. data/examples/text/dfont.rb +48 -0
  123. data/examples/text/family_based_styling.rb +24 -0
  124. data/examples/text/font_calculations.rb +91 -0
  125. data/examples/text/font_size.rb +33 -0
  126. data/examples/text/hyphenation.rb +45 -0
  127. data/examples/text/indent_paragraphs.rb +22 -0
  128. data/examples/text/inline_format.rb +103 -0
  129. data/examples/text/kerning.rb +30 -0
  130. data/examples/text/rotated.rb +98 -0
  131. data/examples/text/shaped_text_box.rb +31 -0
  132. data/examples/text/simple_text.rb +17 -0
  133. data/examples/text/simple_text_ttf.rb +17 -0
  134. data/examples/text/text_box.rb +88 -0
  135. data/examples/text/text_box_returning_excess.rb +51 -0
  136. data/examples/text/text_flow.rb +67 -0
  137. data/lib/prawn.rb +27 -0
  138. data/lib/prawn/canvas.rb +119 -0
  139. data/lib/prawn/chunkable.rb +37 -0
  140. data/lib/prawn/compatibility.rb +51 -0
  141. data/lib/prawn/core.rb +85 -0
  142. data/lib/prawn/core/annotations.rb +61 -0
  143. data/lib/prawn/core/byte_string.rb +9 -0
  144. data/lib/prawn/core/chunk.rb +36 -0
  145. data/lib/prawn/core/destinations.rb +90 -0
  146. data/lib/prawn/core/document_state.rb +78 -0
  147. data/lib/prawn/core/literal_string.rb +16 -0
  148. data/lib/prawn/core/name_tree.rb +165 -0
  149. data/lib/prawn/core/object_store.rb +236 -0
  150. data/lib/prawn/core/page.rb +179 -0
  151. data/lib/prawn/core/pdf_object.rb +108 -0
  152. data/lib/prawn/core/reference.rb +112 -0
  153. data/lib/prawn/core/text.rb +140 -0
  154. data/lib/prawn/core/text/formatted/arranger.rb +266 -0
  155. data/lib/prawn/core/text/formatted/line_wrap.rb +127 -0
  156. data/lib/prawn/core/text/formatted/wrap.rb +112 -0
  157. data/lib/prawn/core/text/line_wrap.rb +209 -0
  158. data/lib/prawn/core/text/wrap.rb +80 -0
  159. data/lib/prawn/document.rb +573 -0
  160. data/lib/prawn/document/bounding_box.rb +425 -0
  161. data/lib/prawn/document/graphics_state.rb +48 -0
  162. data/lib/prawn/document/internals.rb +170 -0
  163. data/lib/prawn/document/page_geometry.rb +136 -0
  164. data/lib/prawn/document/snapshot.rb +87 -0
  165. data/lib/prawn/document_builder.rb +51 -0
  166. data/lib/prawn/document_builder/command.rb +38 -0
  167. data/lib/prawn/document_builder/constructs.rb +2 -0
  168. data/lib/prawn/document_builder/constructs/flowing_text_construct.rb +18 -0
  169. data/lib/prawn/document_builder/constructs/path_construct.rb +9 -0
  170. data/lib/prawn/document_builder/layout.rb +25 -0
  171. data/lib/prawn/document_builder/modifications.rb +2 -0
  172. data/lib/prawn/document_builder/modifications/layout_modification.rb +9 -0
  173. data/lib/prawn/document_builder/modifications/path_modification.rb +9 -0
  174. data/lib/prawn/encoding.rb +121 -0
  175. data/lib/prawn/errors.rb +94 -0
  176. data/lib/prawn/font.rb +341 -0
  177. data/lib/prawn/font/afm.rb +225 -0
  178. data/lib/prawn/font/dfont.rb +42 -0
  179. data/lib/prawn/font/ttf.rb +350 -0
  180. data/lib/prawn/graphics.rb +325 -0
  181. data/lib/prawn/graphics/cap_style.rb +38 -0
  182. data/lib/prawn/graphics/color.rb +205 -0
  183. data/lib/prawn/graphics/dash.rb +71 -0
  184. data/lib/prawn/graphics/join_style.rb +38 -0
  185. data/lib/prawn/graphics/transformation.rb +156 -0
  186. data/lib/prawn/graphics/transparency.rb +99 -0
  187. data/lib/prawn/images.rb +348 -0
  188. data/lib/prawn/images/jpg.rb +46 -0
  189. data/lib/prawn/images/png.rb +226 -0
  190. data/lib/prawn/measurement_extensions.rb +46 -0
  191. data/lib/prawn/measurements.rb +71 -0
  192. data/lib/prawn/outline.rb +278 -0
  193. data/lib/prawn/repeater.rb +129 -0
  194. data/lib/prawn/security.rb +262 -0
  195. data/lib/prawn/security/arcfour.rb +51 -0
  196. data/lib/prawn/stamp.rb +126 -0
  197. data/lib/prawn/table.rb +421 -0
  198. data/lib/prawn/table/accessors.rb +180 -0
  199. data/lib/prawn/table/cell.rb +350 -0
  200. data/lib/prawn/table/cell/in_table.rb +27 -0
  201. data/lib/prawn/table/cell/subtable.rb +65 -0
  202. data/lib/prawn/table/cell/text.rb +125 -0
  203. data/lib/prawn/text.rb +449 -0
  204. data/lib/prawn/text/box.rb +392 -0
  205. data/lib/prawn/text/formatted.rb +4 -0
  206. data/lib/prawn/text/formatted/box.rb +228 -0
  207. data/lib/prawn/text/formatted/fragment.rb +181 -0
  208. data/lib/prawn/text/formatted/parser.rb +213 -0
  209. data/spec/annotations_spec.rb +90 -0
  210. data/spec/bounding_box_spec.rb +190 -0
  211. data/spec/cell_spec.rb +348 -0
  212. data/spec/destinations_spec.rb +15 -0
  213. data/spec/document_spec.rb +473 -0
  214. data/spec/font_spec.rb +324 -0
  215. data/spec/formatted_text_arranger_spec.rb +426 -0
  216. data/spec/formatted_text_box_spec.rb +756 -0
  217. data/spec/formatted_text_fragment_spec.rb +211 -0
  218. data/spec/graphics_spec.rb +446 -0
  219. data/spec/images_spec.rb +96 -0
  220. data/spec/inline_formatted_text_parser_spec.rb +502 -0
  221. data/spec/jpg_spec.rb +25 -0
  222. data/spec/line_wrap_spec.rb +341 -0
  223. data/spec/measurement_units_spec.rb +23 -0
  224. data/spec/name_tree_spec.rb +112 -0
  225. data/spec/object_store_spec.rb +160 -0
  226. data/spec/outline_spec.rb +269 -0
  227. data/spec/pdf_object_spec.rb +170 -0
  228. data/spec/png_spec.rb +237 -0
  229. data/spec/reference_spec.rb +82 -0
  230. data/spec/repeater_spec.rb +96 -0
  231. data/spec/security_spec.rb +120 -0
  232. data/spec/snapshot_spec.rb +138 -0
  233. data/spec/spec_helper.rb +26 -0
  234. data/spec/stamp_spec.rb +108 -0
  235. data/spec/stroke_styles_spec.rb +163 -0
  236. data/spec/table_spec.rb +598 -0
  237. data/spec/template_spec.rb +158 -0
  238. data/spec/text_at_spec.rb +119 -0
  239. data/spec/text_box_spec.rb +742 -0
  240. data/spec/text_spacing_spec.rb +75 -0
  241. data/spec/text_spec.rb +333 -0
  242. data/spec/text_with_inline_formatting_spec.rb +193 -0
  243. data/spec/transparency_spec.rb +89 -0
  244. metadata +331 -0
@@ -0,0 +1,225 @@
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 'prawn/encoding'
10
+
11
+ module Prawn
12
+ class Font
13
+ class AFM < Font
14
+ BUILT_INS = %w[ Courier Helvetica Times-Roman Symbol ZapfDingbats
15
+ Courier-Bold Courier-Oblique Courier-BoldOblique
16
+ Times-Bold Times-Italic Times-BoldItalic
17
+ Helvetica-Bold Helvetica-Oblique Helvetica-BoldOblique ]
18
+
19
+ def unicode?
20
+ false
21
+ end
22
+
23
+ def self.metrics_path
24
+ if m = ENV['METRICS']
25
+ @metrics_path ||= m.split(':')
26
+ else
27
+ @metrics_path ||= [
28
+ ".", "/usr/lib/afm",
29
+ "/usr/local/lib/afm",
30
+ "/usr/openwin/lib/fonts/afm/",
31
+ Prawn::BASEDIR+'/data/fonts/']
32
+ end
33
+ end
34
+
35
+ attr_reader :attributes #:nodoc:
36
+
37
+ def initialize(document, name, options={}) #:nodoc:
38
+ unless BUILT_INS.include?(name)
39
+ raise Prawn::Errors::UnknownFont, "#{name} is not a known font."
40
+ end
41
+
42
+ super
43
+
44
+ @attributes = {}
45
+ @glyph_widths = {}
46
+ @bounding_boxes = {}
47
+ @kern_pairs = {}
48
+
49
+ file_name = @name.dup
50
+ file_name << ".afm" unless file_name =~ /\.afm$/
51
+ file_name = file_name[0] == ?/ ? file_name : find_font(file_name)
52
+
53
+ parse_afm(file_name)
54
+
55
+ @ascender = @attributes["ascender"].to_i
56
+ @descender = @attributes["descender"].to_i
57
+ @line_gap = Float(bbox[3] - bbox[1]) - (@ascender - @descender)
58
+ end
59
+
60
+ # The font bbox, as an array of integers
61
+ #
62
+ def bbox
63
+ @bbox ||= @attributes['fontbbox'].split(/\s+/).map { |e| Integer(e) }
64
+ end
65
+
66
+ # NOTE: String *must* be encoded as WinAnsi
67
+ def compute_width_of(string, options={}) #:nodoc:
68
+ scale = (options[:size] || size) / 1000.0
69
+
70
+ if options[:kerning]
71
+ strings, numbers = kern(string).partition { |e| e.is_a?(String) }
72
+ total_kerning_offset = numbers.inject(0.0) { |s,r| s + r }
73
+ (unscaled_width_of(strings.join) - total_kerning_offset) * scale
74
+ else
75
+ unscaled_width_of(string) * scale
76
+ end
77
+ end
78
+
79
+ # Returns true if the font has kerning data, false otherwise
80
+ #
81
+ def has_kerning_data?
82
+ @kern_pairs.any?
83
+ end
84
+
85
+ # built-in fonts only work with winansi encoding, so translate the
86
+ # string. Changes the encoding in-place, so the argument itself
87
+ # is replaced with a string in WinAnsi encoding.
88
+ #
89
+ def normalize_encoding(text)
90
+ enc = Prawn::Encoding::WinAnsi.new
91
+ text.unpack("U*").collect { |i| enc[i] }.pack("C*")
92
+ end
93
+
94
+ # Perform any changes to the string that need to happen
95
+ # before it is rendered to the canvas. Returns an array of
96
+ # subset "chunks", where each chunk is an array of two elements.
97
+ # The first element is the font subset number, and the second
98
+ # is either a string or an array (for kerned text).
99
+ #
100
+ # For Adobe fonts, there is only ever a single subset, so
101
+ # the first element of the array is "0", and the second is
102
+ # the string itself (or an array, if kerning is performed).
103
+ #
104
+ # The +text+ parameter must be in WinAnsi encoding (cp1252).
105
+ #
106
+ def encode_text(text, options={})
107
+ [[0, options[:kerning] ? kern(text) : text]]
108
+ end
109
+
110
+ private
111
+
112
+ def register(subset)
113
+ font_dict = {:Type => :Font,
114
+ :Subtype => :Type1,
115
+ :BaseFont => name.to_sym}
116
+
117
+ # Symbolic AFM fonts (Symbol, ZapfDingbats) have their own encodings
118
+ font_dict.merge!(:Encoding => :WinAnsiEncoding) unless symbolic?
119
+
120
+ @document.ref!(font_dict)
121
+ end
122
+
123
+ def symbolic?
124
+ attributes["characterset"] == "Special"
125
+ end
126
+
127
+ def find_font(file)
128
+ self.class.metrics_path.find { |f| File.exist? "#{f}/#{file}" } + "/#{file}"
129
+ rescue NoMethodError
130
+ raise Prawn::Errors::UnknownFont,
131
+ "Couldn't find the font: #{file} in any of:\n" +
132
+ self.class.metrics_path.join("\n")
133
+ end
134
+
135
+ def parse_afm(file_name)
136
+ section = []
137
+
138
+ File.foreach(file_name) do |line|
139
+ case line
140
+ when /^Start(\w+)/
141
+ section.push $1
142
+ next
143
+ when /^End(\w+)/
144
+ section.pop
145
+ next
146
+ end
147
+
148
+ case section
149
+ when ["FontMetrics", "CharMetrics"]
150
+ next unless line =~ /^CH?\s/
151
+
152
+ name = line[/\bN\s+(\.?\w+)\s*;/, 1]
153
+ @glyph_widths[name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
154
+ @bounding_boxes[name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
155
+ when ["FontMetrics", "KernData", "KernPairs"]
156
+ next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
157
+ @kern_pairs[[$1, $2]] = $3.to_i
158
+ when ["FontMetrics", "KernData", "TrackKern"],
159
+ ["FontMetrics", "Composites"]
160
+ next
161
+ else
162
+ parse_generic_afm_attribute(line)
163
+ end
164
+ end
165
+ end
166
+
167
+ def parse_generic_afm_attribute(line)
168
+ line =~ /(^\w+)\s+(.*)/
169
+ key, value = $1.to_s.downcase, $2
170
+
171
+ @attributes[key] = @attributes[key] ?
172
+ Array(@attributes[key]) << value : value
173
+ end
174
+
175
+ # converts a string into an array with spacing offsets
176
+ # bewteen characters that need to be kerned
177
+ #
178
+ # String *must* be encoded as WinAnsi
179
+ #
180
+ def kern(string)
181
+ kerned = [[]]
182
+ last_byte = nil
183
+
184
+ kern_pairs = latin_kern_pairs_table
185
+
186
+ string.unpack("C*").each do |byte|
187
+ if k = last_byte && kern_pairs[[last_byte, byte]]
188
+ kerned << -k << [byte]
189
+ else
190
+ kerned.last << byte
191
+ end
192
+ last_byte = byte
193
+ end
194
+
195
+ kerned.map { |e|
196
+ e = (Array === e ? e.pack("C*") : e)
197
+ e.respond_to?(:force_encoding) ? e.force_encoding("Windows-1252") : e
198
+ }
199
+ end
200
+
201
+ def latin_kern_pairs_table
202
+ @kern_pairs_table ||= @kern_pairs.inject({}) do |h,p|
203
+ h[p[0].map { |n| Encoding::WinAnsi::CHARACTERS.index(n) }] = p[1]
204
+ h
205
+ end
206
+ end
207
+
208
+ def latin_glyphs_table
209
+ @glyphs_table ||= (0..255).map do |i|
210
+ @glyph_widths[Encoding::WinAnsi::CHARACTERS[i]].to_i
211
+ end
212
+ end
213
+
214
+ private
215
+
216
+ def unscaled_width_of(string)
217
+ glyph_table = latin_glyphs_table
218
+
219
+ string.unpack("C*").inject(0) do |s,r|
220
+ s + glyph_table[r]
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,42 @@
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 'prawn/font/ttf'
10
+
11
+ module Prawn
12
+ class Font
13
+ class DFont < TTF
14
+
15
+ # Returns a list of the names of all named fonts in the given dfont file.
16
+ # Note that fonts are not required to be named in a dfont file, so the
17
+ # list may be empty even if the file does contain fonts. Also, note that
18
+ # the list is returned in no particular order, so the first font in the
19
+ # list is not necessarily the font at index 0 in the file.
20
+ #
21
+ def self.named_fonts(file)
22
+ TTFunk::ResourceFile.open(file) do |f|
23
+ return f.resources_for("sfnt")
24
+ end
25
+ end
26
+
27
+ # Returns the number of fonts contained in the dfont file.
28
+ #
29
+ def self.font_count(file)
30
+ TTFunk::ResourceFile.open(file) do |f|
31
+ return f.map["sfnt"][:list].length
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def read_ttf_file
38
+ TTFunk::File.from_dfont(@name, @options[:font] || 0)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,350 @@
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
+ class TTF < Font
16
+ attr_reader :ttf, :subsets
17
+
18
+ def unicode?
19
+ true
20
+ end
21
+
22
+ def initialize(document, name, options={})
23
+ super
24
+
25
+ @ttf = read_ttf_file
26
+ @subsets = TTFunk::SubsetCollection.new(@ttf)
27
+
28
+ @attributes = {}
29
+ @bounding_boxes = {}
30
+ @char_widths = {}
31
+ @has_kerning_data = @ttf.kerning.exists? && @ttf.kerning.tables.any?
32
+
33
+ @ascender = Integer(@ttf.ascent * scale_factor)
34
+ @descender = Integer(@ttf.descent * scale_factor)
35
+ @line_gap = Integer(@ttf.line_gap * scale_factor)
36
+ end
37
+
38
+ # NOTE: +string+ must be UTF8-encoded.
39
+ def compute_width_of(string, options={}) #:nodoc:
40
+ scale = (options[:size] || size) / 1000.0
41
+ if options[:kerning]
42
+ kern(string).inject(0) do |s,r|
43
+ if r.is_a?(Numeric)
44
+ s - r
45
+ else
46
+ r.inject(s) { |s2, u| s2 + character_width_by_code(u) }
47
+ end
48
+ end * scale
49
+ else
50
+ string.unpack("U*").inject(0) do |s,r|
51
+ s + character_width_by_code(r)
52
+ end * scale
53
+ end
54
+ end
55
+
56
+ # The font bbox, as an array of integers
57
+ #
58
+ def bbox
59
+ @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
60
+ end
61
+
62
+ # Returns true if the font has kerning data, false otherwise
63
+ def has_kerning_data?
64
+ @has_kerning_data
65
+ end
66
+
67
+ # Perform any changes to the string that need to happen
68
+ # before it is rendered to the canvas. Returns an array of
69
+ # subset "chunks", where the even-numbered indices are the
70
+ # font subset number, and the following entry element is
71
+ # either a string or an array (for kerned text).
72
+ #
73
+ # The +text+ parameter must be UTF8-encoded.
74
+ #
75
+ def encode_text(text,options={})
76
+ text = text.chomp
77
+
78
+ if options[:kerning]
79
+ last_subset = nil
80
+ kern(text).inject([]) do |result, element|
81
+ if element.is_a?(Numeric)
82
+ result.last[1] = [result.last[1]] unless result.last[1].is_a?(Array)
83
+ result.last[1] << element
84
+ result
85
+ else
86
+ encoded = @subsets.encode(element)
87
+
88
+ if encoded.first[0] == last_subset
89
+ result.last[1] << encoded.first[1]
90
+ encoded.shift
91
+ end
92
+
93
+ if encoded.any?
94
+ last_subset = encoded.last[0]
95
+ result + encoded
96
+ else
97
+ result
98
+ end
99
+ end
100
+ end
101
+ else
102
+ @subsets.encode(text.unpack("U*"))
103
+ end
104
+ end
105
+
106
+ def basename
107
+ @basename ||= @ttf.name.postscript_name
108
+ end
109
+
110
+ # not sure how to compute this for true-type fonts...
111
+ def stemV
112
+ 0
113
+ end
114
+
115
+ def italic_angle
116
+ @italic_angle ||= if @ttf.postscript.exists?
117
+ raw = @ttf.postscript.italic_angle
118
+ hi, low = raw >> 16, raw & 0xFF
119
+ hi = -((hi ^ 0xFFFF) + 1) if hi & 0x8000 != 0
120
+ "#{hi}.#{low}".to_f
121
+ else
122
+ 0
123
+ end
124
+ end
125
+
126
+ def cap_height
127
+ @cap_height ||= begin
128
+ height = @ttf.os2.exists? && @ttf.os2.cap_height || 0
129
+ height == 0 ? ascender : height
130
+ end
131
+ end
132
+
133
+ def x_height
134
+ # FIXME: seems like if os2 table doesn't exist, we could
135
+ # just find the height of the lower-case 'x' glyph?
136
+ @ttf.os2.exists? && @ttf.os2.x_height || 0
137
+ end
138
+
139
+ def family_class
140
+ @family_class ||= (@ttf.os2.exists? && @ttf.os2.family_class || 0) >> 8
141
+ end
142
+
143
+ def serif?
144
+ @serif ||= [1,2,3,4,5,7].include?(family_class)
145
+ end
146
+
147
+ def script?
148
+ @script ||= family_class == 10
149
+ end
150
+
151
+ def pdf_flags
152
+ @flags ||= begin
153
+ flags = 0
154
+ flags |= 0x0001 if @ttf.postscript.fixed_pitch?
155
+ flags |= 0x0002 if serif?
156
+ flags |= 0x0008 if script?
157
+ flags |= 0x0040 if italic_angle != 0
158
+ flags |= 0x0004 # assume the font contains at least some non-latin characters
159
+ end
160
+ end
161
+
162
+ def normalize_encoding(text)
163
+ if text.respond_to?(:encode)
164
+ # if we're running under a M17n aware VM, ensure the string provided is
165
+ # UTF-8 (by converting it if necessary)
166
+ begin
167
+ text.encode("UTF-8")
168
+ rescue
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
+ else
175
+ # on a non M17N aware VM, use unpack as a hackish way to verify the
176
+ # string is valid utf-8. I thought it was better than loading iconv
177
+ # though.
178
+ begin
179
+ text.unpack("U*")
180
+ return text.dup
181
+ rescue
182
+ raise Prawn::Errors::IncompatibleStringEncoding, "The string you " +
183
+ "are attempting to render is not encoded in valid UTF-8."
184
+ end
185
+ end
186
+ end
187
+
188
+ private
189
+
190
+ def cmap
191
+ @cmap ||= @ttf.cmap.unicode.first or raise("no unicode cmap for font")
192
+ end
193
+
194
+ # +string+ must be UTF8-encoded.
195
+ #
196
+ # Returns an array. If an element is a numeric, it represents the
197
+ # kern amount to inject at that position. Otherwise, the element
198
+ # is an array of UTF-16 characters.
199
+ def kern(string)
200
+ a = []
201
+
202
+ string.unpack("U*").each do |r|
203
+ if a.empty?
204
+ a << [r]
205
+ elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]])
206
+ kern *= scale_factor
207
+ a << -kern << [r]
208
+ else
209
+ a.last << r
210
+ end
211
+ end
212
+
213
+ a
214
+ end
215
+
216
+ def kern_pairs_table
217
+ @kerning_data ||= has_kerning_data? ? @ttf.kerning.tables.first.pairs : {}
218
+ end
219
+
220
+ def cid_to_gid_map
221
+ max = cmap.code_map.keys.max
222
+ (0..max).map { |cid| cmap[cid] }.pack("n*")
223
+ end
224
+
225
+ def hmtx
226
+ @hmtx ||= @ttf.horizontal_metrics
227
+ end
228
+
229
+ def character_width_by_code(code)
230
+ return 0 unless cmap[code]
231
+ @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor)
232
+ end
233
+
234
+ def scale_factor
235
+ @scale ||= 1000.0 / @ttf.header.units_per_em
236
+ end
237
+
238
+ def register(subset)
239
+ temp_name = @ttf.name.postscript_name.gsub("\0","").to_sym
240
+ ref = @document.ref!(:Type => :Font, :BaseFont => temp_name)
241
+
242
+ # Embed the font metrics in the document after everything has been
243
+ # drawn, just before the document is emitted.
244
+ @document.before_render { |doc| embed(ref, subset) }
245
+
246
+ ref
247
+ end
248
+
249
+ def embed(reference, subset)
250
+ font_content = @subsets[subset].encode
251
+
252
+ # FIXME: we need postscript_name and glyph widths from the font
253
+ # subset. Perhaps this could be done by querying the subset,
254
+ # rather than by parsing the font that the subset produces?
255
+ font = TTFunk::File.new(font_content)
256
+
257
+ # empirically, it looks like Adobe Reader will not display fonts
258
+ # if their font name is more than 33 bytes long. Strange. But true.
259
+ basename = font.name.postscript_name[0, 33].gsub("\0","")
260
+
261
+ raise "Can't detect a postscript name for #{file}" if basename.nil?
262
+
263
+ compressed_font = Zlib::Deflate.deflate(font_content)
264
+
265
+ fontfile = @document.ref!(:Length => compressed_font.size,
266
+ :Length1 => font_content.size,
267
+ :Filter => :FlateDecode )
268
+ fontfile << compressed_font
269
+
270
+ descriptor = @document.ref!(:Type => :FontDescriptor,
271
+ :FontName => basename.to_sym,
272
+ :FontFile2 => fontfile,
273
+ :FontBBox => bbox,
274
+ :Flags => pdf_flags,
275
+ :StemV => stemV,
276
+ :ItalicAngle => italic_angle,
277
+ :Ascent => ascender,
278
+ :Descent => descender,
279
+ :CapHeight => cap_height,
280
+ :XHeight => x_height)
281
+
282
+ hmtx = font.horizontal_metrics
283
+ widths = font.cmap.tables.first.code_map.map { |gid|
284
+ Integer(hmtx.widths[gid] * scale_factor) }[32..-1]
285
+
286
+ # It would be nice to have Encoding set for the macroman subsets,
287
+ # and only do a ToUnicode cmap for non-encoded unicode subsets.
288
+ # However, apparently Adobe Reader won't render MacRoman encoded
289
+ # subsets if original font contains unicode characters. (It has to
290
+ # be some flag or something that ttfunk is simply copying over...
291
+ # but I can't figure out which flag that is.)
292
+ #
293
+ # For now, it's simplest to just create a unicode cmap for every font.
294
+ # It offends my inner purist, but it'll do.
295
+
296
+ map = @subsets[subset].to_unicode_map
297
+
298
+ ranges = [[]]
299
+ lines = map.keys.sort.inject("") do |s, code|
300
+ ranges << [] if ranges.last.length >= 100
301
+ unicode = map[code]
302
+ ranges.last << "<%02x><%04x>" % [code, unicode]
303
+ end
304
+
305
+ range_blocks = ranges.inject("") do |s, list|
306
+ s << "%d beginbfchar\n%s\nendbfchar\n" % [list.length, list.join("\n")]
307
+ end
308
+
309
+ to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip
310
+
311
+ cmap = @document.ref!({})
312
+ cmap << to_unicode_cmap
313
+ cmap.compress_stream
314
+
315
+ reference.data.update(:Subtype => :TrueType,
316
+ :BaseFont => basename.to_sym,
317
+ :FontDescriptor => descriptor,
318
+ :FirstChar => 32,
319
+ :LastChar => 255,
320
+ :Widths => @document.ref!(widths),
321
+ :ToUnicode => cmap)
322
+ end
323
+
324
+ UNICODE_CMAP_TEMPLATE = <<-STR.strip.gsub(/^\s*/, "")
325
+ /CIDInit /ProcSet findresource begin
326
+ 12 dict begin
327
+ begincmap
328
+ /CIDSystemInfo <<
329
+ /Registry (Adobe)
330
+ /Ordering (UCS)
331
+ /Supplement 0
332
+ >> def
333
+ /CMapName /Adobe-Identity-UCS def
334
+ /CMapType 2 def
335
+ 1 begincodespacerange
336
+ <00><ff>
337
+ endcodespacerange
338
+ %s
339
+ endcmap
340
+ CMapName currentdict /CMap defineresource pop
341
+ end
342
+ end
343
+ STR
344
+
345
+ def read_ttf_file
346
+ TTFunk::File.open(@name)
347
+ end
348
+ end
349
+ end
350
+ end