# File lib/prawn/font/ttf.rb, line 22
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
# File lib/prawn/font/ttf.rb, line 106
106: def basename
107: @basename ||= @ttf.name.postscript_name
108: end
The font bbox, as an array of integers
# File lib/prawn/font/ttf.rb, line 58
58: def bbox
59: @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) }
60: end
# File lib/prawn/font/ttf.rb, line 126
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
Perform any changes to the string that need to happen before it is rendered to the canvas. Returns an array of subset “chunks”, where the even-numbered indices are the font subset number, and the following entry element is either a string or an array (for kerned text).
The text parameter must be UTF8-encoded.
# File lib/prawn/font/ttf.rb, line 75
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
# File lib/prawn/font/ttf.rb, line 139
139: def family_class
140: @family_class ||= (@ttf.os2.exists? && @ttf.os2.family_class || 0) >> 8
141: end
# File lib/prawn/font/ttf.rb, line 188
188: def glyph_present?(char)
189: code = char.unpack("U*").first
190: cmap[code] > 0
191: end
Returns true if the font has kerning data, false otherwise
# File lib/prawn/font/ttf.rb, line 63
63: def has_kerning_data?
64: @has_kerning_data
65: end
# File lib/prawn/font/ttf.rb, line 115
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
# File lib/prawn/font/ttf.rb, line 162
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
# File lib/prawn/font/ttf.rb, line 151
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
# File lib/prawn/font/ttf.rb, line 147
147: def script?
148: @script ||= family_class == 10
149: end
# File lib/prawn/font/ttf.rb, line 143
143: def serif?
144: @serif ||= [1,2,3,4,5,7].include?(family_class)
145: end
not sure how to compute this for true-type fonts...
# File lib/prawn/font/ttf.rb, line 111
111: def stemV
112: 0
113: end
# File lib/prawn/font/ttf.rb, line 234
234: def character_width_by_code(code)
235: return 0 unless cmap[code]
236: @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor)
237: end
# File lib/prawn/font/ttf.rb, line 225
225: def cid_to_gid_map
226: max = cmap.code_map.keys.max
227: (0..max).map { |cid| cmap[cid] }.pack("n*")
228: end
# File lib/prawn/font/ttf.rb, line 195
195: def cmap
196: @cmap ||= @ttf.cmap.unicode.first or raise("no unicode cmap for font")
197: end
# File lib/prawn/font/ttf.rb, line 254
254: def embed(reference, subset)
255: font_content = @subsets[subset].encode
256:
257: # FIXME: we need postscript_name and glyph widths from the font
258: # subset. Perhaps this could be done by querying the subset,
259: # rather than by parsing the font that the subset produces?
260: font = TTFunk::File.new(font_content)
261:
262: # empirically, it looks like Adobe Reader will not display fonts
263: # if their font name is more than 33 bytes long. Strange. But true.
264: basename = font.name.postscript_name[0, 33].gsub("\00"","")
265:
266: raise "Can't detect a postscript name for #{file}" if basename.nil?
267:
268: compressed_font = Zlib::Deflate.deflate(font_content)
269:
270: fontfile = @document.ref!(:Length => compressed_font.size,
271: :Length1 => font_content.size,
272: :Filter => :FlateDecode )
273: fontfile << compressed_font
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: lines = 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.compress_stream
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
# File lib/prawn/font/ttf.rb, line 230
230: def hmtx
231: @hmtx ||= @ttf.horizontal_metrics
232: end
string must be UTF8-encoded.
Returns an array. If an element is a numeric, it represents the kern amount to inject at that position. Otherwise, the element is an array of UTF-16 characters.
# File lib/prawn/font/ttf.rb, line 204
204: def kern(string)
205: a = []
206:
207: string.unpack("U*").each do |r|
208: if a.empty?
209: a << [r]
210: elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]])
211: kern *= scale_factor
212: a << -kern << [r]
213: else
214: a.last << r
215: end
216: end
217:
218: a
219: end
# File lib/prawn/font/ttf.rb, line 221
221: def kern_pairs_table
222: @kerning_data ||= has_kerning_data? ? @ttf.kerning.tables.first.pairs : {}
223: end
# File lib/prawn/font/ttf.rb, line 350
350: def read_ttf_file
351: TTFunk::File.open(@name)
352: end
# File lib/prawn/font/ttf.rb, line 243
243: def register(subset)
244: temp_name = @ttf.name.postscript_name.gsub("\00"","").to_sym
245: ref = @document.ref!(:Type => :Font, :BaseFont => temp_name)
246:
247: # Embed the font metrics in the document after everything has been
248: # drawn, just before the document is emitted.
249: @document.before_render { |doc| embed(ref, subset) }
250:
251: ref
252: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.