# 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
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 229 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
# File lib/prawn/font/ttf.rb, line 220 220: def cid_to_gid_map 221: max = cmap.code_map.keys.max 222: (0..max).map { |cid| cmap[cid] }.pack("n*") 223: end
# File lib/prawn/font/ttf.rb, line 190 190: def cmap 191: @cmap ||= @ttf.cmap.unicode.first or raise("no unicode cmap for font") 192: end
# File lib/prawn/font/ttf.rb, line 249 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("\00"","") 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
# File lib/prawn/font/ttf.rb, line 225 225: def hmtx 226: @hmtx ||= @ttf.horizontal_metrics 227: 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 199 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
# File lib/prawn/font/ttf.rb, line 216 216: def kern_pairs_table 217: @kerning_data ||= has_kerning_data? ? @ttf.kerning.tables.first.pairs : {} 218: end
# File lib/prawn/font/ttf.rb, line 345 345: def read_ttf_file 346: TTFunk::File.open(@name) 347: end
# File lib/prawn/font/ttf.rb, line 238 238: def register(subset) 239: temp_name = @ttf.name.postscript_name.gsub("\00"","").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
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.