require 'set' require 'spec_helper' require 'fixtures/fixtures' describe FontProcessor do def self.setup_fonts(fixture) before(:all) do @char_set = JSON.parse('{"charset_id":"1","features":"ALL","unicode":""}') @formats = JSON.parse('{"process_original":true,"convert":true,"derivatives":["woff","svg","swf","inst"]}') @mini_char_set = JSON.parse('{"charset_id":"a","features":"ALL","unicode":"65..120"}') @char_set_uni_vals = [0x20..0x7e,0xa0..0xffff] @mini_char_set_uni_vals = 65..120 @fixture = FontProcessor::Specs::Fixture.new(fixture) @naming_strategy = @fixture.naming_strategy @license_url = "http://custom-url" end subject { FontProcessor::Processor.new(@naming_strategy, @license_url, "01") } after(:all) do @fixture.destroy if @fixture end end context "with a bad PostScript file" do describe "ots failure" do setup_fonts(:ots_failure_font) it "will raise an exception when subsetting the font" do expect { subject.generate_char_set(@char_set, @formats) }.to raise_error(Exception) end end end context "with a bad PostScript file" do describe "ots failure" do setup_fonts(:ots_failure_font) it "will raise an exception when trying to wrap the font as WOFF" do expect { subject.wrap_woff(@char_set, FontProcessor::FontFormat.new(:cff, :otf)) }.to raise_error end end end context "with a font with a STAT table" do describe "stripping" do setup_fonts(:stat_font) it "will retain STAT referenced strings" do path = File.join(@fixture.temp_directory, "stripped.otf") fm = Skytype::FontManipulator.new(File.binread(@naming_strategy.source)) fm.save(path) subject.strip(path) #reload the fm, make sure it's name table has a STAT string, ID 256 fm = Skytype::FontManipulator.new(File.binread(path)) name_table = Skytype::NameTable.new(fm.get_table("name")) expect(name_table.get_string(256, Skytype::NameTable::EN_LANGUAGE_ID).nil?).to be_falsey end end end context "with a PostScript file" do describe ".file_metadata" do setup_fonts(:postscript) it "can detect outlines" do expect(subject.file_metadata(@formats)).to have_postscript_outlines expect(subject.file_metadata(@formats)).not_to have_truetype_outlines end end describe ".generate_char_set" do setup_fonts(:postscript) it "can generate all font files for a given character set" do subject.generate_char_set(@char_set, @formats) expect(File.exist?(File.join(@fixture.temp_directory, "source.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "source.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff_raw.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff_raw.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-inst.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-inst.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-dyna_base.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-dyna_base.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-eot.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-eot.ttf"))).to be false end it "verifies that PUA glyphs are present" do subject.generate_char_set(@char_set, @formats) all_cs = File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf") fm = Skytype::FontManipulator.new(File.binread(all_cs)) FontProcessor::Processor::PUA_PAIRS.each do | pair | expect(fm.get_glyph_for_unicode( pair[0] )).not_to eq 0 end end it "verifies that dyna_base has proper CFF NameDict entries for dyna_base and inst" do subject.generate_char_set(@char_set, @formats) dyna_base_font = File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-dyna_base.otf") fm = Skytype::FontManipulator.new(File.binread(dyna_base_font)) name_table = Skytype::NameTable.new(fm.get_table("name")) expect(fm.get_cff_name).to eq name_table.get_string(Skytype::NameTable::POSTSCRIPT_NAME_ID, Skytype::NameTable::EN_LANGUAGE_ID) inst_font = File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-inst.otf") fm = Skytype::FontManipulator.new(File.binread(inst_font)) name_table = Skytype::NameTable.new(fm.get_table("name")) expect(fm.get_cff_name).to eq name_table.get_string(Skytype::NameTable::POSTSCRIPT_NAME_ID, Skytype::NameTable::EN_LANGUAGE_ID) end context "with a temporarily unlocked subset" do before(:all) do @filename = File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf") end it "contains only the unicode in the character set" do subject.generate_char_set(@char_set, @formats) subject.unlock(@filename) orig = FontProcessor::FontFile.new(File.join(@fixture.temp_directory, "source.otf")) orig_characters = Set.new(orig.unicode) orig_characters.add(FontProcessor::Processor::NON_BREAKING_SPACE) expanded_set = Array.new() @char_set_uni_vals.each { |range| expanded_set += range.entries } expected_characters = (orig_characters & expanded_set).to_a.sort subset = FontProcessor::FontFile.new(@filename) expect(subset.unicode).to eq expected_characters subject.lock(@filename) end end it "can convert to TrueType outlines" do subject.generate_char_set(@char_set, @formats) font_file = FontProcessor::FontFile.new(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf")) expect(font_file.has_truetype_outlines?).to be true expect(font_file.has_postscript_outlines?).to be false end it "adds license urls to ttf character set files" do subject.generate_char_set(@char_set, @formats) metadata = FontProcessor::FontFile.new(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf")) expect(metadata.names['en']['License URL']).to eq @license_url end it "obfuscates the name table" do subject.generate_char_set(@char_set, @formats) metadata = FontProcessor::FontFile.new(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf")) expect(metadata.names['en']['Family']).to be_nil expect(metadata.names['en']['Full name']).to eq "-" expect(metadata.names['en']['PostScript name']).to eq "-" expect(metadata.names['en']['Unique ID']).not_to eq "Fontin" end end describe " .generate_char_set with invalid data" do setup_fonts(:postscript) it "raises an exception" do File.open(File.join(@fixture.temp_directory, "postscript.otf"), "w") { |f| f.puts "I am not a font" } expect { subject.generate_char_set(@char_set, @formats) }.to raise_error(FontProcessor::Processor::UnknownFontFormat) end end context "internally" do setup_fonts(:postscript) it "does not automatically generate a non-breaking space when missing" do min_char_set = { "charset_id" => "min", "unicode" => "32..65", "features" => "NONE" } max_char_set = { "charset_id" => "max", "unicode" => "32..160", "features" => "NONE" } cff = FontProcessor::FontFormat.new(:cff, :otf) subject.subset(min_char_set, @naming_strategy.source, @naming_strategy.source, @naming_strategy.char_set(min_char_set['charset_id'], cff)) subject.subset(max_char_set, @naming_strategy.source, @naming_strategy.char_set(min_char_set['charset_id'], cff), @naming_strategy.char_set(max_char_set['charset_id'], cff)) font_file = FontProcessor::FontFile.new(@naming_strategy.char_set(max_char_set['charset_id'], cff)) expect(font_file.unicode).not_to include(0xA0) end it "automatically generates a non-breaking space when missing" do max_char_set = { "charset_id" => "max", "unicode" => "32..160", "features" => "NONE" } cff = FontProcessor::FontFormat.new(:cff, :otf) subject.generate_char_set(max_char_set, @formats) font_file = FontProcessor::FontFile.new(@naming_strategy.char_set(max_char_set['charset_id'], cff)) expect(font_file.unicode).to include(0xA0) expect(font_file.unicode).to include(0xAD) end it "can copy a name entry over another" do char_set = { "charset_id" => "copy", "unicode" => "32..65", "features" => "NONE" } cff = FontProcessor::FontFormat.new(:cff, :otf) filename = @naming_strategy.char_set(char_set['charset_id'], cff) subject.subset(char_set, @naming_strategy.source, @naming_strategy.source, filename) unique_string = "asdfasdfasdfsa" subject.obfuscate_names(filename, unique_string) subject.copy_name_record(filename, FontProcessor::Processor::UNIQUE_ID, FontProcessor::Processor::FAMILY_ID) file = FontProcessor::FontFile.new(filename) expect(file.names["en"]["Family"]).to eq unique_string expect(file.names["en"]["Full name"]).to eq "-" expect(file.names["en"]["PostScript name"]).to eq "-" expect(file.names["en"]["Unique ID"]).to eq unique_string end end end context "with a TrueType file" do describe ".file_metadata" do setup_fonts(:truetype) it "can detect outlines" do expect(subject.file_metadata(@formats)).not_to have_postscript_outlines expect(subject.file_metadata(@formats)).to have_truetype_outlines end end describe " .generate_char_set" do setup_fonts(:truetype) it "can generate all font files for a given character set" do subject.generate_char_set(@mini_char_set, @formats) expect(File.exist?(File.join(@fixture.temp_directory, "source.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-otf.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-woff.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-woff_raw.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-svg.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-inst.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-eot.ttf"))).to be false end context "with a temporarily unlocked subset" do before(:all) do @filename = File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-otf.ttf") end it "contains only the unicode in the character set" do subject.generate_char_set(@mini_char_set, @formats) subject.unlock(@filename) orig = FontProcessor::FontFile.new(File.join(@fixture.temp_directory, "source.ttf")) orig_characters = Set.new(orig.unicode) expected_characters = (orig_characters & @mini_char_set_uni_vals.entries).to_a.sort subset = FontProcessor::FontFile.new(@filename) expect(subset.unicode).to eq expected_characters subject.lock(@filename) end end it "adds license urls to raw fonts" do subject.generate_char_set(@mini_char_set, @formats) metadata = FontProcessor::FontFile.new(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-otf.ttf")) expect(metadata.names['en']['License URL']).to eq @license_url end it "obfuscates the name table" do subject.generate_char_set(@mini_char_set, @formats) metadata = FontProcessor::FontFile.new(File.join(@fixture.temp_directory, "charset-#{@mini_char_set['charset_id']}-otf.ttf")) expect(metadata.names['en']['Family']).to be_nil expect(metadata.names['en']['Full name']).to eq "-" expect(metadata.names['en']['PostScript name']).to eq "-" expect(metadata.names['en']['Unique ID']).not_to include "Bleeding Cowboys" end end describe " .generate_char_set with limited formats" do setup_fonts(:postscript) it "produces just the original CFF format and no others" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":true,"convert":false,"derivatives":[]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be false end it "produces just the original CFF format and a WOFF" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":true,"convert":false,"derivatives":["woff"]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be false end it "produces the original CFF format and a WOFF, but no SVF because the PS original is not converted" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":true,"convert":false,"derivatives":["woff", "svg"]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be false end it "produces the original CFF format, a WOFF and a SWF" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":true,"convert":false,"derivatives":["woff", "swf"]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be true end it "produces the a WOFF and a SWF, but not original format file" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":false,"convert":false,"derivatives":["woff", "swf", "svg"]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.otf"))).to be false end it "to throw an error if an invalid format is supplied, even if the JSON is valid" do format_data = '{"process_original":false,"convert":false,"derivatives":[]}' expect { subject.generate_char_set(@char_set, JSON.parse(format_data)) }.to raise_error "Invalid format data {\"process_original\"=>false, \"convert\"=>false, \"derivatives\"=>[]}" end it "to throw an error if an invalid format is supplied, even if the JSON is valid" do format_data = '{"process_original":false,"convert":false,"derivatives":["svg"]}' expect { subject.generate_char_set(@char_set, JSON.parse(format_data)) }.to raise_error "Invalid format data {\"process_original\"=>false, \"convert\"=>false, \"derivatives\"=>[\"svg\"]}" end end describe " .generate_char_set with conversion and derivative specifiers" do setup_fonts(:postscript) it "produces just the original CFF format and only the converted file" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":true,"convert":true,"derivatives":[]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be false end it "produces only the converted file" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":false,"convert":true,"derivatives":[]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.otf"))).to be false end it "produces only the converted file formats" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":false,"convert":true,"derivatives":["woff", "swf", "svg"]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.ttf"))).to be true end end describe " .generate_char_set from TrueType with limited formats" do setup_fonts(:truetype) it "ignores a SVG derivtive from a TrueType font" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":false,"convert":false,"derivatives":["svg"]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.ttf"))).to be false end it "produces just the derivatives from a TrueType font" do subject.generate_char_set(@char_set, JSON.parse('{"process_original":false,"convert":false,"derivatives":["woff", "swf", "svg"]}')) expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.otf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.ttf"))).to be true end end describe " .generate_char_set with invalid data" do setup_fonts(:truetype) it "raises an exception" do File.open(File.join(@fixture.temp_directory, "truetype.otf"), "w") { |f| f.puts "I am not a font" } expect { subject.generate_char_set(@char_set, @formats) }.to raise_error(FontProcessor::Processor::UnknownFontFormat) end end describe " .generate_char_set" do setup_fonts(:locked) it "can generate all font files for a given character set" do subject.generate_char_set(@char_set, @formats) expect(File.exist?(File.join(@fixture.temp_directory, "source.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-otf.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-woff.ttf"))).to be true expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-eot.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-svg.ttf"))).to be false expect(File.exist?(File.join(@fixture.temp_directory, "charset-#{@char_set['charset_id']}-swf.ttf"))).to be true end end context "internally" do setup_fonts(:truetype) it "can copy a name entry over another" do char_set = { "charset_id" => "copy", "unicode" => "32..65", "features" => "NONE" } ttf = FontProcessor::FontFormat.new(:ttf, :otf) filename = @naming_strategy.char_set(char_set['charset_id'], ttf) subject.subset(char_set, @naming_strategy.source, @naming_strategy.source, filename) unique_string = "asdfasdfasdfsa" subject.obfuscate_names(filename, unique_string) subject.copy_name_record(filename, FontProcessor::Processor::UNIQUE_ID, FontProcessor::Processor::FAMILY_ID) file = FontProcessor::FontFile.new(filename) expect(file.names["en"]["Family"]).to eq unique_string expect(file.names["en"]["Full name"]).to eq "-" expect(file.names["en"]["PostScript name"]).to eq "-" expect(file.names["en"]["Unique ID"]).to eq unique_string end context "with a file containing bad os2 width values" do setup_fonts(:bad_os2_width_class) it "corrects the values" do filename = @naming_strategy.source subject.fix_os2_weight_and_width(filename) fm = Skytype::FontManipulator.new(File.binread(filename)) os2_data = fm.get_table("OS/2") os2_table = Skytype::OS2Table.new(os2_data) expect(os2_table.get_width).to eq 1 end end end end end