lib/prawn/font/ttf.rb in prawn-1.0.0.rc2 vs lib/prawn/font/ttf.rb in prawn-1.0.0

- old
+ new

@@ -1,35 +1,37 @@ # encoding: utf-8 # prawn/font/ttf.rb : Implements AFM font support for Prawn # -# Copyright May 2008, Gregory Brown / James Healy / Jamis Buck +# Copyright May 2008, Gregory Brown / James Healy / Jamis Buck # All Rights Reserved. # # This is free software. Please see the LICENSE and COPYING files for details. require 'ttfunk' require 'ttfunk/subset_collection' module Prawn class Font + + # @private class TTF < Font attr_reader :ttf, :subsets def unicode? true end - + def initialize(document, name, options={}) super @ttf = read_ttf_file @subsets = TTFunk::SubsetCollection.new(@ttf) @attributes = {} - @bounding_boxes = {} - @char_widths = {} + @bounding_boxes = {} + @char_widths = {} @has_kerning_data = @ttf.kerning.exists? && @ttf.kerning.tables.any? @ascender = Integer(@ttf.ascent * scale_factor) @descender = Integer(@ttf.descent * scale_factor) @line_gap = Integer(@ttf.line_gap * scale_factor) @@ -40,30 +42,30 @@ scale = (options[:size] || size) / 1000.0 if options[:kerning] kern(string).inject(0) do |s,r| if r.is_a?(Numeric) s - r - else + else r.inject(s) { |s2, u| s2 + character_width_by_code(u) } end end * scale else string.codepoints.inject(0) do |s,r| s + character_width_by_code(r) end * scale end - end - + end + # The font bbox, as an array of integers - # + # def bbox @bbox ||= @ttf.bbox.map { |i| Integer(i * scale_factor) } end # Returns true if the font has kerning data, false otherwise def has_kerning_data? - @has_kerning_data + @has_kerning_data 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 @@ -75,11 +77,11 @@ def encode_text(text,options={}) text = text.chomp if options[:kerning] last_subset = nil - kern(text).inject([]) do |result, element| + kern(text).inject([]) do |result, element| if element.is_a?(Numeric) result.last[1] = [result.last[1]] unless result.last[1].is_a?(Array) result.last[1] << element result else @@ -100,11 +102,11 @@ end else @subsets.encode(text.unpack("U*")) end end - + def basename @basename ||= @ttf.name.postscript_name end # not sure how to compute this for true-type fonts... @@ -158,39 +160,47 @@ flags |= 0x0004 # assume the font contains at least some non-latin characters end end def normalize_encoding(text) - text.normalize_to_utf8 + begin + text.encode(::Encoding::UTF_8) + rescue => e + puts e + raise Prawn::Errors::IncompatibleStringEncoding, "Encoding " + + "#{text.encoding} can not be transparently converted to UTF-8. " + + "Please ensure the encoding of the string you are attempting " + + "to use is set correctly" + end end def glyph_present?(char) code = char.codepoints.first cmap[code] > 0 end # Returns the number of characters in +str+ (a UTF-8-encoded string). # def character_count(str) - str.unicode_length + str.length end private def cmap @cmap ||= @ttf.cmap.unicode.first or raise("no unicode cmap for font") 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. def kern(string) a = [] - string.codepoints do |r| + string.each_codepoint do |r| if a.empty? a << [r] elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]]) kern *= scale_factor a << -kern << [r] @@ -213,29 +223,29 @@ def hmtx @hmtx ||= @ttf.horizontal_metrics end - def character_width_by_code(code) + def character_width_by_code(code) return 0 unless cmap[code] # Some TTF fonts have nonzero widths for \n (UTF-8 / ASCII code: 10). # Patch around this as we'll never be drawing a newline with a width. return 0.0 if code == 10 @char_widths[code] ||= Integer(hmtx.widths[cmap[code]] * scale_factor) - end + end def scale_factor @scale ||= 1000.0 / @ttf.header.units_per_em end def register(subset) temp_name = @ttf.name.postscript_name.gsub("\0","").to_sym ref = @document.ref!(:Type => :Font, :BaseFont => temp_name) - # Embed the font metrics in the document after everything has been + # Embed the font metrics in the document after everything has been # drawn, just before the document is emitted. @document.before_render { |doc| embed(ref, subset) } ref end @@ -252,16 +262,14 @@ # if their font name is more than 33 bytes long. Strange. But true. basename = font.name.postscript_name[0, 33].gsub("\0","") raise "Can't detect a postscript name for #{file}" if basename.nil? - compressed_font = Zlib::Deflate.deflate(font_content) + fontfile = @document.ref!(:Length1 => font_content.size) + fontfile.stream << font_content + fontfile.stream.compress! - fontfile = @document.ref!(:Length1 => font_content.size, - :Filter => :FlateDecode ) - fontfile << compressed_font - descriptor = @document.ref!(:Type => :FontDescriptor, :FontName => basename.to_sym, :FontFile2 => fontfile, :FontBBox => bbox, :Flags => pdf_flags, @@ -287,11 +295,11 @@ # It offends my inner purist, but it'll do. map = @subsets[subset].to_unicode_map ranges = [[]] - lines = map.keys.sort.inject("") do |s, code| + map.keys.sort.inject("") do |s, code| ranges << [] if ranges.last.length >= 100 unicode = map[code] ranges.last << "<%02x><%04x>" % [code, unicode] end @@ -301,10 +309,10 @@ to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip cmap = @document.ref!({}) cmap << to_unicode_cmap - cmap.compress_stream + cmap.stream.compress! reference.data.update(:Subtype => :TrueType, :BaseFont => basename.to_sym, :FontDescriptor => descriptor, :FirstChar => 32,