prawn 0.3.0 → 0.4.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 (87) hide show
  1. data/Rakefile +3 -1
  2. data/data/fonts/Action Man.dfont +0 -0
  3. data/examples/general/measurement_units.rb +2 -2
  4. data/examples/graphics/image_flow.rb +2 -2
  5. data/examples/graphics/stroke_bounds.rb +1 -1
  6. data/examples/m17n/win_ansi_charset.rb +3 -3
  7. data/examples/text/dfont.rb +49 -0
  8. data/examples/text/flowing_text_with_header_and_footer.rb +2 -48
  9. data/examples/text/font_calculations.rb +7 -6
  10. data/examples/text/font_size.rb +4 -4
  11. data/examples/text/text_flow.rb +1 -1
  12. data/lib/prawn.rb +6 -3
  13. data/lib/prawn/compatibility.rb +12 -17
  14. data/lib/prawn/document.rb +10 -10
  15. data/lib/prawn/document/internals.rb +8 -3
  16. data/lib/prawn/document/text.rb +39 -57
  17. data/lib/prawn/document/text/box.rb +1 -2
  18. data/lib/prawn/document/text/wrapping.rb +59 -0
  19. data/lib/prawn/errors.rb +0 -8
  20. data/lib/prawn/font.rb +192 -277
  21. data/lib/prawn/font/afm.rb +199 -0
  22. data/lib/prawn/font/dfont.rb +31 -0
  23. data/lib/prawn/font/ttf.rb +318 -0
  24. data/lib/prawn/graphics.rb +7 -2
  25. data/lib/prawn/images/png.rb +1 -1
  26. data/lib/prawn/reference.rb +7 -4
  27. data/spec/font_spec.rb +154 -61
  28. data/spec/text_spec.rb +47 -6
  29. data/vendor/pdf-inspector/lib/pdf/inspector.rb +1 -1
  30. data/vendor/ttfunk/example.rb +42 -2
  31. data/vendor/ttfunk/lib/ttfunk.rb +96 -42
  32. data/vendor/ttfunk/lib/ttfunk/directory.rb +17 -0
  33. data/vendor/ttfunk/lib/ttfunk/encoding/mac_roman.rb +88 -0
  34. data/vendor/ttfunk/lib/ttfunk/encoding/windows_1252.rb +69 -0
  35. data/vendor/ttfunk/lib/ttfunk/reader.rb +44 -0
  36. data/vendor/ttfunk/lib/ttfunk/resource_file.rb +78 -0
  37. data/vendor/ttfunk/lib/ttfunk/subset.rb +18 -0
  38. data/vendor/ttfunk/lib/ttfunk/subset/base.rb +141 -0
  39. data/vendor/ttfunk/lib/ttfunk/subset/mac_roman.rb +46 -0
  40. data/vendor/ttfunk/lib/ttfunk/subset/unicode.rb +48 -0
  41. data/vendor/ttfunk/lib/ttfunk/subset/unicode_8bit.rb +63 -0
  42. data/vendor/ttfunk/lib/ttfunk/subset/windows_1252.rb +51 -0
  43. data/vendor/ttfunk/lib/ttfunk/subset_collection.rb +72 -0
  44. data/vendor/ttfunk/lib/ttfunk/table.rb +37 -18
  45. data/vendor/ttfunk/lib/ttfunk/table/cmap.rb +24 -84
  46. data/vendor/ttfunk/lib/ttfunk/table/cmap/format00.rb +54 -0
  47. data/vendor/ttfunk/lib/ttfunk/table/cmap/format04.rb +126 -0
  48. data/vendor/ttfunk/lib/ttfunk/table/cmap/subtable.rb +79 -0
  49. data/vendor/ttfunk/lib/ttfunk/table/glyf.rb +64 -0
  50. data/vendor/ttfunk/lib/ttfunk/table/glyf/compound.rb +81 -0
  51. data/vendor/ttfunk/lib/ttfunk/table/glyf/simple.rb +37 -0
  52. data/vendor/ttfunk/lib/ttfunk/table/head.rb +38 -19
  53. data/vendor/ttfunk/lib/ttfunk/table/hhea.rb +35 -21
  54. data/vendor/ttfunk/lib/ttfunk/table/hmtx.rb +40 -13
  55. data/vendor/ttfunk/lib/ttfunk/table/kern.rb +69 -38
  56. data/vendor/ttfunk/lib/ttfunk/table/kern/format0.rb +62 -0
  57. data/vendor/ttfunk/lib/ttfunk/table/loca.rb +43 -0
  58. data/vendor/ttfunk/lib/ttfunk/table/maxp.rb +34 -11
  59. data/vendor/ttfunk/lib/ttfunk/table/name.rb +109 -42
  60. data/vendor/ttfunk/lib/ttfunk/table/os2.rb +78 -0
  61. data/vendor/ttfunk/lib/ttfunk/table/post.rb +91 -0
  62. data/vendor/ttfunk/lib/ttfunk/table/post/format10.rb +43 -0
  63. data/vendor/ttfunk/lib/ttfunk/table/post/format20.rb +35 -0
  64. data/vendor/ttfunk/lib/ttfunk/table/post/format25.rb +23 -0
  65. data/vendor/ttfunk/lib/ttfunk/table/post/format30.rb +17 -0
  66. data/vendor/ttfunk/lib/ttfunk/table/post/format40.rb +17 -0
  67. data/vendor/ttfunk/lib/ttfunk/table/simple.rb +14 -0
  68. metadata +54 -25
  69. data/examples/table/addressbook.csv +0 -6
  70. data/examples/table/cell.rb +0 -40
  71. data/examples/table/currency.csv +0 -1834
  72. data/examples/table/fancy_table.rb +0 -62
  73. data/examples/table/ruport_formatter.rb +0 -53
  74. data/examples/table/table.rb +0 -51
  75. data/examples/table/table_alignment.rb +0 -18
  76. data/examples/table/table_border_color.rb +0 -17
  77. data/examples/table/table_colspan.rb +0 -19
  78. data/examples/table/table_header_color.rb +0 -19
  79. data/examples/table/table_header_underline.rb +0 -15
  80. data/lib/prawn/document/table.rb +0 -338
  81. data/lib/prawn/font/cmap.rb +0 -59
  82. data/lib/prawn/font/metrics.rb +0 -378
  83. data/lib/prawn/font/wrapping.rb +0 -47
  84. data/lib/prawn/graphics/cell.rb +0 -264
  85. data/spec/metrics_spec.rb +0 -62
  86. data/spec/table_spec.rb +0 -179
  87. data/vendor/ttfunk/lib/ttfunk/table/directory.rb +0 -25
