fontist 3.0.0 → 3.0.2

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/discover-fonts.yml +76 -0
  3. data/.github/workflows/rake.yml +103 -8
  4. data/.rubocop_todo.yml +179 -139
  5. data/TODO.audit-docs.md +164 -0
  6. data/TODO.improve-docs.md +114 -0
  7. data/TODO.upgrade-excavate.md +107 -0
  8. data/docs/guide/formulas.md +37 -1
  9. data/docs/guide/how-it-works.md +13 -0
  10. data/docs/guide/platforms/windows.md +67 -0
  11. data/fontist.gemspec +2 -2
  12. data/lib/fontist/cache/store.rb +1 -1
  13. data/lib/fontist/cli.rb +2 -1
  14. data/lib/fontist/errors.rb +24 -3
  15. data/lib/fontist/extract.rb +1 -0
  16. data/lib/fontist/font.rb +2 -2
  17. data/lib/fontist/font_finder.rb +1 -2
  18. data/lib/fontist/font_installer.rb +16 -14
  19. data/lib/fontist/format_matcher.rb +4 -2
  20. data/lib/fontist/format_spec.rb +1 -1
  21. data/lib/fontist/formula.rb +15 -3
  22. data/lib/fontist/formula_picker.rb +5 -3
  23. data/lib/fontist/import/create_formula.rb +5 -0
  24. data/lib/fontist/import/formula_builder.rb +10 -1
  25. data/lib/fontist/import/google/data_sources/github.rb +4 -4
  26. data/lib/fontist/import/google/font_database.rb +8 -8
  27. data/lib/fontist/import/google/formula_builders/formula_builder_v4.rb +1 -1
  28. data/lib/fontist/import/google/formula_builders/formula_builder_v5.rb +9 -3
  29. data/lib/fontist/import/google/metadata_adapter.rb +6 -6
  30. data/lib/fontist/import/google/models/font_family.rb +1 -1
  31. data/lib/fontist/import/import_display.rb +5 -5
  32. data/lib/fontist/import/macos_importer.rb +1 -1
  33. data/lib/fontist/import/upgrade_formulas.rb +1 -3
  34. data/lib/fontist/import/v4_to_v5_migrator.rb +2 -1
  35. data/lib/fontist/import/windows/fod_capabilities.yml +654 -0
  36. data/lib/fontist/import/windows/windows_license.txt +4 -0
  37. data/lib/fontist/import/windows.rb +162 -0
  38. data/lib/fontist/import.rb +3 -1
  39. data/lib/fontist/import_source.rb +1 -0
  40. data/lib/fontist/indexes/directory_snapshot.rb +2 -2
  41. data/lib/fontist/indexes/incremental_scanner.rb +2 -2
  42. data/lib/fontist/indexes.rb +8 -4
  43. data/lib/fontist/macos/catalog/asset.rb +2 -2
  44. data/lib/fontist/macos_import_source.rb +0 -1
  45. data/lib/fontist/repo.rb +1 -1
  46. data/lib/fontist/resource.rb +5 -1
  47. data/lib/fontist/resources/windows_fod_resource.rb +51 -0
  48. data/lib/fontist/resources.rb +1 -0
  49. data/lib/fontist/system_index.rb +5 -5
  50. data/lib/fontist/utils/downloader.rb +8 -3
  51. data/lib/fontist/utils/system.rb +19 -2
  52. data/lib/fontist/validation.rb +1 -1
  53. data/lib/fontist/validator.rb +2 -2
  54. data/lib/fontist/version.rb +1 -1
  55. data/lib/fontist/windows_fod_metadata.rb +83 -0
  56. data/lib/fontist/windows_import_source.rb +54 -0
  57. data/lib/fontist.rb +4 -1
  58. data/script/generate_windows_formulas.rb +24 -0
  59. data/script/validate_windows_fod_ci.rb +175 -0
  60. metadata +17 -6
