prawn-core 0.5.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 (186) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +56 -0
  3. data/README +121 -0
  4. data/Rakefile +74 -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/16bit.alpha +0 -0
  29. data/data/images/16bit.dat +0 -0
  30. data/data/images/16bit.png +0 -0
  31. data/data/images/arrow.png +0 -0
  32. data/data/images/arrow2.png +0 -0
  33. data/data/images/barcode_issue.png +0 -0
  34. data/data/images/dice.alpha +0 -0
  35. data/data/images/dice.dat +0 -0
  36. data/data/images/dice.png +0 -0
  37. data/data/images/dice_interlaced.png +0 -0
  38. data/data/images/fractal.jpg +0 -0
  39. data/data/images/letterhead.jpg +0 -0
  40. data/data/images/page_white_text.alpha +0 -0
  41. data/data/images/page_white_text.dat +0 -0
  42. data/data/images/page_white_text.png +0 -0
  43. data/data/images/pigs.jpg +0 -0
  44. data/data/images/rails.dat +0 -0
  45. data/data/images/rails.png +0 -0
  46. data/data/images/ruport.png +0 -0
  47. data/data/images/ruport_data.dat +0 -0
  48. data/data/images/ruport_transparent.png +0 -0
  49. data/data/images/ruport_type0.png +0 -0
  50. data/data/images/stef.jpg +0 -0
  51. data/data/images/tru256.bmp +0 -0
  52. data/data/images/web-links.dat +1 -0
  53. data/data/images/web-links.png +0 -0
  54. data/data/shift_jis_text.txt +1 -0
  55. data/examples/bounding_box/bounding_boxes.rb +44 -0
  56. data/examples/bounding_box/russian_boxes.rb +37 -0
  57. data/examples/column_box/column_box_example.rb +44 -0
  58. data/examples/general/background.rb +20 -0
  59. data/examples/general/canvas.rb +16 -0
  60. data/examples/general/measurement_units.rb +52 -0
  61. data/examples/general/metadata-info.rb +17 -0
  62. data/examples/general/multi_page_layout.rb +17 -0
  63. data/examples/general/page_geometry.rb +32 -0
  64. data/examples/graphics/basic_images.rb +24 -0
  65. data/examples/graphics/cmyk.rb +13 -0
  66. data/examples/graphics/curves.rb +12 -0
  67. data/examples/graphics/hexagon.rb +14 -0
  68. data/examples/graphics/image_fit.rb +16 -0
  69. data/examples/graphics/image_flow.rb +38 -0
  70. data/examples/graphics/image_position.rb +18 -0
  71. data/examples/graphics/line.rb +33 -0
  72. data/examples/graphics/png_types.rb +23 -0
  73. data/examples/graphics/polygons.rb +17 -0
  74. data/examples/graphics/remote_images.rb +12 -0
  75. data/examples/graphics/ruport_style_helpers.rb +20 -0
  76. data/examples/graphics/stroke_bounds.rb +21 -0
  77. data/examples/m17n/chinese_text_wrapping.rb +20 -0
  78. data/examples/m17n/euro.rb +16 -0
  79. data/examples/m17n/sjis.rb +29 -0
  80. data/examples/m17n/utf8.rb +14 -0
  81. data/examples/m17n/win_ansi_charset.rb +55 -0
  82. data/examples/text/alignment.rb +19 -0
  83. data/examples/text/dfont.rb +49 -0
  84. data/examples/text/family_based_styling.rb +25 -0
  85. data/examples/text/font_calculations.rb +92 -0
  86. data/examples/text/font_size.rb +34 -0
  87. data/examples/text/kerning.rb +31 -0
  88. data/examples/text/simple_text.rb +18 -0
  89. data/examples/text/simple_text_ttf.rb +18 -0
  90. data/examples/text/span.rb +30 -0
  91. data/examples/text/text_box.rb +26 -0
  92. data/examples/text/text_flow.rb +68 -0
  93. data/lib/prawn/compatibility.rb +38 -0
  94. data/lib/prawn/core.rb +79 -0
  95. data/lib/prawn/document.rb +399 -0
  96. data/lib/prawn/document/annotations.rb +63 -0
  97. data/lib/prawn/document/bounding_box.rb +377 -0
  98. data/lib/prawn/document/column_box.rb +89 -0
  99. data/lib/prawn/document/destinations.rb +81 -0
  100. data/lib/prawn/document/internals.rb +133 -0
  101. data/lib/prawn/document/page_geometry.rb +149 -0
  102. data/lib/prawn/document/span.rb +55 -0
  103. data/lib/prawn/document/text.rb +186 -0
  104. data/lib/prawn/document/text/box.rb +83 -0
  105. data/lib/prawn/document/text/wrapping.rb +59 -0
  106. data/lib/prawn/encoding.rb +121 -0
  107. data/lib/prawn/errors.rb +49 -0
  108. data/lib/prawn/font.rb +292 -0
  109. data/lib/prawn/font/afm.rb +202 -0
  110. data/lib/prawn/font/dfont.rb +31 -0
  111. data/lib/prawn/font/ttf.rb +327 -0
  112. data/lib/prawn/graphics.rb +257 -0
  113. data/lib/prawn/graphics/color.rb +141 -0
  114. data/lib/prawn/images.rb +339 -0
  115. data/lib/prawn/images/jpg.rb +45 -0
  116. data/lib/prawn/images/png.rb +217 -0
  117. data/lib/prawn/literal_string.rb +14 -0
  118. data/lib/prawn/measurement_extensions.rb +46 -0
  119. data/lib/prawn/measurements.rb +71 -0
  120. data/lib/prawn/name_tree.rb +165 -0
  121. data/lib/prawn/pdf_object.rb +77 -0
  122. data/lib/prawn/reference.rb +59 -0
  123. data/spec/annotations_spec.rb +90 -0
  124. data/spec/bounding_box_spec.rb +141 -0
  125. data/spec/destinations_spec.rb +15 -0
  126. data/spec/document_spec.rb +178 -0
  127. data/spec/font_spec.rb +274 -0
  128. data/spec/graphics_spec.rb +209 -0
  129. data/spec/images_spec.rb +79 -0
  130. data/spec/jpg_spec.rb +25 -0
  131. data/spec/measurement_units_spec.rb +23 -0
  132. data/spec/name_tree_spec.rb +103 -0
  133. data/spec/pdf_object_spec.rb +117 -0
  134. data/spec/png_spec.rb +236 -0
  135. data/spec/reference_spec.rb +42 -0
  136. data/spec/span_spec.rb +45 -0
  137. data/spec/spec_helper.rb +23 -0
  138. data/spec/text_box_spec.rb +83 -0
  139. data/spec/text_spec.rb +178 -0
  140. data/vendor/pdf-inspector/README +18 -0
  141. data/vendor/pdf-inspector/lib/pdf/inspector.rb +25 -0
  142. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +80 -0
  143. data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +16 -0
  144. data/vendor/pdf-inspector/lib/pdf/inspector/text.rb +31 -0
  145. data/vendor/pdf-inspector/lib/pdf/inspector/xobject.rb +19 -0
  146. data/vendor/ttfunk/data/fonts/DejaVuSans.ttf +0 -0
  147. data/vendor/ttfunk/data/fonts/comicsans.ttf +0 -0
  148. data/vendor/ttfunk/example.rb +45 -0
  149. data/vendor/ttfunk/lib/ttfunk.rb +102 -0
  150. data/vendor/ttfunk/lib/ttfunk/directory.rb +17 -0
  151. data/vendor/ttfunk/lib/ttfunk/encoding/mac_roman.rb +88 -0
  152. data/vendor/ttfunk/lib/ttfunk/encoding/windows_1252.rb +69 -0
  153. data/vendor/ttfunk/lib/ttfunk/reader.rb +44 -0
  154. data/vendor/ttfunk/lib/ttfunk/resource_file.rb +78 -0
  155. data/vendor/ttfunk/lib/ttfunk/subset.rb +18 -0
  156. data/vendor/ttfunk/lib/ttfunk/subset/base.rb +141 -0
  157. data/vendor/ttfunk/lib/ttfunk/subset/mac_roman.rb +46 -0
  158. data/vendor/ttfunk/lib/ttfunk/subset/unicode.rb +48 -0
  159. data/vendor/ttfunk/lib/ttfunk/subset/unicode_8bit.rb +63 -0
  160. data/vendor/ttfunk/lib/ttfunk/subset/windows_1252.rb +51 -0
  161. data/vendor/ttfunk/lib/ttfunk/subset_collection.rb +72 -0
  162. data/vendor/ttfunk/lib/ttfunk/table.rb +46 -0
  163. data/vendor/ttfunk/lib/ttfunk/table/cmap.rb +34 -0
  164. data/vendor/ttfunk/lib/ttfunk/table/cmap/format00.rb +54 -0
  165. data/vendor/ttfunk/lib/ttfunk/table/cmap/format04.rb +126 -0
  166. data/vendor/ttfunk/lib/ttfunk/table/cmap/subtable.rb +79 -0
  167. data/vendor/ttfunk/lib/ttfunk/table/glyf.rb +64 -0
  168. data/vendor/ttfunk/lib/ttfunk/table/glyf/compound.rb +81 -0
  169. data/vendor/ttfunk/lib/ttfunk/table/glyf/simple.rb +37 -0
  170. data/vendor/ttfunk/lib/ttfunk/table/head.rb +44 -0
  171. data/vendor/ttfunk/lib/ttfunk/table/hhea.rb +41 -0
  172. data/vendor/ttfunk/lib/ttfunk/table/hmtx.rb +47 -0
  173. data/vendor/ttfunk/lib/ttfunk/table/kern.rb +79 -0
  174. data/vendor/ttfunk/lib/ttfunk/table/kern/format0.rb +62 -0
  175. data/vendor/ttfunk/lib/ttfunk/table/loca.rb +43 -0
  176. data/vendor/ttfunk/lib/ttfunk/table/maxp.rb +40 -0
  177. data/vendor/ttfunk/lib/ttfunk/table/name.rb +119 -0
  178. data/vendor/ttfunk/lib/ttfunk/table/os2.rb +78 -0
  179. data/vendor/ttfunk/lib/ttfunk/table/post.rb +91 -0
  180. data/vendor/ttfunk/lib/ttfunk/table/post/format10.rb +43 -0
  181. data/vendor/ttfunk/lib/ttfunk/table/post/format20.rb +35 -0
  182. data/vendor/ttfunk/lib/ttfunk/table/post/format25.rb +23 -0
  183. data/vendor/ttfunk/lib/ttfunk/table/post/format30.rb +17 -0
  184. data/vendor/ttfunk/lib/ttfunk/table/post/format40.rb +17 -0
  185. data/vendor/ttfunk/lib/ttfunk/table/simple.rb +14 -0
  186. metadata +245 -0
