fontisan 0.2.13 → 0.2.16

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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +56 -196
  3. data/Gemfile +1 -1
  4. data/docs/.gitignore +17 -0
  5. data/docs/.vitepress/config.ts +317 -0
  6. data/docs/.vitepress/theme/components/ApiMethod.vue +127 -0
  7. data/docs/.vitepress/theme/components/Badge.vue +51 -0
  8. data/docs/.vitepress/theme/components/FeatureComparison.vue +87 -0
  9. data/docs/.vitepress/theme/components/HeroCodeBlock.vue +98 -0
  10. data/docs/.vitepress/theme/components/WithinHero.vue +30 -0
  11. data/docs/.vitepress/theme/index.ts +22 -0
  12. data/docs/.vitepress/theme/style.css +330 -0
  13. data/docs/api/conversion-options.md +141 -0
  14. data/docs/api/converters/curve-converter.md +34 -0
  15. data/docs/api/converters/hint-converter.md +34 -0
  16. data/docs/api/converters/outline-converter.md +27 -0
  17. data/docs/api/font-loader.md +111 -0
  18. data/docs/api/font-writer.md +103 -0
  19. data/docs/api/index.md +79 -0
  20. data/docs/api/models/glyph-accessor.md +43 -0
  21. data/docs/api/models/glyph.md +40 -0
  22. data/docs/api/models/table-analyzer.md +35 -0
  23. data/docs/api/sfnt-font.md +53 -0
  24. data/docs/api/type1-font.md +43 -0
  25. data/docs/api/validators/font-validator.md +31 -0
  26. data/docs/api/validators/helper.md +36 -0
  27. data/docs/api/validators/profile.md +39 -0
  28. data/docs/cli/convert.md +87 -0
  29. data/docs/cli/dump-table.md +110 -0
  30. data/docs/cli/export.md +176 -0
  31. data/docs/cli/features.md +124 -0
  32. data/docs/cli/glyphs.md +90 -0
  33. data/docs/cli/index.md +208 -0
  34. data/docs/cli/info.md +254 -0
  35. data/docs/cli/instance.md +122 -0
  36. data/docs/cli/ls.md +95 -0
  37. data/docs/cli/optical-size.md +94 -0
  38. data/docs/cli/pack.md +125 -0
  39. data/docs/cli/scripts.md +105 -0
  40. data/docs/cli/subset.md +39 -0
  41. data/docs/cli/tables.md +84 -0
  42. data/docs/cli/unicode.md +101 -0
  43. data/docs/cli/validate.md +48 -0
  44. data/docs/cli/variable.md +126 -0
  45. data/docs/cli/version.md +46 -0
  46. data/docs/guide/cli/convert.md +108 -0
  47. data/docs/guide/cli/export.md +138 -0
  48. data/docs/guide/cli/index.md +99 -0
  49. data/docs/guide/cli/info.md +144 -0
  50. data/docs/guide/cli/pack.md +155 -0
  51. data/docs/guide/cli/subset.md +118 -0
  52. data/docs/guide/cli/validate.md +139 -0
  53. data/docs/guide/color-fonts/bitmaps.md +177 -0
  54. data/docs/guide/color-fonts/colr-cpal.md +175 -0
  55. data/docs/guide/color-fonts/index.md +140 -0
  56. data/docs/guide/color-fonts/svg.md +154 -0
  57. data/docs/guide/color.md +51 -0
  58. data/docs/guide/comparisons/font-validator.md +222 -0
  59. data/docs/guide/comparisons/fonttools.md +200 -0
  60. data/docs/guide/comparisons/index.md +83 -0
  61. data/docs/guide/comparisons/lcdf-typetools.md +205 -0
  62. data/docs/guide/contributing.md +279 -0
  63. data/docs/guide/conversion/collections.md +251 -0
  64. data/docs/guide/conversion/curves.md +246 -0
  65. data/docs/guide/conversion/index.md +157 -0
  66. data/docs/guide/conversion/options.md +251 -0
  67. data/docs/guide/conversion/ttf-otf.md +184 -0
  68. data/docs/guide/conversion/type1.md +208 -0
  69. data/docs/guide/conversion/web.md +240 -0
  70. data/docs/guide/conversion.md +39 -0
  71. data/docs/guide/formats/collections.md +147 -0
  72. data/docs/guide/formats/dfont.md +99 -0
  73. data/docs/guide/formats/index.md +65 -0
  74. data/docs/guide/formats/otf.md +103 -0
  75. data/docs/guide/formats/svg.md +97 -0
  76. data/docs/guide/formats/ttf.md +105 -0
  77. data/docs/guide/formats/type1.md +118 -0
  78. data/docs/guide/formats/woff.md +115 -0
  79. data/docs/guide/hinting/autohint.md +141 -0
  80. data/docs/guide/hinting/conversion.md +161 -0
  81. data/docs/guide/hinting/index.md +86 -0
  82. data/docs/guide/hinting/postscript.md +149 -0
  83. data/docs/guide/hinting/truetype.md +135 -0
  84. data/docs/guide/hinting.md +44 -0
  85. data/docs/guide/index.md +152 -0
  86. data/docs/guide/installation.md +116 -0
  87. data/docs/guide/migrations/extract-ttc.md +549 -0
  88. data/docs/guide/migrations/font-validator.md +260 -0
  89. data/docs/guide/migrations/fonttools.md +208 -0
  90. data/docs/guide/migrations/index.md +64 -0
  91. data/docs/guide/migrations/otfinfo.md +197 -0
  92. data/docs/guide/quick-start.md +204 -0
  93. data/docs/guide/type1.md +58 -0
  94. data/docs/guide/universal-outline.md +151 -0
  95. data/docs/guide/validation/custom.md +195 -0
  96. data/docs/guide/validation/helpers.md +188 -0
  97. data/docs/guide/validation/index.md +132 -0
  98. data/docs/guide/validation/profiles.md +156 -0
  99. data/docs/guide/validation.md +47 -0
  100. data/docs/guide/variable-fonts/advanced.md +231 -0
  101. data/docs/guide/variable-fonts/axes.md +209 -0
  102. data/docs/guide/variable-fonts/conversion.md +197 -0
  103. data/docs/guide/variable-fonts/index.md +84 -0
  104. data/docs/guide/variable-fonts/instances.md +187 -0
  105. data/docs/guide/variable-fonts/named-instances.md +194 -0
  106. data/docs/guide/variable-fonts/static.md +168 -0
  107. data/docs/guide/variable.md +58 -0
  108. data/docs/guide/woff.md +59 -0
  109. data/docs/index.md +136 -0
  110. data/docs/lychee.toml +37 -0
  111. data/docs/package-lock.json +2560 -0
  112. data/docs/package.json +15 -0
  113. data/docs/public/apple-touch-icon.png +0 -0
  114. data/docs/public/favicon-96x96.png +0 -0
  115. data/docs/public/favicon.ico +0 -0
  116. data/docs/public/favicon.svg +1 -0
  117. data/docs/public/logo-full.svg +1 -0
  118. data/docs/public/logo.svg +1 -0
  119. data/docs/public/site.webmanifest +21 -0
  120. data/docs/public/web-app-manifest-192x192.png +0 -0
  121. data/docs/public/web-app-manifest-512x512.png +0 -0
  122. data/fontisan.gemspec +1 -1
  123. data/lib/fontisan/commands/features_command.rb +0 -1
  124. data/lib/fontisan/commands/info_command.rb +5 -5
  125. data/lib/fontisan/commands/scripts_command.rb +0 -1
  126. data/lib/fontisan/converters/format_converter.rb +2 -1
  127. data/lib/fontisan/converters/type1_converter.rb +65 -60
  128. data/lib/fontisan/hints/hint_converter.rb +2 -1
  129. data/lib/fontisan/loading_modes.rb +0 -2
  130. data/lib/fontisan/open_type_font.rb +0 -40
  131. data/lib/fontisan/sfnt_font.rb +41 -22
  132. data/lib/fontisan/tables/glyf/compound_glyph.rb +0 -1
  133. data/lib/fontisan/true_type_collection.rb +8 -8
  134. data/lib/fontisan/true_type_font.rb +1 -59
  135. data/lib/fontisan/type1/afm_parser.rb +2 -1
  136. data/lib/fontisan/type1/cff_to_type1_converter.rb +24 -19
  137. data/lib/fontisan/type1/private_dict.rb +28 -7
  138. data/lib/fontisan/type1/seac_expander.rb +22 -17
  139. data/lib/fontisan/variable/delta_applicator.rb +3 -3
  140. data/lib/fontisan/variation/optimizer.rb +0 -1
  141. data/lib/fontisan/version.rb +1 -1
  142. data/lib/fontisan/woff2_font.rb +2 -2
  143. data/lib/fontisan/woff_font.rb +3 -3
  144. data/lib/fontisan.rb +3 -2
  145. metadata +122 -4