@@ -0,0 +1,164 @@
1
+ # TODO: Documentation Audit
2
+
3
+ ## Purpose
4
+ Audit all Fontist Ruby gem functionality to ensure complete documentation coverage.
5
+
6
+ ## Reference Files
7
+ - docs/guide/how-it-works.md
8
+ - docs/api/
9
+ - docs/cli/
10
+ - docs/guide/concepts/
11
+
12
+ **Last Updated:** 2026-03-10
13
+
14
+ ---
15
+
16
+ ## ✅ COMPLETED
17
+
18
+ ### 1. Configuration Priority System
19
+ Documented in `/guide/how-it-works.md`
20
+
21
+ **Priority Order (Highest to Lowest):**
22
+
23
+ | Priority | Source | Example | Notes |
24
+ |----------|--------|---------|-------|
25
+ | 1 | ENV VAR | `FONTIST_PATH=/custom` | Always wins |
26
+ | 2 | Config file | `~/.fontist/config.yml` | Persistent settings |
27
+ | 3 | CLI option | `--location user` | Per-command override |
28
+ | 4 | Ruby API | `Fontist.preferred_family = true` | Programmatic |
29
+ | 5 | Default | Built-in defaults | Fallback |
30
+
31
+ ### 2. Installation Locations
32
+ Documented in `/guide/how-it-works.md`
33
+
34
+ | Type | Path | Managed? | Use Case |
35
+ |------|------|----------|----------|
36
+ | fontist | `~/.fontist/fonts/{formula}/` | ✅ Yes | Default, isolated |
37
+ | user | `~/Library/Fonts/fontist/` (macOS) | ✅ Yes | User-wide access |
38
+ | system | `/Library/Fonts/fontist/` (macOS) | ✅ Yes | All users |
39
+
40
+ ### 3. Font Indexes
41
+ Documented in `/guide/how-it-works.md`
42
+
43
+ - Formula Index: Maps font names → formula files
44
+ - System Index: OS-installed fonts
45
+ - Fontist Index: Fonts installed via Fontist
46
+ - User Index: User-installed fonts
47
+
48
+ ### 4. Formula Repository
49
+ Documented in `/guide/formulas.md`
50
+
51
+ - Location: `~/.fontist/versions/v4/formulas/`
52
+ - Git clone from `https://github.com/fontist/formulas.git`
53
+ - Auto-updates via `fontist update`
54
+ - Private repos via `fontist repo`
55
+
56
+ ### 5. CLI Commands
57
+ All documented in `/docs/cli/`
58
+
59
+ ### 6. Ruby API Classes
60
+ All documented in `/docs/api/`
61
+
62
+ ---
63
+
64
+ ## 🔄 IN PROGRESS
65
+
66
+ - [ ] Add configuration priority table to how-it-works.md
67
+ - [ ] Add managed vs non-managed location explanation
68
+ - [ ] Document all ENV variables in reference page
69
+
70
+ ---
71
+
72
+ ## Configuration Reference
73
+
74
+ ### All Configuration Settings
75
+
76
+ | Setting | ENV VAR | Config Key | CLI Option | Default |
77
+ |---------|---------|------------|------------|---------|
78
+ | Base path | `FONTIST_PATH` | - | - | `~/.fontist` |
79
+ | Fonts path | - | `fonts_path` | - | `~/.fontist/fonts` |
80
+ | Formulas path | `FONTIST_FORMULAS_PATH` | - | `--formulas-path` | (auto) |
81
+ | Install location | `FONTIST_INSTALL_LOCATION` | `fonts_install_location` | `--location` | `fontist` |
82
+ | User fonts path | `FONTIST_USER_FONTS_PATH` | `user_fonts_path` | - | Platform default |
83
+ | System fonts path | `FONTIST_SYSTEM_FONTS_PATH` | `system_fonts_path` | - | Platform default |
84
+ | Preferred family | - | `preferred_family` | `--preferred-family` | `false` |
85
+ | No cache | - | - | `--no-cache` | `false` |
86
+ | Quiet mode | - | - | `--quiet` | `false` |
87
+ | Verbose | - | - | `--verbose` | `false` |
88
+ | Open timeout | - | `open_timeout` | - | `60` |
89
+ | Read timeout | - | `read_timeout` | - | `60` |
90
+ | Google Fonts key | `GOOGLE_FONTS_API_KEY` | `google_fonts_key` | - | `nil` |
91
+ | Import cache | `FONTIST_IMPORT_CACHE` | - | - | `~/.fontist/import_cache` |
92
+
93
+ ---
94
+
95
+ ## Environment Variables Reference
96
+
97
+ | Variable | Description | Default |
98
+ |----------|-------------|---------|
99
+ | `FONTIST_PATH` | Base directory for all Fontist data | `~/.fontist` |
100
+ | `FONTIST_FORMULAS_PATH` | Custom formulas directory | (auto) |
101
+ | `FONTIST_INSTALL_LOCATION` | Default install location (`fontist`, `user`, `system`) | `fontist` |
102
+ | `FONTIST_USER_FONTS_PATH` | Custom user fonts path | Platform default |
103
+ | `FONTIST_SYSTEM_FONTS_PATH` | Custom system fonts path | Platform default |
104
+ | `FONTIST_IMPORT_CACHE` | Import cache directory | `~/.fontist/import_cache` |
105
+ | `FONTIST_NO_MIRRORS` | Disable formula index mirrors | `false` |
106
+ | `GOOGLE_FONTS_API_KEY` | Google Fonts API key | (none) |
107
+
108
+ ---
109
+
110
+ ## Managed vs Non-Managed Locations
111
+
112
+ ### Managed Locations (Fontist controls)
113
+ - `~/.fontist/fonts/` - Always managed
114
+ - `~/Library/Fonts/fontist/` - Managed subdirectory
115
+ - `/Library/Fonts/fontist/` - Managed subdirectory
116
+
117
+ **Behavior:** Safe to replace existing fonts
118
+
119
+ ### Non-Managed Locations (custom paths via ENV)
120
+ - `FONTIST_USER_FONTS_PATH=~/Library/Fonts` → installs directly
121
+ - `FONTIST_SYSTEM_FONTS_PATH=/Library/Fonts` → installs directly
122
+
123
+ **Behavior:** Uses unique filenames (`-fontist` suffix) to avoid conflicts
124
+
125
+ ---
126
+
127
+ ## Directory Structure
128
+
129
+ ```
130
+ ~/.fontist/
131
+ ├── fonts/ # Installed font files
132
+ │ └── {formula-key}/
133
+ │ └── *.ttf
134
+ ├── versions/
135
+ │ └── v4/
136
+ │ ├── formulas/ # Git clone of formulas repo
137
+ │ │ └── Formulas/
138
+ │ │ ├── roboto.yml
139
+ │ │ └── private/ # Custom formula repos
140
+ │ ├── formula_index.default_family.yml
141
+ │ ├── formula_index.preferred_family.yml
142
+ │ └── filename_index.yml
143
+ ├── config.yml # User configuration
144
+ ├── downloads/ # Temporary downloads
145
+ ├── import_cache/ # Imported font cache
146
+ ├── system_index.default_family.yml # System fonts index
147
+ ├── fontist_index.default_family.yml # Fontist fonts index
148
+ └── user_index.default_family.yml # User fonts index
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Remaining Minor Gaps
154
+
155
+ - [ ] Document import system (`fontist import`) internals
156
+ - [ ] Document cache system internals
157
+ - [ ] Document formula picker algorithm
158
+ - [ ] Document locking mechanism (for concurrent operations)
159
+
160
+ ---
161
+
162
+ ## Status: Nearly Complete
163
+ **Priority:** High
164
+ **Notes:** Core functionality documented, minor gaps remain
@@ -0,0 +1,114 @@
1
+ # Fontist Documentation Improvement Plan
2
+
3
+ **Goal:** Achieve 100% documentation coverage of README.adoc content in docs/
4
+
5
+ **Status:** ✅ Complete
6
+
7
+ ---
8
+
9
+ ## Phase 1: Platform-Specific Guides (Critical)
10
+
11
+ ### 1.1 Create macOS Platform Guide
12
+ - [x] Create `/docs/guide/platforms/macos.md`
13
+ - [x] Document supplementary fonts framework
14
+ - [x] Document framework versioning (Font3-Font8)
15
+ - [x] Document platform tags (`macos-fontX`)
16
+ - [x] Document version compatibility checking
17
+ - [x] Include macOS version → framework table
18
+ - [x] Add link to Fontist blog post
19
+
20
+ ### 1.2 Create Windows Platform Guide
21
+ - [x] Create `/docs/guide/platforms/windows.md`
22
+ - [x] Document Windows font management differences
23
+ - [x] Document Windows font locations
24
+ - [x] Document file locking considerations
25
+ - [x] Document path handling
26
+ - [x] Document registry integration
27
+ - [x] Document administrator privileges
28
+ - [x] Include platform compatibility table
29
+
30
+ ---
31
+
32
+ ## Phase 2: Installation Improvements
33
+
34
+ ### 2.1 Update Installation Guide
35
+ - [x] Add detailed dependency table (json, brotli, seven-zip, libmspack, ffi-libarchive-binary)
36
+ - [x] Add Windows RubyInstaller DevKit steps (`ridk install`)
37
+ - [x] Add Git as prerequisite for update/repo commands
38
+ - [x] Add note about native extension compilation
39
+
40
+ ---
41
+
42
+ ## Phase 3: Formula Advanced Features
43
+
44
+ ### 3.1 Update Formulas Guide
45
+ - [x] Add `override:` key documentation (already present)
46
+ - [x] Add Frutiger fonts example (already present)
47
+ - [x] Add HTTP authentication for private repos (already present)
48
+ - [x] Add authorization headers example (already present)
49
+ - [x] Add token scope requirements (already present)
50
+
51
+ ### 3.2 Update create-formula CLI
52
+ - [x] Add `--name-prefix` option with example (already documented)
53
+
54
+ ---
55
+
56
+ ## Phase 4: How It Works Improvements
57
+
58
+ ### 4.1 Update How It Works Guide
59
+ - [x] Add managed vs non-managed locations section (already present)
60
+ - [x] Add font discovery behavior explanation (already present)
61
+ - [x] Add import cache environment variables (already present)
62
+
63
+ ---
64
+
65
+ ## Phase 5: Maintainer Documentation (Clearly Marked)
66
+
67
+ ### 5.1 Create Maintainer Import Guide
68
+ - [x] Create `/docs/guide/maintainer/index.md`
69
+ - [x] Create `/docs/guide/maintainer/import.md`
70
+ - [x] Mark as "Maintainer Only" clearly
71
+ - [x] Document `fontist import google` command
72
+ - [x] Document Google Fonts API integration
73
+ - [x] Document import source architecture
74
+ - [x] Document versioned filenames
75
+ - [x] Document import cache Ruby API
76
+
77
+ ### 5.2 Update CLI Import Reference
78
+ - [x] Add `fontist import google` to `/docs/cli/import.md` (already present)
79
+ - [x] Add maintainer-only notice
80
+
81
+ ---
82
+
83
+ ## Phase 6: Navigation Updates
84
+
85
+ ### 6.1 Update VitePress Config
86
+ - [x] Add Platforms section to Guide sidebar
87
+ - [x] Add Maintainer section to Guide sidebar (collapsed)
88
+ - [x] Update navigation as needed
89
+
90
+ ---
91
+
92
+ ## Phase 7: Verification
93
+
94
+ ### 7.1 Build and Test
95
+ - [x] Run `npm run build` to verify no errors
96
+ - [x] Run lychee link checker (manual verification with curl - lychee crashed)
97
+ - [x] Verify all new pages are accessible
98
+ - [x] Cross-check against README.adoc
99
+
100
+ ---
101
+
102
+ ## Progress Tracking
103
+
104
+ | Phase | Status | Items Done | Items Total |
105
+ |-------|--------|------------|-------------|
106
+ | 1. Platform Guides | ✅ Complete | 14 | 14 |
107
+ | 2. Installation | ✅ Complete | 4 | 4 |
108
+ | 3. Formula Features | ✅ Complete | 7 | 7 |
109
+ | 4. How It Works | ✅ Complete | 3 | 3 |
110
+ | 5. Maintainer Docs | ✅ Complete | 10 | 10 |
111
+ | 6. Navigation | ✅ Complete | 3 | 3 |
112
+ | 7. Verification | ✅ Complete | 4 | 4 |
113
+
114
+ **Total Progress: 45/45 items (100%)**
@@ -0,0 +1,107 @@
1
+ # TODO: Fix Excavate Memory Leaks and Upgrade Fontist
2
+
3
+ ## Problem
4
+ Fontist CI tests pass but fail at the end with:
5
+ ```
6
+ failed to allocate memory (NoMemoryError)
7
+ ```
8
+
9
+ This happens after repeated archive extractions during test runs.
10
+
11
+ ## Root Cause Analysis
12
+ Memory leaks in Excavate gem due to:
13
+ 1. Temp directories not cleaned up on errors
14
+ 2. Incorrect `ensure`/`rescue` order in `extract_and_replace` method
15
+
16
+ ## Remaining Work
17
+
18
+ ### 1. Fix Excavate Gem (in /Users/mulgogi/src/fontist/excavate)
19
+
20
+ #### archive.rb - Fix `extract_and_replace` method (lines 217-229)
21
+ Current broken code has `ensure` BEFORE `rescue` which is invalid Ruby syntax:
22
+ ```ruby
23
+ def extract_and_replace(archive)
24
+ target = Dir.mktmpdir
25
+ extract_recursively(archive, target)
26
+ replace_archive_with_contents(archive, target)
27
+ ensure # <-- WRONG: ensure must come AFTER rescue
28
+ FileUtils.rm_rf(target)
29
+ rescue StandardError # <-- This causes syntax error
30
+ raise unless TYPES.key?(normalized_extension(archive))
31
+ end
32
+ ```
33
+
34
+ Fix - swap order so `rescue` comes before `ensure`:
35
+ ```ruby
36
+ def extract_and_replace(archive)
37
+ target = Dir.mktmpdir
38
+ extract_recursively(archive, target)
39
+ replace_archive_with_contents(archive, target)
40
+ rescue StandardError
41
+ raise unless TYPES.key?(normalized_extension(archive))
42
+ ensure
43
+ FileUtils.rm_rf(target)
44
+ end
45
+ ```
46
+
47
+ ### 2. Run Excavate Tests
48
+ ```bash
49
+ cd /Users/mulgogi/src/fontist/excavate
50
+ bundle exec rspec
51
+ ```
52
+ All 65 tests should pass.
53
+
54
+ ### 3. Bump Excavate Version
55
+ Edit `/Users/mulgogi/src/fontist/excavate/lib/excavate/version.rb`:
56
+ ```ruby
57
+ VERSION = "1.0.3"
58
+ ```
59
+
60
+ ### 4. Commit and Push Excavate
61
+ ```bash
62
+ cd /Users/mulgogi/src/fontist/excavate
63
+ git add -A
64
+ git commit -m "fix: prevent memory leaks by ensuring proper resource cleanup
65
+
66
+ - Fix extract_and_replace to use correct rescue/ensure order
67
+ - Add ensure block to extract_by_filter for temp dir cleanup"
68
+ git push origin main
69
+ ```
70
+
71
+ ### 5. Release Excavate Gem
72
+ ```bash
73
+ cd /Users/mulgogi/src/fontist/excavate
74
+ # Tag and push to rubygems
75
+ git tag v1.0.3
76
+ git push --tags
77
+ gem build excavate.gemspec
78
+ gem push excavate-1.0.3.gem
79
+ ```
80
+
81
+ ### 6. Update Fontist to Use New Excavate
82
+ ```bash
83
+ cd /Users/mulgogi/src/fontist/fontist
84
+ # Update Gemfile.lock to use excavate >= 1.0.3
85
+ bundle update excavate
86
+ ```
87
+
88
+ ### 7. Run Fontist Tests
89
+ ```bash
90
+ cd /Users/mulgogi/src/fontist/fontist
91
+ bundle exec rspec
92
+ ```
93
+ Tests should pass WITHOUT the `NoMemoryError` at the end.
94
+
95
+ ### 8. Create PR and Merge
96
+ - Create PR in fontist repository
97
+ - Reference issue #451
98
+ - Merge after CI passes
99
+
100
+ ## Files Modified
101
+
102
+ ### Excavate
103
+ - `lib/excavate/archive.rb` - Fix `extract_and_replace` method
104
+
105
+ ## Notes
106
+ - Do NOT add `ensure` blocks with `reader&.close` to extractors - the Omnizip readers don't have a `close` method
107
+ - The fix is simple: correct the `rescue`/`ensure` order in `extract_and_replace`
@@ -26,7 +26,7 @@ The main formula repository is [fontist/formulas](https://github.com/fontist/for
26
26
  - Google Fonts (Roboto, Open Sans, etc.)
27
27
  - SIL Fonts
28
28
  - macOS system fonts
29
- - Windows fonts
29
+ - Windows fonts (including Windows Features on Demand)
30
30
  - Many other open source fonts
31
31
 
32
32
  ### Local Storage
@@ -257,6 +257,42 @@ fontist create-formula https://dl.winehq.org/wine/source/10.x/wine-10.18.tar.xz
257
257
 
258
258
  This generates a formula where all fonts have names prefixed with "Wine ", making it clear these are Wine compatibility fonts rather than the original Microsoft fonts.
259
259
 
260
+ ### Windows Features on Demand (FOD) Formulas
261
+
262
+ Windows FOD formulas use `source: windows_fod` to install fonts via PowerShell's `Add-WindowsCapability`. These formulas don't have download URLs — instead, they specify a Windows capability name:
263
+
264
+ ```yaml
265
+ schema_version: 5
266
+ name: Japanese Supplemental Fonts
267
+ platforms:
268
+ - windows
269
+ resources:
270
+ japanese_supplemental_fonts:
271
+ source: windows_fod
272
+ capability_name: "Language.Fonts.Jpan~~~und-JPAN~0.0.1.0"
273
+ fonts:
274
+ - name: Meiryo
275
+ styles:
276
+ - family_name: Meiryo
277
+ type: Regular
278
+ font: Meiryo.ttc
279
+ import_source:
280
+ type: windows
281
+ capability_name: "Language.Fonts.Jpan~~~und-JPAN~0.0.1.0"
282
+ min_windows_version: "10.0"
283
+ ```
284
+
285
+ Key fields for Windows FOD formulas:
286
+
287
+ | Field | Description |
288
+ |-------|-------------|
289
+ | `resources.*.source` | Must be `windows_fod` |
290
+ | `resources.*.capability_name` | The Windows FOD capability identifier (e.g., `Language.Fonts.Jpan~~~und-JPAN~0.0.1.0`) |
291
+ | `import_source.type` | Must be `windows` |
292
+ | `import_source.min_windows_version` | Minimum Windows version required (default: `10.0`) |
293
+
294
+ These formulas are restricted to the `windows` platform. Attempting to install them on macOS or Linux raises a `PlatformMismatchError`.
295
+
260
296
  ## Related
261
297
 
262
298
  - [How Fontist Works](/guide/how-it-works) - Internal architecture and indexes
@@ -291,6 +291,19 @@ When you run `fontist install "Open Sans"`:
291
291
  └─────────────────────────────────────────────────────────────┘
292
292
  ```