@@ -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.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
@@ -0,0 +1,31 @@
1
+ require 'prawn/font/ttf'
2
+
3
+ module Prawn
4
+ class Font
5
+ class DFont < TTF
6
+ # Returns a list of the names of all named fonts in the given dfont file.
7
+ # Note that fonts are not required to be named in a dfont file, so the
8
+ # list may be empty even if the file does contain fonts. Also, note that
9
+ # the list is returned in no particular order, so the first font in the
10
+ # list is not necessarily the font at index 0 in the file.
11
+ def self.named_fonts(file)
12
+ TTFunk::ResourceFile.open(file) do |file|
13
+ return file.resources_for("sfnt")
14
+ end
15
+ end
16
+
17
+ # Returns the number of fonts contained in the dfont file.
18
+ def self.font_count(file)
19
+ TTFunk::ResourceFile.open(file) do |file|
20
+ return file.map["sfnt"][:list].length
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def read_ttf_file
27
+ TTFunk::File.from_dfont(@name, @options[:font] || 0)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,327 @@
1
+ require 'ttfunk'
2
+ require 'ttfunk/subset_collection'
3
+
4
+ module Prawn
5
+ class Font
6
+ class TTF < Font
7
+ attr_reader :ttf, :subsets
8
+
9
+ def initialize(document, name, options={})
10
+ super
11
+
12
+ @ttf = read_ttf_file
13
+ @subsets = TTFunk::SubsetCollection.new(@ttf)
14
+
15
+ @attributes = {}
16
+ @bounding_boxes = {}
17
+ @char_widths = {}
18
+ @has_kerning_data = @ttf.kerning.exists? && @ttf.kerning.tables.any?
19
+
20
+ @ascender = Integer(@ttf.ascent * scale_factor)
21
+ @descender = Integer(@ttf.descent * scale_factor)
22
+ @line_gap = Integer(@ttf.line_gap * scale_factor)
23
+ end
24
+
25
+ # +string+ must be UTF8-encoded.
26
+ def compute_width_of(string, options={})
27
+ scale = (options[:size] || size) / 1000.0
28
+ if options[:kerning]
29
+ kern(string).inject(0) do |s,r|
30
+ if r.is_a?(Numeric)
31
+ s - r
32
+ else
33
+ r.inject(s) { |s2, u| s2 + character_width_by_code(u) }
34
+ end
35
+ end * scale
36
+ else
37
+ string.unpack("U*").inject(0) do |s,r|
38
+ s + character_width_by_code(r)
39
+ end * scale
40
+ end
41
+ end
42
+
43
+ def bbox
44
+ @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
45
+ end
46
+
47
+ def has_kerning_data?
48
+ @has_kerning_data
49
+ end
50
+
51
+ # Perform any changes to the string that need to happen
52
+ # before it is rendered to the canvas. Returns an array of
53
+ # subset "chunks", where the even-numbered indices are the
54
+ # font subset number, and the following entry element is
55
+ # either a string or an array (for kerned text).
56
+ #
57
+ # The +text+ parameter must be UTF8-encoded.
58
+ def encode_text(text,options={})
59
+ text = text.chomp
60
+
61
+ if options[:kerning]
62
+ last_subset = nil
63
+ kern(text).inject([]) do |result, element|
64
+ if element.is_a?(Numeric)
65
+ result.last[1] = [result.last[1]] unless result.last[1].is_a?(Array)
66
+ result.last[1] << element
67
+ result
68
+ else
69
+ encoded = @subsets.encode(element)
70
+
71
+ if encoded.first[0] == last_subset
72
+ result.last[1] << encoded.first[1]
73
+ encoded.shift
74
+ end
75
+
76
+ if encoded.any?
77
+ last_subset = encoded.last[0]
78
+ result + encoded
79
+ else
80
+ result
81
+ end
82
+ end
83
+ end
84
+ else
85
+ @subsets.encode(text.unpack("U*"))
86
+ end
87
+ end
88
+
89
+ def basename
90
+ @basename ||= @ttf.name.postscript_name
91
+ end
92
+
93
+ # not sure how to compute this for true-type fonts...
94
+ def stemV
95
+ 0
96
+ end
97
+
98
+ def italic_angle
99
+ @italic_angle ||= if @ttf.postscript.exists?
100
+ raw = @ttf.postscript.italic_angle
101
+ hi, low = raw >> 16, raw & 0xFF
102
+ hi = -((hi ^ 0xFFFF) + 1) if hi & 0x8000 != 0
103
+ "#{hi}.#{low}".to_f
104
+ else
105
+ 0
106
+ end
107
+ end
108
+
109
+ def cap_height
110
+ @cap_height ||= begin
111
+ height = @ttf.os2.exists? && @ttf.os2.cap_height || 0
112
+ height == 0 ? ascender : height
113
+ end
114
+ end
115
+
116
+ def x_height
117
+ # FIXME: seems like if os2 table doesn't exist, we could
118
+ # just find the height of the lower-case 'x' glyph?
119
+ @ttf.os2.exists? && @ttf.os2.x_height || 0
120
+ end
121
+
122
+ def family_class
123
+ @family_class ||= (@ttf.os2.exists? && @ttf.os2.family_class || 0) >> 8
124
+ end
125
+
126
+ def serif?
127
+ @serif ||= [1,2,3,4,5,7].include?(family_class)
128
+ end
129
+
130
+ def script?
131
+ @script ||= family_class == 10
132
+ end
133
+
134
+ def pdf_flags
135
+ @flags ||= begin
136
+ flags = 0
137
+ flags |= 0x0001 if @ttf.postscript.fixed_pitch?
138
+ flags |= 0x0002 if serif?
139
+ flags |= 0x0008 if script?
140
+ flags |= 0x0040 if italic_angle != 0
141
+ flags |= 0x0004 # assume the font contains at least some non-latin characters
142
+ end
143
+ end
144
+
145
+ def normalize_encoding(text)
146
+ if text.respond_to?(:encode)
147
+ # if we're running under a M17n aware VM, ensure the string provided is
148
+ # UTF-8 (by converting it if necessary)
149
+ begin
150
+ text.encode("UTF-8")
151
+ rescue
152
+ raise Prawn::Errors::IncompatibleStringEncoding, "Encoding " +
153
+ "#{text.encoding} can not be transparently converted to UTF-8. " +
154
+ "Please ensure the encoding of the string you are attempting " +
155
+ "to use is set correctly"
156
+ end
157
+ else
158
+ # on a non M17N aware VM, use unpack as a hackish way to verify the
159
+ # string is valid utf-8. I thought it was better than loading iconv
160
+ # though.
161
+ begin
162
+ text.unpack("U*")
163
+ return text.dup
164
+ rescue
165
+ raise Prawn::Errors::IncompatibleStringEncoding, "The string you " +
166
+ "are attempting to render is not encoded in valid UTF-8."
167
+ end
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def cmap
174
+ @cmap ||= @ttf.cmap.unicode.first or raise("no unicode cmap for font")
175
+ end
176
+
177
+ # +string+ must be UTF8-encoded.
178
+ #
179
+ # Returns an array. If an element is a numeric, it represents the
180
+ # kern amount to inject at that position. Otherwise, the element
181
+ # is an array of UTF-16 characters.
182
+ def kern(string)
183
+ a = []
184
+
185
+ string.unpack("U*").each do |r|
186
+ if a.empty?
187
+ a << [r]
188
+ elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]])
189
+ kern *= scale_factor
190
+ a << -kern << [r]
191
+ else
192
+ a.last << r
193
+ end
194
+ end
195
+
196
+ a
197
+ end
198
+
199
+ def kern_pairs_table
200
+ @kerning_data ||= has_kerning_data? ? @ttf.kerning.tables.first.pairs : {}
201
+ end
202
+
203
+ def cid_to_gid_map
204
+ max = cmap.code_map.keys.max
205
+ (0..max).map { |cid| cmap[cid] }.pack("n*")
206
+ end
207
+
208
+ def hmtx
209
+ @hmtx ||= @ttf.horizontal_metrics
210
+ end
211
+
212
+ def character_width_by_code(code)
213
+ return 0 unless cmap[code]
214
+ @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor)
215
+ end
216
+
217
+ def scale_factor
218
+ @scale ||= 1000.0 / @ttf.header.units_per_em
219
+ end
220
+
221
+ def register(subset)
222
+ temp_name = @ttf.name.postscript_name.gsub("\0","").to_sym
223
+ @document.ref(:Type => :Font, :BaseFont => temp_name) { |ref| embed(ref, subset) }
224
+ end
225
+
226
+ def embed(reference, subset)
227
+ font_content = @subsets[subset].encode
228
+
229
+ # FIXME: we need postscript_name and glyph widths from the font
230
+ # subset. Perhaps this could be done by querying the subset,
231
+ # rather than by parsing the font that the subset produces?
232
+ font = TTFunk::File.new(font_content)
233
+
234
+ # empirically, it looks like Adobe Reader will not display fonts
235
+ # if their font name is more than 33 bytes long. Strange. But true.
236
+ basename = font.name.postscript_name[0, 33].gsub("\0","")
237
+
238
+ raise "Can't detect a postscript name for #{file}" if basename.nil?
239
+
240
+ compressed_font = Zlib::Deflate.deflate(font_content)
241
+
242
+ fontfile = @document.ref(:Length => compressed_font.size,
243
+ :Length1 => font_content.size,
244
+ :Filter => :FlateDecode )
245
+ fontfile << compressed_font
246
+
247
+ descriptor = @document.ref(:Type => :FontDescriptor,
248
+ :FontName => basename.to_sym,
249
+ :FontFile2 => fontfile,
250
+ :FontBBox => bbox,
251
+ :Flags => pdf_flags,
252
+ :StemV => stemV,
253
+ :ItalicAngle => italic_angle,
254
+ :Ascent => ascender,
255
+ :Descent => descender,
256
+ :CapHeight => cap_height,
257
+ :XHeight => x_height)
258
+
259
+ hmtx = font.horizontal_metrics
260
+ widths = font.cmap.tables.first.code_map.map { |gid|
261
+ Integer(hmtx.widths[gid] * scale_factor) }[32..-1]
262
+
263
+ # It would be nice to have Encoding set for the macroman subsets,
264
+ # and only do a ToUnicode cmap for non-encoded unicode subsets.
265
+ # However, apparently Adobe Reader won't render MacRoman encoded
266
+ # subsets if original font contains unicode characters. (It has to
267
+ # be some flag or something that ttfunk is simply copying over...
268
+ # but I can't figure out which flag that is.)
269
+ #
270
+ # For now, it's simplest to just create a unicode cmap for every font.
271
+ # It offends my inner purist, but it'll do.
272
+
273
+ map = @subsets[subset].to_unicode_map
274
+
275
+ ranges = [[]]
276
+ lines = map.keys.sort.inject("") do |s, code|
277
+ ranges << [] if ranges.last.length >= 100
278
+ unicode = map[code]
279
+ ranges.last << "<%02x><%04x>" % [code, unicode]
280
+ end
281
+
282
+ range_blocks = ranges.inject("") do |s, list|
283
+ s << "%d beginbfchar\n%s\nendbfchar\n" % [list.length, list.join("\n")]
284
+ end
285
+
286
+ to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip
287
+
288
+ cmap = @document.ref({})
289
+ cmap << to_unicode_cmap
290
+ cmap.compress_stream
291
+
292
+ reference.data.update(:Subtype => :TrueType,
293
+ :BaseFont => basename.to_sym,
294
+ :FontDescriptor => descriptor,
295
+ :FirstChar => 32,
296
+ :LastChar => 255,
297
+ :Widths => @document.ref(widths),
298
+ :ToUnicode => cmap)
299
+ end
300
+
301
+ UNICODE_CMAP_TEMPLATE = <<-STR.strip.gsub(/^\s*/, "")
302
+ /CIDInit /ProcSet findresource begin
303
+ 12 dict begin
304
+ begincmap
305
+ /CIDSystemInfo <<
306
+ /Registry (Adobe)
307
+ /Ordering (UCS)
308
+ /Supplement 0
309
+ >> def
310
+ /CMapName /Adobe-Identity-UCS def
311
+ /CMapType 2 def
312
+ 1 begincodespacerange
313
+ <00><ff>
314
+ endcodespacerange
315
+ %s
316
+ endcmap
317
+ CMapName currentdict /CMap defineresource pop
318
+ end
319
+ end
320
+ STR
321
+
322
+ def read_ttf_file
323
+ TTFunk::File.open(@name)
324
+ end
325
+ end
326
+ end
327
+ end