# -*- 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-2023 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/builder' module HexaPDF module Font module TrueType # Subsets a TrueType font in the context of PDF. # # TrueType fonts can be embedded into PDF either as a simple font or as a composite font. This # subsetter implements the functionality needed when embedding a TrueType subset for a # composite font. # # This means in particular that the resulting font file cannot be used outside of the PDF. class Subsetter # Creates a new Subsetter for the given TrueType Font object. def initialize(font) @font = font @glyph_map = {0 => 0} @last_id = 0 end # Includes the glyph with the given ID in the subset and returns the new subset glyph ID. # # Can be called multiple times with the same glyph ID, always returning the correct new # subset glyph ID. def use_glyph(glyph_id) return @glyph_map[glyph_id] if @glyph_map.key?(glyph_id) @last_id += 1 # Handle codes for ASCII characters \r (13), (, ) (40, 41) and \ (92) specially so that # they never appear in the output (PDF serialization would need to escape them) if @last_id == 13 || @last_id == 40 || @last_id == 92 @glyph_map[:"s#{@last_id}"] = @last_id if @last_id == 40 @last_id += 1 @glyph_map[:"s#{@last_id}"] = @last_id end @last_id += 1 end @glyph_map[glyph_id] = @last_id end # Returns the new subset glyph ID for the given glyph ID, or +nil+ if the glyph isn't # subset. def subset_glyph_id(glyph_id) @glyph_map[glyph_id] end # Builds the subset font file and returns it as a binary string. def build_font glyf, locations = build_glyf_table loca = build_loca_table(locations) hmtx = build_hmtx_table head = build_head_table(modified: Time.now, loca_type: 1) hhea = build_hhea_table(@glyph_map.size) maxp = build_maxp_table(@glyph_map.size) tables = { 'head' => head, 'hhea' => hhea, 'maxp' => maxp, 'glyf' => glyf, 'loca' => loca, 'hmtx' => hmtx, } tables['cvt '] = @font[:'cvt '].raw_data if @font[:'cvt '] tables['fpgm'] = @font[:fpgm].raw_data if @font[:fpgm] tables['prep'] = @font[:prep].raw_data if @font[:prep] Builder.build(tables) end private # Builds the glyf table. def build_glyf_table add_glyph_components orig_glyf = @font[:glyf] table = ''.b locations = [] @glyph_map.each_key do |old_gid| glyph = orig_glyf[old_gid.kind_of?(Symbol) ? 0 : old_gid] locations << table.size data = glyph.raw_data if glyph.compound? data = data.dup glyph.component_offsets.each_with_index do |offset, index| data[offset, 2] = [@glyph_map[glyph.components[index]]].pack('n') end end table << data end locations << table.size [table, locations] end # Builds the loca table given the locations. def build_loca_table(locations) locations.pack('N*') end # Builds the hmtx table. def build_hmtx_table hmtx = @font[:hmtx] data = ''.b @glyph_map.each_key do |old_gid| metric = hmtx[old_gid.kind_of?(Symbol) ? 0 : old_gid] data << [metric.advance_width, metric.left_side_bearing].pack('n2') end data end # Builds the hhea table, adjusting the value of the number of horizontal metrics. def build_hhea_table(num_of_long_hor_metrics) data = @font[:hhea].raw_data data[-2, 2] = [num_of_long_hor_metrics].pack('n') data end # Builds the head table, adjusting the modification time and location table type. def build_head_table(modified:, loca_type:) data = @font[:head].raw_data data[8, 4] = "\0\0\0\0" data[28, 8] = [(modified - TrueType::Table::TIME_EPOCH).to_i].pack('q>') data[-4, 2] = [loca_type].pack('n') data end # Builds the maxp table, adjusting the number of glyphs. def build_maxp_table(nr_of_glyphs) data = @font[:maxp].raw_data data[4, 2] = [nr_of_glyphs].pack('n') data end # Adds the components of compound glyphs to the subset. def add_glyph_components glyf = @font[:glyf] @glyph_map.keys.each do |gid| next if gid.kind_of?(Symbol) glyf[gid].components&.each {|cgid| use_glyph(cgid) } end end end end end end