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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d195924806ae6e5a9ca3f0796493ec611303a30f39aeed2293829a6d3317a06
|
|
4
|
+
data.tar.gz: 96183528b1693ff11796622f2df05541a371606cfaddd8bc8ae5fccacf42b488
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c991caf6890e43dd5d95c47fe187421f536b4dc14c70f1a2c035ab254212356a4f85886e118832e3586721726fc69411b71739654fcf017ecaa9d09e5a2aa7a
|
|
7
|
+
data.tar.gz: dd7c245e25cc82d4024a49404938c624462a0b8d1593764c5c2c5bdb0555da052ce7bb436079d6959eaf17359f601034bf0c4c46b064b8be6cb419abc941829a
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# 12 — Type 1 generators duck-typing cleanup
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P0
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
50+ `respond_to?` calls in `lib/fontisan/type1/` check for methods that
|
|
8
|
+
don't exist on the BinData Name/OS/2/Post tables. Every check returns
|
|
9
|
+
false and the generators silently fall through to empty/unknown values.
|
|
10
|
+
|
|
11
|
+
## Sites (by file)
|
|
12
|
+
|
|
13
|
+
### `pfm_generator.rb` (10)
|
|
14
|
+
- :506 `respond_to?(:copyright)` — Name table has no `#copyright`
|
|
15
|
+
- :521 `respond_to?(:full_font_name)` — Name table has no `#full_font_name`
|
|
16
|
+
- :523 `respond_to?(:font_family)` — Name table has no `#font_family`
|
|
17
|
+
- :525 `respond_to?(:postscript_name)` — Name table has no `#postscript_name`
|
|
18
|
+
- :541/:543 `respond_to?(:us_weight_class)` / `respond_to?(:weight_class)` — OS/2 has neither
|
|
19
|
+
- :372/:374 `respond_to?(:cap_height)` / `respond_to?(:s_typo_ascender)` — same
|
|
20
|
+
- :382 `respond_to?(:x_height)` — OS/2 has no `#x_height`
|
|
21
|
+
- :567 `respond_to?(:is_fixed_pitch)` — Post has no `#is_fixed_pitch`
|
|
22
|
+
- :148 `respond_to?(:unicode_mappings)` — Cmap DOES have this; check unnecessary
|
|
23
|
+
|
|
24
|
+
### `pfa_generator.rb` (6)
|
|
25
|
+
- :137/:142 `respond_to?(:version_string)` / `:copyright` on Name
|
|
26
|
+
- :164 `respond_to?(:typo_ascender)` on OS/2
|
|
27
|
+
- :181/:189 `respond_to?(:weight_class)` on OS/2
|
|
28
|
+
- :307 `respond_to?(:points)` on glyph — same broken pattern as #20
|
|
29
|
+
|
|
30
|
+
### `pfb_generator.rb` (3)
|
|
31
|
+
- :162/:170 `respond_to?(:weight_class)` on OS/2
|
|
32
|
+
- :276 `respond_to?(:points)` on glyph
|
|
33
|
+
|
|
34
|
+
### `afm_generator.rb` (16)
|
|
35
|
+
- :170/:173 `respond_to?(:underline_position)` / `:underline_thickness` on Post
|
|
36
|
+
- :255/:257 `respond_to?(:us_weight_class)` / `:weight_class`
|
|
37
|
+
- :283 `respond_to?(:italic_angle)`
|
|
38
|
+
- :297 `respond_to?(:is_fixed_pitch)`
|
|
39
|
+
- :311/:323 `respond_to?(:version_string)` / `:copyright` on Name
|
|
40
|
+
- :339/:341/:343 `respond_to?(:unicode_mappings)` / `:unicode_bmp_mapping` / `:subtables` on Cmap
|
|
41
|
+
- :346/:348/:352 subtable field probes
|
|
42
|
+
- :368/:370/:371 `respond_to?(:font_bounding_box)` / `:x_min` / `:y_min` on Head
|
|
43
|
+
- :393 `respond_to?(:parse_with_context)` on Loca
|
|
44
|
+
- :401 `respond_to?(:glyph_for)` on Glyf
|
|
45
|
+
- :405/:407/:408 `respond_to?(:bounding_box)` / `:x_min` etc. on glyph
|
|
46
|
+
|
|
47
|
+
### `inf_generator.rb` (10)
|
|
48
|
+
- :187/:202/:217/:269/:281/:293/:305 Name/Post field probes
|
|
49
|
+
- :229/:231 OS/2 weight class probes
|
|
50
|
+
- :257 `respond_to?(:italic_angle)`
|
|
51
|
+
|
|
52
|
+
### `generator.rb` (1)
|
|
53
|
+
- :203 `respond_to?(:postscript_name)` on Name
|
|
54
|
+
|
|
55
|
+
### `decryptor.rb` (1)
|
|
56
|
+
- :115 `respond_to?(:b)` on String — always true for Ruby strings
|
|
57
|
+
|
|
58
|
+
## Approach
|
|
59
|
+
1. Add a `NameIds` constants module if not present (NAME_COPYRIGHT=0,
|
|
60
|
+
NAME_FONT_FAMILY=1, NAME_FULL_FONT_NAME=4, NAME_VERSION=5,
|
|
61
|
+
NAME_POSTSCRIPT_NAME=6, NAME_MANUFACTURER=8, NAME_LICENSE=13).
|
|
62
|
+
2. Replace `name_table.respond_to?(:X) ? name_table.X(1) : ""` with
|
|
63
|
+
`name_table&.english_name(NAME_ID_X) || ""`.
|
|
64
|
+
3. For OS/2 fields, declare the BinData field accessor (most already
|
|
65
|
+
exist) and access directly: `os2&.us_weight_class`.
|
|
66
|
+
4. For Post fields, same: `post&.italic_angle`.
|
|
67
|
+
5. For Cmap, `cmap.unicode_mappings` is the documented API — drop the
|
|
68
|
+
`respond_to?` and just call it (nil guard at the boundary).
|
|
69
|
+
6. For Loca parse_with_context, Loca responds to it always — drop the
|
|
70
|
+
check.
|
|
71
|
+
7. For glyph bounding boxes, use the typed SimpleGlyph/CompoundGlyph
|
|
72
|
+
`bounding_box` method (always present).
|
|
73
|
+
8. For `glyph.respond_to?(:points)`, see TODO 15 — the underlying bug
|
|
74
|
+
is fixed there.
|
|
75
|
+
|
|
76
|
+
## Acceptance criteria
|
|
77
|
+
- 0 `respond_to?` in `lib/fontisan/type1/` (excluding `respond_to_missing?`)
|
|
78
|
+
- All Type 1 generation specs pass with real Libertinus fixture
|
|
79
|
+
- AFM/PFM/PFA/PFB/INF output now contains real Name table values
|
|
80
|
+
(previously was empty for most fields)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 13 — Hint extractor duck-typing cleanup
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P1
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
15 `respond_to?` calls in `lib/fontisan/hints/postscript_hint_extractor.rb`
|
|
8
|
+
probe PrivateDict fields that are always declared on the BinData record.
|
|
9
|
+
Plus 2 sites in `postscript_hint_extractor.rb:60-62` that probe Charstring
|
|
10
|
+
shape (`:data` vs `:bytes`).
|
|
11
|
+
|
|
12
|
+
## Sites
|
|
13
|
+
- :60/:62 `charstring.respond_to?(:data)` / `:bytes` — Charstring has one shape
|
|
14
|
+
- :292/:296/:300/:304 `respond_to?(:blue_values)` / `:other_blues` / `:family_blues` / `:family_other_blues`
|
|
15
|
+
- :308/:312/:316 `respond_to?(:blue_scale)` / `:blue_shift` / `:blue_fuzz`
|
|
16
|
+
- :320/:324 `respond_to?(:std_hw)` / `:std_vw`
|
|
17
|
+
- :328/:332 `respond_to?(:stem_snap_h)` / `:stem_snap_v`
|
|
18
|
+
- :336/:340 `respond_to?(:force_bold)` / `:language_group`
|
|
19
|
+
|
|
20
|
+
## Approach
|
|
21
|
+
1. Verify which fields are declared on `Tables::Cff::PrivateDict`.
|
|
22
|
+
2. For declared fields, drop the `respond_to?` and read directly with
|
|
23
|
+
nil-coalescence: `private_dict.blue_values`.
|
|
24
|
+
3. For undeclared fields (rare BinData optional), either declare them
|
|
25
|
+
with a default `nil` or remove the dead branch.
|
|
26
|
+
4. For Charstring, standardize on one accessor (probably `#data`).
|
|
27
|
+
|
|
28
|
+
## Acceptance criteria
|
|
29
|
+
- 0 `respond_to?` in `lib/fontisan/hints/`
|
|
30
|
+
- All hint extraction specs pass
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# 14 — Font interface type checks
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P1
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
~30 `respond_to?` calls probe whether an object is font-like by
|
|
8
|
+
checking for `:table`, `:tables`, `:table_data`, `:has_table?`,
|
|
9
|
+
`:header`, `:cff?`. The codebase has a typed font hierarchy
|
|
10
|
+
(`SfntFont`, `TrueTypeFont`, `OpenTypeFont`, `WoffFont`, `Woff2Font`,
|
|
11
|
+
`Type1Font`) — these should be checked with `is_a?`.
|
|
12
|
+
|
|
13
|
+
## Sites (by category)
|
|
14
|
+
|
|
15
|
+
### Collection builders
|
|
16
|
+
- `collection/builder.rb:61` `respond_to?(:table_data)`
|
|
17
|
+
- `collection/dfont_builder.rb:47` `respond_to?(:table_data)`
|
|
18
|
+
|
|
19
|
+
### Subset
|
|
20
|
+
- `subset/builder.rb:115` `respond_to?(:table)`
|
|
21
|
+
- `subset/table_strategy/cff2.rb` — already cleaned
|
|
22
|
+
|
|
23
|
+
### Converters
|
|
24
|
+
- `converters/woff2_encoder.rb:221` `respond_to?(:table_names)`
|
|
25
|
+
- `converters/woff2_encoder.rb:250` `respond_to?(:has_table?)`
|
|
26
|
+
- `converters/woff2_encoder.rb:313/:315/:317` font shape probes
|
|
27
|
+
- `converters/outline_converter.rb:278/:282` `respond_to?(:tables)` / `:table`
|
|
28
|
+
- `converters/table_copier.rb:33/:37/:76/:80` font shape probes
|
|
29
|
+
- `converters/woff_writer.rb:116/:305` font shape / `:cff?`
|
|
30
|
+
- `converters/type1_converter.rb:115/:160/:222/:723-749` font shape + font_info probes
|
|
31
|
+
- `converters/format_converter.rb:415` `respond_to?(:table)`
|
|
32
|
+
|
|
33
|
+
### Stitcher
|
|
34
|
+
- `stitcher/source.rb:83` `respond_to?(:has_table?)`
|
|
35
|
+
- `stitcher/source.rb:193/:220/:228` table parse probes
|
|
36
|
+
- `stitcher/source.rb:287/:289/:375/:377` glyph type probes (see TODO 15)
|
|
37
|
+
|
|
38
|
+
### Variable
|
|
39
|
+
- `variable/delta_aplicator.rb:263` `respond_to?(:table_data)`
|
|
40
|
+
- `variable/static_font_builder.rb:80` `respond_to?(:tables)`
|
|
41
|
+
- `variation/variation_preserver.rb:151/:152` `respond_to?(:has_table?)` / `:table_data`
|
|
42
|
+
|
|
43
|
+
### Commands
|
|
44
|
+
- `commands/validate_command.rb:216` `respond_to?(:table)`
|
|
45
|
+
- `commands/pack_command.rb:177/:188` `respond_to?(:header)`
|
|
46
|
+
|
|
47
|
+
### Validation
|
|
48
|
+
- `validation/collection_validator.rb:249/:259` `respond_to?(:has_table?)`
|
|
49
|
+
|
|
50
|
+
### Woff2 table transformer
|
|
51
|
+
- `woff2/table_transformer.rb:153` `respond_to?(:table_data)`
|
|
52
|
+
|
|
53
|
+
### Other
|
|
54
|
+
- `commands/unpack_command.rb:210` `respond_to?(:table)`
|
|
55
|
+
|
|
56
|
+
## Approach
|
|
57
|
+
1. Define `SfntFont` as the type for any TrueType/OpenType/WOFF/WOFF2
|
|
58
|
+
font (already the base class).
|
|
59
|
+
2. Define `Type1Font` similarly (already exists).
|
|
60
|
+
3. Replace `respond_to?(:table)` / `:tables` / `:table_data` / `:has_table?`
|
|
61
|
+
with `is_a?(SfntFont)` (or `is_a?(Type1Font)` where applicable).
|
|
62
|
+
4. Where the input legitimately can be either SfntFont or Type1Font,
|
|
63
|
+
accept a union: `font.is_a?(SfntFont) || font.is_a?(Type1Font)`.
|
|
64
|
+
5. For `respond_to?(:header)` in pack_command, the SfntFont exposes
|
|
65
|
+
`sfnt_version` directly (already used elsewhere).
|
|
66
|
+
6. For converters/type1_converter.rb font_info probes, the `font_info`
|
|
67
|
+
is always a `Type1::FontInfo` record — replace with `is_a?`.
|
|
68
|
+
|
|
69
|
+
## Affected specs
|
|
70
|
+
- spec/fontisan/collection/builder_spec.rb (uses doubles)
|
|
71
|
+
- spec/fontisan/subset/builder_spec.rb (uses doubles)
|
|
72
|
+
- spec/fontisan/converters/* (most use real fonts; check each)
|
|
73
|
+
- spec/fontisan/stitcher/* (check)
|
|
74
|
+
|
|
75
|
+
The collection/builder_spec and subset/builder_spec were identified in
|
|
76
|
+
PR #135 as using doubles that block the is_a?(SfntFont) migration.
|
|
77
|
+
Migrate them to real SfntFont instances or SimpleDelegator-based fakes.
|
|
78
|
+
|
|
79
|
+
## Acceptance criteria
|
|
80
|
+
- 0 `respond_to?(:table)` / `:tables` / `:table_data` / `:has_table?` /
|
|
81
|
+
`:header` / `:cff?` in `lib/fontisan/`
|
|
82
|
+
- All affected specs pass with real fonts or SimpleDelegator-based fakes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 15 — Glyph type checks + SimpleGlyph#points bug
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P0
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
~15 `respond_to?` calls probe glyph shape (`:simple?`, `:compound?`,
|
|
8
|
+
`:points`, `:bounding_box`). Plus the latent bug discovered in PR #135:
|
|
9
|
+
`Type1::TTFToType1Converter#extract_points` calls `glyph.points.each`
|
|
10
|
+
but `Tables::SimpleGlyph` has no `points` method. The `respond_to?`
|
|
11
|
+
check silently masked this for years.
|
|
12
|
+
|
|
13
|
+
## Sites
|
|
14
|
+
- `type1/ttf_to_type1_converter.rb:140` `respond_to?(:points)` — masks bug
|
|
15
|
+
- `type1/pfa_generator.rb:307` `respond_to?(:points)` — same bug
|
|
16
|
+
- `type1/pfb_generator.rb:276` `respond_to?(:points)` — same bug
|
|
17
|
+
- `ufo/convert/from_bin_data.rb:192/:312/:323/:325` `respond_to?(:simple?)` / `:compound?`
|
|
18
|
+
- `ufo/convert/from_bin_data.rb:361` `respond_to?(:glyph_name)`
|
|
19
|
+
- `stitcher/source.rb:287/:289/:375/:377` `respond_to?(:simple?)` / `:compound?`
|
|
20
|
+
- `hints/truetype_hint_extractor.rb:66` `respond_to?(:instructions)`
|
|
21
|
+
- `tables/glyf.rb:243` already fixed in PR #135
|
|
22
|
+
|
|
23
|
+
## SimpleGlyph API surface (current)
|
|
24
|
+
- `num_contours`, `num_points`
|
|
25
|
+
- `points_for_contour(index)` → Array<Hash{x,y,on_curve}>
|
|
26
|
+
- `point_at(index)`, `on_curve?(index)`
|
|
27
|
+
- `bounding_box` → [x_min, y_min, x_max, y_max]
|
|
28
|
+
- `simple?`, `compound?`, `empty?`
|
|
29
|
+
|
|
30
|
+
## Approach
|
|
31
|
+
1. Add `Tables::SimpleGlyph#points` — returns a flat Array<Hash> across
|
|
32
|
+
all contours. Single source of truth, lazy-built.
|
|
33
|
+
2. Add `Tables::CompoundGlyph#points` — returns the union of component
|
|
34
|
+
points (or empty if no components).
|
|
35
|
+
3. Replace all `respond_to?(:points)` with `is_a?(Tables::SimpleGlyph)`.
|
|
36
|
+
4. Replace `respond_to?(:simple?)` / `:compound?` with `is_a?` against
|
|
37
|
+
`Tables::SimpleGlyph`, `Tables::CompoundGlyph`, `Tables::Cff::CFFGlyph`.
|
|
38
|
+
5. For `:bounding_box`, the typed glyph classes all have it — drop the
|
|
39
|
+
check.
|
|
40
|
+
6. For `:glyph_name` on Post glyph records, declare it as a method (or
|
|
41
|
+
always-present BinData field).
|
|
42
|
+
7. For `:instructions` on SimpleGlyph, declare it.
|
|
43
|
+
|
|
44
|
+
## Acceptance criteria
|
|
45
|
+
- 0 `respond_to?(:simple?)` / `:compound?` / `:points` / `:bounding_box` /
|
|
46
|
+
`:glyph_name` / `:instructions` in `lib/fontisan/`
|
|
47
|
+
- Type 1 conversion now produces non-empty CharString data for simple
|
|
48
|
+
glyphs (the previously-broken `extract_points` path)
|
|
49
|
+
- All Type 1 specs pass
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 16 — Variation table checks
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
~15 `respond_to?` calls in `lib/fontisan/variation/` probe variation
|
|
8
|
+
table shape (`:item_variation_store`, `:region_list`, `:num_axes`,
|
|
9
|
+
`:advance_height_delta_set`, etc.). All variation tables are typed
|
|
10
|
+
BinData records — these checks should be `is_a?` or direct calls.
|
|
11
|
+
|
|
12
|
+
## Sites
|
|
13
|
+
- `variation/validator.rb:106/:136/:142/:144/:199/:211/:275/:278/:281/:286`
|
|
14
|
+
- `variation/metrics_adjuster.rb:230/:233/:310`
|
|
15
|
+
- `variable/metric_delta_processor.rb:224/:229/:234`
|
|
16
|
+
- `variable/delta_applicator.rb:296`
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
1. For each variation table (Fvar, Hvar, Vvar, Mvar, Cff2, ItemVariationStore),
|
|
20
|
+
verify the BinData-declared fields and methods.
|
|
21
|
+
2. Replace `respond_to?(:item_variation_store)` with `is_a?(Tables::Hvar)`
|
|
22
|
+
or `is_a?(Tables::Vvar)` etc., then call `.item_variation_store`
|
|
23
|
+
directly.
|
|
24
|
+
3. For optional fields (rare in variation tables), declare them with
|
|
25
|
+
defaults on the BinData record.
|
|
26
|
+
4. For `metrics_adjuster.rb:310` `respond_to?(:number_of_h_metrics=)`,
|
|
27
|
+
Hhea is a BinData record with a declared writer — drop the check.
|
|
28
|
+
|
|
29
|
+
## Acceptance criteria
|
|
30
|
+
- 0 `respond_to?` in `lib/fontisan/variation/` and `lib/fontisan/variable/`
|
|
31
|
+
- All variation specs pass
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 17 — UFO convert and Stitcher cleanup
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
~10 `respond_to?` calls in `ufo/convert/from_bin_data.rb` probe BinData
|
|
8
|
+
table shape. Plus 1 site in `ufo/compile/fvar.rb`.
|
|
9
|
+
|
|
10
|
+
## Sites
|
|
11
|
+
- `ufo/convert/from_bin_data.rb:64/:82` Name records access
|
|
12
|
+
- `ufo/convert/from_bin_data.rb:103/:105` Post italic_angle
|
|
13
|
+
- `ufo/convert/from_bin_data.rb:131` Cmap unicode_mappings
|
|
14
|
+
- `ufo/convert/from_bin_data.rb:149/:155` Hmtx parse/metric
|
|
15
|
+
- `ufo/convert/from_bin_data.rb:172` Loca parse
|
|
16
|
+
- `ufo/convert/from_bin_data.rb:192/:312/:323/:325` Glyph type (see TODO 15)
|
|
17
|
+
- `ufo/convert/from_bin_data.rb:361` Post glyph_name
|
|
18
|
+
- `ufo/compile/fvar.rb:23/:24` Info axes/named_instances
|
|
19
|
+
- `stitcher/source.rb:83/:193/:220/:228/:287/:289/:375/:377` (see TODO 14/15)
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
1. Replace `name_table.respond_to?(:name_records)` with `is_a?(Tables::Name)`.
|
|
23
|
+
2. Replace `post.respond_to?(:italic_angle)` with `is_a?(Tables::Post)`.
|
|
24
|
+
3. Replace `cmap_table.respond_to?(:unicode_mappings)` with `is_a?(Tables::Cmap)`.
|
|
25
|
+
4. Replace `hmtx.respond_to?(:parse_with_context)` with `is_a?(Tables::Hmtx)`.
|
|
26
|
+
5. Replace `loca.respond_to?(:parse_with_context)` with `is_a?(Tables::Loca)`.
|
|
27
|
+
6. For `ufo/compile/fvar.rb`, the `font.info` is always a `Ufo::Info`.
|
|
28
|
+
Check `Ufo::Info#axes` exists; if not, add it.
|
|
29
|
+
|
|
30
|
+
## Acceptance criteria
|
|
31
|
+
- 0 `respond_to?` in `lib/fontisan/ufo/convert/` and `lib/fontisan/ufo/compile/`
|
|
32
|
+
- 0 stitcher/source.rb respond_to? (split across TODO 14/15)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 18 — Generic value and IO checks
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P3
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
~20 `respond_to?` calls probe generic value shape (`:to_i`, `:bytesize`,
|
|
8
|
+
`:to_binary_s`, `:empty?`, `:b`) or IO capabilities (`:seek`, `:rewind`).
|
|
9
|
+
Some are valid duck-typing at system boundaries; others are dead
|
|
10
|
+
defensive code.
|
|
11
|
+
|
|
12
|
+
## Sites
|
|
13
|
+
### Generic value conversion
|
|
14
|
+
- `export/ttx_generator.rb:67/:474` `respond_to?(:to_i)`
|
|
15
|
+
- `export/transformers/name_transformer.rb:47`
|
|
16
|
+
- `export/transformers/post_transformer.rb:36`
|
|
17
|
+
- `export/transformers/head_transformer.rb:45`
|
|
18
|
+
- `export/transformers/maxp_transformer.rb:48`
|
|
19
|
+
- `export/transformers/os2_transformer.rb:104`
|
|
20
|
+
- `export/transformers/hhea_transformer.rb:44`
|
|
21
|
+
- `export/transformers/font_to_ttx.rb:106`
|
|
22
|
+
- `export/exporter.rb:184/:204/:209` `respond_to?(:to_binary_s)` / `:checksum`
|
|
23
|
+
- `export/ttx_generator.rb:296/:435`
|
|
24
|
+
|
|
25
|
+
### String/bytes shape
|
|
26
|
+
- `utilities/padding.rb:30/:55` `respond_to?(:bytesize)`
|
|
27
|
+
- `utilities/brotli_wrapper.rb:136` `respond_to?(:bytesize)`
|
|
28
|
+
- `type1/decryptor.rb:115` `respond_to?(:b)` — String always has `.b`
|
|
29
|
+
|
|
30
|
+
### IO capability
|
|
31
|
+
- `binary/base_record.rb:27/:37` `respond_to?(:empty?)` / `:rewind`
|
|
32
|
+
- `tables/cff/index.rb:61` `respond_to?(:seek)`
|
|
33
|
+
|
|
34
|
+
### Charstring shape
|
|
35
|
+
- `hints/postscript_hint_extractor.rb:60/:62` (see TODO 13)
|
|
36
|
+
|
|
37
|
+
### Pattern matching
|
|
38
|
+
- `optimizers/charstring_rewriter.rb:130` `respond_to?(:positions)`
|
|
39
|
+
|
|
40
|
+
## Approach
|
|
41
|
+
1. For `respond_to?(:to_i)`, replace with `Integer(value) rescue value`
|
|
42
|
+
— accepts Integer-coercible values, falls back to original.
|
|
43
|
+
2. For `respond_to?(:bytesize)`, narrow the type at the boundary: accept
|
|
44
|
+
`String` only, call `.bytesize` directly.
|
|
45
|
+
3. For `respond_to?(:to_binary_s)`, all BinData records respond to it.
|
|
46
|
+
Drop the check; if a non-BinData object is passed, fix the caller.
|
|
47
|
+
4. For `respond_to?(:b)` on String, just call `.b` (String always has it).
|
|
48
|
+
5. For IO `respond_to?(:seek)` / `:rewind`, accept `IO` / `StringIO`
|
|
49
|
+
only. Convert String inputs to StringIO at the boundary.
|
|
50
|
+
6. For `respond_to?(:empty?)`, the BinData array always responds — drop.
|
|
51
|
+
|
|
52
|
+
## Acceptance criteria
|
|
53
|
+
- 0 unnecessary `respond_to?` in `lib/fontisan/export/`, `lib/fontisan/utilities/`,
|
|
54
|
+
`lib/fontisan/binary/`
|
|
55
|
+
- Where duck-typing is genuinely needed at a system boundary, document
|
|
56
|
+
it with a comment
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 19 — Hand-rolled serialization migration to lutaml-model
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
5 sites hand-roll `to_h` / `to_hash` on model classes, bypassing
|
|
8
|
+
lutaml-model's declarative serialization. Violates the absolute rule:
|
|
9
|
+
"NEVER write `def to_h`, `def to_hash`, `def from_h`, `def from_hash` on
|
|
10
|
+
a model class."
|
|
11
|
+
|
|
12
|
+
## Sites
|
|
13
|
+
- `lib/fontisan/tables/hmtx.rb:54` `def to_h` — manual hash from instance vars
|
|
14
|
+
- `lib/fontisan/tables/cff/dict.rb:112` `def to_h` — manual hash
|
|
15
|
+
- `lib/fontisan/type1/generator.rb:167` `def to_hash` — manual result hash
|
|
16
|
+
- `lib/fontisan/type1/conversion_options.rb:105` `def to_hash` — manual hash
|
|
17
|
+
- `lib/fontisan/ufo/point.rb:32` `def to_h` — manual hash for GLIF output
|
|
18
|
+
|
|
19
|
+
## Approach
|
|
20
|
+
For each model class:
|
|
21
|
+
|
|
22
|
+
1. Make it inherit from `Lutaml::Model::Serializable`.
|
|
23
|
+
2. Replace instance variables with `attribute :name, :type` declarations.
|
|
24
|
+
3. Replace `to_h` / `to_hash` with a `key_value do ... end` mapping block.
|
|
25
|
+
4. Callers use `model.to_hash` (framework-provided) instead of
|
|
26
|
+
hand-rolled `to_h`.
|
|
27
|
+
|
|
28
|
+
### Special considerations
|
|
29
|
+
|
|
30
|
+
- **`Ufo::Point#to_h`**: produces a Hash for GLIF XML output. The
|
|
31
|
+
lutaml-model `key_value` mapping with `:x`, `:y`, `:type`, `:smooth`
|
|
32
|
+
fields handles this. The `smooth: true` only-if-set behavior is
|
|
33
|
+
modeled via lutaml-model's optional attribute pattern.
|
|
34
|
+
|
|
35
|
+
- **`Type1::Generator#to_hash`**: this is a *result* hash, not a model
|
|
36
|
+
serialization. Rename to `#to_result_hash` (or expose individual
|
|
37
|
+
accessors) to clarify it's not lutaml-model-shaped. OR convert the
|
|
38
|
+
result to a `Models::Type1GenerationResult` model.
|
|
39
|
+
|
|
40
|
+
- **`Type1::ConversionOptions#to_hash`**: this is a value object
|
|
41
|
+
representing conversion options. Migrate to lutaml-model with
|
|
42
|
+
`attribute` declarations for each option.
|
|
43
|
+
|
|
44
|
+
- **`Tables::Hmtx#to_h`**: BinData records already provide `snapshot`.
|
|
45
|
+
Remove the hand-rolled `to_h` and let callers use `snapshot`.
|
|
46
|
+
|
|
47
|
+
- **`Tables::Cff::Dict#to_h`**: same — use `snapshot`.
|
|
48
|
+
|
|
49
|
+
## Acceptance criteria
|
|
50
|
+
- 0 `def to_h` / `def to_hash` in `lib/fontisan/` (excluding lutaml-model
|
|
51
|
+
framework methods)
|
|
52
|
+
- All serialization goes through `lutaml-model`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 20 — Variation validator type checks
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
11 `respond_to?` calls in `lib/fontisan/variation/validator.rb` probe
|
|
8
|
+
typed BinData records (Cff2, Hvar, ItemVariationStore, RegionList).
|
|
9
|
+
The checks were left in place in PR #136 because `validator_spec.rb`
|
|
10
|
+
uses minimal doubles without all the methods stubbed.
|
|
11
|
+
|
|
12
|
+
## Sites
|
|
13
|
+
- :106 `cff2.respond_to?(:num_axes)` — Cff2 always declares num_axes
|
|
14
|
+
- :136 `table.respond_to?(:item_variation_store)` — Hvar/Vvar/Mvar all do
|
|
15
|
+
- :142 `store.respond_to?(:region_list)` — ItemVariationStore has it
|
|
16
|
+
- :144 `region_list.respond_to?(:axis_count)` — RegionList has it
|
|
17
|
+
- :199 `hvar.respond_to?(:item_variation_store)` — Hvar has it
|
|
18
|
+
- :211 `store.respond_to?(:item_variation_data)` — ItemVariationStore has it
|
|
19
|
+
- :275/:278/:281/:286 region/region_list/region_axes checks
|
|
20
|
+
|
|
21
|
+
## Approach
|
|
22
|
+
1. Replace `respond_to?` with direct method calls.
|
|
23
|
+
2. Wrap in begin/rescue NoMethodError OR pre-validate with is_a?.
|
|
24
|
+
3. Update `validator_spec.rb` doubles to provide all methods the
|
|
25
|
+
validator now calls directly. Use Struct-based fakes if helpful.
|
|
26
|
+
|
|
27
|
+
## Acceptance criteria
|
|
28
|
+
- 0 `respond_to?` in `lib/fontisan/variation/validator.rb`
|
|
29
|
+
- All variation specs pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 21 — XML builder send() → tag!
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
3 `xml.send(tag.to_sym)` calls in export/ use `.send` to create XML
|
|
8
|
+
elements with dynamic tag names. This violates the "no private send"
|
|
9
|
+
rule even though it's a common Ruby XML builder pattern.
|
|
10
|
+
|
|
11
|
+
## Sites
|
|
12
|
+
- `lib/fontisan/models/ttx/tables/binary_table.rb:21` `xml.send(tag.to_sym)`
|
|
13
|
+
- `lib/fontisan/export/ttx_generator.rb:297` `xml.send(tag.to_sym)`
|
|
14
|
+
- `lib/fontisan/export/ttx_generator.rb:313` `xml.send(clean_tag.to_sym)`
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
Replace `xml.send(tag.to_sym) { ... }` with `xml.tag!(tag) { ... }`
|
|
18
|
+
(or the Builder-native equivalent public method). Builder::XmlMarkup
|
|
19
|
+
and Nokogiri::XML::Builder both expose `tag!` as the public dynamic
|
|
20
|
+
element creator.
|
|
21
|
+
|
|
22
|
+
## Acceptance criteria
|
|
23
|
+
- 0 `send(` in `lib/fontisan/export/` and `lib/fontisan/models/ttx/`
|
|
24
|
+
- All export/ttx specs pass
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 22 — Type1Converter font_info fields
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
6 `respond_to?` calls in `lib/fontisan/converters/type1_converter.rb`
|
|
8
|
+
probe `font_info` for typed fields (family_name, full_name, version,
|
|
9
|
+
copyright, weight, notice). The font_info is always a `Type1::FontInfo`
|
|
10
|
+
model — these fields are declared.
|
|
11
|
+
|
|
12
|
+
## Sites
|
|
13
|
+
- :723/:728/:733/:738/:744/:749 font_info field probes
|
|
14
|
+
|
|
15
|
+
Plus 1 site at :160: `font.font_dictionary.respond_to?(:reload)` —
|
|
16
|
+
FontDictionary is a typed class.
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
1. Verify `Type1::FontInfo` declares all the probed fields.
|
|
20
|
+
2. Replace `font_info.respond_to?(:family_name)` with direct call.
|
|
21
|
+
3. For `font_dictionary.respond_to?(:reload)`, use `is_a?(Type1Font::FontDictionary)`.
|
|
22
|
+
|
|
23
|
+
## Acceptance criteria
|
|
24
|
+
- 0 `respond_to?` in `lib/fontisan/converters/type1_converter.rb`
|
|
25
|
+
- All Type1 converter specs pass
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 23 — Boundary type checks (padding, base_record)
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P3
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
4 `respond_to?` calls in `utilities/padding.rb` and `binary/base_record.rb`
|
|
8
|
+
are at String/IO system boundaries. They're the last remaining
|
|
9
|
+
"legitimate" duck-typing sites, but the rule is absolute: no
|
|
10
|
+
respond_to?.
|
|
11
|
+
|
|
12
|
+
## Sites
|
|
13
|
+
- `padding.rb:30/:55` `size.respond_to?(:bytesize) ? size.bytesize : size`
|
|
14
|
+
— accepts both String (uses bytesize) and Integer (uses self)
|
|
15
|
+
- `base_record.rb:27` `io.respond_to?(:empty?) && io.empty?` — String check
|
|
16
|
+
- `base_record.rb:37` `io.respond_to?(:rewind)` — IO check (String has no rewind)
|
|
17
|
+
|
|
18
|
+
## Approach
|
|
19
|
+
|
|
20
|
+
### padding.rb
|
|
21
|
+
- Change the signature: accept Integer only. Callers that have a
|
|
22
|
+
String compute `.bytesize` before calling.
|
|
23
|
+
|
|
24
|
+
### base_record.rb
|
|
25
|
+
- Convert String input to StringIO at the boundary (in `self.read`).
|
|
26
|
+
After conversion, `io` is always a StringIO/IO and responds to
|
|
27
|
+
`rewind`/`empty?` natively. Drop the respond_to? guards.
|
|
28
|
+
|
|
29
|
+
## Acceptance criteria
|
|
30
|
+
- 0 `respond_to?` in `lib/fontisan/utilities/` and `lib/fontisan/binary/`
|
|
31
|
+
- All padding and base_record specs pass
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 24 — Validator dynamic field_key
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P3
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
`lib/fontisan/validators/validator.rb:294` uses `table.respond_to?(field_key)`
|
|
8
|
+
to dynamically check whether a table has a named field. This is a
|
|
9
|
+
meta-programming pattern for running field-by-field validation rules.
|
|
10
|
+
|
|
11
|
+
## Site
|
|
12
|
+
- :294 `value = if table.respond_to?(field_key)`
|
|
13
|
+
|
|
14
|
+
## Approach
|
|
15
|
+
Replace the dynamic field lookup with BinData's `snapshot` hash.
|
|
16
|
+
BinData records expose all declared fields via `snapshot`, which
|
|
17
|
+
returns a hash of field name → value. Use `snapshot.key?(field_key)`
|
|
18
|
+
instead of `respond_to?`.
|
|
19
|
+
|
|
20
|
+
Alternatively, declare a typed `FieldValueAccessor` on each table
|
|
21
|
+
model that exposes declared fields as a hash.
|
|
22
|
+
|
|
23
|
+
## Acceptance criteria
|
|
24
|
+
- 0 `respond_to?` in `lib/fontisan/validators/validator.rb`
|
|
25
|
+
- All validator specs pass
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 25 — Serialization migration to lutaml-model
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P2
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
6 sites hand-roll `to_h` / `to_hash` / `to_json` on model classes,
|
|
8
|
+
violating the absolute rule against hand-rolled serialization.
|
|
9
|
+
|
|
10
|
+
## Sites
|
|
11
|
+
- `lib/fontisan/tables/hmtx.rb:54` `MetricRecord#to_h` — BinData inner record
|
|
12
|
+
- `lib/fontisan/tables/cff/dict.rb:112` `Dict#to_h` — internal hash accessor
|
|
13
|
+
- `lib/fontisan/type1/generator.rb:167` `Generator#to_hash` — option snapshot
|
|
14
|
+
- `lib/fontisan/type1/conversion_options.rb:105` `ConversionOptions#to_hash` — option snapshot
|
|
15
|
+
- `lib/fontisan/ufo/point.rb:32` `Point#to_h` — GLIF XML output
|
|
16
|
+
- `lib/fontisan/export/exporter.rb:65` `Result#to_json` — export payload
|
|
17
|
+
|
|
18
|
+
## Approach (per site)
|
|
19
|
+
|
|
20
|
+
### Hmtx::MetricRecord#to_h
|
|
21
|
+
BinData records expose `snapshot` returning a hash. Remove `to_h`,
|
|
22
|
+
update callers (in stitcher/, type1/) to use `metric.snapshot` or
|
|
23
|
+
extract `[:advance_width]` / `[:lsb]` directly.
|
|
24
|
+
|
|
25
|
+
### Cff::Dict#to_h
|
|
26
|
+
`Dict` wraps an internal `@dict` hash. `to_h` returns `@dict.dup`.
|
|
27
|
+
Rename to `#raw_hash` (or `#pairs`) to make clear it's an accessor,
|
|
28
|
+
not serialization. Update callers in `cff/table_builder.rb`,
|
|
29
|
+
`cff/private_dict_writer.rb`.
|
|
30
|
+
|
|
31
|
+
### Type1::Generator#to_hash and Type1::ConversionOptions#to_hash
|
|
32
|
+
These are option snapshots, not wire serialization. Rename to
|
|
33
|
+
`#to_options_hash` to clarify intent. Migrate `ConversionOptions`
|
|
34
|
+
to a `Lutaml::Model::Serializable` for declarative attribute
|
|
35
|
+
management.
|
|
36
|
+
|
|
37
|
+
### Ufo::Point#to_h
|
|
38
|
+
Used for GLIF XML output. Migrate `Ufo::Point` to inherit from
|
|
39
|
+
`Lutaml::Model::Serializable` with `:x`, `:y`, `:type`, `:smooth`
|
|
40
|
+
attributes and a `key_value` mapping.
|
|
41
|
+
|
|
42
|
+
### Export::Result#to_json
|
|
43
|
+
Used for export payload. Migrate to a `Models::ExportResult`
|
|
44
|
+
lutaml-model with JSON mapping.
|
|
45
|
+
|
|
46
|
+
## Acceptance criteria
|
|
47
|
+
- 0 `def to_h` / `def to_hash` / `def to_json` in `lib/fontisan/`
|
|
48
|
+
- All serialization goes through lutaml-model or BinData snapshot
|
|
49
|
+
- All affected specs pass
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 26 — Variation preserver spec migration
|
|
2
|
+
|
|
3
|
+
## Priority
|
|
4
|
+
P3
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
`lib/fontisan/variation/variation_preserver.rb:153` still uses
|
|
8
|
+
`respond_to?(:has_table?) && respond_to?(:table_data)` because the
|
|
9
|
+
spec uses `instance_double(TrueTypeFont)` with extensive per-test
|
|
10
|
+
stubs. Replacing with `is_a?(SfntSource)` requires migrating the
|
|
11
|
+
spec to use `SpecHelpers::FakeFont`.
|
|
12
|
+
|
|
13
|
+
## Site
|
|
14
|
+
- `variation_preserver.rb:151-154` (with TODO comment)
|
|
15
|
+
- `spec/fontisan/variation/variation_preserver_spec.rb` (entire file)
|
|
16
|
+
|
|
17
|
+
## Approach
|
|
18
|
+
1. Update spec to use `SpecHelpers::FakeFont` instead of `instance_double`.
|
|
19
|
+
2. For per-test variations, mutate `font.tables_hash` instead of
|
|
20
|
+
stubbing `has_table?`/`table_data`.
|
|
21
|
+
3. Replace `respond_to?` check in production with `is_a?(SfntSource)`.
|
|
22
|
+
|
|
23
|
+
## Acceptance criteria
|
|
24
|
+
- 0 `respond_to?` in `lib/fontisan/variation/variation_preserver.rb`
|
|
25
|
+
- All variation_preserver specs pass
|