# -*- encoding: utf-8; frozen_string_literal: true -*-
#
#--
# This file is part of HexaPDF.
#
# HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
# Copyright (C) 2014-2020 Thomas Leitner
#
# HexaPDF is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License version 3 as
# published by the Free Software Foundation with the addition of the
# following permission added to Section 15 as permitted in Section 7(a):
# FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
# THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
# INFRINGEMENT OF THIRD PARTY RIGHTS.
#
# HexaPDF 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 Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with HexaPDF. If not, see .
#
# The interactive user interfaces in modified source and object code
# versions of HexaPDF must display Appropriate Legal Notices, as required
# under Section 5 of the GNU Affero General Public License version 3.
#
# In accordance with Section 7(b) of the GNU Affero General Public
# License, a covered work must retain the producer line in every PDF that
# is created or manipulated using HexaPDF.
#
# If the GNU Affero General Public License doesn't fit your need,
# commercial licenses are available at .
#++
require 'hexapdf/font/true_type/table'
module HexaPDF
module Font
module TrueType
class Table
# The 'glyf' table contains the instructions for rendering glyphs and some additional glyph
# information.
#
# This is probably always the largest table in a TrueType font, so care is taken to perform
# operations lazily.
#
# See: https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
class Glyf < Table
# Represents the definition of a glyph. Since the purpose of this implementation is not
# editing or rendering glyphs, the raw glyph data is only decoded so far as to get general
# information about the glyph.
class Glyph
# Contains the raw byte data of the glyph.
attr_reader :raw_data
# The number of contours in the glyph. A zero or positive number implies a simple glyph,
# a negative number a glyph made up from multiple components
attr_reader :number_of_contours
# The minimum x value for coordinate data.
attr_reader :x_min
# The minimum y value for coordinate data.
attr_reader :y_min
# The maximum x value for coordinate data.
attr_reader :x_max
# The maximum y value for coordinate data.
attr_reader :y_max
# The array with the component glyph IDs, or +nil+ if this is not a compound glyph.
attr_reader :components
# The array with the component glyph offsets, or +nil+ if this is not a compound glyph.
attr_reader :component_offsets
# Creates a new glyph from the given raw data.
def initialize(raw_data)
@raw_data = raw_data
@number_of_contours, @x_min, @y_min, @x_max, @y_max = @raw_data.unpack('s>5')
@number_of_contours ||= 0
@x_min ||= 0
@y_min ||= 0
@x_max ||= 0
@y_max ||= 0
@components = nil
@component_offsets = nil
parse_compound_glyph if compound?
end
# Returns +true+ if this a compound glyph.
def compound?
number_of_contours < 0
end
private
FLAG_ARG_1_AND_2_ARE_WORDS = 1 << 0 #:nodoc:
FLAG_MORE_COMPONENTS = 1 << 5 #:nodoc:
FLAG_WE_HAVE_A_SCALE = 1 << 3 #:nodoc:
FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6 #:nodoc:
FLAG_WE_HAVE_A_TWO_BY_TWO = 1 << 7 #:nodoc:
# Parses the raw data to get the component glyphs.
#
# This is needed because the component glyphs are referenced by their glyph IDs and
# those may change when subsetting the font.
def parse_compound_glyph
@components = []
@component_offsets = []
index = 10
while true
flags, glyph_id = raw_data[index, 4].unpack('n2')
@components << glyph_id
@component_offsets << (index + 2)
break if flags & FLAG_MORE_COMPONENTS == 0
index += 4 # fields flags and glyphIndex
index += (flags & FLAG_ARG_1_AND_2_ARE_WORDS == 0 ? 2 : 4) # arguments
if flags & FLAG_WE_HAVE_A_TWO_BY_TWO != 0 # transformation
index += 8
elsif flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE != 0
index += 4
elsif flags & FLAG_WE_HAVE_A_SCALE != 0
index += 2
end
end
end
end
# The mapping from glyph ID to Glyph object or +nil+ (if the glyph has no outline).
attr_accessor :glyphs
# Returns the Glyph object for the given glyph ID. If the glyph has no outline (e.g. the
# space character), an empty Glyph object is returned.
def [](glyph_id)
return @glyphs[glyph_id] if @glyphs.key?(glyph_id)
offset = font[:loca].offset(glyph_id)
length = font[:loca].length(glyph_id)
if length == 0
@glyphs[glyph_id] = Glyph.new('')
else
raw_data = with_io_pos(directory_entry.offset + offset) { io.read(length) }
@glyphs[glyph_id] = Glyph.new(raw_data)
end
end
private
# Nothing to parse here since we lazily parse glyphs.
def parse_table
@glyphs = {}
end
end
end
end
end
end