293
293
 
294
+ ### Resource Dispatch
295
+
296
+ Fontist selects a download strategy based on the formula's `source` field:
297
+
298
+ | Source | Resource Class | Behavior |
299
+ |--------|---------------|----------|
300
+ | `google` | `GoogleResource` | Downloads from Google Fonts API |
301
+ | `apple_cdn` | `AppleCDNResource` | Downloads from Apple CDN |
302
+ | `windows_fod` | `WindowsFodResource` | Installs via PowerShell `Add-WindowsCapability` |
303
+ | (default) | `ArchiveResource` | Downloads and extracts archive via excavate |
304
+
305
+ For Windows FOD fonts, the flow differs from archives — instead of downloading a file, Fontist checks if the Windows capability is installed and installs it via PowerShell if needed. Font files are then read from `C:\Windows\Fonts`.
306
+
294
307
  ---
295
308
 
296
309
  ## Index Management
@@ -78,6 +78,73 @@ fontist install "Roboto" --location=system
78
78
 
79
79
  ---
80
80
 
81
+ ## Windows Features on Demand (FOD) Fonts
82
+
83
+ Windows includes supplementary fonts that aren't pre-installed but can be enabled through **Features on Demand** (FOD). These include fonts for Japanese, Korean, Arabic, Pan-European, and other scripts.
84
+
85
+ ### What Are FOD Fonts?
86
+
87
+ Windows FOD fonts are installed using `Add-WindowsCapability`, which downloads font packages from Windows Update. Fontist automates this process — when a formula specifies `source: windows_fod`, Fontist calls the appropriate PowerShell command to install the capability.
88
+
89
+ ### Installing FOD Fonts
90
+
91
+ ```powershell
92
+ # Install a specific supplemental font
93
+ fontist install "Meiryo"
94
+
95
+ # Install Pan-European supplemental fonts
96
+ fontist install "Arial Nova"
97
+ ```
98
+
99
+ Fontist checks whether the capability is already installed. If not, it runs:
100
+
101
+ ```powershell
102
+ Add-WindowsCapability -Online -Name 'Language.Fonts.Jpan~~~und-JPAN~0.0.1.0'
103
+ ```
104
+
105
+ ### Requirements
106
+
107
+ | Requirement | Details |
108
+ |-------------|---------|
109
+ | Windows version | Windows 10 or later |
110
+ | Internet connection | Required (fonts download from Windows Update) |
111
+ | Administrator | Required on Windows 10 for system-wide installation |
112
+ | WSUS/SCCM | Must not block Features on Demand |
113
+
114
+ ### Available FOD Fonts
115
+
116
+ Fontist includes metadata for all Windows FOD font capabilities. Some common ones:
117
+
118
+ | Capability | Fonts Included |
119
+ |------------|---------------|
120
+ | Japanese Supplemental Fonts | Meiryo, MS Gothic, MS Mincho |
121
+ | Korean Supplemental Fonts | Batang, Dotum, Gulim |
122
+ | Arabic Script Supplemental Fonts | Sakkal Majalla, Simplified Arabic |
123
+ | Pan-European Supplemental Fonts | Arial Nova, Georgia Pro, Gill Sans Nova |
124
+ | Traditional Chinese Supplemental Fonts | MingLiU, DFKai-SB |
125
+
126
+ ### FOD Font Installation Flow
127
+
128
+ 1. Fontist looks up the font in the formula index
129
+ 2. Checks if the Windows FOD capability is already installed via `Get-WindowsCapability`
130
+ 3. If not installed, runs `Add-WindowsCapability` to download and install it
131
+ 4. Returns paths to the installed font files in `C:\Windows\Fonts`
132
+
133
+ ### Troubleshooting FOD Installation
134
+
135
+ If you see a `WindowsFodInstallError`:
136
+
137
+ 1. **No internet connection** — FOD fonts require Windows Update access
138
+ 2. **Insufficient permissions** — Run as Administrator on Windows 10
139
+ 3. **WSUS/SCCM policy** — Your organization may block FOD downloads; contact your IT administrator
140
+ 4. **Capability not found** — The capability name may differ on your Windows version; run `Get-WindowsCapability -Online -Name 'Language.Fonts.*'` to list available capabilities
141
+
142
+ ::: info Platform Restriction
143
+ Windows FOD fonts are only installable on Windows. Fontist will raise a `PlatformMismatchError` if you attempt to install them on macOS or Linux.
144
+ :::
145
+
146
+ ---
147
+
81
148
  ## Windows-Specific Considerations
