woff 1.0.0 → 1.1.0
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 +4 -4
- data/.codeclimate.yml +24 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +13 -0
- data/README.md +25 -4
- data/Rakefile +5 -0
- data/bin/rake +17 -0
- data/lib/woff.rb +8 -1
- data/lib/woff/builder.rb +66 -15
- data/lib/woff/file.rb +152 -0
- data/lib/woff/version.rb +1 -1
- data/requirements.txt +1 -0
- data/spec/builder_spec.rb +49 -0
- data/spec/data/font-with-no-metadata.woff +0 -0
- data/spec/spec_helper.rb +7 -0
- data/woff.gemspec +9 -2
- data/woffTools/Lib/woffTools/__init__.py +1176 -0
- data/woffTools/Lib/woffTools/test/__init__.py +0 -0
- data/woffTools/Lib/woffTools/test/test_validate.py +2657 -0
- data/woffTools/Lib/woffTools/tools/__init__.py +0 -0
- data/woffTools/Lib/woffTools/tools/css.py +292 -0
- data/woffTools/Lib/woffTools/tools/info.py +296 -0
- data/woffTools/Lib/woffTools/tools/proof.py +210 -0
- data/woffTools/Lib/woffTools/tools/support.py +417 -0
- data/woffTools/Lib/woffTools/tools/validate.py +2504 -0
- data/woffTools/License.txt +21 -0
- data/woffTools/README.txt +31 -0
- data/woffTools/setup.py +35 -0
- data/woffTools/woff-all +28 -0
- data/woffTools/woff-css +5 -0
- data/woffTools/woff-info +5 -0
- data/woffTools/woff-proof +5 -0
- data/woffTools/woff-validate +5 -0
- metadata +94 -9
- data/lib/woff/data.rb +0 -44
data/.travis.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
language: ruby
|
2
|
+
python: "3.4"
|
3
|
+
before_install:
|
4
|
+
- curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
|
5
|
+
- sudo python get-pip.py
|
6
|
+
- python -V
|
7
|
+
- pip -V
|
8
|
+
install:
|
9
|
+
- bundle install
|
10
|
+
- sudo pip install -r requirements.txt
|
11
|
+
rvm:
|
12
|
+
- 2.2
|
13
|
+
- 2.3
|
data/README.md
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
-
#
|
1
|
+
# WOFF
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/rb/woff)
|
4
|
+
[](https://travis-ci.org/friendsoftheweb/woff-rb)
|
5
|
+
[](https://codeclimate.com/github/friendsoftheweb/woff-rb)
|
6
|
+
|
7
|
+
This reads binary data from WOFF files in pure Ruby and allows limited
|
8
|
+
modification of metadata.
|
4
9
|
|
5
10
|
## Installation
|
6
11
|
|
7
12
|
Add this line to your application's Gemfile:
|
8
13
|
|
9
|
-
gem 'woff'
|
14
|
+
gem 'woff', '~> 1.1.0'
|
10
15
|
|
11
16
|
And then execute:
|
12
17
|
|
@@ -16,9 +21,25 @@ Or install it yourself as:
|
|
16
21
|
|
17
22
|
$ gem install woff
|
18
23
|
|
24
|
+
## WOFF2 Support
|
25
|
+
|
26
|
+
The gem can currently read, but not write to WOFF2 files. Both reading and writing
|
27
|
+
WOFF2 files should be considered in development, with API changes likely.
|
28
|
+
|
19
29
|
## Usage
|
20
30
|
|
21
|
-
|
31
|
+
Used in generation of WOFF files. Returns the data to be written to a file or
|
32
|
+
sent to the Zip gem of your choice.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
woff = WOFF::Builder.new("/Users/Desktop/sample.woff")
|
36
|
+
|
37
|
+
# This will set or update the metadata's licensee name to `The Friends` and the
|
38
|
+
# metadata's license id to `L012356093901`.
|
39
|
+
data = woff.font_with_licensee_and_id("The Friends", "L012356093901")
|
40
|
+
|
41
|
+
File.binwrite("/Users/Desktop/sample-with-metadata.woff", data)
|
42
|
+
```
|
22
43
|
|
23
44
|
## Contributing
|
24
45
|
|
data/Rakefile
CHANGED
data/bin/rake
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'rake' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
|
10
|
+
require "pathname"
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
|
+
Pathname.new(__FILE__).realpath)
|
13
|
+
|
14
|
+
require "rubygems"
|
15
|
+
require "bundler/setup"
|
16
|
+
|
17
|
+
load Gem.bin_path("rake", "rake")
|
data/lib/woff.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require "woff/version"
|
2
2
|
require "zlib"
|
3
|
+
require "brotli"
|
3
4
|
require "bindata"
|
4
5
|
require "rexml/document"
|
6
|
+
require "woff/file"
|
5
7
|
require "woff/builder"
|
6
|
-
require "woff/data"
|
7
8
|
|
8
9
|
module WOFF
|
9
10
|
class FontNotFoundError < StandardError
|
@@ -11,4 +12,10 @@ module WOFF
|
|
11
12
|
super(msg)
|
12
13
|
end
|
13
14
|
end
|
15
|
+
|
16
|
+
class InvalidSignatureError < StandardError
|
17
|
+
def initialize(msg = "The WOFF file contains an invalid WOFF or WOFF2 signature.")
|
18
|
+
super(msg)
|
19
|
+
end
|
20
|
+
end
|
14
21
|
end
|
data/lib/woff/builder.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
module WOFF
|
2
|
-
# Used in generation of WOFF files
|
3
|
-
#
|
4
|
-
# the output for use in one time use downloads.
|
2
|
+
# Used in generation of WOFF files with modified metadata for licensee and
|
3
|
+
# license id information.
|
5
4
|
#
|
6
5
|
# woff = WOFF::Builder.new("/Users/Josh/Desktop/sample.woff")
|
7
|
-
# woff.
|
6
|
+
# woff.font_with_licensee_and_id("The Friends", "L012356093901")
|
8
7
|
#
|
9
8
|
class Builder
|
10
9
|
def initialize(file)
|
@@ -12,36 +11,88 @@ module WOFF
|
|
12
11
|
end
|
13
12
|
|
14
13
|
def font_with_licensee_and_id(name, id)
|
15
|
-
|
14
|
+
font_with_metadata(licensee: name, license_id: id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def font_with_metadata(licensee: nil, license_id: nil, license_text: nil, description: nil)
|
18
|
+
metadata_xml = data.metadata.length > 0 ? compressor.inflate(data.metadata) : default_metadata
|
16
19
|
metadata_doc = REXML::Document.new(metadata_xml)
|
17
20
|
|
18
|
-
if
|
19
|
-
metadata_doc.root.elements["licensee"]
|
20
|
-
|
21
|
-
|
21
|
+
if licensee
|
22
|
+
if metadata_doc.root.elements["licensee"]
|
23
|
+
metadata_doc.root.elements["licensee"].attributes["name"] = licensee
|
24
|
+
else
|
25
|
+
metadata_doc.root.add_element "licensee", { "name" => licensee }
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
|
-
if
|
25
|
-
metadata_doc.root.elements["license"]
|
26
|
-
|
27
|
-
|
29
|
+
if license_id
|
30
|
+
if metadata_doc.root.elements["license"]
|
31
|
+
metadata_doc.root.elements["license"].attributes["id"] = license_id
|
32
|
+
else
|
33
|
+
metadata_doc.root.add_element "license", { "id" => license_id }
|
34
|
+
end
|
28
35
|
end
|
29
36
|
|
30
|
-
|
37
|
+
if license_text
|
38
|
+
license_el = metadata_doc.root.elements["license"]
|
39
|
+
unless license_el
|
40
|
+
license_el = metadata_doc.root.add_element "license"
|
41
|
+
end
|
42
|
+
|
43
|
+
license_text_el = license_el.elements["text"]
|
44
|
+
unless license_text_el
|
45
|
+
license_text_el = license_el.add_element("text", { "lang" => "en "})
|
46
|
+
end
|
47
|
+
|
48
|
+
license_text_el.text = license_text
|
49
|
+
end
|
50
|
+
|
51
|
+
if description
|
52
|
+
description_el = metadata_doc.root.elements["description"]
|
53
|
+
unless description_el
|
54
|
+
description_el = metadata_doc.root.add_element "description"
|
55
|
+
end
|
56
|
+
|
57
|
+
description_text_el = description_el.elements["text"]
|
58
|
+
unless description_text_el
|
59
|
+
description_text_el = description_el.add_element("text", { "lang" => "en "})
|
60
|
+
end
|
61
|
+
|
62
|
+
description_text_el.text = description
|
63
|
+
end
|
64
|
+
|
65
|
+
compressed_metadata = compressor.deflate(metadata_doc.to_s)
|
31
66
|
|
32
67
|
data.meta_orig_length = metadata_doc.to_s.bytesize
|
33
68
|
data.metadata = compressed_metadata
|
34
69
|
data.meta_length = compressed_metadata.bytesize
|
70
|
+
data.meta_offset = data.metadata.abs_offset # "Offset to metadata block, from beginning of WOFF file."
|
71
|
+
|
35
72
|
data.data_length = data.num_bytes
|
36
73
|
|
37
74
|
data.to_binary_s
|
38
75
|
end
|
39
76
|
|
77
|
+
|
40
78
|
private
|
41
79
|
attr_reader :location
|
42
80
|
|
43
81
|
def data
|
44
|
-
@data ||= WOFF::
|
82
|
+
@data ||= WOFF::File.read(::File.open(location))
|
83
|
+
end
|
84
|
+
|
85
|
+
def compressor
|
86
|
+
case data
|
87
|
+
when WOFF::File::V1
|
88
|
+
::Zlib
|
89
|
+
when WOFF::File::V2
|
90
|
+
::Brotli
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_metadata
|
95
|
+
%Q{<?xml version="1.0" encoding="UTF-8"?><metadata version="1.0"></metadata>}
|
45
96
|
end
|
46
97
|
end
|
47
98
|
end
|
data/lib/woff/file.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
class UIntBase128 < BinData::BasePrimitive
|
2
|
+
# TODO: This should, like actually encode the value. This file might help:
|
3
|
+
# https://github.com/khaledhosny/woff2/blob/f43ad222715f58ea62a004b54e4b6a31e589e762/src/variable_length.cc
|
4
|
+
def value_to_binary_string(value)
|
5
|
+
"0"
|
6
|
+
end
|
7
|
+
|
8
|
+
def read_and_return_value(io)
|
9
|
+
value = 0
|
10
|
+
offset = 0
|
11
|
+
|
12
|
+
loop do
|
13
|
+
byte = io.readbytes(1).unpack('C')[0]
|
14
|
+
value |= (byte & 0x7F) << offset
|
15
|
+
offset += 7
|
16
|
+
if byte & 0x80 == 0
|
17
|
+
break
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def sensible_default
|
25
|
+
0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module WOFF
|
30
|
+
class File
|
31
|
+
def self.read(file)
|
32
|
+
data = Data.read(file)
|
33
|
+
file.rewind
|
34
|
+
|
35
|
+
if data.signature == 0x774F4646
|
36
|
+
V1.read(file)
|
37
|
+
elsif data.signature == 0x774F4632
|
38
|
+
V2.read(file)
|
39
|
+
else
|
40
|
+
raise WOFF::InvalidSignatureError
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Data < ::BinData::Record
|
45
|
+
endian :big
|
46
|
+
|
47
|
+
uint32 :signature
|
48
|
+
end
|
49
|
+
|
50
|
+
class V1 < ::BinData::Record
|
51
|
+
endian :big
|
52
|
+
count_bytes_remaining :bytes_remaining
|
53
|
+
|
54
|
+
uint32 :signature
|
55
|
+
uint32 :flavor
|
56
|
+
uint32 :data_length
|
57
|
+
uint16 :num_tables
|
58
|
+
uint16 :reserved
|
59
|
+
uint32 :total_s_fnt_size
|
60
|
+
uint16 :major_version
|
61
|
+
uint16 :minor_version
|
62
|
+
uint32 :meta_offset
|
63
|
+
uint32 :meta_length
|
64
|
+
uint32 :meta_orig_length
|
65
|
+
uint32 :priv_offset
|
66
|
+
uint32 :priv_length
|
67
|
+
|
68
|
+
array :table_directory, initial_length: :num_tables do
|
69
|
+
uint32 :tag
|
70
|
+
uint32 :table_offset
|
71
|
+
uint32 :comp_length
|
72
|
+
uint32 :orig_length
|
73
|
+
uint32 :orig_checksum
|
74
|
+
end
|
75
|
+
|
76
|
+
string :fonts, read_length: lambda {
|
77
|
+
dir = table_directory.sort_by { |entry| entry["table_offset"] }
|
78
|
+
|
79
|
+
first_table_start = dir.first["table_offset"]
|
80
|
+
last_table_end = dir.last["table_offset"] + dir.last["comp_length"]
|
81
|
+
|
82
|
+
table_length = last_table_end - first_table_start
|
83
|
+
|
84
|
+
# Next largest number divisible by 4
|
85
|
+
(table_length / 4.0).ceil * 4
|
86
|
+
}
|
87
|
+
|
88
|
+
string :metadata, read_length: :meta_length
|
89
|
+
|
90
|
+
rest :private_data
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
class V2 < ::BinData::Record
|
95
|
+
endian :big
|
96
|
+
count_bytes_remaining :bytes_remaining
|
97
|
+
|
98
|
+
uint32 :signature
|
99
|
+
uint32 :flavor
|
100
|
+
uint32 :data_length
|
101
|
+
uint16 :num_tables
|
102
|
+
uint16 :reserved
|
103
|
+
uint32 :total_s_fnt_size
|
104
|
+
uint32 :total_compressed_size
|
105
|
+
uint16 :major_version
|
106
|
+
uint16 :minor_version
|
107
|
+
uint32 :meta_offset
|
108
|
+
uint32 :meta_length
|
109
|
+
uint32 :meta_orig_length
|
110
|
+
uint32 :priv_offset
|
111
|
+
uint32 :priv_length
|
112
|
+
|
113
|
+
array :table_directory, initial_length: :num_tables do
|
114
|
+
uint8 :flags
|
115
|
+
uint32 :tag, onlyif: -> {
|
116
|
+
bits = flags.to_binary_s.unpack('B*')[0]
|
117
|
+
tag_index = bits[0..5].to_i(2)
|
118
|
+
|
119
|
+
tag_index == 63
|
120
|
+
}
|
121
|
+
u_int_base128 :orig_length
|
122
|
+
u_int_base128 :transform_length, onlyif: -> {
|
123
|
+
bits = flags.to_binary_s.unpack('B*')[0]
|
124
|
+
tag_index = bits[0..5].to_i(2)
|
125
|
+
transform_version = bits[6..7].to_i(2)
|
126
|
+
is_loca_or_glyf = [10, 11].include?(tag_index)
|
127
|
+
|
128
|
+
(is_loca_or_glyf && transform_version != 3) || (!is_loca_or_glyf && transform_version != 0)
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
struct :collection_directory, onlyif: :has_collection? do
|
133
|
+
uint32 :version
|
134
|
+
uint16 :num_fonts # 255UInt16
|
135
|
+
|
136
|
+
array :collection_font_entries, initial_length: :num_fonts do
|
137
|
+
uint16 :num_collection_tables # 255UInt16
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
string :compressed_data, read_length: :total_compressed_size
|
142
|
+
|
143
|
+
string :metadata, read_length: :meta_length
|
144
|
+
|
145
|
+
rest :private_data
|
146
|
+
|
147
|
+
def has_collection?
|
148
|
+
flavor == 0x74746366
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/woff/version.rb
CHANGED
data/requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
fonttools==3.1.1
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# This uses the forked and embedded woffTools script, it needs python available, as well
|
2
|
+
# as woffTools python dependencies.
|
3
|
+
|
4
|
+
require 'spec_helper.rb'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
describe WOFF::Builder do
|
8
|
+
let(:tmpdir_path) { Dir.mktmpdir }
|
9
|
+
let(:output_path) { File.join(tmpdir_path, "with_licensee_and_id.woff") }
|
10
|
+
let(:no_metadata_woff_path) { File.expand_path("../data/font-with-no-metadata.woff", __FILE__) }
|
11
|
+
|
12
|
+
let(:validate_script) { File.expand_path("../../woffTools/Lib/woffTools/tools/validate.py", __FILE__) }
|
13
|
+
|
14
|
+
after do
|
15
|
+
FileUtils.rm_r(tmpdir_path)
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#font_with_licensee_and_id" do
|
19
|
+
let(:licensee) { "Some Licensee" }
|
20
|
+
let(:id) { "L012356093901" }
|
21
|
+
|
22
|
+
before do
|
23
|
+
woff = WOFF::Builder.new(no_metadata_woff_path)
|
24
|
+
|
25
|
+
data = woff.font_with_licensee_and_id("The Friends", "L012356093901")
|
26
|
+
File.binwrite(output_path, data)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is valid according to woffTools" do
|
30
|
+
expect(system("python", validate_script, output_path, "-q")).to be(true)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#font_with_metadata" do
|
35
|
+
let(:licensee) { "Some Licensee" }
|
36
|
+
let(:license_id) { "L012356093901" }
|
37
|
+
let(:license_text) { "Do the right things" }
|
38
|
+
let(:description) { "very nice font" }
|
39
|
+
|
40
|
+
let(:woff) { woff = WOFF::Builder.new(no_metadata_woff_path) }
|
41
|
+
|
42
|
+
it "can set just license_text and description" do
|
43
|
+
data = woff.font_with_metadata(license_text: license_text, description: description)
|
44
|
+
File.binwrite(output_path, data)
|
45
|
+
|
46
|
+
expect(system("python", validate_script, output_path, "-q")).to be(true)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
Binary file
|