@@ -0,0 +1,204 @@
1
+ ---
2
+ title: Quick Start
3
+ ---
4
+
5
+ # Quick Start
6
+
7
+ This guide covers the most common Fontisan workflows.
8
+
9
+ ## Loading Fonts
10
+
11
+ Fontisan automatically detects font formats:
12
+
13
+ ```ruby
14
+ require 'fontisan'
15
+
16
+ # TrueType
17
+ font = Fontisan::FontLoader.load('font.ttf')
18
+
19
+ # OpenType
20
+ font = Fontisan::FontLoader.load('font.otf')
21
+
22
+ # WOFF/WOFF2
23
+ font = Fontisan::FontLoader.load('font.woff')
24
+ font = Fontisan::FontLoader.load('font.woff2')
25
+
26
+ # Type 1
27
+ font = Fontisan::FontLoader.load('font.pfb')
28
+ font = Fontisan::FontLoader.load('font.pfa')
29
+
30
+ # Collections
31
+ fonts = Fontisan::FontLoader.load('fonts.ttc')
32
+ fonts = Fontisan::FontLoader.load('fonts.otc')
33
+ ```
34
+
35
+ ## Font Information
36
+
37
+ ### Basic Information
38
+
39
+ ```ruby
40
+ font = Fontisan::FontLoader.load('font.ttf')
41
+
42
+ # Access tables directly
43
+ name_table = font.tables['name']
44
+ head_table = font.tables['head']
45
+
46
+ # Get family name
47
+ family = name_table.family_name
48
+
49
+ # Get style
50
+ style = name_table.subfamily_name
51
+
52
+ # Get metrics
53
+ units_per_em = head_table.units_per_em
54
+ ```
55
+
56
+ ### Using CLI
57
+
58
+ ```bash
59
+ # Get comprehensive info
60
+ fontisan info font.ttf
61
+
62
+ # Output in different formats
63
+ fontisan info font.ttf --format yaml
64
+ fontisan info font.ttf --format json
65
+ ```
66
+
67
+ ## Font Conversion
68
+
69
+ ### Basic Conversion
70
+
71
+ ```ruby
72
+ # TTF to OTF
73
+ Fontisan.convert('input.ttf', output_format: :otf)
74
+
75
+ # OTF to WOFF2
76
+ Fontisan.convert('input.otf', output_format: :woff2)
77
+
78
+ # Type 1 to OTF
79
+ Fontisan.convert('input.pfb', output_format: :otf)
80
+ ```
81
+
82
+ ### With Options
83
+
84
+ ```ruby
85
+ options = Fontisan::ConversionOptions.new(
86
+ from: :ttf,
87
+ to: :otf,
88
+ opening: { autohint: true, convert_curves: true },
89
+ generating: { hinting_mode: 'auto' }
90
+ )
91
+
92
+ Fontisan.convert('input.ttf', output_format: :otf, options: options)
93
+ ```
94
+
95
+ ### Using CLI
96
+
97
+ ```bash
98
+ # Basic conversion
99
+ fontisan convert input.ttf --to otf --output output.otf
100
+
101
+ # Show recommended options
102
+ fontisan convert input.ttf --to otf --show-options
103
+
104
+ # Use a preset
105
+ fontisan convert font.pfb --to otf --preset type1_to_modern --output output.otf
106
+
107
+ # With autohint
108
+ fontisan convert input.ttf --to otf --autohint --hinting-mode auto --output output.otf
109
+ ```
110
+
111
+ ## Font Validation
112
+
113
+ ### Using Ruby API
114
+
115
+ ```ruby
116
+ # Validate with default profile
117
+ result = Fontisan::FontValidator.validate('font.ttf')
118
+
119
+ # Validate with specific profile
120
+ result = Fontisan::FontValidator.validate('font.ttf', profile: :google_fonts)
121
+
122
+ # Check results
123
+ if result.passed?
124
+ puts "✓ Font is valid"
125
+ else
126
+ puts "✗ Validation failed:"
127
+ result.errors.each do |error|
128
+ puts " - #{error.code}: #{error.message}"
129
+ end
130
+ end
131
+ ```
132
+
133
+ ### Using CLI
134
+
135
+ ```bash
136
+ # Validate with Google Fonts profile
137
+ fontisan validate font.ttf --profile google_fonts
138
+
139
+ # Validate with Microsoft profile
140
+ fontisan validate font.ttf --profile microsoft
141
+
142
+ # Validate multiple files
143
+ fontisan validate fonts/*.ttf --profile production
144
+ ```
145
+
146
+ ## Working with Collections
147
+
148
+ ### List Fonts in Collection
149
+
150
+ ```bash
151
+ fontisan ls fonts.ttc
152
+ ```
153
+
154
+ ### Extract Fonts from Collection
155
+
156
+ ```bash
157
+ # Extract all fonts
158
+ fontisan unpack fonts.ttc --output-dir ./extracted
159
+
160
+ # Extract with format conversion
161
+ fontisan unpack fonts.ttc --output-dir ./extracted --format otf
162
+ ```
163
+
164
+ ### Create Collection
165
+
166
+ ```bash
167
+ # Pack fonts into TTC
168
+ fontisan pack font1.ttf font2.ttf font3.ttf --output fonts.ttc
169
+
170
+ # Pack with deduplication
171
+ fontisan pack font1.ttf font2.ttf --output fonts.ttc --deduplicate
172
+ ```
173
+
174
+ ## Variable Fonts
175
+
176
+ ### Get Axis Information
177
+
178
+ ```ruby
179
+ font = Fontisan::FontLoader.load('variable-font.ttf')
180
+ fvar = font.tables['fvar']
181
+
182
+ fvar.axes.each do |axis|
183
+ puts "#{axis.tag}: #{axis.min_value} - #{axis.max_value}"
184
+ end
185
+ ```
186
+
187
+ ### Generate Instance
188
+
189
+ ```ruby
190
+ # Generate static instance
191
+ instance = Fontisan::Variation::InstanceGenerator.new(font).generate(
192
+ 'wght' => 700,
193
+ 'ital' => 1
194
+ )
195
+
196
+ Fontisan::FontWriter.write(instance, 'bold-italic.ttf')
197
+ ```
198
+
199
+ ## Next Steps
200
+
201
+ - [Font Formats](/guide/formats/) — Detailed format documentation
202
+ - [Conversion Guide](/guide/conversion/) — Advanced conversion options
203
+ - [Validation](/guide/validation/) — Validation profiles and helpers
204
+ - [CLI Reference](/guide/cli/) — Complete CLI documentation
@@ -0,0 +1,58 @@
1
+ ---
2
+ outline: [2, 3]
3
+ ---
4
+
5
+ # Type 1 Fonts
6
+
7
+ Fontisan provides comprehensive support for Adobe Type 1 fonts.
8
+
9
+ ## Overview
10
+
11
+ Adobe Type 1 fonts are a legacy format based on PostScript outlines. Fontisan can read, validate, and convert these fonts to modern formats.
12
+
13
+ ## Reading Type 1 Fonts
14
+
15
+ ```ruby
16
+ require 'fontisan'
17
+
18
+ # Load a Type 1 font
19
+ font = Fontisan::Type1.load('font.pfb')
20
+
21
+ # Access font properties
22
+ puts font.family_name
23
+ puts font.full_name
24
+ puts font.num_glyphs
25
+ ```
26
+
27
+ ## Converting Type 1 Fonts
28
+
29
+ ```ruby
30
+ # Convert to TrueType
31
+ Fontisan.convert('font.pfb', output_format: :ttf)
32
+
33
+ # Convert to OpenType
34
+ Fontisan.convert('font.pfb', output_format: :otf)
35
+
36
+ # Convert to WOFF2
37
+ Fontisan.convert('font.pfb', output_format: :woff2)
38
+ ```
39
+
40
+ ## Handling Encodings
41
+
42
+ Type 1 fonts may use various encodings:
43
+
44
+ ```ruby
45
+ font = Fontisan::Type1.load('font.pfb')
46
+
47
+ # Get the font encoding
48
+ puts font.encoding
49
+
50
+ # List available glyphs
51
+ font.glyphs.each do |glyph|
52
+ puts "#{glyph.name}: #{glyph.codepoint}"
53
+ end
54
+ ```
55
+
56
+ ## Related
57
+
58
+ - [Font Conversion](/guide/conversion) - General conversion guide
@@ -0,0 +1,151 @@
1
+ ---
2
+ title: Universal Outline Model
3
+ ---
4
+
5
+ # Universal Outline Model
6
+
7
+ The Fontisan Universal Outline Model (UOM) provides a format-agnostic representation of glyph contours that enables seamless conversion between different font formats.
8
+
9
+ ## Overview
10
+
11
+ The UOM is based on a self-stable algorithm for converting soft glyph contours to outline format. This architecture allows:
12
+
13
+ - Importing glyphs from any font format (TrueType, OpenType, CFF)
14
+ - Converting glyph elements between formats
15
+ - Operating on outlines with transformations
16
+ - Serialization and storage
17
+
18
+ ## Core Components
19
+
20
+ ### Locker
21
+
22
+ The Locker is an object-oriented model for storing imported outlines and glyphs. Storage is based on monotonic spirals computed from 2D points and curves.
23
+
24
+ **Features:**
25
+ - Invisible conversion from TrueType, CFF OpenType, and ColorGlyph formats
26
+ - Preserves original glyph geometry
27
+ - Supports compound glyph decomposition
28
+
29
+ ### Translator
30
+
31
+ The Translator is an object-oriented model for converting between PostScript CFF charsets.
32
+
33
+ **Features:**
34
+ - PostScript Type 2/3/composite encoding and decoding
35
+ - CFF INDEX structure building
36
+ - CFF DICT structure building
37
+ - Charset conversion
38
+
39
+ ### ColorGlyph
40
+
41
+ ColorGlyph provides support for layered CFF color glyphs with on-demand rasterization.
42
+
43
+ **Features:**
44
+ - Composite font support
45
+ - Multi-layer color font representation
46
+ - CFF fonts stacked on top of each other
47
+ - Advanced color glyphs
48
+ - Raster image support (PNG/JPG) combined with TrueType outlines
49
+
50
+ ## Universal Fonts
51
+
52
+ Fontisan provides universal font capabilities:
53
+
54
+ | Capability | Description |
55
+ |------------|-------------|
56
+ | Import | Import TrueType contours into UOM |
57
+ | Operate | Transform, clean, improve, and render outlines |
58
+ | Convert | Convert UOM contours to TTF/OTF |
59
+ | Serialize | Save and share font structures |
60
+ | Color Support | Work with advanced color fonts |
61
+
62
+ ## Universal Glyphs
63
+
64
+ The universal glyph model enables:
65
+
66
+ - Universal Outline Model (UOM) for TrueType contours and CFF color glyphs
67
+ - Repository for custom fonts
68
+ - Custom Unicode assignments and configuration
69
+ - Outline import/export (TrueType and OTF/CFF)
70
+ - Rendering for advanced font types
71
+ - Universal layer stacking for color glyph combinations
72
+
73
+ ## Universal Color Layers
74
+
75
+ Universal color layers support advanced color font operations:
76
+
77
+ | Feature | Description |
78
+ |---------|-------------|
79
+ | Import | Import embedded TTF/OTF color layers |
80
+ | Assemble | Assemble from individual TTF/OTF slices |
81
+ | Manage | Advanced layer map management in TTF color fonts |
82
+ | Blend | Advanced color layer blending style management |
83
+ | Convert | Gray/Overprint/Color-Full image comps and layer conversion |
84
+ | Raster | Smart vector combos from raster images |
85
+ | PNG | Import and generate PNG block ruler layers |
86
+
87
+ ## Curve Conversion
88
+
89
+ The UOM handles bidirectional curve conversion:
90
+
91
+ - **TrueType → CFF**: Quadratic to cubic curve conversion
92
+ - **CFF → TrueType**: Cubic to quadratic curve conversion
93
+ - **CFF2 Support**: Variable font CFF2 outlines
94
+
95
+ ## Ruby API
96
+
97
+ ### Loading Outlines
98
+
99
+ ```ruby
100
+ require 'fontisan'
101
+
102
+ # Load font and access universal outlines
103
+ font = Fontisan::FontLoader.load('font.ttf')
104
+
105
+ # Access glyphs through universal model
106
+ glyphs = font.glyphs
107
+
108
+ glyphs.each do |glyph|
109
+ # Access universal outline representation
110
+ outline = glyph.universal_outline
111
+
112
+ # Get contour count
113
+ puts "Glyph #{glyph.name}: #{outline.contour_count} contours"
114
+
115
+ # Access points and curves
116
+ outline.contours.each do |contour|
117
+ contour.commands.each do |cmd|
118
+ case cmd.type
119
+ when :move_to
120
+ puts " Move to #{cmd.x}, #{cmd.y}"
121
+ when :line_to
122
+ puts " Line to #{cmd.x}, #{cmd.y}"
123
+ when :quad_to
124
+ puts " Quadratic curve to #{cmd.x}, #{cmd.y}"
125
+ when :curve_to
126
+ puts " Cubic curve to #{cmd.x}, #{cmd.y}"
127
+ end
128
+ end
129
+ end
130
+ end
131
+ ```
132
+
133
+ ### Converting Outlines
134
+
135
+ ```ruby
136
+ # Convert between curve types
137
+ converter = Fontisan::Converters::CurveConverter.new
138
+
139
+ # TrueType (quadratic) to CFF (cubic)
140
+ cff_outlines = converter.quad_to_cubic(truetype_outlines)
141
+
142
+ # CFF (cubic) to TrueType (quadratic)
143
+ ttf_outlines = converter.cubic_to_quad(cff_outlines)
144
+ ```
145
+
146
+ ## Related Documentation
147
+
148
+ - [Curve Converter API](/api/converters/curve-converter) — Curve conversion details
149
+ - [Outline Converter API](/api/converters/outline-converter) — Full outline conversion
150
+ - [Font Conversion Guide](/guide/conversion/) — Conversion workflows
151
+ - [Color Fonts Guide](/guide/color-fonts/) — Color font support
@@ -0,0 +1,195 @@
1
+ ---
2
+ title: Custom Validators
3
+ ---
4
+
5
+ # Custom Validators
6
+
7
+ Create custom validation rules using Fontisan's validation DSL.
8
+
9
+ ## Overview
10
+
11
+ Custom validators inherit from `Fontisan::Validators::Validator` and define validation logic using the DSL.
12
+
13
+ ## DSL Methods
14
+
15
+ The DSL provides 6 check methods:
16
+
17
+ | Method | Description |
18
+ |--------|-------------|
19
+ | `check_table` | Validate table-level properties |
20
+ | `check_field` | Validate specific field values |
21
+ | `check_structure` | Validate font structure and relationships |
22
+ | `check_usability` | Validate usability and best practices |
23
+ | `check_instructions` | Validate TrueType instructions/hinting |
24
+ | `check_glyphs` | Validate individual glyphs |
25
+
26
+ ## Creating a Validator
27
+
28
+ ### Basic Structure
29
+
30
+ ```ruby
31
+ require 'fontisan/validators/validator'
32
+
33
+ class MyFontValidator < Fontisan::Validators::Validator
34
+ private
35
+
36
+ def define_checks
37
+ # Define your checks here
38
+ end
39
+ end
40
+ ```
41
+
42
+ ### Table Validation
43
+
44
+ ```ruby
45
+ def define_checks
46
+ # Check name table
47
+ check_table :name_validation, 'name', severity: :error do |table|
48
+ table.valid_version? &&
49
+ table.valid_encoding_heuristics? &&
50
+ table.family_name_present? &&
51
+ table.postscript_name_valid?
52
+ end
53
+
54
+ # Check head table
55
+ check_table :head_validation, 'head', severity: :error do |table|
56
+ table.valid_magic? &&
57
+ table.valid_version? &&
58
+ table.valid_units_per_em?
59
+ end
60
+ end
61
+ ```
62
+
63
+ ### Structure Validation
64
+
65
+ ```ruby
66
+ def define_checks
67
+ check_structure :required_tables, severity: :error do |font|
68
+ %w[name head maxp hhea].all? { |tag| !font.table(tag).nil? }
69
+ end
70
+
71
+ check_structure :optional_tables, severity: :warning do |font|
72
+ # Check for recommended optional tables
73
+ %w[cmap hmtx].all? { |tag| !font.table(tag).nil? }
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Field Validation
79
+
80
+ ```ruby
81
+ def define_checks
82
+ check_field :units_per_em, 'head.units_per_em', severity: :error do |value|
83
+ [1000, 2048].include?(value)
84
+ end
85
+
86
+ check_field :font_revision, 'head.font_revision', severity: :warning do |value|
87
+ value > 0 && value < 100
88
+ end
89
+ end
90
+ ```
91
+
92
+ ### Glyph Validation
93
+
94
+ ```ruby
95
+ def define_checks
96
+ check_glyphs :glyph_bounds, severity: :warning do |glyph|
97
+ # Check if glyph has valid bounds
98
+ !glyph.bounds.nil? &&
99
+ glyph.bounds.min_x >= -10000 &&
100
+ glyph.bounds.max_x <= 10000
101
+ end
102
+ end
103
+ ```
104
+
105
+ ## Severity Levels
106
+
107
+ | Severity | Description |
108
+ |----------|-------------|
109
+ | `:fatal` | Critical error, font unusable |
110
+ | `:error` | Error, font may not work correctly |
111
+ | `:warning` | Warning, best practice violation |
112
+ | `:info` | Informational, no impact |
113
+
114
+ ## Using Custom Validators
115
+
116
+ ```ruby
117
+ # Load font
118
+ font = Fontisan::FontLoader.load('font.ttf')
119
+
120
+ # Create validator instance
121
+ validator = MyFontValidator.new(font)
122
+
123
+ # Run validation
124
+ report = validator.validate
125
+
126
+ # Check results
127
+ if report.valid?
128
+ puts "Font is valid!"
129
+ else
130
+ report.errors.each do |error|
131
+ puts "#{error.check_id}: #{error.message}"
132
+ end
133
+ end
134
+ ```
135
+
136
+ ## Complete Example
137
+
138
+ ```ruby
139
+ require 'fontisan/validators/validator'
140
+
141
+ class WebFontValidator < Fontisan::Validators::Validator
142
+ private
143
+
144
+ def define_checks
145
+ # Required tables for web fonts
146
+ check_structure :web_required_tables, severity: :error do |font|
147
+ required = %w[name head maxp hhea cmap hmtx]
148
+ required.all? { |tag| !font.table(tag).nil? }
149
+ end
150
+
151
+ # Name records completeness
152
+ check_table :name_completeness, 'name', severity: :error do |table|
153
+ [
154
+ table.family_name_present?,
155
+ table.subfamily_name_present?,
156
+ table.unique_id_present?,
157
+ table.full_name_present?,
158
+ table.version_present?
159
+ ].all?
160
+ end
161
+
162
+ # UPM should be power of 2 for web
163
+ check_field :web_units_per_em, 'head.units_per_em', severity: :info do |value|
164
+ [256, 512, 1024, 2048, 4096].include?(value)
165
+ end
166
+
167
+ # Check for GSUB (recommended for web)
168
+ check_structure :web_gsub_presence, severity: :info do |font|
169
+ !font.table('GSUB').nil?
170
+ end
171
+ end
172
+ end
173
+
174
+ # Use the validator
175
+ font = Fontisan::FontLoader.load('font.ttf')
176
+ validator = WebFontValidator.new(font)
177
+ report = validator.validate
178
+
179
+ puts report.to_summary
180
+ ```
181
+
182
+ ## Integrating with Profiles
183
+
184
+ Custom validators can be integrated into validation profiles:
185
+
186
+ ```ruby
187
+ # Create a custom profile
188
+ profile = Fontisan::Validators::ValidationProfile.new(
189
+ name: :my_custom,
190
+ validators: [MyFontValidator]
191
+ )
192
+
193
+ # Use the profile
194
+ report = Fontisan.validate('font.ttf', profile: profile)
195
+ ```