woff 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/woff.svg)](https://badge.fury.io/rb/woff)
|
4
|
+
[![Build Status](https://travis-ci.org/friendsoftheweb/woff-rb.svg?branch=master)](https://travis-ci.org/friendsoftheweb/woff-rb)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/friendsoftheweb/woff-rb/badges/gpa.svg)](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
|