fontisan 0.4.43 → 0.4.44
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/12-type1-generators-duck-typing.md +80 -0
- data/TODO.bug-fixes/13-hint-extractor-duck-typing.md +30 -0
- data/TODO.bug-fixes/14-font-interface-type-checks.md +82 -0
- data/TODO.bug-fixes/15-glyph-type-checks-and-simple-glyph-points-bug.md +49 -0
- data/TODO.bug-fixes/16-variation-table-checks.md +31 -0
- data/TODO.bug-fixes/17-ufo-and-stitcher-cleanup.md +32 -0
- data/TODO.bug-fixes/18-generic-value-and-io-checks.md +56 -0
- data/TODO.bug-fixes/19-serialization-migration.md +52 -0
- data/TODO.bug-fixes/20-variation-validator-type-checks.md +29 -0
- data/TODO.bug-fixes/21-xml-builder-send.md +24 -0
- data/TODO.bug-fixes/22-type1-converter-font-info.md +25 -0
- data/TODO.bug-fixes/23-boundary-type-checks.md +31 -0
- data/TODO.bug-fixes/24-validator-dynamic-field-key.md +25 -0
- data/TODO.bug-fixes/25-serialization-migration.md +49 -0
- data/TODO.bug-fixes/26-variation-preserver-spec-migration.md +25 -0
- data/TODO.bug-fixes/27-table-class-registry.md +50 -0
- data/TODO.bug-fixes/28-spec-doubles-cleanup.md +40 -0
- data/TODO.bug-fixes/29-strategy-registries.md +30 -0
- data/TODO.bug-fixes/README.md +24 -0
- data/lib/fontisan/binary/base_record.rb +13 -12
- data/lib/fontisan/collection/builder.rb +3 -5
- data/lib/fontisan/collection/dfont_builder.rb +4 -11
- data/lib/fontisan/collection/writer.rb +1 -1
- data/lib/fontisan/commands/pack_command.rb +4 -4
- data/lib/fontisan/commands/unpack_command.rb +1 -1
- data/lib/fontisan/commands/validate_command.rb +1 -1
- data/lib/fontisan/converters/format_converter.rb +2 -2
- data/lib/fontisan/converters/glyf_table_builder.rb +1 -1
- data/lib/fontisan/converters/outline_converter.rb +2 -6
- data/lib/fontisan/converters/table_copier.rb +4 -12
- data/lib/fontisan/converters/type1_converter.rb +12 -37
- data/lib/fontisan/converters/woff2_encoder.rb +8 -10
- data/lib/fontisan/converters/woff_writer.rb +3 -3
- data/lib/fontisan/export/exporter.rb +8 -9
- data/lib/fontisan/export/table_serializer.rb +71 -186
- data/lib/fontisan/export/transformers/font_to_ttx.rb +18 -18
- data/lib/fontisan/export/transformers/head_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/hhea_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/maxp_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/name_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/os2_transformer.rb +1 -1
- data/lib/fontisan/export/transformers/post_transformer.rb +1 -1
- data/lib/fontisan/export/ttx_generator.rb +36 -37
- data/lib/fontisan/font_writer.rb +1 -1
- data/lib/fontisan/glyph_accessor.rb +7 -9
- data/lib/fontisan/hints/postscript_hint_extractor.rb +24 -64
- data/lib/fontisan/hints/truetype_hint_extractor.rb +1 -1
- data/lib/fontisan/models/ttx/tables/binary_table.rb +1 -1
- data/lib/fontisan/optimizers/charstring_rewriter.rb +1 -1
- data/lib/fontisan/sfnt_font.rb +2 -0
- data/lib/fontisan/sfnt_source.rb +28 -0
- data/lib/fontisan/stitcher/source.rb +8 -10
- data/lib/fontisan/subset/builder.rb +2 -2
- data/lib/fontisan/subset/table_strategy/cff2.rb +2 -3
- data/lib/fontisan/svg_to_glyf/assembler.rb +59 -20
- data/lib/fontisan/svg_to_glyf/document.rb +28 -9
- data/lib/fontisan/svg_to_glyf/geometry/normalizer.rb +34 -17
- data/lib/fontisan/tables/cff/charstring.rb +3 -0
- data/lib/fontisan/tables/cff/dict.rb +6 -3
- data/lib/fontisan/tables/cff/index.rb +1 -1
- data/lib/fontisan/tables/cff/private_dict_writer.rb +2 -2
- data/lib/fontisan/tables/cff/table_builder.rb +1 -1
- data/lib/fontisan/tables/cff.rb +3 -0
- data/lib/fontisan/tables/cff2/table_builder.rb +2 -4
- data/lib/fontisan/tables/cff2.rb +3 -0
- data/lib/fontisan/tables/cmap.rb +3 -0
- data/lib/fontisan/tables/cvar.rb +3 -0
- data/lib/fontisan/tables/fvar.rb +3 -0
- data/lib/fontisan/tables/glyf/simple_glyph.rb +9 -0
- data/lib/fontisan/tables/glyf.rb +12 -8
- data/lib/fontisan/tables/glyf_table.rb +1 -1
- data/lib/fontisan/tables/gvar.rb +3 -0
- data/lib/fontisan/tables/head.rb +3 -0
- data/lib/fontisan/tables/hhea.rb +3 -0
- data/lib/fontisan/tables/hmtx.rb +3 -7
- data/lib/fontisan/tables/hvar.rb +4 -0
- data/lib/fontisan/tables/loca.rb +3 -0
- data/lib/fontisan/tables/maxp.rb +3 -0
- data/lib/fontisan/tables/mvar.rb +3 -0
- data/lib/fontisan/tables/name.rb +3 -0
- data/lib/fontisan/tables/os2.rb +3 -0
- data/lib/fontisan/tables/post.rb +3 -0
- data/lib/fontisan/tables/registry.rb +71 -0
- data/lib/fontisan/tables/vvar.rb +3 -0
- data/lib/fontisan/tables.rb +2 -0
- data/lib/fontisan/type1/afm_generator.rb +12 -65
- data/lib/fontisan/type1/charstring_converter.rb +2 -2
- data/lib/fontisan/type1/conversion_options.rb +3 -2
- data/lib/fontisan/type1/decryptor.rb +1 -1
- data/lib/fontisan/type1/generator.rb +14 -9
- data/lib/fontisan/type1/inf_generator.rb +10 -35
- data/lib/fontisan/type1/pfa_generator.rb +36 -26
- data/lib/fontisan/type1/pfb_generator.rb +24 -12
- data/lib/fontisan/type1/pfm_generator.rb +14 -31
- data/lib/fontisan/type1/ttf_to_type1_converter.rb +30 -24
- data/lib/fontisan/ufo/bounds.rb +91 -0
- data/lib/fontisan/ufo/compile/fvar.rb +2 -2
- data/lib/fontisan/ufo/contour.rb +5 -0
- data/lib/fontisan/ufo/convert/from_bin_data.rb +15 -29
- data/lib/fontisan/ufo/info.rb +4 -0
- data/lib/fontisan/ufo/point.rb +0 -7
- data/lib/fontisan/ufo.rb +1 -0
- data/lib/fontisan/utilities/brotli_wrapper.rb +3 -5
- data/lib/fontisan/utilities/padding.rb +9 -8
- data/lib/fontisan/validation/collection_validator.rb +2 -2
- data/lib/fontisan/validators/validator.rb +4 -4
- data/lib/fontisan/variable/delta_applicator.rb +4 -11
- data/lib/fontisan/variable/metric_delta_processor.rb +3 -3
- data/lib/fontisan/variable/static_font_builder.rb +19 -23
- data/lib/fontisan/variation/instance_font_wrapper.rb +41 -0
- data/lib/fontisan/variation/instance_writer.rb +19 -17
- data/lib/fontisan/variation/metrics_adjuster.rb +17 -20
- data/lib/fontisan/variation/validator.rb +26 -38
- data/lib/fontisan/variation/variable_svg_generator.rb +0 -48
- data/lib/fontisan/variation/variation_preserver.rb +2 -3
- data/lib/fontisan/variation.rb +1 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/sfnt_checksum.rb +1 -1
- data/lib/fontisan/woff2/table_transformer.rb +22 -29
- data/lib/fontisan/woff2_font.rb +9 -8
- data/lib/fontisan/woff_font.rb +2 -0
- data/lib/fontisan.rb +1 -0
- metadata +24 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# 27 — Table class registry (OCP: eliminate case/when tag dispatch)
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
The codebase has 8+ `case tag` dispatches that map a string tag like
|
|
8
|
+
`"HVAR"` to a Ruby class like `Tables::Hvar`. Each new table requires
|
|
9
|
+
updating every dispatch site. This violates OCP and DRY.
|
|
10
|
+
|
|
11
|
+
## Sites
|
|
12
|
+
- `lib/fontisan/variable/delta_applicator.rb:269` — case tag → table class
|
|
13
|
+
- `lib/fontisan/variable/static_font_builder.rb:91` — case tag → update method
|
|
14
|
+
- `lib/fontisan/variable/static_font_builder.rb:185` — case tag → table class
|
|
15
|
+
- `lib/fontisan/woff2/table_transformer.rb:36` — case tag → transform strategy
|
|
16
|
+
- `lib/fontisan/woff2/table_transformer.rb:62` — case tag → inverse transform
|
|
17
|
+
- `lib/fontisan/export/ttx_generator.rb:100` — case tag → transformer
|
|
18
|
+
- `lib/fontisan/variation/metrics_adjuster.rb:221` — case tag → metric
|
|
19
|
+
- `lib/fontisan/variation/instance_writer.rb:259` — case tag → writer
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
Create `Tables::Registry` as a single source of truth for tag → class.
|
|
23
|
+
Each table file registers itself via a `register_tag` call:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# lib/fontisan/tables/hvar.rb
|
|
27
|
+
module Fontisan
|
|
28
|
+
module Tables
|
|
29
|
+
class Hvar < Binary::BaseRecord
|
|
30
|
+
register_tag "HVAR"
|
|
31
|
+
...
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`Tables::Registry.for(tag)` returns the class. Dispatch sites become:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
table_class = Tables::Registry.for(tag)
|
|
41
|
+
return nil unless table_class
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This is OCP-compliant: adding a new table means adding `register_tag`
|
|
45
|
+
in the table's file, not modifying dispatch sites.
|
|
46
|
+
|
|
47
|
+
## Acceptance criteria
|
|
48
|
+
- 0 `case tag` dispatches in `lib/fontisan/`
|
|
49
|
+
- All callers use `Tables::Registry.for(tag)` instead
|
|
50
|
+
- All existing specs pass
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 28 — Spec doubles cleanup
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P3
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
**343 `double()` / `instance_double()` calls remain in specs** despite
|
|
8
|
+
the absolute rule "NEVER use `double()` in specs". Per file:
|
|
9
|
+
|
|
10
|
+
| File | Count |
|
|
11
|
+
|------|-------|
|
|
12
|
+
| spec/fontisan/converters/type1_converter_spec.rb | 58 |
|
|
13
|
+
| spec/fontisan/converters/format_converter_spec.rb | 33 |
|
|
14
|
+
| spec/fontisan/converters/type1_property_spec.rb | 28 |
|
|
15
|
+
| spec/fontisan/tables/cff2/table_builder_spec.rb | 25 |
|
|
16
|
+
| spec/fontisan/variation/validator_spec.rb | 24 |
|
|
17
|
+
| spec/fontisan/variation/subsetter_spec.rb | 17 |
|
|
18
|
+
| spec/fontisan/variation/metrics_adjuster_spec.rb | 15 |
|
|
19
|
+
| spec/fontisan/variation/converter_spec.rb | 14 |
|
|
20
|
+
| spec/fontisan/collection/variable_font_builder_spec.rb | 12 |
|
|
21
|
+
| spec/fontisan/converters/woff2_encoder_spec.rb | 11 |
|
|
22
|
+
| spec/fontisan/collection/table_deduplicator_spec.rb | 9 |
|
|
23
|
+
| spec/fontisan/collection/table_analyzer_spec.rb | 7 |
|
|
24
|
+
| spec/fontisan/tables/cff/cff_glyph_spec.rb | 6 |
|
|
25
|
+
| spec/fontisan/hints/truetype_hint_extractor_spec.rb | 6 |
|
|
26
|
+
| spec/fontisan/commands/unpack_command_spec.rb | 6 |
|
|
27
|
+
| (other files, 5 or fewer each) | ~74 |
|
|
28
|
+
|
|
29
|
+
## Approach
|
|
30
|
+
Each spec migration replaces `double("Foo", attr: value)` with either:
|
|
31
|
+
1. A real instance of the doubled class, OR
|
|
32
|
+
2. A `Struct`-based fake in a uniquely-named module (e.g., `XxxFakes::Foo`).
|
|
33
|
+
3. The shared `SpecHelpers::FakeFont` where the double is font-shaped.
|
|
34
|
+
|
|
35
|
+
PR #135/#136/#137 migrated ~10 spec files this way. The pattern is
|
|
36
|
+
established; remaining files are mechanical work.
|
|
37
|
+
|
|
38
|
+
## Acceptance criteria
|
|
39
|
+
- 0 `double()` / `instance_double()` in `spec/fontisan/`
|
|
40
|
+
- All specs pass
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 29 — Strategy registries for per-tag dispatch (OCP completion)
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
6 remaining `case tag` dispatches map a table tag to per-table
|
|
8
|
+
behavior (update, transform, emit, etc.). These are OCP violations:
|
|
9
|
+
adding a new table's behavior requires editing every dispatch site.
|
|
10
|
+
|
|
11
|
+
## Sites
|
|
12
|
+
- `export/ttx_generator.rb:100` — 13-way case → generate_X_table
|
|
13
|
+
- `variable/static_font_builder.rb:91` — 4-way case → update_X_table
|
|
14
|
+
- `variation/instance_writer.rb:259` — 5-way case → table class parse
|
|
15
|
+
- `variation/metrics_adjuster.rb:221` — 5-way case → MVAR metric
|
|
16
|
+
- `woff2/table_transformer.rb:36` — 3-way case → transform_X
|
|
17
|
+
- `woff2/table_transformer.rb:62` — 3-way case → transform version
|
|
18
|
+
|
|
19
|
+
## Approach
|
|
20
|
+
Each case/when becomes a class-level dispatch hash mapping tag →
|
|
21
|
+
method name. The dispatch site reads from the hash. Adding a new
|
|
22
|
+
table means adding one line to the hash in the same file.
|
|
23
|
+
|
|
24
|
+
For dispatches that use `Tables::Registry.for(tag)` to look up a
|
|
25
|
+
class (like instance_writer), extend the Registry to also know how
|
|
26
|
+
to construct from raw data.
|
|
27
|
+
|
|
28
|
+
## Acceptance criteria
|
|
29
|
+
- 0 `case tag` dispatches in `lib/fontisan/`
|
|
30
|
+
- All specs pass
|
data/TODO.bug-fixes/README.md
CHANGED
|
@@ -23,3 +23,27 @@ Each file is `NN-short-name.md` where `NN` is the priority order.
|
|
|
23
23
|
- [x] ~~[07 — Encapsulation violations (instance_variable_get/set, send to private)](07-encapsulation-violations.md)~~ ✓ Done (v0.4.42)
|
|
24
24
|
- [x] ~~[08 — respond_to? duck typing violations](08-respond-to-violations.md)~~ ✓ Done (v0.4.42)
|
|
25
25
|
- [x] ~~[11 — Remaining respond_to? violations](11-remaining-respond-to.md)~~ ✓ Done
|
|
26
|
+
|
|
27
|
+
### P3 — Encapsulation and architecture cleanup (post-#135 audit)
|
|
28
|
+
- [x] ~~[12 — Type 1 generators duck-typing](12-type1-generators-duck-typing.md)~~ ✓ Done (#136)
|
|
29
|
+
- [x] ~~[13 — Hint extractor duck-typing](13-hint-extractor-duck-typing.md)~~ ✓ Done (#136)
|
|
30
|
+
- [x] ~~[14 — Font interface type checks](14-font-interface-type-checks.md)~~ ✓ Done (#136)
|
|
31
|
+
- [x] ~~[15 — Glyph type checks + SimpleGlyph#points bug](15-glyph-type-checks-and-simple-glyph-points-bug.md)~~ ✓ Done (#136)
|
|
32
|
+
- [x] ~~[16 — Variation table checks](16-variation-table-checks.md)~~ ✓ Done (#137)
|
|
33
|
+
- [x] ~~[17 — UFO and Stitcher cleanup](17-ufo-and-stitcher-cleanup.md)~~ ✓ Done (#136)
|
|
34
|
+
- [x] ~~[18 — Generic value and IO checks](18-generic-value-and-io-checks.md)~~ ✓ Done (#137)
|
|
35
|
+
- [x] ~~[19 — Hand-rolled serialization migration to lutaml-model](19-serialization-migration.md)~~ ✓ Done (#137)
|
|
36
|
+
|
|
37
|
+
### P3 — Final encapsulation cleanup (post-#136 audit)
|
|
38
|
+
- [x] ~~[20 — Variation validator type checks](20-variation-validator-type-checks.md)~~ ✓ Done (#137)
|
|
39
|
+
- [x] ~~[21 — XML builder send() → tag!](21-xml-builder-send.md)~~ ✓ Done (#137)
|
|
40
|
+
- [x] ~~[22 — Type1Converter font_info fields](22-type1-converter-font-info.md)~~ ✓ Done (#137)
|
|
41
|
+
- [x] ~~[23 — Boundary type checks (padding, base_record)](23-boundary-type-checks.md)~~ ✓ Done (#137)
|
|
42
|
+
- [x] ~~[24 — Validator dynamic field_key](24-validator-dynamic-field-key.md)~~ ✓ Done (#137)
|
|
43
|
+
- [x] ~~[25 — Serialization migration to lutaml-model](25-serialization-migration.md)~~ ✓ Done (#137)
|
|
44
|
+
- [x] ~~[26 — Variation preserver spec migration](26-variation-preserver-spec-migration.md)~~ ✓ Done (#137)
|
|
45
|
+
|
|
46
|
+
### P3 — Architecture polish (post-#137 audit)
|
|
47
|
+
- [x] ~~[27 — Table class registry (OCP: eliminate case/when tag dispatch)](27-table-class-registry.md)~~ ✓ Done (#138)
|
|
48
|
+
- [ ] [28 — Spec doubles cleanup](28-spec-doubles-cleanup.md)
|
|
49
|
+
- [x] ~~[29 — Strategy registries for per-tag dispatch](29-strategy-registries.md)~~ ✓ Done
|
|
@@ -22,21 +22,22 @@ module Fontisan
|
|
|
22
22
|
class BaseRecord < BinData::Record
|
|
23
23
|
endian :big # OpenType uses big-endian byte order
|
|
24
24
|
|
|
25
|
-
# Override read to handle nil
|
|
25
|
+
# Override read to handle nil/empty/String inputs gracefully.
|
|
26
|
+
#
|
|
27
|
+
# String inputs are wrapped in a StringIO so the rest of the
|
|
28
|
+
# read path always works against an IO-like object.
|
|
29
|
+
#
|
|
30
|
+
# @param io [String, IO, StringIO, nil] binary data or IO source
|
|
26
31
|
def self.read(io)
|
|
27
|
-
return new if io.nil?
|
|
32
|
+
return new if io.nil?
|
|
33
|
+
|
|
34
|
+
io = StringIO.new(io) if io.is_a?(String)
|
|
35
|
+
return new if io.is_a?(StringIO) && io.eof?
|
|
28
36
|
|
|
29
37
|
# Store the original data for later parsing
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
instance = super(StringIO.new(data))
|
|
34
|
-
else
|
|
35
|
-
# For IO objects, read to string first
|
|
36
|
-
data = io.read
|
|
37
|
-
io.rewind if io.respond_to?(:rewind)
|
|
38
|
-
instance = super(io)
|
|
39
|
-
end
|
|
38
|
+
data = io.read
|
|
39
|
+
io.rewind
|
|
40
|
+
instance = super(io)
|
|
40
41
|
|
|
41
42
|
instance.raw_data = data
|
|
42
43
|
instance
|
|
@@ -56,11 +56,9 @@ module Fontisan
|
|
|
56
56
|
end
|
|
57
57
|
raise ArgumentError, "fonts must be an array" unless fonts.is_a?(Array)
|
|
58
58
|
|
|
59
|
-
unless fonts.all?
|
|
60
|
-
f.respond_to?(:table_data)
|
|
61
|
-
end
|
|
59
|
+
unless fonts.all?(SfntSource)
|
|
62
60
|
raise ArgumentError,
|
|
63
|
-
"all fonts must
|
|
61
|
+
"all fonts must be SfntSource instances (TrueTypeFont, OpenTypeFont, WoffFont, Woff2Font)"
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
@fonts = fonts
|
|
@@ -320,7 +318,7 @@ module Fontisan
|
|
|
320
318
|
return [] unless font.has_table?("fvar")
|
|
321
319
|
|
|
322
320
|
fvar_table = font.table("fvar")
|
|
323
|
-
return [] unless fvar_table
|
|
321
|
+
return [] unless fvar_table&.axes
|
|
324
322
|
|
|
325
323
|
fvar_table.axes.map(&:axis_tag).sort
|
|
326
324
|
end
|
|
@@ -42,8 +42,8 @@ module Fontisan
|
|
|
42
42
|
end
|
|
43
43
|
raise ArgumentError, "fonts must be an array" unless fonts.is_a?(Array)
|
|
44
44
|
|
|
45
|
-
unless fonts.all?
|
|
46
|
-
raise ArgumentError, "all fonts must
|
|
45
|
+
unless fonts.all?(SfntSource)
|
|
46
|
+
raise ArgumentError, "all fonts must be SfntSource instances"
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
@fonts = fonts
|
|
@@ -150,17 +150,10 @@ module Fontisan
|
|
|
150
150
|
|
|
151
151
|
# Detect sfnt version from font
|
|
152
152
|
#
|
|
153
|
-
# @param font [
|
|
153
|
+
# @param font [SfntFont] Font object
|
|
154
154
|
# @return [Integer] sfnt version
|
|
155
155
|
def detect_sfnt_version(font)
|
|
156
|
-
|
|
157
|
-
font.header.sfnt_version
|
|
158
|
-
elsif font.respond_to?(:table_data)
|
|
159
|
-
# Auto-detect from tables
|
|
160
|
-
FontWriter.detect_sfnt_version(font.table_data)
|
|
161
|
-
else
|
|
162
|
-
0x00010000 # Default to TrueType
|
|
163
|
-
end
|
|
156
|
+
FontWriter.detect_sfnt_version(font.table_data)
|
|
164
157
|
end
|
|
165
158
|
|
|
166
159
|
# Build resource fork header (16 bytes)
|
|
@@ -213,7 +213,7 @@ module Fontisan
|
|
|
213
213
|
binary << table_info[:data]
|
|
214
214
|
|
|
215
215
|
# Pad to 4-byte boundary
|
|
216
|
-
padding = Utilities::Padding.boundary(table_info[:data])
|
|
216
|
+
padding = Utilities::Padding.boundary(table_info[:data].bytesize)
|
|
217
217
|
binary << ("\x00" * padding) if padding.positive?
|
|
218
218
|
end
|
|
219
219
|
end
|
|
@@ -174,9 +174,9 @@ module Fontisan
|
|
|
174
174
|
# @param font [Object] Font object
|
|
175
175
|
# @return [Boolean]
|
|
176
176
|
def truetype_font?(font)
|
|
177
|
-
return false unless font.
|
|
177
|
+
return false unless font.is_a?(SfntSource)
|
|
178
178
|
|
|
179
|
-
sfnt = font.
|
|
179
|
+
sfnt = font.sfnt_version
|
|
180
180
|
[0x00010000, 0x74727565].include?(sfnt) # 0x74727565 = 'true'
|
|
181
181
|
end
|
|
182
182
|
|
|
@@ -185,9 +185,9 @@ module Fontisan
|
|
|
185
185
|
# @param font [Object] Font object
|
|
186
186
|
# @return [Boolean]
|
|
187
187
|
def opentype_font?(font)
|
|
188
|
-
return false unless font.
|
|
188
|
+
return false unless font.is_a?(SfntSource)
|
|
189
189
|
|
|
190
|
-
sfnt = font.
|
|
190
|
+
sfnt = font.sfnt_version
|
|
191
191
|
sfnt == 0x4F54544F # 'OTTO'
|
|
192
192
|
end
|
|
193
193
|
|
|
@@ -207,7 +207,7 @@ module Fontisan
|
|
|
207
207
|
def generate_filename(font, index)
|
|
208
208
|
# Try to get font name from name table
|
|
209
209
|
base_name = nil
|
|
210
|
-
if font.
|
|
210
|
+
if font.is_a?(SfntSource) && font.table("name")
|
|
211
211
|
name_table = font.table("name")
|
|
212
212
|
# Try to get PostScript name, then family name
|
|
213
213
|
base_name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME) ||
|
|
@@ -213,7 +213,7 @@ module Fontisan
|
|
|
213
213
|
# @param index [Integer] Font index (fallback)
|
|
214
214
|
# @return [String] Font name
|
|
215
215
|
def extract_font_name(font, index)
|
|
216
|
-
return "Font #{index}" unless font.
|
|
216
|
+
return "Font #{index}" unless font.is_a?(SfntSource)
|
|
217
217
|
|
|
218
218
|
name_table = font.table("name")
|
|
219
219
|
return "Font #{index}" unless name_table
|
|
@@ -412,9 +412,9 @@ _options)
|
|
|
412
412
|
# rather than the SFNT table interface
|
|
413
413
|
is_type1 = font.is_a?(Type1Font)
|
|
414
414
|
|
|
415
|
-
unless is_type1 || font.
|
|
415
|
+
unless is_type1 || font.is_a?(SfntSource)
|
|
416
416
|
raise ArgumentError,
|
|
417
|
-
"Font must
|
|
417
|
+
"Font must be an SfntSource instance or a Type1Font"
|
|
418
418
|
end
|
|
419
419
|
|
|
420
420
|
unless target_format.is_a?(Symbol)
|
|
@@ -34,7 +34,7 @@ module Fontisan
|
|
|
34
34
|
glyf_data << glyph_data
|
|
35
35
|
|
|
36
36
|
# Add padding to 4-byte boundary
|
|
37
|
-
padding = Utilities::Padding.boundary(glyf_data)
|
|
37
|
+
padding = Utilities::Padding.boundary(glyf_data.bytesize)
|
|
38
38
|
glyf_data << ("\x00" * padding) if padding.positive?
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -275,12 +275,8 @@ module Fontisan
|
|
|
275
275
|
def validate(font, target_format)
|
|
276
276
|
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
277
277
|
|
|
278
|
-
unless font.
|
|
279
|
-
raise ArgumentError, "Font must
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
unless font.respond_to?(:table)
|
|
283
|
-
raise ArgumentError, "Font must respond to :table"
|
|
278
|
+
unless font.is_a?(SfntSource)
|
|
279
|
+
raise ArgumentError, "Font must be an SfntSource instance"
|
|
284
280
|
end
|
|
285
281
|
|
|
286
282
|
source_format = detect_format(font)
|
|
@@ -30,12 +30,8 @@ module Fontisan
|
|
|
30
30
|
def convert(font, _options = {})
|
|
31
31
|
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
32
32
|
|
|
33
|
-
unless font.
|
|
34
|
-
raise ArgumentError, "Font must
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
unless font.respond_to?(:table_data)
|
|
38
|
-
raise ArgumentError, "Font must respond to :table_data"
|
|
33
|
+
unless font.is_a?(SfntSource)
|
|
34
|
+
raise ArgumentError, "Font must be an SfntSource instance"
|
|
39
35
|
end
|
|
40
36
|
|
|
41
37
|
target_format = detect_format(font)
|
|
@@ -73,12 +69,8 @@ module Fontisan
|
|
|
73
69
|
def validate(font, target_format)
|
|
74
70
|
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
75
71
|
|
|
76
|
-
unless font.
|
|
77
|
-
raise ArgumentError, "Font must
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
unless font.respond_to?(:table_data)
|
|
81
|
-
raise ArgumentError, "Font must respond to :table_data"
|
|
72
|
+
unless font.is_a?(SfntSource)
|
|
73
|
+
raise ArgumentError, "Font must be an SfntSource instance"
|
|
82
74
|
end
|
|
83
75
|
|
|
84
76
|
# Detect source format and verify it matches target
|
|
@@ -112,9 +112,9 @@ module Fontisan
|
|
|
112
112
|
def validate(font, target_format)
|
|
113
113
|
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
114
114
|
|
|
115
|
-
unless font.
|
|
115
|
+
unless font.is_a?(Type1Font) || font.is_a?(SfntSource)
|
|
116
116
|
raise ArgumentError,
|
|
117
|
-
"Font must be Type1Font or
|
|
117
|
+
"Font must be a Type1Font or SfntSource instance"
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
source_format = detect_format(font)
|
|
@@ -156,10 +156,9 @@ module Fontisan
|
|
|
156
156
|
decompose_seac_glyphs(font)
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
-
# Read all font dictionary records if requested
|
|
160
|
-
if conv_options.opening_option?(:read_all_records) && font.font_dictionary
|
|
161
|
-
|
|
162
|
-
font.font_dictionary.reload
|
|
159
|
+
# Read all font dictionary records if requested.
|
|
160
|
+
if conv_options.opening_option?(:read_all_records) && font.font_dictionary
|
|
161
|
+
font.font_dictionary.parse(font.font_dictionary.raw_data)
|
|
163
162
|
end
|
|
164
163
|
end
|
|
165
164
|
|
|
@@ -219,7 +218,7 @@ module Fontisan
|
|
|
219
218
|
:otf
|
|
220
219
|
else
|
|
221
220
|
# Try to detect from tables
|
|
222
|
-
if font.
|
|
221
|
+
if font.is_a?(SfntSource)
|
|
223
222
|
if font.tables.key?("glyf")
|
|
224
223
|
:ttf
|
|
225
224
|
elsif font.tables.key?("CFF ") || font.tables.key?("CFF2")
|
|
@@ -720,37 +719,13 @@ module Fontisan
|
|
|
720
719
|
|
|
721
720
|
# Extract font names with fallbacks
|
|
722
721
|
font_name = font.font_name || font_dict&.font_name || "Unnamed"
|
|
723
|
-
family_name =
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
end
|
|
728
|
-
full_name = if font_info.respond_to?(:full_name)
|
|
729
|
-
font_info.full_name || font_dict&.full_name || family_name
|
|
730
|
-
else
|
|
731
|
-
font_dict&.full_name || family_name
|
|
732
|
-
end
|
|
733
|
-
version = if font_info.respond_to?(:version)
|
|
734
|
-
font_info.version || font.version || "001.000"
|
|
735
|
-
else
|
|
736
|
-
font.version || "001.000"
|
|
737
|
-
end
|
|
738
|
-
copyright = if font_info.respond_to?(:copyright)
|
|
739
|
-
font_info.copyright || font_dict&.raw_data&.dig(:copyright) || ""
|
|
740
|
-
else
|
|
741
|
-
font_dict&.raw_data&.dig(:copyright) || ""
|
|
742
|
-
end
|
|
722
|
+
family_name = font_info&.family_name || font_dict&.family_name || font_name
|
|
723
|
+
full_name = font_info&.full_name || font_dict&.full_name || family_name
|
|
724
|
+
version = font_info&.version || font.version || "001.000"
|
|
725
|
+
copyright = font_info&.copyright || font_dict&.raw_data&.dig(:copyright) || ""
|
|
743
726
|
postscript_name = font_name
|
|
744
|
-
weight =
|
|
745
|
-
|
|
746
|
-
else
|
|
747
|
-
"Regular"
|
|
748
|
-
end
|
|
749
|
-
notice = if font_info.respond_to?(:notice)
|
|
750
|
-
font_info.notice
|
|
751
|
-
else
|
|
752
|
-
""
|
|
753
|
-
end
|
|
727
|
+
weight = font_info&.weight || "Regular"
|
|
728
|
+
notice = font_info&.notice || ""
|
|
754
729
|
|
|
755
730
|
# Build name records (Windows Unicode, English US)
|
|
756
731
|
# Platform ID 3 (Windows), Encoding ID 1 (Unicode BMP), Language ID 0x0409 (US English)
|
|
@@ -218,7 +218,7 @@ module Fontisan
|
|
|
218
218
|
# are applied by `Woff2::EncoderRules`, not here — this method is a
|
|
219
219
|
# pure reader.
|
|
220
220
|
def collect_tables(font, _options = {})
|
|
221
|
-
table_names = if font.
|
|
221
|
+
table_names = if font.is_a?(SfntSource)
|
|
222
222
|
font.table_names
|
|
223
223
|
else
|
|
224
224
|
%w[head hhea maxp OS/2 name cmap post hmtx glyf loca
|
|
@@ -247,7 +247,7 @@ module Fontisan
|
|
|
247
247
|
#
|
|
248
248
|
# @return [Hash{Symbol => Object}, nil]
|
|
249
249
|
def apply_glyf_loca_transform!(table_data, font)
|
|
250
|
-
return nil unless font.
|
|
250
|
+
return nil unless font.is_a?(SfntSource)
|
|
251
251
|
return nil unless font.has_table?("glyf") && font.has_table?("loca")
|
|
252
252
|
|
|
253
253
|
glyf_data = table_data["glyf"]
|
|
@@ -310,12 +310,10 @@ module Fontisan
|
|
|
310
310
|
end
|
|
311
311
|
|
|
312
312
|
def get_table_data(font, tag)
|
|
313
|
-
if font.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
table&.to_binary_s if table.respond_to?(:to_binary_s)
|
|
318
|
-
end
|
|
313
|
+
return font.table_data[tag] if font.is_a?(SfntSource)
|
|
314
|
+
|
|
315
|
+
table = font.table(tag)
|
|
316
|
+
table&.to_binary_s
|
|
319
317
|
end
|
|
320
318
|
|
|
321
319
|
# Build table directory entries.
|
|
@@ -418,7 +416,7 @@ module Fontisan
|
|
|
418
416
|
size += 16 if glyf_transform # synthetic loca directory entry
|
|
419
417
|
|
|
420
418
|
table_data.each_value do |data|
|
|
421
|
-
size += Utilities::Padding.aligned_size(data)
|
|
419
|
+
size += Utilities::Padding.aligned_size(data.bytesize)
|
|
422
420
|
end
|
|
423
421
|
|
|
424
422
|
if glyf_transform
|
|
@@ -506,7 +504,7 @@ module Fontisan
|
|
|
506
504
|
# font block MUST be padded with null bytes so the total file
|
|
507
505
|
# length is a multiple of 4. fontTools' WOFF2Writer does the
|
|
508
506
|
# same: `pad(directory + compressedFont, size=4)`.
|
|
509
|
-
pad = Utilities::Padding.boundary(woff2_data)
|
|
507
|
+
pad = Utilities::Padding.boundary(woff2_data.bytesize)
|
|
510
508
|
woff2_data << ("\x00" * pad) if pad.positive?
|
|
511
509
|
update_woff2_length!(woff2_data)
|
|
512
510
|
woff2_data
|
|
@@ -113,8 +113,8 @@ module Fontisan
|
|
|
113
113
|
|
|
114
114
|
raise ArgumentError, "Font cannot be nil" if font.nil?
|
|
115
115
|
|
|
116
|
-
unless font.
|
|
117
|
-
raise ArgumentError, "Font must
|
|
116
|
+
unless font.is_a?(SfntSource)
|
|
117
|
+
raise ArgumentError, "Font must be an SfntSource instance"
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
@@ -302,7 +302,7 @@ module Fontisan
|
|
|
302
302
|
def write_woff_header(io, font, total_size, total_sfnt_size, num_tables,
|
|
303
303
|
compressed_metadata, metadata_offset, metadata_size,
|
|
304
304
|
private_offset, private_size)
|
|
305
|
-
flavor = if font.
|
|
305
|
+
flavor = if font.is_a?(SfntSource) && font.cff?
|
|
306
306
|
Constants::SFNT_VERSION_OTTO
|
|
307
307
|
else
|
|
308
308
|
Constants::SFNT_VERSION_TRUETYPE
|
|
@@ -181,7 +181,7 @@ module Fontisan
|
|
|
181
181
|
# @return [void]
|
|
182
182
|
def export_binary_fallback(export_model, tag, error)
|
|
183
183
|
table = @font.table(tag)
|
|
184
|
-
binary_data = table
|
|
184
|
+
binary_data = table ? table.to_binary_s : ""
|
|
185
185
|
checksum = calculate_table_checksum(tag)
|
|
186
186
|
|
|
187
187
|
export_model.add_table(
|
|
@@ -201,14 +201,13 @@ module Fontisan
|
|
|
201
201
|
table_entry = @font.tables.find { |entry| entry.tag == tag }
|
|
202
202
|
return 0 unless table_entry
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
end
|
|
204
|
+
checksum = table_entry.checksum
|
|
205
|
+
return checksum.to_i if checksum
|
|
206
|
+
|
|
207
|
+
# Calculate from binary data
|
|
208
|
+
table = @font.table(tag)
|
|
209
|
+
data = table ? table.to_binary_s : ""
|
|
210
|
+
Utilities::ChecksumCalculator.calculate(data)
|
|
212
211
|
end
|
|
213
212
|
|
|
214
213
|
# Format checksum as hex string
|