82
149
 
83
150
  ### File Locking
data/fontist.gemspec CHANGED
@@ -31,11 +31,11 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.add_dependency "down", "~> 5.0"
33
33
  spec.add_dependency "excavate", "~> 1.0", ">= 1.0.3"
34
- spec.add_dependency "fontisan", "~> 0.2", ">= 0.2.11"
34
+ spec.add_dependency "fontisan", "~> 0.2", ">= 0.2.16"
35
35
  spec.add_dependency "fuzzy_match", "~> 2.1"
36
36
  spec.add_dependency "git", "> 1.0"
37
37
  spec.add_dependency "json", "~> 2.0"
38
- spec.add_dependency "lutaml-model", "~> 0.7"
38
+ spec.add_dependency "lutaml-model", "~> 0.8.0"
39
39
  spec.add_dependency "marcel", "~> 1.0"
40
40
  spec.add_dependency "nokogiri", "~> 1.0"
41
41
  spec.add_dependency "octokit", "~> 4.0"
@@ -57,7 +57,7 @@ module Fontist
57
57
  attr_reader :cache_dir
58
58
 
59
59
  def ensure_cache_dir
60
- FileUtils.mkdir_p(@cache_dir) unless Dir.exist?(@cache_dir)
60
+ FileUtils.mkdir_p(@cache_dir)
61
61
  end
