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,