lib/prawn/font/ttf.rb in prawn-0.15.0 vs lib/prawn/font/ttf.rb in prawn-1.0.0.rc1

- old
+ new

@@ -1,37 +1,35 @@ # 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) @@ -42,30 +40,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| + string.unpack("U*").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 @@ -77,11 +75,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 @@ -102,11 +100,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... @@ -160,47 +158,65 @@ flags |= 0x0004 # assume the font contains at least some non-latin characters end end def normalize_encoding(text) - begin - text.encode(::Encoding::UTF_8) - rescue => e - puts e - raise Prawn::Errors::IncompatibleStringEncoding, "Encoding " + + if text.respond_to?(:encode) + # if we're running under a M17n aware VM, ensure the string provided is + # UTF-8 (by converting it if necessary) + begin + text.encode("UTF-8") + rescue + 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 + else + # on a non M17N aware VM, use unpack as a hackish way to verify the + # string is valid utf-8. I thought it was better than loading iconv + # though. + begin + text.unpack("U*") + return text.dup + rescue + raise Prawn::Errors::IncompatibleStringEncoding, "The string you " + + "are attempting to render is not encoded in valid UTF-8." + end end end def glyph_present?(char) - code = char.codepoints.first + code = char.unpack("U*").first cmap[code] > 0 end # Returns the number of characters in +str+ (a UTF-8-encoded string). # def character_count(str) - str.length + if str.respond_to?(:encode) + str.length + else + str.unpack("U*").length + end 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.each_codepoint do |r| + string.unpack("U*").each do |r| if a.empty? a << [r] elsif (kern = kern_pairs_table[[cmap[a.last.last], cmap[r]]]) kern *= scale_factor a << -kern << [r] @@ -223,29 +239,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 @@ -262,14 +278,17 @@ # 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? - fontfile = @document.ref!(:Length1 => font_content.size) - fontfile.stream << font_content - fontfile.stream.compress! + compressed_font = Zlib::Deflate.deflate(font_content) + fontfile = @document.ref!(:Length => compressed_font.size, + :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, @@ -295,11 +314,11 @@ # It offends my inner purist, but it'll do. map = @subsets[subset].to_unicode_map ranges = [[]] - map.keys.sort.inject("") do |s, code| + lines = map.keys.sort.inject("") do |s, code| ranges << [] if ranges.last.length >= 100 unicode = map[code] ranges.last << "<%02x><%04x>" % [code, unicode] end @@ -309,10 +328,10 @@ to_unicode_cmap = UNICODE_CMAP_TEMPLATE % range_blocks.strip cmap = @document.ref!({}) cmap << to_unicode_cmap - cmap.stream.compress! + cmap.compress_stream reference.data.update(:Subtype => :TrueType, :BaseFont => basename.to_sym, :FontDescriptor => descriptor, :FirstChar => 32,