62
62
 
63
63
  def cache_path(key)
data/lib/fontist/cli.rb CHANGED
@@ -338,7 +338,8 @@ module Fontist
338
338
  "Uses `fnmatch` patterns."
339
339
  option :name_prefix, desc: "Prefix to add to all font family names, " \
340
340
  "e.g. 'Wine ' for compatibility fonts"
341
- option :schema_version, type: :numeric, default: 5, desc: "Formula schema version (default: 5)"
341
+ option :schema_version, type: :numeric, default: 5,
342
+ desc: "Formula schema version (default: 5)"
342
343
  def create_formula(url)
343
344
  handle_class_options(options)
344
345
  name = Fontist::Import::CreateFormula.new(url, options).call
@@ -159,9 +159,30 @@ module Fontist
159
159
 
160
160
  def build_message(font_name)
161
161
  "Font '#{font_name}' is only available for: #{@required_platforms.join(', ')}. " \
162
- "Your current platform is: #{@current_platform}. " \
163
- "This font is licensed exclusively for the specified platform(s) and " \
164
- "cannot be installed on your system."
162
+ "Your current platform is: #{@current_platform}. " \
163
+ "This font is licensed exclusively for the specified platform(s) and " \
164
+ "cannot be installed on your system."
165
+ end
166
+ end
167
+
168
+ class WindowsFodInstallError < GeneralError
169
+ attr_reader :capability_name
170
+
171
+ def initialize(capability_name, stderr_output = nil)
172
+ @capability_name = capability_name
173
+ super(build_message(capability_name, stderr_output))
174
+ end
175
+
176
+ private
177
+
178
+ def build_message(cap_name, stderr)
179
+ msg = "Failed to install Windows font capability '#{cap_name}'."
180
+ msg += "\n#{stderr}" if stderr && !stderr.strip.empty?
181
+ msg += "\n\nPossible causes:" \
182
+ "\n - No internet connection (Windows Update required)" \
183
+ "\n - Insufficient permissions (admin required on Windows 10)" \
184
+ "\n - WSUS/SCCM policy blocking Features on Demand"
185
+ msg
165
186
  end
