# TTF/Ruby, a library to read and write TrueType fonts in Ruby. # Copyright (C) 2006 Mathieu Blondel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA module Font module TTF module Table # Glyf is the Glyph data table. class Glyf < Font::TTF::FontChunk # Base class for SimpleGlyph and CompositeGlyph. class Glyph < Font::TTF::FontChunk attr_accessor :num_contours, :x_min, :y_min, :x_max, :y_max def initialize(table, offset=nil) @table = table @offset_from_table = offset super(@table.font, @table.offset + @offset_from_table) if exists_in_file? @font.at_offset(@offset) do @num_contours = @font.read_short @x_min = @font.read_fword @y_min = @font.read_fword @x_max = @font.read_fword @y_max = @font.read_fword end end end def dump raw = (@num_contours || 0).to_short raw += (@x_min || 0).to_fword raw += (@y_min || 0).to_fword raw += (@x_max || 0).to_fword raw += (@y_max || 0).to_fword end # Returns whether the glyph is composite or not. def composite? self.class == CompositeGlyph end # Returns whether is simple (i.e. not composite) or not. def simple? self.class == SimpleGlyph end end # SimpleGlyph class. class SimpleGlyph < Glyph Point = Struct.new(:rel_x, :abs_x, :rel_y, :abs_y, :end_of_contour, :on_curve) # Point is helper class which gives information on a point # such as it absolute (abs_x, abs_y) and relative (rel_x, rel_y) # coordinates class Point # Whether the point is end of contour or not. alias :end_of_contour? :end_of_contour # Whether the point is on curve or not. alias :on_curve? :on_curve # Whether the point is off curve or not. def off_curve? not on_curve? end end FLAG_ON_CURVE = 0b1 FLAG_X_SHORT_VECTOR = 0b10 FLAG_Y_SHORT_VECTOR = 0b100 FLAG_REPEAT = 0b1000 FLAG_X_IS_SAME = 0b10000 FLAG_Y_IS_SAME = 0b100000 attr_accessor :end_pts_of_contours, :instructions, :flags, :x_coordinates, :y_coordinates def initialize(*args) super(*args) if exists_in_file? offs = @offset + IO::SIZEOF_SHORT + 4 * IO::SIZEOF_FWORD @font.at_offset(offs) do @end_pts_of_contours = @font.read_ushorts(@num_contours) instruction_len = @font.read_ushort @instructions = @font.read_bytes(instruction_len) unless @end_pts_of_contours.empty? num_points = @end_pts_of_contours.last + 1 else num_points = 0 end @flags = [] while @flags.length < num_points flag = @font.read_byte @flags << flag if flag & FLAG_REPEAT != 0 @font.read_byte.times do @flags << flag end end end @x_coordinates = [] @y_coordinates = [] [[@x_coordinates, FLAG_X_SHORT_VECTOR, FLAG_X_IS_SAME], [@y_coordinates, FLAG_Y_SHORT_VECTOR, FLAG_Y_IS_SAME] ].each do |coordinates, short, same| num_points.times do |i| flag = @flags[i] if flag & short != 0 # if the coordinate is a BYTE if flag & same != 0 coordinates << @font.read_byte else coordinates << -@font.read_byte end else # the coordinate is a SHORT if flag & same != 0 # same so 0 (relative coordinates) coordinates << 0 else coordinates << @font.read_short end end end end @len = @font.pos - @offset end end end # Returns an Array of [x,y] pairs of relative coordinates. def rel_coordinates coords = [] @x_coordinates.length.times do |i| coords << [@x_coordinates[i], @y_coordinates[i]] end coords end # Returns an Array of [x,y] pairs of absolute coordinates. def abs_coordinates abs_x = 0 abs_y = 0 coords = [] rel_coordinates.each do |rel_x, rel_y| abs_x += rel_x abs_y += rel_y coords << [abs_x, abs_y] end coords end # Returns an Array of Point objects. def points x_abs = 0 y_abs = 0 pnts = [] @x_coordinates.length.times do |i| pnt = Point.new pnt.rel_x = @x_coordinates[i] pnt.rel_y = @y_coordinates[i] x_abs += pnt.rel_x y_abs += pnt.rel_y pnt.abs_x = x_abs pnt.abs_y = y_abs pnt.end_of_contour = @end_pts_of_contours.include? i pnt.on_curve = (@flags[i] & FLAG_ON_CURVE != 0) pnts << pnt end pnts end def dump raw = super raw += @end_pts_of_contours.to_ushorts raw += @instructions.length.to_ushort raw += @instructions.to_bytes tmp = "" [[@x_coordinates, FLAG_X_SHORT_VECTOR, FLAG_X_IS_SAME], [@y_coordinates, FLAG_Y_SHORT_VECTOR, FLAG_Y_IS_SAME] ].each do |coordinates, short, same| coordinates.each_with_index do |coord, i| if 0 <= coord and coord <= 255 @flags[i] = (@flags[i] | short) | same tmp += coord.to_byte elsif -255 <= coord and coord < 0 @flags[i] = (@flags[i] | short) & ~same tmp += (-coord).to_byte elsif coord == 0 @flags[i] = (@flags[i] & ~short) | same else @flags[i] = (@flags[i] & ~short) & ~same tmp += coord.to_short end end end # We write all flags rather than using the flag_repeat trick # So we unset the "repeat" bit for all flags # TODO: implement the repeat feature (this saves space) raw += @flags.collect { |f| f & ~FLAG_REPEAT }.to_bytes raw += tmp end end # CompositeGlyph class. class CompositeGlyph < Glyph GlyphComponent = Struct.new(:flags, :index, :args, :scale, :xscale, :yscale, :scale01, :scale10) ARG_1_AND_2_ARE_WORDS = 0b1 ARGS_ARE_XY_VALUES = 0b10 ROUND_XY_TO_GRID = 0b100 WE_HAVE_A_SCALE = 0b1000 RESERVED = 0b10000 MORE_COMPONENTS = 0b100000 WE_HAVE_AN_X_AND_Y_SCALE = 0b1000000 WE_HAVE_A_TWO_BY_TWO = 0b10000000 WE_HAVE_INSTRUCTIONS = 0b100000000 USE_MY_METRICS = 0b1000000000 # An Array of GlyphComponent objects attr_accessor :components # An Array of instructions (Fixnums) attr_accessor :instructions def initialize(*args) super(*args) if exists_in_file? offs = @offset + IO::SIZEOF_SHORT + 4 * IO::SIZEOF_FWORD @font.at_offset(offs) do @components = [] continue = true while continue gc = GlyphComponent.new gc.flags = @font.read_ushort gc.index = @font.read_ushort gc.args = [] if gc.flags & ARG_1_AND_2_ARE_WORDS != 0 gc.args[0] = @font.read_short gc.args[1] = @font.read_short else gc.args[0] = @font.read_ushort end if gc.flags & WE_HAVE_A_SCALE != 0 gc.scale = @font.read_f2dot14 elsif gc.flags & WE_HAVE_AN_X_AND_Y_SCALE != 0 gc.xscale = @font.read_f2dot14 gc.yscale = @font.read_f2dot14 elsif gc.flags & WE_HAVE_A_TWO_BY_TWO != 0 gc.xscale = @font.read_f2dot14 gc.scale01 = @font.read_f2dot14 gc.scale10 = @font.read_f2dot14 gc.yscale = @font.read_f2dot14 end @components << gc continue = (gc.flags & MORE_COMPONENTS != 0) end if @components.last.flags & \ WE_HAVE_INSTRUCTIONS != 0 inst_len = @font.read_ushort @instructions = @font.read_bytes(inst_len) else @instructions = [] end @len = @font.pos - @offset end end end def dump raw = super components_len = @components.length @components.each_with_index do |gc, i| flags = gc.flags tmp = "" if not gc.args.nil? and gc.args.length == 2 flags |= ARG_1_AND_2_ARE_WORDS tmp += gc.args[0].to_short tmp += gc.args[1].to_short else flags &= ~ARG_1_AND_2_ARE_WORDS tmp += gc.args[0].to_ushort end if not gc.scale.nil? flags |= WE_HAVE_A_SCALE tmp += gc.scale.to_f2dot14 elsif not gc.scale01.nil? flags |= WE_HAVE_A_TWO_BY_TWO tmp += gc.xscale.to_f2dot14 tmp += gc.scale01.to_f2dot14 tmp += gc.scale10.to_f2dot14 tmp += gc.yscale.to_f2dot14 elsif not gc.xscale.nil? flags |= WE_HAVE_AN_X_AND_Y_SCALE tmp += gc.xscale.to_f2dot14 tmp += gc.yscale.to_f2dot14 end if i < components_len - 1 flags |= MORE_COMPONENTS else flags &= ~MORE_COMPONENTS if @instructions.length > 0 flags |= WE_HAVE_INSTRUCTIONS else flags &= ~WE_HAVE_INSTRUCTIONS end end raw += flags.to_ushort raw += gc.index.to_ushort raw += tmp end unless @instructions.empty? raw += @instructions.length.to_ushort raw += @instructions.to_bytes end raw end end attr_accessor :glyphs def initialize(*args) super(*args) end # Returns the kind of glyph at offset, i.e. either SimpleGlyph # or CompositeGlyph. def kind_of_glyph_at_offset(offs_from_table) @font.at_offset(@offset + offs_from_table) do num_contours = @font.read_short if num_contours >= 0 SimpleGlyph else CompositeGlyph end end end def get_glyph_at_offset(offs_from_table) klass = kind_of_glyph_at_offset(offs_from_table) klass.new(self, offs_from_table) end def get_glyphs loca = @font.get_table(:loca) glyphs = [] loca.glyph_offsets[0...-1].each do |off| glyphs << get_glyph_at_offset(off) end glyphs end private :get_glyphs # Returns all Glyph (SimpleGlyph or CompositeGlyph) in an Array. # This method may be real overkill if you just need to access a few glyphs. # In this case, you should use the loca table (Font::TTF::Table::Loca) # to get offsets and get_glyph_at_offset to get glyph associated with them. def glyphs @glyphs ||= get_glyphs end # Sets glyphs. new_glyphs is an Array of Glyph objects. def glyphs=(new_glyphs) @glyphs = new_glyphs @font.get_table(:maxp).num_glyphs = @glyphs.length @font.get_table(:post).num_glyphs = @glyphs.length end # Iterates over each glyph. # It does not load all glyphs like glyphs.each would do. def each_glyph loca = @font.get_table(:loca) glyphs = [] loca.glyph_offsets[0...-1].each do |off| glyph = get_glyph_at_offset(off) yield glyph end end # Dumps the glyf table in binary raw format as may be found in a font # file. def dump raw = "" offs = 0 glyph_offsets = [] glyphs.each do |glyph| glyph_offsets << offs dump = glyph.dump len = dump.length raw += dump # offsets should be multiples of SIZEOF_ULONG diff = len % IO::SIZEOF_ULONG raw += " " * diff offs += len + diff end # An additional offset is added so that the length of the last # glyph can be calculated: # len of last glyph = additional offs - last glyph offs glyph_offsets << offs # 2 ** 16 * 2 = 131072 is the maximum size supported # if the short format is used in the loca table # Using shorts saves two bytes per glyph! if offs < 131072 @font.get_table(:head).index_to_loc_format = \ Font::TTF::Table::Head::SHORT_FORMAT else @font.get_table(:head).index_to_loc_format = \ Font::TTF::Table::Head::LONG_FORMAT end @font.get_table(:loca).glyph_offsets = glyph_offsets raw end end end end end