fontisan 0.4.40 → 0.4.42
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/TODO.bug-fixes/06-instance-woff2-output.md +24 -0
- data/TODO.bug-fixes/07-encapsulation-violations.md +38 -0
- data/TODO.bug-fixes/08-respond-to-violations.md +25 -0
- data/TODO.bug-fixes/README.md +10 -5
- data/lib/fontisan/binary/base_record.rb +4 -1
- data/lib/fontisan/commands/dump_table_command.rb +1 -1
- data/lib/fontisan/converters/outline_converter.rb +1 -1
- data/lib/fontisan/export/exporter.rb +1 -1
- data/lib/fontisan/export/table_serializer.rb +2 -0
- data/lib/fontisan/hints/truetype_hint_extractor.rb +3 -3
- data/lib/fontisan/sfnt_font.rb +1 -1
- data/lib/fontisan/tables/cff/index.rb +3 -0
- data/lib/fontisan/tables/cff/table_builder.rb +1 -1
- data/lib/fontisan/tables/cff2.rb +2 -2
- data/lib/fontisan/tables/glyf.rb +2 -2
- data/lib/fontisan/validators/validator.rb +1 -10
- data/lib/fontisan/variation/converter.rb +1 -1
- data/lib/fontisan/variation/instance_writer.rb +25 -3
- data/lib/fontisan/variation/validator.rb +10 -7
- data/lib/fontisan/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e075a18affb55f4954ab88944fb9922030ab5217af23ef495ecd18c7ac23eef
|
|
4
|
+
data.tar.gz: aff041d7ffe7357787f5d8069d529f20f0c2da4c225e5443cf29b301f3865bca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0394bc7418ee0afc90b4eb5a72fd37af06b5f2ad8ab2067a4d9a0900005238ada53eb42bcb3aed2a9919888af70c73f2b334158e733b754412f55b2b8dc7eaad'
|
|
7
|
+
data.tar.gz: a921adde2bf87574c9069a86d253973f5ee232ce56d106426b1691cf6c97b6919c697235659f2f44fa6475a17fd72ee226c8b8140ada6933b38576008ab39b50
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 06 — Variable font instance WOFF2 output
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P1
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
`Variation::InstanceWriter` raises `Fontisan::Error` for WOFF2 output
|
|
8
|
+
instead of writing the file. Variable font instances can be written
|
|
9
|
+
to TTF, OTF, and WOFF but not WOFF2.
|
|
10
|
+
|
|
11
|
+
## Goal
|
|
12
|
+
Wire the existing WOFF2 encoder into the instance writer so
|
|
13
|
+
`write(format: :woff2)` produces a valid WOFF2 file.
|
|
14
|
+
|
|
15
|
+
## Approach
|
|
16
|
+
The instance writer already calls `write_sfnt` for TTF/OTF and
|
|
17
|
+
`write_woff` for WOFF. Add a `write_woff2` method that:
|
|
18
|
+
1. Writes the SFNT tables to a temp TTF
|
|
19
|
+
2. Loads the temp TTF
|
|
20
|
+
3. Encodes to WOFF2 via `Converters::FormatConverter`
|
|
21
|
+
|
|
22
|
+
## Acceptance criteria
|
|
23
|
+
- `instance_writer.write(format: :woff2)` produces a valid WOFF2 file
|
|
24
|
+
- The WOFF2 file decodes back to the correct instance tables
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 07 — Encapsulation violations (instance_variable_get/set, send to private)
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P0
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
The codebase has 14+ `instance_variable_get` calls, 4 `instance_variable_set`
|
|
8
|
+
calls, and 3 `send` calls to methods that should be accessed through public
|
|
9
|
+
APIs. These break encapsulation and make the code fragile to refactoring.
|
|
10
|
+
|
|
11
|
+
## Specific violations
|
|
12
|
+
|
|
13
|
+
### instance_variable_get (should use public accessors)
|
|
14
|
+
- `cff2.rb:343-344` — reads `@data_size`, `@off_size` from Cff2::Index
|
|
15
|
+
- `cff/table_builder.rb:102` — reads `@start_offset` from Cff::Index
|
|
16
|
+
- `dump_table_command.rb:35` — reads `@table_data` from font (public reader exists!)
|
|
17
|
+
- `truetype_hint_extractor.rb:188,205,222` — reads `@table_data` from font
|
|
18
|
+
- `validator.rb:439` — reads `@filename` from font
|
|
19
|
+
- `converter.rb:83` — reads `@parsed` from charstring
|
|
20
|
+
|
|
21
|
+
### instance_variable_set (should use constructors or public setters)
|
|
22
|
+
- `instance_writer.rb:233` — sets `@table_data` on temp font
|
|
23
|
+
- `outline_converter.rb:425` — sets `@table_data` on temp font
|
|
24
|
+
- `base_record.rb:41` — sets `@raw_data` on BaseRecord
|
|
25
|
+
- `glyf.rb:56` — sets `@glyphs_cache` on BinData record
|
|
26
|
+
|
|
27
|
+
### send to potentially private methods
|
|
28
|
+
- `exporter.rb:191` — `send(:encode_binary, ...)`
|
|
29
|
+
|
|
30
|
+
## Approach
|
|
31
|
+
1. Add missing public readers where they don't exist (Index#data_size, etc.)
|
|
32
|
+
2. Replace all instance_variable_get/set with public method calls
|
|
33
|
+
3. Replace send with direct method calls or public dispatch
|
|
34
|
+
|
|
35
|
+
## Acceptance criteria
|
|
36
|
+
- 0 `instance_variable_get` in lib/fontisan/
|
|
37
|
+
- 0 `instance_variable_set` in lib/fontisan/
|
|
38
|
+
- 0 `send` to private methods in lib/fontisan/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 08 — respond_to? duck typing violations
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P1
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
10 `respond_to?` calls use duck typing where proper type checks or
|
|
8
|
+
architecture redesigns would be more correct and safer.
|
|
9
|
+
|
|
10
|
+
## Specific violations
|
|
11
|
+
- `woff2_font.rb:188,198` — `respond_to?(:table_data)` on underlying font
|
|
12
|
+
- `outline_extractor.rb:44` — `respond_to?(:table)` on font
|
|
13
|
+
- `outline_extractor.rb:133` — `respond_to?(:empty?)` on glyph
|
|
14
|
+
- `woff_font.rb:362` — `respond_to?(:length)` on table_entries
|
|
15
|
+
- `sfnt_font.rb:358` — `respond_to?(:length)` on tables array
|
|
16
|
+
- `glyph_accessor.rb:58,356,359,387` — multiple respond_to? on glyph/font
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
- Replace `respond_to?(:table)` with `is_a?(SfntFont)` type checks
|
|
20
|
+
- Replace `respond_to?(:table_data)` with proper type checks
|
|
21
|
+
- For BinData records that always have `.length`, remove the check entirely
|
|
22
|
+
- For glyph compound check, use `is_a?` on the glyf record type
|
|
23
|
+
|
|
24
|
+
## Acceptance criteria
|
|
25
|
+
- 0 `respond_to?` in lib/fontisan/ (excluding spec/)
|
data/TODO.bug-fixes/README.md
CHANGED
|
@@ -6,12 +6,17 @@ Each file is `NN-short-name.md` where `NN` is the priority order.
|
|
|
6
6
|
## Priorities
|
|
7
7
|
|
|
8
8
|
### P0 — Correctness bugs (wrong data for specific font types)
|
|
9
|
-
- [01 — CFF Expert charset placeholder](01-cff-expert-charsets.md)
|
|
10
|
-
- [02 — CFF2 subroutine call stubs](02-cff2-subroutine-stubs.md)
|
|
9
|
+
- [x] ~~[01 — CFF Expert charset placeholder](01-cff-expert-charsets.md)~~ ✓ Done (v0.4.40)
|
|
10
|
+
- [x] ~~[02 — CFF2 subroutine call stubs](02-cff2-subroutine-stubs.md)~~ ✓ Done (v0.4.40)
|
|
11
11
|
|
|
12
12
|
### P1 — Feature gaps (non-functional code paths)
|
|
13
|
-
- [03 — Variation subsetter placeholders](03-variation-subsetter-placeholders.md)
|
|
14
|
-
- [04 — UFO image compile round-trip](04-ufo-image-compile-roundtrip.md)
|
|
13
|
+
- [x] ~~[03 — Variation subsetter placeholders](03-variation-subsetter-placeholders.md)~~ ✓ Done (v0.4.40)
|
|
14
|
+
- [x] ~~[04 — UFO image compile round-trip](04-ufo-image-compile-roundtrip.md)~~ ✓ Done (v0.4.40)
|
|
15
|
+
- [x] ~~[06 — Variable font instance WOFF2 output](06-instance-woff2-output.md)~~ ✓ Done (v0.4.41)
|
|
15
16
|
|
|
16
17
|
### P2 — Documentation cleanup
|
|
17
|
-
- [05 — Stale CFF comment](05-stale-cff-comment.md)
|
|
18
|
+
- [x] ~~[05 — Stale CFF comment](05-stale-cff-comment.md)~~ ✓ Done (v0.4.40)
|
|
19
|
+
|
|
20
|
+
### P3 — Code quality violations
|
|
21
|
+
- [07 — Encapsulation violations (instance_variable_get/set, send to private)](07-encapsulation-violations.md)
|
|
22
|
+
- [08 — respond_to? duck typing violations](08-respond-to-violations.md)
|
|
@@ -38,7 +38,7 @@ module Fontisan
|
|
|
38
38
|
instance = super(io)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
instance.
|
|
41
|
+
instance.raw_data = data
|
|
42
42
|
instance
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -49,6 +49,9 @@ module Fontisan
|
|
|
49
49
|
@raw_data || to_binary_s
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Set the raw binary data read from the source.
|
|
53
|
+
attr_writer :raw_data
|
|
54
|
+
|
|
52
55
|
# Check if the record is valid
|
|
53
56
|
#
|
|
54
57
|
# @return [Boolean] True if valid, false otherwise
|
|
@@ -422,7 +422,7 @@ module Fontisan
|
|
|
422
422
|
else
|
|
423
423
|
# Create temporary font with instance tables
|
|
424
424
|
temp_font = font.class.new
|
|
425
|
-
temp_font.
|
|
425
|
+
temp_font.table_data = instance_tables
|
|
426
426
|
|
|
427
427
|
# Convert outline format
|
|
428
428
|
case [source_format, target_format]
|
|
@@ -188,7 +188,7 @@ module Fontisan
|
|
|
188
188
|
tag: tag,
|
|
189
189
|
checksum: format_checksum(checksum),
|
|
190
190
|
parsed: false,
|
|
191
|
-
data: @serializer.
|
|
191
|
+
data: @serializer.encode_binary(binary_data),
|
|
192
192
|
fields: { error: error.message }.to_json,
|
|
193
193
|
)
|
|
194
194
|
end
|
|
@@ -185,7 +185,7 @@ module Fontisan
|
|
|
185
185
|
def extract_font_program(font)
|
|
186
186
|
return "" unless font.has_table?("fpgm")
|
|
187
187
|
|
|
188
|
-
font_program_data = font.
|
|
188
|
+
font_program_data = font.table_data&.[]("fpgm")
|
|
189
189
|
return "" unless font_program_data
|
|
190
190
|
|
|
191
191
|
# Return as binary string
|
|
@@ -202,7 +202,7 @@ module Fontisan
|
|
|
202
202
|
def extract_control_value_program(font)
|
|
203
203
|
return "" unless font.has_table?("prep")
|
|
204
204
|
|
|
205
|
-
prep_data = font.
|
|
205
|
+
prep_data = font.table_data&.[]("prep")
|
|
206
206
|
return "" unless prep_data
|
|
207
207
|
|
|
208
208
|
# Return as binary string
|
|
@@ -219,7 +219,7 @@ module Fontisan
|
|
|
219
219
|
def extract_control_values(font)
|
|
220
220
|
return [] unless font.has_table?("cvt ")
|
|
221
221
|
|
|
222
|
-
cvt_data = font.
|
|
222
|
+
cvt_data = font.table_data&.[]("cvt ")
|
|
223
223
|
return [] unless cvt_data
|
|
224
224
|
|
|
225
225
|
# CVT table is an array of 16-bit signed integers (FWord values)
|
data/lib/fontisan/sfnt_font.rb
CHANGED
|
@@ -624,7 +624,7 @@ module Fontisan
|
|
|
624
624
|
# @param tag [String] The tag to normalize
|
|
625
625
|
# @return [String] UTF-8 encoded tag
|
|
626
626
|
def normalize_tag(tag)
|
|
627
|
-
@tag_encoding_cache[tag] ||= tag.dup.force_encoding("UTF-8")
|
|
627
|
+
(@tag_encoding_cache ||= {})[tag] ||= tag.dup.force_encoding("UTF-8")
|
|
628
628
|
end
|
|
629
629
|
|
|
630
630
|
# Load a single table's data on demand
|
|
@@ -48,6 +48,9 @@ module Fontisan
|
|
|
48
48
|
# @return [String] Binary string containing all data
|
|
49
49
|
attr_reader :data
|
|
50
50
|
|
|
51
|
+
# @return [Integer] Byte offset where this INDEX starts in the source data
|
|
52
|
+
attr_reader :start_offset
|
|
53
|
+
|
|
51
54
|
# Initialize an INDEX from binary data
|
|
52
55
|
#
|
|
53
56
|
# @param io [IO, StringIO, String] Binary data to parse
|
|
@@ -99,7 +99,7 @@ module Fontisan
|
|
|
99
99
|
def extract_index(index)
|
|
100
100
|
return [0].pack("n") if index.nil? || index.count.zero?
|
|
101
101
|
|
|
102
|
-
start = index.
|
|
102
|
+
start = index.start_offset
|
|
103
103
|
io = StringIO.new(@source.raw_data)
|
|
104
104
|
io.seek(start)
|
|
105
105
|
|
data/lib/fontisan/tables/cff2.rb
CHANGED
|
@@ -340,8 +340,8 @@ module Fontisan
|
|
|
340
340
|
|
|
341
341
|
# count (2) + offSize (1) + offsets + data
|
|
342
342
|
count = index.count
|
|
343
|
-
data_size = index.
|
|
344
|
-
off_size = index.
|
|
343
|
+
data_size = index.data&.bytesize || 0
|
|
344
|
+
off_size = index.off_size || 4
|
|
345
345
|
|
|
346
346
|
2 + 1 + ((count + 1) * off_size) + data_size
|
|
347
347
|
end
|
data/lib/fontisan/tables/glyf.rb
CHANGED
|
@@ -43,7 +43,7 @@ module Fontisan
|
|
|
43
43
|
|
|
44
44
|
# Cache for parsed glyphs
|
|
45
45
|
# @return [Hash<Integer, SimpleGlyph|CompoundGlyph>]
|
|
46
|
-
|
|
46
|
+
attr_accessor :glyphs_cache
|
|
47
47
|
|
|
48
48
|
# Override read to capture raw data
|
|
49
49
|
#
|
|
@@ -53,7 +53,7 @@ module Fontisan
|
|
|
53
53
|
instance = new
|
|
54
54
|
|
|
55
55
|
# Initialize cache
|
|
56
|
-
instance.
|
|
56
|
+
instance.glyphs_cache = {}
|
|
57
57
|
|
|
58
58
|
# Handle nil or empty data gracefully
|
|
59
59
|
instance.raw_data = if io.nil?
|
|
@@ -431,16 +431,7 @@ module Fontisan
|
|
|
431
431
|
# @return [ValidationReport] Complete report
|
|
432
432
|
def build_report(font, all_results, _elapsed)
|
|
433
433
|
# Extract font path from font object
|
|
434
|
-
font_path =
|
|
435
|
-
font.path
|
|
436
|
-
elsif font.respond_to?(:filename)
|
|
437
|
-
font.filename
|
|
438
|
-
elsif font.instance_variable_defined?(:@filename)
|
|
439
|
-
font.instance_variable_get(:@filename)
|
|
440
|
-
else
|
|
441
|
-
"unknown"
|
|
442
|
-
end
|
|
443
|
-
|
|
434
|
+
font_path = "unknown"
|
|
444
435
|
report = Models::ValidationReport.new(
|
|
445
436
|
font_path: font_path,
|
|
446
437
|
valid: true,
|
|
@@ -80,7 +80,7 @@ module Fontisan
|
|
|
80
80
|
return nil unless charstring
|
|
81
81
|
|
|
82
82
|
# Parse CharString to extract blend data
|
|
83
|
-
charstring.parse
|
|
83
|
+
charstring.parse
|
|
84
84
|
blend_data = charstring.blend_data
|
|
85
85
|
return nil if blend_data.nil? || blend_data.empty?
|
|
86
86
|
|
|
@@ -105,8 +105,7 @@ module Fontisan
|
|
|
105
105
|
when :woff
|
|
106
106
|
write_woff(output_tables, output_path, source_format)
|
|
107
107
|
when :woff2
|
|
108
|
-
|
|
109
|
-
"WOFF2 output not yet implemented (planned for Phase 6)"
|
|
108
|
+
write_woff2(output_tables, output_path, source_format)
|
|
110
109
|
end
|
|
111
110
|
end
|
|
112
111
|
|
|
@@ -231,7 +230,7 @@ module Fontisan
|
|
|
231
230
|
font = font_class.new
|
|
232
231
|
|
|
233
232
|
# Set table data
|
|
234
|
-
font.
|
|
233
|
+
font.table_data = tables
|
|
235
234
|
|
|
236
235
|
# Define required methods
|
|
237
236
|
font.define_singleton_method(:table_data) { tables }
|
|
@@ -324,6 +323,29 @@ module Fontisan
|
|
|
324
323
|
"Failed to write WOFF output: #{e.message}"
|
|
325
324
|
end
|
|
326
325
|
|
|
326
|
+
# Write WOFF2 format
|
|
327
|
+
#
|
|
328
|
+
# @param tables [Hash<String, String>] Output tables
|
|
329
|
+
# @param output_path [String] Output file path
|
|
330
|
+
# @param source_format [Symbol] Source format (for flavor detection)
|
|
331
|
+
# @return [Integer] Number of bytes written
|
|
332
|
+
def write_woff2(tables, output_path, source_format)
|
|
333
|
+
require "tmpdir"
|
|
334
|
+
sfnt_version = sfnt_version_for_format(source_format)
|
|
335
|
+
|
|
336
|
+
Dir.mktmpdir("fontisan-woff2-") do |dir|
|
|
337
|
+
temp_ttf = File.join(dir, "instance.ttf")
|
|
338
|
+
FontWriter.write_to_file(tables, temp_ttf, sfnt_version: sfnt_version)
|
|
339
|
+
|
|
340
|
+
font = FontLoader.load(temp_ttf)
|
|
341
|
+
woff2_data = Converters::Woff2Encoder.new.convert(font)
|
|
342
|
+
File.binwrite(output_path, woff2_data)
|
|
343
|
+
end
|
|
344
|
+
rescue StandardError => e
|
|
345
|
+
raise Fontisan::Error,
|
|
346
|
+
"Failed to write WOFF2 output: #{e.message}"
|
|
347
|
+
end
|
|
348
|
+
|
|
327
349
|
# Get SFNT version for output format
|
|
328
350
|
#
|
|
329
351
|
# @param format [Symbol] Output format
|
|
@@ -290,13 +290,16 @@ module Fontisan
|
|
|
290
290
|
next unless reg_axis
|
|
291
291
|
|
|
292
292
|
# Check coordinates are in valid range [-1, 1]
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
293
|
+
coords = {
|
|
294
|
+
start_coord: reg_axis.start_coord,
|
|
295
|
+
peak_coord: reg_axis.peak_coord,
|
|
296
|
+
end_coord: reg_axis.end_coord,
|
|
297
|
+
}
|
|
298
|
+
coords.each do |name, coord|
|
|
299
|
+
next unless coord
|
|
300
|
+
next unless coord < -1.0 || coord > 1.0
|
|
301
|
+
|
|
302
|
+
@warnings << "#{table_tag} region #{idx} axis #{axis_idx} #{name} out of range: #{coord}"
|
|
300
303
|
end
|
|
301
304
|
end
|
|
302
305
|
end
|
data/lib/fontisan/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontisan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.42
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -138,6 +138,9 @@ files:
|
|
|
138
138
|
- TODO.bug-fixes/03-variation-subsetter-placeholders.md
|
|
139
139
|
- TODO.bug-fixes/04-ufo-image-compile-roundtrip.md
|
|
140
140
|
- TODO.bug-fixes/05-stale-cff-comment.md
|
|
141
|
+
- TODO.bug-fixes/06-instance-woff2-output.md
|
|
142
|
+
- TODO.bug-fixes/07-encapsulation-violations.md
|
|
143
|
+
- TODO.bug-fixes/08-respond-to-violations.md
|
|
141
144
|
- TODO.bug-fixes/README.md
|
|
142
145
|
- TODO.improvements/01-cbdt-cblc-gid-stable-propagation.md
|
|
143
146
|
- TODO.improvements/02-collection-outline-priority.md
|