166
187
  end
167
188
 
@@ -32,6 +32,7 @@ module Fontist
32
32
  return true if options.nil?
33
33
  return options.empty? if options.respond_to?(:empty?)
34
34
  return options.file.nil? && options.fonts_sub_dir.nil? if options.is_a?(ExtractOptions)
35
+
35
36
  false
36
37
  end
37
38
  end
data/lib/fontist/font.rb CHANGED
@@ -338,9 +338,9 @@ module Fontist
338
338
  all_fonts = [fontist_fonts, user_fonts, system_fonts].compact.flatten
339
339
  return unless all_fonts && !all_fonts.empty?
340
340
 
341
- uninstalled_paths = all_fonts.map do |font|
341
+ uninstalled_paths = all_fonts.filter_map do |font|
342
342
  uninstall_font_at_location(font.path)
343
- end.compact
343
+ end
344
344
 
345
345
  return if uninstalled_paths.empty?
346
346
 
@@ -75,7 +75,7 @@ module Fontist
75
75
  def extract_resource_names(formula)
76
76
  return [] unless formula.resources
77
77
 
78
- Array(formula.resources).map(&:name).compact
78
+ Array(formula.resources).filter_map(&:name)
79
79
  end
80
80
 
81
81
  def build_font_match(formula, name, resource)
@@ -123,7 +123,6 @@ module Fontist
123
123
  matcher = FormatMatcher.new(@format_spec)
124
124
  matcher.filter_resources(resources)
125
125
  end
126
-
127
126
  end
128
127
 
129
128
  # Result object for font matches