@@ -0,0 +1,199 @@
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 width_of(string, options={})
57
+ scale = (options[:size] || size) / 1000.0
58
+
59
+ if options[:kerning]
60
+ no_kern_opts = options.merge(:kerning => false)
61
+ kern(string).inject(0) do |s,r|
62
+ if r.is_a? String
63
+ s + width_of(r, no_kern_opts)
64
+ else
65
+ s - (r * scale)
66
+ end
67
+ end
68
+ else
69
+ string.unpack("C*").inject(0) do |s,r|
70
+ s + latin_glyphs_table[r]
71
+ end * scale
72
+ end
73
+ end
74
+
75
+ def has_kerning_data?
76
+ @kern_pairs.any?
77
+ end
78
+
79
+ # built-in fonts only work with winansi encoding, so translate the
80
+ # string. Changes the encoding in-place, so the argument itself
81
+ # is replaced with a string in WinAnsi encoding.
82
+ def normalize_encoding(text)
83
+ enc = Prawn::Encoding::WinAnsi.new
84
+ text.replace text.unpack("U*").collect { |i| enc[i] }.pack("C*")
85
+ end
86
+
87
+ # Perform any changes to the string that need to happen
88
+ # before it is rendered to the canvas. Returns an array of
89
+ # subset "chunks", where each chunk is an array of two elements.
90
+ # The first element is the font subset number, and the second
91
+ # is either a string or an array (for kerned text).
92
+ #
93
+ # For Adobe fonts, there is only ever a single subset, so
94
+ # the first element of the array is "0", and the second is
95
+ # the string itself (or an array, if kerning is performed).
96
+ #
97
+ # The +text+ parameter must be in WinAnsi encoding (cp1252).
98
+ def encode_text(text, options={})
99
+ [[0, options[:kerning] ? kern(text) : text]]
100
+ end
101
+
102
+ private
103
+
104
+ def register(subset)
105
+ @document.ref(:Type => :Font,
106
+ :Subtype => :Type1,
107
+ :BaseFont => name.to_sym,
108
+ :Encoding => :WinAnsiEncoding)
109
+ end
110
+
111
+ def find_font(file)
112
+ self.class.metrics_path.find { |f| File.exist? "#{f}/#{file}" } + "/#{file}"
113
+ rescue NoMethodError
114
+ raise Prawn::Errors::UnknownFont,
115
+ "Couldn't find the font: #{file} in any of:\n" +
116
+ self.class.metrics_path.join("\n")
117
+ end
118
+
119
+ def parse_afm(file_name)
120
+ section = []
121
+
122
+ File.foreach(file_name) do |line|
123
+ case line
124
+ when /^Start(\w+)/
125
+ section.push $1
126
+ next
127
+ when /^End(\w+)/
128
+ section.pop
129
+ next
130
+ end
131
+
132
+ case section
133
+ when ["FontMetrics", "CharMetrics"]
134
+ next unless line =~ /^CH?\s/
135
+
136
+ name = line[/\bN\s+(\.?\w+)\s*;/, 1]
137
+ @glyph_widths[name] = line[/\bWX\s+(\d+)\s*;/, 1].to_i
138
+ @bounding_boxes[name] = line[/\bB\s+([^;]+);/, 1].to_s.rstrip
139
+ when ["FontMetrics", "KernData", "KernPairs"]
140
+ next unless line =~ /^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/
141
+ @kern_pairs[[$1, $2]] = $3.to_i
142
+ when ["FontMetrics", "KernData", "TrackKern"],
143
+ ["FontMetrics", "Composites"]
144
+ next
145
+ else
146
+ parse_generic_afm_attribute(line)
147
+ end
148
+ end
149
+ end
150
+
151
+ def parse_generic_afm_attribute(line)
152
+ line =~ /(^\w+)\s+(.*)/
153
+ key, value = $1.to_s.downcase, $2
154
+
155
+ @attributes[key] = @attributes[key] ?
156
+ Array(@attributes[key]) << value : value
157
+ end
158
+
159
+ # converts a string into an array with spacing offsets
160
+ # bewteen characters that need to be kerned
161
+ #
162
+ # String *must* be encoded as WinAnsi
163
+ #
164
+ def kern(string)
165
+ kerned = string.unpack("C*").inject([]) do |a,r|
166
+ if a.last.is_a? Array
167
+ if k = latin_kern_pairs_table[[a.last.last, r]]
168
+ a << k << [r]
169
+ else
170
+ a.last << r
171
+ end
172
+ else
173
+ a << [r]
174
+ end
175
+ a
176
+ end
177
+
178
+ kerned.map { |r|
179
+ i = r.is_a?(Array) ? r.pack("C*") : r
180
+ i.force_encoding("Windows-1252") if i.respond_to?(:force_encoding)
181
+ i.is_a?(Numeric) ? -i : i
182
+ }
183
+ end
184
+
185
+ def latin_kern_pairs_table
186
+ @kern_pairs_table ||= @kern_pairs.inject({}) do |h,p|
187
+ h[p[0].map { |n| Encoding::WinAnsi::CHARACTERS.index(n) }] = p[1]
188
+ h
189
+ end
190
+ end
191
+
192
+ def latin_glyphs_table
193
+ @glyphs_table ||= (0..255).map do |i|
194
+ @glyph_widths[Encoding::WinAnsi::CHARACTERS[i]].to_i
195
+ end
196
+ end
197
+ end
198
+ end
199
+ 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,318 @@
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 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
+ rescue
164
+ raise Prawn::Errors::IncompatibleStringEncoding, "The string you " +
165
+ "are attempting to render is not encoded in valid UTF-8."
166
+ end
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ def cmap
173
+ @cmap ||= @ttf.cmap.unicode.first or raise("no unicode cmap for font")
174
+ end
175
+
176
+ # +string+ must be UTF8-encoded.
177
+ #
178
+ # Returns an array. If an element is a numeric, it represents the
179
+ # kern amount to inject at that position. Otherwise, the element
180
+ # is an array of UTF-16 characters.
181
+ def kern(string)
182
+ a = []
183
+
184
+ string.unpack("U*").each do |r|
185
+ if a.empty?
186
+ a << [r]
187
+ elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]])
188
+ kern *= scale_factor
189
+ a << -kern << [r]
190
+ else
191
+ a.last << r
192
+ end
193
+ end
194
+
195
+ a
196
+ end
197
+
198
+ def kern_pairs_table
199
+ @kerning_data ||= has_kerning_data? ? @ttf.kerning.tables.first.pairs : {}
200
+ end
201
+
202
+ def cid_to_gid_map
203
+ max = cmap.code_map.keys.max
204
+ (0..max).map { |cid| cmap[cid] }.pack("n*")
205
+ end
206
+
207
+ def hmtx
208
+ @hmtx ||= @ttf.horizontal_metrics
209
+ end
210
+
211
+ def character_width_by_code(code)
212
+ return 0 unless cmap[code]
213
+ @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor)
214
+ end
215
+
216
+ def scale_factor
217
+ @scale ||= 1000.0 / @ttf.header.units_per_em
218
+ end
219
+
220
+ def register(subset)
221
+ temp_name = @ttf.name.postscript_name
222
+ @document.ref(:Type => :Font, :BaseFont => temp_name) { |ref| embed(ref, subset) }
223
+ end
224
+
225
+ def embed(reference, subset)
226
+ font_content = @subsets[subset].encode
227
+
228
+ # FIXME: we need postscript_name and glyph widths from the font
229
+ # subset. Perhaps this could be done by querying the subset,
230
+ # rather than by parsing the font that the subset produces?
231
+ font = TTFunk::File.new(font_content)
232
+ basename = font.name.postscript_name
233
+
234
+ raise "Can't detect a postscript name for #{file}" if basename.nil?
235
+
236
+ compressed_font = Zlib::Deflate.deflate(font_content)
237
+
238
+ fontfile = @document.ref(:Length => compressed_font.size,
239
+ :Length1 => font_content.size,
240
+ :Filter => :FlateDecode )
241
+ fontfile << compressed_font
242
+
243
+ descriptor = @document.ref(:Type => :FontDescriptor,
244
+ :FontName => basename,
245
+ :FontFile2 => fontfile,
246
+ :FontBBox => bbox,
247
+ :Flags => pdf_flags,
248
+ :StemV => stemV,
249
+ :ItalicAngle => italic_angle,
250
+ :Ascent => ascender,
251
+ :Descent => descender,
252
+ :CapHeight => cap_height,
253
+ :XHeight => x_height)
254
+
255
+ hmtx = font.horizontal_metrics
256
+ widths = font.cmap.tables.first.code_map.map { |gid|
257
+ Integer(hmtx.widths[gid] * scale_factor) }
258
+
259
+ reference.data.update(:Subtype => :TrueType,
260
+ :BaseFont => basename,
261
+ :FontDescriptor => descriptor,
262
+ :FirstChar => 0,
263
+ :LastChar => 255,
264
+ :Widths => @document.ref(widths))
265
+
266
+ if @subsets[subset].unicode?
267
+ map = @subsets[subset].to_unicode_map
268
+
269
+ ranges = [[]]
270
+ lines = map.keys.sort.inject("") do |s, code|
271
+ ranges << [] if ranges.last.length >= 100
272
+ unicode = map[code]
273
+ ranges.last << "<%02x><%04x>" % [code, unicode]
274
+ end
275
+
276
+ range_blocks = ranges.inject("") do |s, list|
277
+ s << "%d beginbfchar\n%s\nendbfchar\n" % [list.length, list.join("\n")]
278
+ end
279
+
280
+ to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip
281
+
282
+ cmap = @document.ref({})
283
+ cmap << to_unicode_cmap
284
+ cmap.compress_stream
285
+
286
+ @references[subset].data[:ToUnicode] = cmap
287
+ else
288
+ @references[subset].data[:Encoding] = :MacRomanEncoding
289
+ end
290
+ end
291
+
292
+ UNICODE_CMAP_TEMPLATE = <<-STR.strip.gsub(/^\s*/, "")
293
+ /CIDInit /ProcSet findresource begin
294
+ 12 dict begin
295
+ begincmap
296
+ /CIDSystemInfo <<
297
+ /Registry (Adobe)
298
+ /Ordering (UCS)
299
+ /Supplement 0
300
+ >> def
301
+ /CMapName /Adobe-Identity-UCS def
302
+ /CMapType 2 def
303
+ 1 begincodespacerange
304
+ <00><ff>
305
+ endcodespacerange
306
+ %s
307
+ endcmap
308
+ CMapName currentdict /CMap defineresource pop
309
+ end
310
+ end
311
+ STR
312
+
313
+ def read_ttf_file
314
+ TTFunk::File.open(@name)
315
+ end
316
+ end
317
+ end
318
+ end