fontprocessor 27.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rbenv-version +1 -0
- data/.rspec +0 -0
- data/.travis.yml +17 -0
- data/CHANGELOG.md +64 -0
- data/CHANGELOG_FPV14.md +20 -0
- data/Dockerfile +3 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +156 -0
- data/Guardfile +5 -0
- data/README.md +6 -0
- data/Rakefile +53 -0
- data/Rakefile.base +57 -0
- data/bin/process-file +41 -0
- data/config/development.yml +5 -0
- data/config/production.yml +3 -0
- data/config/staging.yml +3 -0
- data/config/test.yml +5 -0
- data/fontprocessor.gemspec +37 -0
- data/lib/fontprocessor/config.rb +85 -0
- data/lib/fontprocessor/external/batik/batik-ttf2svg.jar +0 -0
- data/lib/fontprocessor/external/batik/lib/batik-svggen.jar +0 -0
- data/lib/fontprocessor/external/batik/lib/batik-util.jar +0 -0
- data/lib/fontprocessor/external/fontforge/subset.py +30 -0
- data/lib/fontprocessor/external/fontforge/utils.py +66 -0
- data/lib/fontprocessor/external_execution.rb +117 -0
- data/lib/fontprocessor/font_file.rb +149 -0
- data/lib/fontprocessor/font_file_naming_strategy.rb +63 -0
- data/lib/fontprocessor/font_format.rb +29 -0
- data/lib/fontprocessor/process_font_job.rb +227 -0
- data/lib/fontprocessor/processed_font_iterator.rb +89 -0
- data/lib/fontprocessor/processor.rb +790 -0
- data/lib/fontprocessor/version.rb +3 -0
- data/lib/fontprocessor.rb +16 -0
- data/scripts/build_and_test.sh +15 -0
- data/scripts/get_production_source_map.rb +53 -0
- data/spec/fixtures/bad_os2_width_class.otf +0 -0
- data/spec/fixtures/extra_language_names.otf +0 -0
- data/spec/fixtures/fixtures.rb +35 -0
- data/spec/fixtures/locked.otf +0 -0
- data/spec/fixtures/op_size.otf +0 -0
- data/spec/fixtures/ots_failure_font.otf +0 -0
- data/spec/fixtures/postscript.otf +0 -0
- data/spec/fixtures/stat_font.otf +0 -0
- data/spec/fixtures/truetype.otf +0 -0
- data/spec/lib/fontprocessor/config_spec.rb +38 -0
- data/spec/lib/fontprocessor/external_execution_spec.rb +33 -0
- data/spec/lib/fontprocessor/font_file_naming_strategy_spec.rb +13 -0
- data/spec/lib/fontprocessor/font_file_spec.rb +110 -0
- data/spec/lib/fontprocessor/process_font_job_spec.rb +317 -0
- data/spec/lib/fontprocessor/processed_font_iterator_spec.rb +128 -0
- data/spec/lib/fontprocessor/processor_spec.rb +466 -0
- data/spec/spec_helper.rb +4 -0
- data/tasks/fonts.rake +30 -0
- data/worker.rb +23 -0
- metadata +312 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'ots-ruby'
|
3
|
+
require 'skytype'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
require 'fontprocessor/config'
|
7
|
+
require 'fontprocessor/external_execution'
|
8
|
+
require 'fontprocessor/font_file'
|
9
|
+
require 'fontprocessor/font_file_naming_strategy'
|
10
|
+
require 'fontprocessor/font_format'
|
11
|
+
require 'fontprocessor/process_font_job'
|
12
|
+
require 'fontprocessor/processed_font_iterator'
|
13
|
+
require 'fontprocessor/processor'
|
14
|
+
|
15
|
+
module FontProcessor
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# This is a script that can be run on a production `typekit` server to create
|
2
|
+
# a CSV file mapping font base IDs and processing versions to locations in the
|
3
|
+
# production source files bucket. This can be used to download those source
|
4
|
+
# files for local development and testing with an in-progress font processing
|
5
|
+
# version.
|
6
|
+
#
|
7
|
+
# The input to this script should be a CSV with family names and slugs. The
|
8
|
+
# output is another CSV file with individual font variations in each of those
|
9
|
+
# families, along with FVD and source file location in the S3 bucket.
|
10
|
+
#
|
11
|
+
# TO BE RUN IN A PRODUCTION TYPEKIT CONSOLE
|
12
|
+
|
13
|
+
data = File.read("/tmp/source_families.csv")
|
14
|
+
data = data.split("\n")[1..-1].join("\n") # Remove first line
|
15
|
+
family_data = FasterCSV.parse(data, {
|
16
|
+
:headers => ["Name", "Slug", "Notes"],
|
17
|
+
:return_headers => false,
|
18
|
+
:skip_blanks => true
|
19
|
+
})
|
20
|
+
|
21
|
+
client = FontBase::Client.new do |c|
|
22
|
+
c.connection = Mongo::Connection.from_uri(Typekit.config.font_base_db_uri)
|
23
|
+
c.db_name = Typekit.config.font_base_db
|
24
|
+
c.s3_bucket = Typekit.config.s3_font_base_source
|
25
|
+
end
|
26
|
+
|
27
|
+
output_data = FasterCSV.generate do |csv|
|
28
|
+
csv << ["Family Name", "Variation Name", "Slug", "FVD", "Font Base ID", "CFF Filename", "CFF Source ID", "TTF Filename", "TTF Source ID"]
|
29
|
+
family_data.each do |family_row|
|
30
|
+
tff = TypekitFontFamily.get_by_slug(family_row["Slug"])
|
31
|
+
tff.font_variations.each do |fv|
|
32
|
+
if ff = fv.current_font_file
|
33
|
+
font_base_id = ff.font_base_id
|
34
|
+
files = client.get(font_base_id)["files"]
|
35
|
+
cff_filename, cff_source_id = if files.has_key?("cff")
|
36
|
+
[files["cff"]["filename"], files["cff"]["key"]]
|
37
|
+
else
|
38
|
+
[nil, nil]
|
39
|
+
end
|
40
|
+
ttf_filename, ttf_source_id = if files.has_key?("ttf")
|
41
|
+
[files["ttf"]["filename"], files["ttf"]["key"]]
|
42
|
+
else
|
43
|
+
[nil, nil]
|
44
|
+
end
|
45
|
+
csv << [tff.name, fv.name, family_row["Slug"], fv.fvd, font_base_id, cff_filename, cff_source_id, ttf_filename, ttf_source_id]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
File.open("/tmp/source_files.csv", "w") do |f|
|
52
|
+
f.write(output_data)
|
53
|
+
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
|
3
|
+
module FontProcessor
|
4
|
+
module Specs
|
5
|
+
class Fixture
|
6
|
+
|
7
|
+
def initialize(kind)
|
8
|
+
@kind = kind
|
9
|
+
FileUtils.mkdir_p(temp_directory)
|
10
|
+
FileUtils.cp(filepath, temp_directory)
|
11
|
+
end
|
12
|
+
|
13
|
+
def destroy
|
14
|
+
FileUtils.rm_rf(temp_directory)
|
15
|
+
end
|
16
|
+
|
17
|
+
def naming_strategy
|
18
|
+
FontProcessor::FontFileNamingStrategy.new(filename, temp_directory)
|
19
|
+
end
|
20
|
+
|
21
|
+
def temp_directory
|
22
|
+
File.join(Dir.tmpdir, @kind.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def filename
|
27
|
+
"#{@kind}.otf"
|
28
|
+
end
|
29
|
+
|
30
|
+
def filepath
|
31
|
+
File.join(File.dirname(__FILE__), filename)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FontProcessor::Config do
|
4
|
+
subject { FontProcessor::Config }
|
5
|
+
|
6
|
+
describe "the environment" do
|
7
|
+
it "defaults to development" do
|
8
|
+
expect(subject.environment).to eq "development"
|
9
|
+
end
|
10
|
+
context "when set to test" do
|
11
|
+
before(:all) do
|
12
|
+
ENV['ENV'] = "test"
|
13
|
+
end
|
14
|
+
after(:all) do
|
15
|
+
ENV.delete('ENV')
|
16
|
+
end
|
17
|
+
it "is test" do
|
18
|
+
expect(subject.environment).to eq "test"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "has an s3 source bucket" do
|
24
|
+
expect(subject.s3_source_bucket).to eq "typekit-development-source-fonts"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "has an s3 processed bucket" do
|
28
|
+
expect(subject.s3_processed_bucket).to eq "typekit-development-processed-fonts"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "has a mongo host" do
|
32
|
+
expect(subject.mongo_host).to eq "localhost"
|
33
|
+
end
|
34
|
+
|
35
|
+
it "has a mongo db" do
|
36
|
+
expect(subject.mongo_db).to eq "fontbase_development"
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FontProcessor::ExternalStdOutCommand do
|
4
|
+
it "should be able to execute a normal binary and get it's output" do
|
5
|
+
result = FontProcessor::ExternalStdOutCommand.run('echo asdf', :expect_output => true)
|
6
|
+
expect(result).to eq "asdf\n"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be able to kill a hanging binary" do
|
10
|
+
result = FontProcessor::ExternalStdOutCommand.run('cat', :timeout => 1)
|
11
|
+
expect(result).to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be able to execute a normal binary and get it's stderr with it's stdout" do
|
15
|
+
result = FontProcessor::ExternalStdOutCommand.run('cat asdfjhasdjfkhasdkjfhasjdkhf', :expect_output => true)
|
16
|
+
expect(result).to include("No such file or directory")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe FontProcessor::ExternalJSONProgram do
|
21
|
+
it "should be able to execute a program that takes and receives JSON" do
|
22
|
+
result = FontProcessor::ExternalJSONProgram.run('more', {:param1 => 'test'})
|
23
|
+
expect(result.key?('param1')).to be true
|
24
|
+
end
|
25
|
+
it "should be able to kill a hanging binary" do
|
26
|
+
expect { FontProcessor::ExternalJSONProgram.run('sleep 2', :timeout => 1) }.to raise_error(FontProcessor::ExternalProgramError, "sleep 2: Took longer than 1 seconds to respond")
|
27
|
+
end
|
28
|
+
it "should be able to handle a binary that outputs to stderr first" do
|
29
|
+
FontProcessor::ExternalJSONProgram.run('python -c "import sys;sys.stdin.read();sys.stderr.write(\'e\ne\');sys.stdout.write(\'{\\"1\\": [\'+\'1,\'*((65535/2)+1)+\'2]}\n\')"', {})
|
30
|
+
# If this didn't hang then we are good, otherwise we have a problem
|
31
|
+
expect(1).to eq 1
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FontProcessor::FontFileNamingStrategy do
|
4
|
+
before(:all) do
|
5
|
+
@naming_strategy = FontProcessor::FontFileNamingStrategy.new("postscript.otf", "fixtures")
|
6
|
+
@charset_id = "1"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "can name a character set" do
|
10
|
+
expect(@naming_strategy.char_set(@charset_id, FontProcessor::FontFormat.new(:ttf, :otf))).to eq "fixtures/charset-#{@charset_id}-otf.ttf"
|
11
|
+
expect(@naming_strategy.char_set(@charset_id, FontProcessor::FontFormat.new(:cff, :otf))).to eq "fixtures/charset-#{@charset_id}-otf.otf"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fixtures/fixtures'
|
3
|
+
|
4
|
+
describe FontProcessor::FontFile do
|
5
|
+
context "with a PostScript file" do
|
6
|
+
before(:each) do
|
7
|
+
@fixture = FontProcessor::Specs::Fixture.new(:postscript)
|
8
|
+
@file = FontProcessor::FontFile.new(@fixture.naming_strategy.source)
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:each) do
|
12
|
+
@fixture.destroy
|
13
|
+
end
|
14
|
+
|
15
|
+
it "can inspect the name table" do
|
16
|
+
expect(@file.names.keys.count).to eq 1
|
17
|
+
expect(@file.names["en"].keys.sort).to eq ["Unique ID", "Version", "Trademark",
|
18
|
+
"PostScript name", "Subfamily", "Description", "Designer", "Designer URL",
|
19
|
+
"License Description", "License URL", "Vendor URL", "Manufacturer",
|
20
|
+
"Full name", "Family", "Copyright", "Compatible Full", "PostScript CID",
|
21
|
+
"Preferred Family", "Preferred Subfamily", "Reserved",
|
22
|
+
"Sample text", "WWS Family Name", "WWS Subfamily Name"].sort
|
23
|
+
expect(@file.names["en"]["PostScript name"]).to eq "Lobster1.4"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "can inspect it's metrics" do
|
27
|
+
metrics = @file.metrics
|
28
|
+
expect(metrics.keys.sort).to eq ["hhea_linegap", "os2_weight", "upos",
|
29
|
+
"max_descent", "os2_width", "os2_panose", "hhea_descent", "max_ascent",
|
30
|
+
"optical_size", "os2_windescent", "os2_typolinegap", "os2_typoascent",
|
31
|
+
"os2_typodescent", "os2_winascent", "em", "hhea_ascent"].sort
|
32
|
+
|
33
|
+
expect(metrics['em']).to eq 1000
|
34
|
+
expect(metrics['max_ascent']).to eq 1000
|
35
|
+
expect(metrics['max_descent']).to eq -250
|
36
|
+
expect(metrics['upos']).to eq -50
|
37
|
+
expect(metrics['hhea_descent']).to eq -250
|
38
|
+
expect(metrics['hhea_ascent']).to eq 1000
|
39
|
+
expect(metrics['hhea_linegap']).to eq 28
|
40
|
+
expect(metrics['os2_windescent']).to eq 250
|
41
|
+
expect(metrics['os2_winascent']).to eq 1000
|
42
|
+
expect(metrics['os2_typoascent']).to eq 700
|
43
|
+
expect(metrics['os2_typodescent']).to eq -250
|
44
|
+
expect(metrics['os2_typolinegap']).to eq 56
|
45
|
+
expect(metrics['os2_weight']).to eq 400
|
46
|
+
expect(metrics['os2_width']).to eq 5
|
47
|
+
# the test font does not have an optical size, so it should be nil
|
48
|
+
expect(metrics['optical_size']).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "can get the optical size from a fon with it" do
|
52
|
+
opt_size_fixture = FontProcessor::Specs::Fixture.new(:op_size)
|
53
|
+
opt_file = FontProcessor::FontFile.new(opt_size_fixture.naming_strategy.source)
|
54
|
+
metrics = opt_file.metrics
|
55
|
+
expect(metrics.keys.sort).to eq ["hhea_linegap", "os2_weight", "upos",
|
56
|
+
"max_descent", "os2_width", "os2_panose", "hhea_descent", "max_ascent",
|
57
|
+
"os2_windescent", "os2_typolinegap", "os2_typoascent", "optical_size",
|
58
|
+
"os2_typodescent", "os2_winascent", "em", "hhea_ascent"].sort
|
59
|
+
expect(metrics['optical_size']).to eq 180
|
60
|
+
end
|
61
|
+
|
62
|
+
it "can identify the Unicode codepoints contained within a font" do
|
63
|
+
characters = [(32..126), (161..174), (176..255), (260..265)]
|
64
|
+
characters = characters.map { |r| r.to_a }.flatten
|
65
|
+
characters.each do |c|
|
66
|
+
expect(@file.unicode.include?(c)).to be true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "can extract OpenType features" do
|
71
|
+
feature_hash = JSON.parse @file.features
|
72
|
+
expect(feature_hash.has_key?("GPOS")).to be true
|
73
|
+
expect(feature_hash.has_key?("GSUB")).to be true
|
74
|
+
gpos_features = feature_hash["GPOS"]
|
75
|
+
expect(gpos_features).to eq ["kern"]
|
76
|
+
gsub_features = feature_hash["GSUB"]
|
77
|
+
expect(gsub_features).to eq ["aalt", "liga", "salt"]
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
it "contains PostScript outlines" do
|
82
|
+
expect(@file.has_postscript_outlines?).to be true
|
83
|
+
expect(@file.has_truetype_outlines?).to be false
|
84
|
+
end
|
85
|
+
|
86
|
+
it "has a helper function to compact arrays of consecutive integers into ranges" do
|
87
|
+
expect(@file.send(:compact_ranges, [1,2,3,7,8,9])).to eq [Range.new(1,3), Range.new(7,9)]
|
88
|
+
expect(@file.send(:compact_ranges, [1,3,7,2,8,9])).to eq [Range.new(1,3), Range.new(7,9)]
|
89
|
+
expect(@file.send(:compact_ranges, [1,3,7,2,8,9,11])).to eq [Range.new(1,3), Range.new(7,9), 11]
|
90
|
+
expect(@file.send(:compact_ranges, [1,2,3,5,7,8,9,11,13])).to eq [Range.new(1,3), 5, Range.new(7,9), 11, 13]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with a file containing TrueType outlines" do
|
95
|
+
before(:each) do
|
96
|
+
@fixture = FontProcessor::Specs::Fixture.new(:truetype)
|
97
|
+
naming_strategy = @fixture.naming_strategy
|
98
|
+
@file = FontProcessor::FontFile.new(naming_strategy.source)
|
99
|
+
end
|
100
|
+
|
101
|
+
after(:each) do
|
102
|
+
@fixture.destroy
|
103
|
+
end
|
104
|
+
|
105
|
+
it "contains TrueType outlines" do
|
106
|
+
expect(@file.has_truetype_outlines?).to be true
|
107
|
+
expect(@file.has_postscript_outlines?).to be false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FontProcessor::ProcessFontJob do
|
4
|
+
before(:each) do
|
5
|
+
@status_key = "status:/a00000000000000000000001/20/1/woffsvgswf"
|
6
|
+
@font_base_id = "a00000000000000000000001"
|
7
|
+
@request_data = '{"version":"0.3","request_type":"web_processing_request","font_base_id":"a00000000000000000000001","fpv":"21","charset":{"charset_id":"1","unicode":"0x0020..0x007e,0x00a0..0xffff","features":"ALL"},"formats":{"process_original":true,"convert":true,"derivatives":["dyna_base","woff","woff2","inst","svg","swf"]}}'
|
8
|
+
@no_convert = '{"version":"0.3","request_type":"web_processing_request","font_base_id":"a00000000000000000000001","fpv":"21","charset":{"charset_id":"1","unicode":"0x0020..0x007e,0x00a0..0xffff","features":"ALL"},"formats":{"process_original":true,"convert":false,"derivatives":["dyna_base","woff","woff2","inst","svg","swf"]}}'
|
9
|
+
@charset_id = "1"
|
10
|
+
@fpv = "21"
|
11
|
+
@directory = "/tmp/#{@font_base_id}-#{@charset_id}"
|
12
|
+
end
|
13
|
+
subject { FontProcessor::ProcessFontJob }
|
14
|
+
|
15
|
+
describe "integration tests" do
|
16
|
+
it "can successfully process a font" do
|
17
|
+
expect(subject).to receive(:fetch_files).ordered
|
18
|
+
expect(subject).to receive(:process_files).ordered
|
19
|
+
expect(subject).to receive(:upload_metadata).ordered
|
20
|
+
expect(subject).to receive(:upload_files).ordered
|
21
|
+
redis = double("Redis")
|
22
|
+
expect(redis).to receive(:set).with(@status_key, "success")
|
23
|
+
expect(redis).to receive(:expire).with(@status_key, 86400)
|
24
|
+
expect(subject).to receive(:redis).ordered.twice.and_return(redis)
|
25
|
+
|
26
|
+
subject.perform(@status_key, @request_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can successfully process a font without conversion" do
|
30
|
+
expect(subject).to receive(:fetch_files).ordered
|
31
|
+
expect(subject).to receive(:process_files).ordered
|
32
|
+
expect(subject).to receive(:upload_metadata).ordered
|
33
|
+
expect(subject).to receive(:upload_files).ordered
|
34
|
+
redis = double("Redis")
|
35
|
+
expect(redis).to receive(:set).with(@status_key, "success")
|
36
|
+
expect(redis).to receive(:expire).with(@status_key, 86400)
|
37
|
+
expect(subject).to receive(:redis).ordered.twice.and_return(redis)
|
38
|
+
|
39
|
+
subject.perform(@status_key, @no_convert)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can handle an error while processing the font" do
|
43
|
+
expect(subject).to receive(:fetch_files).ordered
|
44
|
+
expect(subject).to receive(:process_files).ordered.and_raise(RuntimeError)
|
45
|
+
redis = double("Redis")
|
46
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
47
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
48
|
+
expect(subject).to receive(:redis).ordered.twice.and_return(redis)
|
49
|
+
|
50
|
+
expect { subject.perform(@status_key, @request_data) }.to raise_exception(Exception)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can handle an error while retrieving the font data" do
|
54
|
+
expect(subject).to receive(:fetch_files).and_raise(FontBase::Error)
|
55
|
+
redis = double("Redis")
|
56
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
57
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
58
|
+
expect(subject).to receive(:redis).twice.and_return(redis)
|
59
|
+
|
60
|
+
expect { subject.perform(@status_key, @request_data) }.to raise_exception(FontBase::Error)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "can handle an error while uploading the processed font metadata" do
|
64
|
+
expect(subject).to receive(:fetch_files).ordered
|
65
|
+
expect(subject).to receive(:process_files).ordered
|
66
|
+
expect(subject).to receive(:upload_metadata).and_raise(FontBase::Error)
|
67
|
+
redis = double("Redis")
|
68
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
69
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
70
|
+
expect(subject).to receive(:redis).ordered.twice.and_return(redis)
|
71
|
+
|
72
|
+
expect { subject.perform(@status_key, @request_data) }.to raise_exception(FontBase::Error)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "can handle an error while uploading processed font data" do
|
76
|
+
expect(subject).to receive(:fetch_files).ordered
|
77
|
+
expect(subject).to receive(:process_files).ordered
|
78
|
+
expect(subject).to receive(:upload_metadata).ordered
|
79
|
+
expect(subject).to receive(:upload_files).ordered.and_raise(Aws::S3::Errors::ServiceError.new('message', 'service error'))
|
80
|
+
redis = double("Redis")
|
81
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
82
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
83
|
+
expect(subject).to receive(:redis).ordered.twice.and_return(redis)
|
84
|
+
|
85
|
+
expect { subject.perform(@status_key, @request_data) }.to raise_exception(Aws::S3::Errors::ServiceError)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "can handle an error for an invalid request type" do
|
89
|
+
redis = double("Redis")
|
90
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
91
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
92
|
+
expect(subject).to receive(:redis).twice.and_return(redis)
|
93
|
+
bad_req_type = JSON.parse(@request_data)
|
94
|
+
bad_req_type['request_type'] = "desktop_processing_request"
|
95
|
+
expect { subject.perform(@status_key, bad_req_type) }.to raise_exception(RuntimeError)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "can handle an error for a request without charset data" do
|
99
|
+
redis = double("Redis")
|
100
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
101
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
102
|
+
expect(subject).to receive(:redis).twice.and_return(redis)
|
103
|
+
bad_req = JSON.parse(@request_data)
|
104
|
+
bad_req.delete('charset')
|
105
|
+
expect { subject.perform(@status_key, bad_req) }.to raise_exception(RuntimeError, /No charset in JSON Request/)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "can handle an error for a request without charset_id in the charset data" do
|
109
|
+
redis = double("Redis")
|
110
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
111
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
112
|
+
expect(subject).to receive(:redis).twice.and_return(redis)
|
113
|
+
bad_req = JSON.parse(@request_data)
|
114
|
+
bad_req['charset'].delete('charset_id')
|
115
|
+
expect { subject.perform(@status_key, bad_req) }.to raise_exception(RuntimeError, /No charset_id set in JSON Request's charset/)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "can handle an error for a request without unicode in the charset data" do
|
119
|
+
redis = double("Redis")
|
120
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
121
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
122
|
+
expect(subject).to receive(:redis).twice.and_return(redis)
|
123
|
+
bad_req = JSON.parse(@request_data)
|
124
|
+
bad_req['charset'].delete('unicode')
|
125
|
+
expect { subject.perform(@status_key, bad_req) }.to raise_exception(RuntimeError, /No unicode set in JSON Request's charset/)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "can handle an error for a request without features in the charset data" do
|
129
|
+
redis = double("Redis")
|
130
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
131
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
132
|
+
expect(subject).to receive(:redis).twice.and_return(redis)
|
133
|
+
bad_req = JSON.parse(@request_data)
|
134
|
+
bad_req['charset'].delete('features')
|
135
|
+
expect { subject.perform(@status_key, bad_req) }.to raise_exception(RuntimeError, /No features set in JSON Request's charset/)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "can handle an error for a request without an fpv" do
|
139
|
+
redis = double("Redis")
|
140
|
+
expect(redis).to receive(:set).with(@status_key, "error")
|
141
|
+
expect(redis).to receive(:expire).with(@status_key, 30)
|
142
|
+
expect(subject).to receive(:redis).twice.and_return(redis)
|
143
|
+
bad_req = JSON.parse(@request_data)
|
144
|
+
bad_req.delete('fpv')
|
145
|
+
expect { subject.perform(@status_key, bad_req) }.to raise_exception(RuntimeError, /No fpv set on JSON Request/)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "fetching files" do
|
150
|
+
before(:each) do
|
151
|
+
FileUtils.mkdir_p(@directory)
|
152
|
+
end
|
153
|
+
after(:each) do
|
154
|
+
FileUtils.rm_r(@directory)
|
155
|
+
end
|
156
|
+
it "successfully handles a single PostScript file" do
|
157
|
+
client = double("FontBase::Client")
|
158
|
+
expect(client).to receive(:get_files).with(@font_base_id).and_return({"cff" => "cff-data"})
|
159
|
+
expect(subject).to receive(:client).and_return(client)
|
160
|
+
subject.fetch_files(@font_base_id, @directory)
|
161
|
+
|
162
|
+
expect(File.read(File.join(@directory, "original.otf"))).to eq "cff-data"
|
163
|
+
end
|
164
|
+
|
165
|
+
it "successfully handles a single TrueType file" do
|
166
|
+
client = double("FontBase::Client")
|
167
|
+
expect(client).to receive(:get_files).with(@font_base_id).and_return({"ttf" => "ttf-data"})
|
168
|
+
expect(subject).to receive(:client).and_return(client)
|
169
|
+
subject.fetch_files(@font_base_id, @directory)
|
170
|
+
|
171
|
+
expect(File.read(File.join(@directory, "original.ttf"))).to eq "ttf-data"
|
172
|
+
end
|
173
|
+
|
174
|
+
it "successfully handles a PostScript and TrueType file" do
|
175
|
+
client = double("FontBase::Client")
|
176
|
+
expect(client).to receive(:get_files).with(@font_base_id).and_return({"ttf" => "ttf-data", "cff" => "cff-data"})
|
177
|
+
expect(subject).to receive(:client).and_return(client)
|
178
|
+
subject.fetch_files(@font_base_id, @directory)
|
179
|
+
|
180
|
+
expect(File.read(File.join(@directory, "original.ttf"))).to eq "ttf-data"
|
181
|
+
expect(File.read(File.join(@directory, "original.otf"))).to eq "cff-data"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "processing files" do
|
186
|
+
before(:each) do
|
187
|
+
FileUtils.mkdir_p(@directory)
|
188
|
+
end
|
189
|
+
after(:each) do
|
190
|
+
FileUtils.rm_r(@directory)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "successfully handles a PostScript file" do
|
194
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "postscript.otf"),
|
195
|
+
File.join(@directory, "original.otf"))
|
196
|
+
|
197
|
+
parsed_data = JSON.parse(@request_data)
|
198
|
+
subject.process_files(@font_base_id, parsed_data['charset'], parsed_data['formats'], @directory)
|
199
|
+
iterator = FontProcessor::ProcessedFontIterator.new(@font_base_id, @charset_id, @fpv, @directory, true)
|
200
|
+
expect(iterator.valid?).to be true
|
201
|
+
expect(File.exist?(File.join(@directory, "charset-#{@charset_id}-otf.otf"))).to be true
|
202
|
+
end
|
203
|
+
|
204
|
+
it "successfully processes a TrueType file" do
|
205
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "truetype.otf"),
|
206
|
+
File.join(@directory, "original.ttf"))
|
207
|
+
|
208
|
+
parsed_data = JSON.parse(@request_data)
|
209
|
+
subject.process_files(@font_base_id, parsed_data['charset'], parsed_data['formats'], @directory)
|
210
|
+
iterator = FontProcessor::ProcessedFontIterator.new(@font_base_id, @charset_id, @fpv, @directory, true)
|
211
|
+
expect(iterator.valid?).to be true
|
212
|
+
expect(File.exist?(File.join(@directory, "charset-#{@charset_id}-otf.ttf"))).to be true
|
213
|
+
end
|
214
|
+
|
215
|
+
it "successfully processes a PostScript file if given both" do
|
216
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "postscript.otf"),
|
217
|
+
File.join(@directory, "original.otf"))
|
218
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "truetype.otf"),
|
219
|
+
File.join(@directory, "original.ttf"))
|
220
|
+
parsed_data = JSON.parse(@request_data)
|
221
|
+
subject.process_files(@font_base_id, parsed_data['charset'], parsed_data['formats'], @directory)
|
222
|
+
iterator = FontProcessor::ProcessedFontIterator.new(@font_base_id, @charset_id, @fpv, @directory, true)
|
223
|
+
expect(iterator.valid?).to be true
|
224
|
+
expect(File.exist?(File.join(@directory, "charset-#{@charset_id}-otf.otf"))).to be true
|
225
|
+
end
|
226
|
+
|
227
|
+
it "successfully handles a PostScript file" do
|
228
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "postscript.otf"),
|
229
|
+
File.join(@directory, "original.otf"))
|
230
|
+
|
231
|
+
parsed_data = JSON.parse(@request_data)
|
232
|
+
parsed_data['formats']['convert'] = false
|
233
|
+
subject.process_files(@font_base_id, parsed_data['charset'], parsed_data['formats'], @directory)
|
234
|
+
iterator = FontProcessor::ProcessedFontIterator.new(@font_base_id, @charset_id, @fpv, @directory, false)
|
235
|
+
expect(iterator.valid?).to be true
|
236
|
+
expect(File.exist?(File.join(@directory, "charset-#{@charset_id}-otf.otf"))).to be true
|
237
|
+
end
|
238
|
+
|
239
|
+
it "successfully subsets a Truetype file" do
|
240
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "truetype.otf"),
|
241
|
+
File.join(@directory, "original.ttf"))
|
242
|
+
|
243
|
+
charset_data = {
|
244
|
+
"charset_id" => "3",
|
245
|
+
"features" => "NONE",
|
246
|
+
"unicode" => "32..32,65..90,97..122"
|
247
|
+
}
|
248
|
+
format_data = {
|
249
|
+
"process_original" => true,
|
250
|
+
"convert" => true,
|
251
|
+
"derivatives" => [ "dyna_base", "woff", "woff2", "inst", "svg", "swf" ]
|
252
|
+
}
|
253
|
+
subject.process_files(@font_base_id, charset_data, format_data, @directory, false)
|
254
|
+
iterator = FontProcessor::ProcessedFontIterator.new(@font_base_id, charset_data['charset_id'], @fpv, @directory, true)
|
255
|
+
expect(iterator.valid?).to be true
|
256
|
+
file = File.join(@directory, "charset-3-otf.ttf")
|
257
|
+
# process_files calls into generate_char_set, then into processor.generate_internal_char_set
|
258
|
+
# This method now automatically adds nb-space and soft-hyphen, so we need to expect
|
259
|
+
# U+A0 (160)
|
260
|
+
expect(FontProcessor::FontFile.new(file).unicode_ranges).to eq [32, 65..90, 97..122, 160]
|
261
|
+
end
|
262
|
+
|
263
|
+
it "throws MissingFilesException when neither are found" do
|
264
|
+
FileUtils.cp(File.join(File.dirname(__FILE__), "..", "..", "fixtures", "postscript.otf"), @directory)
|
265
|
+
|
266
|
+
expect { subject.select_preferred_original(@directory)}.to raise_error(FontProcessor::MissingFilesException)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
describe "uploading metadata" do
|
270
|
+
it "successfully updates the metadata in fontbase" do
|
271
|
+
fake_font_file = double("FontFile", :unicode => "unicode data", :metrics => "metrics data", :names => "names data", :glyph_count => "1", :optical_size => "180", :features => "kern,size")
|
272
|
+
expected_metadata = {
|
273
|
+
:glyphs => "unicode data",
|
274
|
+
:metrics => "metrics data",
|
275
|
+
:names => "names data",
|
276
|
+
:glyph_count => "1",
|
277
|
+
:optical_size => "180",
|
278
|
+
:features => "kern,size"
|
279
|
+
}
|
280
|
+
|
281
|
+
client = double("FontBase::Client")
|
282
|
+
expect(client).to receive(:set_font_processing_version_attributes).with(@font_base_id, @fpv, expected_metadata).and_return({"ttf" => "ttf-data"})
|
283
|
+
expect(subject).to receive(:client).and_return(client)
|
284
|
+
|
285
|
+
subject.upload_metadata(@fpv, @font_base_id, fake_font_file)
|
286
|
+
end
|
287
|
+
it "successfully updates the metadata in fontbase with no optical size" do
|
288
|
+
fake_font_file = double("FontFile", :unicode => "unicode data", :metrics => "metrics data", :names => "names data", :glyph_count => "1", :optical_size => nil, :features => "kern")
|
289
|
+
expected_metadata = {
|
290
|
+
:glyphs => "unicode data",
|
291
|
+
:metrics => "metrics data",
|
292
|
+
:names => "names data",
|
293
|
+
:glyph_count => "1",
|
294
|
+
:optical_size => nil,
|
295
|
+
:features => "kern"
|
296
|
+
}
|
297
|
+
|
298
|
+
client = double("FontBase::Client")
|
299
|
+
expect(client).to receive(:set_font_processing_version_attributes).with(@font_base_id, @fpv, expected_metadata).and_return({"ttf" => "ttf-data"})
|
300
|
+
expect(subject).to receive(:client).and_return(client)
|
301
|
+
|
302
|
+
subject.upload_metadata(@fpv, @font_base_id, fake_font_file)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
describe "uploading files" do
|
306
|
+
it "attempts to upload all valid files" do
|
307
|
+
iterator = [["file1", "key"]]
|
308
|
+
expect(FontProcessor::ProcessedFontIterator).to receive(:new).and_return(iterator)
|
309
|
+
expect(File).to receive(:binread).with("file1").and_return("")
|
310
|
+
expect_any_instance_of(Aws::S3::Client).to receive(:put_object).with(key: "key",
|
311
|
+
body: "",
|
312
|
+
bucket: FontProcessor::Config.s3_processed_bucket)
|
313
|
+
parsed_data = JSON.parse(@request_data)
|
314
|
+
subject.upload_files(parsed_data['fpv'], parsed_data['font_base_id'], parsed_data['charset']['charset_id'], parsed_data['formats'], @directory)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|