mimeforge 0.1.2 → 0.2.0

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
4
4
  project uses [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.2.0] - 2026-10-08
7
+
8
+ - Fallback icon: `mimeforge default` and `mimeforge --all` write `default.svg` (generic body without a label), and
9
+ `--png` adds `<size>/default.png`; library: `renderDefaultIcon()`. For apps that need a file to point at when an
10
+ extension has no icon.
11
+ - PNG export no longer loads system fonts: `--all --png 16` takes about half a second instead of minutes.
12
+
6
13
  ## [0.1.2] - 2026-10-08
7
14
 
8
15
  Documentation only.
package/README.md CHANGED
@@ -29,7 +29,7 @@ And, frankly, why not? :)
29
29
 
30
30
  ## Contents
31
31
 
32
- [Quick start](#quick-start) · [Everyday use](#everyday-use) · [Customize](#customize) ([colors](#change-colors) · [label](#change-the-label) · [font](#change-the-font) · [bodies](#change-the-bodies) · [config file](#use-a-config-file) · [PNG](#export-png)) · [Edge cases](#edge-cases) · [Library API](#library-api) · [CLI reference](#cli-reference) · [Categories](#categories) · [Development](#development)
32
+ [Quick start](#quick-start) · [Static hosts](#use-it-on-a-static-host-with-a-fallback-icon) · [Everyday use](#everyday-use) · [Customize](#customize) ([colors](#change-colors) · [label](#change-the-label) · [font](#change-the-font) · [bodies](#change-the-bodies) · [config file](#use-a-config-file) · [PNG](#export-png)) · [Edge cases](#edge-cases) · [Library API](#library-api) · [CLI reference](#cli-reference) · [Categories](#categories) · [Development](#development)
33
33
 
34
34
  ## Quick start
35
35
 
@@ -85,11 +85,39 @@ Then `npm run icons` and reference `/icons/<ext>.svg`. `public/icons/manifest.js
85
85
  category, which is handy for lookups. If you only show a few types, list them instead of `--all`. To render icons at
86
86
  runtime instead (a server, a build plugin), use the [library API](#library-api).
87
87
 
88
+ ### Use it on a static host (with a fallback icon)
89
+
90
+ If your app serves a prebuilt set of files, say `/m/<size>/<ext>.png`, generate the whole set once:
91
+
92
+ ```bash
93
+ mimeforge --all --png 16,20,32,48 -o m
94
+ ```
95
+
96
+ ```
97
+ m/pdf.svg m/16/pdf.png m/20/pdf.png m/32/pdf.png m/48/pdf.png
98
+ m/default.svg m/16/default.png … the fallback, see below
99
+ m/manifest.json { "pdf": "pdf", "gdoc": "word", … } every known extension and its category
100
+ ```
101
+
102
+ Extensions MimeForge does not know (a `.hoppa` file) have no file of their own. `default.svg` and
103
+ `<size>/default.png` are the fallback for those: the generic page with a question mark and **no label** (an unknown
104
+ extension cannot be named). Point your app at it when an icon is missing:
105
+
106
+ ```js
107
+ const manifest = await (await fetch('/m/manifest.json')).json();
108
+ const src = ext in manifest ? `/m/32/${ext}.png` : '/m/32/default.png';
109
+ ```
110
+
111
+ or let the browser do it: `<img src="/m/32/hoppa.png" onerror="this.onerror=null;this.src='/m/32/default.png'">`.
112
+ To give one unknown extension its own icon instead, generate it explicitly (`mimeforge hoppa`, which draws the generic
113
+ body with the label `HOPPA`) or, from code, with a category of your choice (`renderIcon('hoppa', { category: 'archive' })`).
114
+
88
115
  ## Everyday use
89
116
 
90
117
  ```bash
91
118
  mimeforge pdf docx webm -o icons # a few icons
92
- mimeforge --all -o icons # every known extension, plus icons/manifest.json (ext → category)
119
+ mimeforge --all -o icons # every known extension, plus icons/default.svg and icons/manifest.json (ext → category)
120
+ mimeforge default -o icons # only the fallback icon: generic body, no label
93
121
  mimeforge --list # which extension gets which category
94
122
  mimeforge --test-run # visual check, see below
95
123
  ```
@@ -209,7 +237,7 @@ optional dependency `@resvg/resvg-js` (installed by default; if it is missing, M
209
237
 
210
238
  | Situation | What happens |
211
239
  |---|---|
212
- | Unknown extension (`mimeforge xyz`) | generic body (question mark) with the label `XYZ` |
240
+ | Unknown extension (`mimeforge xyz`) | generic body (question mark) with the label `XYZ`; for a label-less fallback file use `mimeforge default` (also written by `--all`) |
213
241
  | Long extension (`webmanifest`) | up to 6 letters are drawn at full size; longer text is scaled down proportionally to fit the label, and below about a third of the size letters are dropped from the end |
214
242
  | Light accent color | label text switches to a dark ink when white would have less than 3:1 contrast |
215
243
  | Character the font does not have | skipped |
@@ -229,9 +257,10 @@ how to change it.
229
257
  Install with `npm i mimeforge`. The package is ESM, so use `import` (from CommonJS: `const { renderIcon } = await import('mimeforge')`).
230
258
 
231
259
  ```ts
232
- import { renderIcon, loadFont, loadBodies, createPalette } from 'mimeforge';
260
+ import { renderIcon, renderDefaultIcon, loadFont, loadBodies, createPalette } from 'mimeforge';
233
261
 
234
262
  renderIcon('pdf').svg; // SVG string
263
+ renderDefaultIcon().svg; // fallback icon: generic body, no label
235
264
  renderIcon('docx', { accent: '#0a7', label: 'WORD' }).svg; // custom accent and label text
236
265
 
237
266
  renderIcon('xyz', {
@@ -243,14 +272,15 @@ renderIcon('xyz', {
243
272
  });
244
273
  ```
245
274
 
246
- `renderIcon` returns `{ svg, ext, category, accent }`. Other exports: `categoryFor`, `allExtensions`, `CATEGORIES`,
275
+ `renderIcon` returns `{ svg, ext, category, accent }`. Other exports: `renderDefaultIcon`, `categoryFor`, `allExtensions`, `CATEGORIES`,
247
276
  `DEFAULT_PALETTE`, `validateBody`, `renderLabel`, `bitmapFont`, `outlineFont` (see `src/index.ts`). Types are included.
248
277
 
249
278
  ## CLI reference
250
279
 
251
280
  ```
252
281
  mimeforge <ext...> [options] write <out>/<ext>.svg for the given extensions
253
- mimeforge --all [options] every known extension + manifest.json
282
+ mimeforge --all [options] every known extension + default.svg + manifest.json
283
+ mimeforge default [options] only the fallback icon (generic body, no label)
254
284
  mimeforge --test-run [options] HTML preview page (default dir: out/test-run)
255
285
  ```
256
286
 
package/dist/cli.js CHANGED
@@ -8,13 +8,14 @@ import { renderPng } from './png.js';
8
8
  import { loadFont } from './font.js';
9
9
  import { allExtensions, categoryFor } from './mime.js';
10
10
  import { createPalette } from './palette.js';
11
- import { renderIcon } from './render.js';
11
+ import { renderDefaultIcon, renderIcon } from './render.js';
12
12
  import { testRun } from './testrun.js';
13
13
  import { ROOT } from './paths.js';
14
14
  const HELP = `MimeForge – generate file-type icons (SVG) from file extensions
15
15
 
16
16
  Usage
17
17
  mimeforge <ext...> [options] write <out>/<ext>.svg for the given extensions
18
+ ("default" = the fallback icon without a label)
18
19
  mimeforge --all [options] every known extension (mime-db + extras) + manifest.json
19
20
  mimeforge --test-run [options] write an HTML page with all body types at 16/48/128 px
20
21
 
@@ -107,21 +108,22 @@ async function main() {
107
108
  const out = resolve(values.out ?? cfg.out ?? 'out');
108
109
  mkdirSync(out, { recursive: true });
109
110
  const pngSizes = (values.png ? values.png.split(',').map(Number) : cfg.png ?? []).filter((n) => Number.isFinite(n) && n > 0);
110
- const exts = values.all ? allExtensions() : positionals.map((e) => e.toLowerCase().replace(/^\./, ''));
111
+ const exts = values.all ? [...allExtensions(), 'default'] : positionals.map((e) => e.toLowerCase().replace(/^\./, ''));
111
112
  const manifest = {};
112
113
  let bytes = 0;
113
114
  for (const ext of exts) {
114
- const r = renderIcon(ext, opts);
115
+ const r = ext === 'default' ? renderDefaultIcon(opts) : renderIcon(ext, opts);
115
116
  writeFileSync(join(out, `${ext}.svg`), r.svg);
116
117
  for (const size of pngSizes) {
117
118
  mkdirSync(join(out, String(size)), { recursive: true });
118
119
  writeFileSync(join(out, String(size), `${ext}.png`), await renderPng(r.svg, size));
119
120
  }
120
- manifest[ext] = r.category;
121
+ if (ext !== 'default')
122
+ manifest[ext] = r.category;
121
123
  bytes += r.svg.length;
122
124
  }
123
125
  if (values.all)
124
126
  writeFileSync(join(out, 'manifest.json'), JSON.stringify(manifest, null, 1));
125
- console.log(`${exts.length} icon(s) → ${out} (${(bytes / 1024).toFixed(0)} KB total, ${(bytes / exts.length / 1024).toFixed(1)} KB avg)`);
127
+ console.log(`${exts.length} icon(s)${values.all ? ' (incl. default)' : ''} → ${out} (${(bytes / 1024).toFixed(0)} KB total, ${(bytes / exts.length / 1024).toFixed(1)} KB avg)`);
126
128
  }
127
129
  main().catch((e) => fail(e.message));
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { renderIcon, type RenderOptions, type RenderResult } from './render.js';
1
+ export { renderIcon, renderDefaultIcon, type RenderOptions, type RenderResult } from './render.js';
2
2
  export { CATEGORIES, classify, classifyMime, isCategory, type Category } from './categories.js';
3
3
  export { allExtensions, categoryFor, extToMime } from './mime.js';
4
4
  export { DEFAULT_PALETTE, createPalette, accentFor, type Palette } from './palette.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { renderIcon } from './render.js';
1
+ export { renderIcon, renderDefaultIcon } from './render.js';
2
2
  export { CATEGORIES, classify, classifyMime, isCategory } from './categories.js';
3
3
  export { allExtensions, categoryFor, extToMime } from './mime.js';
4
4
  export { DEFAULT_PALETTE, createPalette, accentFor } from './palette.js';
package/dist/png.js CHANGED
@@ -7,5 +7,6 @@ export async function renderPng(svg, size) {
7
7
  catch {
8
8
  throw new Error('PNG export needs the optional dependency @resvg/resvg-js (npm i @resvg/resvg-js).');
9
9
  }
10
- return new mod.Resvg(svg, { fitTo: { mode: 'width', value: size } }).render().asPng();
10
+ // Our icons contain no <text>, so skip loading system fonts (it dominates the render time).
11
+ return new mod.Resvg(svg, { fitTo: { mode: 'width', value: size }, font: { loadSystemFonts: false } }).render().asPng();
11
12
  }
package/dist/render.d.ts CHANGED
@@ -24,3 +24,8 @@ export interface RenderResult {
24
24
  accent: string;
25
25
  }
26
26
  export declare function renderIcon(extension: string, opts?: RenderOptions): RenderResult;
27
+ /**
28
+ * The fallback icon for extensions that have no icon of their own: the generic body without a label
29
+ * (an unknown extension cannot be named). Written as `default.svg` by `mimeforge --all`.
30
+ */
31
+ export declare function renderDefaultIcon(opts?: RenderOptions): RenderResult;
package/dist/render.js CHANGED
@@ -21,3 +21,10 @@ export function renderIcon(extension, opts = {}) {
21
21
  }
22
22
  return { svg: svg.replace(/>\s*\n\s*</g, '><').trim() + '\n', ext, category, accent };
23
23
  }
24
+ /**
25
+ * The fallback icon for extensions that have no icon of their own: the generic body without a label
26
+ * (an unknown extension cannot be named). Written as `default.svg` by `mimeforge --all`.
27
+ */
28
+ export function renderDefaultIcon(opts = {}) {
29
+ return renderIcon('default', { ...opts, category: 'generic', label: false });
30
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mimeforge",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Generate file-type icons as SVG from a file extension: 24 bundled bodies, extension labels, pluggable fonts, bodies and colors.",
5
5
  "author": {
6
6
  "name": "Levent Kurt",