tfrere HF Staff Cursor commited on
Commit
fc2a05d
·
1 Parent(s): 4d4a785

chore: clean up repo for open source release

Browse files

- Replace CRA boilerplate README with proper project documentation
- Add MIT LICENSE
- Clean package.json (name, description, author, license, repository)
- Remove CRA boilerplate files (logo.svg, App.test.js, setupTests.js, reportWebVitals.js)
- Remove dev artifacts (font-names-results/, scripts/, yarn.lock)
- Clean up index.html title and meta description
- Simplify .gitignore

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (10) hide show
  1. .gitignore +11 -14
  2. LICENSE +21 -0
  3. README.md +129 -0
  4. package.json +9 -3
  5. public/index.html +2 -29
  6. src/App.test.js +0 -8
  7. src/index.js +0 -6
  8. src/logo.svg +0 -1
  9. src/reportWebVitals.js +0 -13
  10. src/setupTests.js +0 -5
.gitignore CHANGED
@@ -1,34 +1,31 @@
1
- # dependencies
2
  /node_modules
3
  src/typography/new-pipe/node_modules
4
- src/typography/generated/
5
- /.pnp
6
- .pnp.js
7
-
8
- # testing
9
- /coverage
10
 
11
- # production
12
  /build
 
13
 
14
- # misc
15
- .DS_Store
16
  .env.local
17
  .env.development.local
18
  .env.test.local
19
  .env.production.local
20
 
 
 
 
 
21
  npm-debug.log*
22
  yarn-debug.log*
23
  yarn-error.log*
24
 
 
25
  src/typography/new-pipe/input
26
  src/typography/new-pipe/output
27
  src/typography/new-pipe/public
 
28
 
29
- # Large generated data (not needed for app)
30
  public/data/embeddings.json
31
  public/data/font-map.json
32
-
33
- # Temp docs
34
- TEST_LIVE_UMAP.md
 
1
+ # Dependencies
2
  /node_modules
3
  src/typography/new-pipe/node_modules
 
 
 
 
 
 
4
 
5
+ # Build output
6
  /build
7
+ /coverage
8
 
9
+ # Environment
 
10
  .env.local
11
  .env.development.local
12
  .env.test.local
13
  .env.production.local
14
 
15
+ # OS
16
+ .DS_Store
17
+
18
+ # Logs
19
  npm-debug.log*
20
  yarn-debug.log*
21
  yarn-error.log*
22
 
23
+ # Pipeline working directories
24
  src/typography/new-pipe/input
25
  src/typography/new-pipe/output
26
  src/typography/new-pipe/public
27
+ src/typography/generated/
28
 
29
+ # Large generated data (regenerate with pipeline)
30
  public/data/embeddings.json
31
  public/data/font-map.json
 
 
 
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Thibaud Frere
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -9,3 +9,132 @@ pinned: false
9
  app_build_command: "CI=false npm run build"
10
  app_file: "build/index.html"
11
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  app_build_command: "CI=false npm run build"
10
  app_file: "build/index.html"
11
  ---
12
+
13
+ # FontMap
14
+
15
+ An interactive map of 1,100+ Google Fonts organized by visual similarity, using [FontCLIP](https://github.com/kinit-sk/FontCLIP) embeddings and UMAP dimensionality reduction.
16
+
17
+ Inspired by [IDEO's Font Map](https://medium.com/ideo-stories/organizing-the-world-of-fonts-with-ai-7d9e49ff2b25) (2017, Kevin Ho) — rebuilt from scratch with modern ML, fully open source, and documented.
18
+
19
+ **[Live demo](https://huggingface.co/spaces/tfrere/fontmap)**
20
+
21
+ ![FontMap screenshot](https://huggingface.co/spaces/tfrere/fontmap/resolve/main/public/screenshot.png)
22
+
23
+ ## Features
24
+
25
+ - **Visual similarity map** — fonts that look alike are physically close together
26
+ - **1,192 Google Fonts** — full catalog, family variants merged
27
+ - **Click any font** — see details + 5 nearest visual neighbors
28
+ - **Filter by category** — serif, sans-serif, display, handwriting, monospace
29
+ - **Category colors** — toggle color coding to see how clusters map to official categories
30
+ - **Search** — find any font by name
31
+ - **Zoom & pan** — explore the full map with smooth D3.js navigation
32
+
33
+ ## How it works
34
+
35
+ ```
36
+ Font images ──→ FontCLIP (512D) ──→ PCA (50D) ──→ UMAP (2D) ──→ Interactive map
37
+
38
+ k-NN (similar fonts)
39
+ ```
40
+
41
+ 1. **Render** — each font is rendered as a 224×224 composite glyph image
42
+ 2. **Embed** — [FontCLIP](https://github.com/kinit-sk/FontCLIP) (CLIP ViT-B/32 fine-tuned for typography) encodes each image into a 512-dimensional vector
43
+ 3. **Reduce** — PCA compresses to 50D, then UMAP with spectral initialization projects to 2D (n_neighbors=12, min_dist=1.0)
44
+ 4. **Merge** — font family variants (Regular, Bold, Italic…) are fused into a single representative point
45
+ 5. **Neighbors** — k-NN is computed in the original 512D space for "similar fonts" recommendations
46
+ 6. **Visualize** — React + D3.js renders all glyphs from a single SVG sprite (zero individual network requests)
47
+
48
+ ## Getting started
49
+
50
+ ### Run locally
51
+
52
+ ```bash
53
+ git clone https://github.com/tfrere/fontmap.git
54
+ cd fontmap
55
+ npm install
56
+ npm start
57
+ ```
58
+
59
+ Open [http://localhost:3000](http://localhost:3000).
60
+
61
+ ### Build for production
62
+
63
+ ```bash
64
+ npm run build
65
+ ```
66
+
67
+ ### Deploy to Hugging Face Spaces
68
+
69
+ The project is configured for [HF Spaces static SDK](https://huggingface.co/docs/hub/spaces-sdks-static) with a build step:
70
+
71
+ ```yaml
72
+ sdk: static
73
+ app_build_command: "CI=false npm run build"
74
+ app_file: "build/index.html"
75
+ ```
76
+
77
+ ## Regenerating the map
78
+
79
+ The embedding pipeline lives in `src/typography/new-pipe/`. See its [README](src/typography/new-pipe/README.md) for full instructions.
80
+
81
+ **Quick summary:**
82
+
83
+ ```bash
84
+ # 1. Generate FontCLIP embeddings (requires Python + GPU, ~1h one-time)
85
+ cd src/typography/new-pipe/python-pipeline
86
+ python run_fontclip.py
87
+
88
+ # 2. Test different UMAP configurations
89
+ cd ..
90
+ npm run batch-umap
91
+ npm run copy-to-debug
92
+ # Compare visually at http://localhost:3000/#/debug-umap
93
+
94
+ # 3. Deploy chosen config to production
95
+ npm run deploy fontclip-spectral
96
+ ```
97
+
98
+ ## Project structure
99
+
100
+ ```
101
+ fontmap/
102
+ ├── public/
103
+ │ ├── data/
104
+ │ │ ├── char/ # Individual glyph SVGs (~1,200 files)
105
+ │ │ ├── sentences/ # Sentence preview SVGs
106
+ │ │ ├── font-sprite.svg # All glyphs in a single sprite (3 MB)
107
+ │ │ └── typography_data.json # Font positions + metadata
108
+ │ └── debug-umap/ # Pre-computed UMAP configs for comparison
109
+ ├── src/
110
+ │ ├── components/
111
+ │ │ ├── FontMap/ # Main map component (production)
112
+ │ │ ├── DebugUMAP/ # UMAP comparison tool (development)
113
+ │ │ └── FontMapV2/ # Experimental canvas renderer
114
+ │ ├── hooks/ # Shared hooks (data loading, dark mode)
115
+ │ ├── store/ # Zustand state management
116
+ │ └── typography/new-pipe/ # Embedding + UMAP generation pipeline
117
+ │ └── python-pipeline/ # FontCLIP + PCA + UMAP (Python)
118
+ └── package.json
119
+ ```
120
+
121
+ ## Tech stack
122
+
123
+ | Layer | Tech |
124
+ |-------|------|
125
+ | Frontend | React 19, D3.js v7, Zustand |
126
+ | Rendering | SVG sprite + D3 zoom/pan |
127
+ | Embeddings | FontCLIP (CLIP ViT-B/32 fine-tuned for typography) |
128
+ | Dim. reduction | PCA + UMAP (spectral init) via `umap-learn` |
129
+ | Hosting | Hugging Face Spaces (static SDK) |
130
+
131
+ ## Credits
132
+
133
+ - **[FontCLIP](https://github.com/kinit-sk/FontCLIP)** — Typography-aware CLIP model by KInIT
134
+ - **[IDEO Font Map](https://medium.com/ideo-stories/organizing-the-world-of-fonts-with-ai-7d9e49ff2b25)** — Original inspiration by Kevin Ho (2017)
135
+ - **[Google Fonts](https://fonts.google.com)** — Font catalog
136
+ - **[UMAP](https://umap-learn.readthedocs.io/)** — Dimensionality reduction by Leland McInnes
137
+
138
+ ## License
139
+
140
+ MIT
package.json CHANGED
@@ -1,8 +1,14 @@
1
  {
2
- "name": "react-template",
3
- "version": "0.1.0",
4
- "private": true,
 
 
5
  "homepage": ".",
 
 
 
 
6
  "dependencies": {
7
  "@testing-library/dom": "^10.4.0",
8
  "@testing-library/jest-dom": "^6.6.3",
 
1
  {
2
+ "name": "fontmap",
3
+ "version": "1.0.0",
4
+ "description": "Interactive map of 1,100+ Google Fonts organized by visual similarity using FontCLIP + UMAP",
5
+ "author": "Thibaud Frere",
6
+ "license": "MIT",
7
  "homepage": ".",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/tfrere/fontmap"
11
+ },
12
  "dependencies": {
13
  "@testing-library/dom": "^10.4.0",
14
  "@testing-library/jest-dom": "^6.6.3",
public/index.html CHANGED
@@ -5,43 +5,16 @@
5
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
  <meta name="theme-color" content="#000000" />
8
- <meta
9
- name="description"
10
- content="Web site created using create-react-app"
11
- />
12
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13
- <!--
14
- manifest.json provides metadata used when your web app is installed on a
15
- user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16
- -->
17
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18
- <!-- Google Fonts -->
19
  <link rel="preconnect" href="https://fonts.googleapis.com">
20
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
21
  <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap" rel="stylesheet">
22
- <!--
23
- Notice the use of %PUBLIC_URL% in the tags above.
24
- It will be replaced with the URL of the `public` folder during the build.
25
- Only files inside the `public` folder can be referenced from the HTML.
26
-
27
- Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
28
- work correctly both with client-side routing and a non-root public URL.
29
- Learn how to configure a non-root public URL by running `npm run build`.
30
- -->
31
- <title>React App</title>
32
  </head>
33
  <body>
34
  <noscript>You need to enable JavaScript to run this app.</noscript>
35
  <div id="root"></div>
36
- <!--
37
- This HTML file is a template.
38
- If you open it directly in the browser, you will see an empty page.
39
-
40
- You can add webfonts, meta tags, or analytics to this file.
41
- The build step will place the bundled scripts into the <body> tag.
42
-
43
- To begin the development, run `npm start` or `yarn start`.
44
- To create a production bundle, use `npm run build` or `yarn build`.
45
- -->
46
  </body>
47
  </html>
 
5
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
  <meta name="theme-color" content="#000000" />
8
+ <meta name="description" content="Interactive map of 1,100+ Google Fonts organized by visual similarity using FontCLIP + UMAP" />
 
 
 
9
  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
 
 
 
 
10
  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
 
11
  <link rel="preconnect" href="https://fonts.googleapis.com">
12
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
  <link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap" rel="stylesheet">
14
+ <title>FontMap</title>
 
 
 
 
 
 
 
 
 
15
  </head>
16
  <body>
17
  <noscript>You need to enable JavaScript to run this app.</noscript>
18
  <div id="root"></div>
 
 
 
 
 
 
 
 
 
 
19
  </body>
20
  </html>
src/App.test.js DELETED
@@ -1,8 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import App from './App';
3
-
4
- test('renders learn react link', () => {
5
- render(<App />);
6
- const linkElement = screen.getByText(/learn react/i);
7
- expect(linkElement).toBeInTheDocument();
8
- });
 
 
 
 
 
 
 
 
 
src/index.js CHANGED
@@ -2,7 +2,6 @@ import React from 'react';
2
  import ReactDOM from 'react-dom/client';
3
  import './index.css';
4
  import App from './App';
5
- import reportWebVitals from './reportWebVitals';
6
 
7
  const root = ReactDOM.createRoot(document.getElementById('root'));
8
  root.render(
@@ -10,8 +9,3 @@ root.render(
10
  <App />
11
  </React.StrictMode>
12
  );
13
-
14
- // If you want to start measuring performance in your app, pass a function
15
- // to log results (for example: reportWebVitals(console.log))
16
- // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17
- reportWebVitals();
 
2
  import ReactDOM from 'react-dom/client';
3
  import './index.css';
4
  import App from './App';
 
5
 
6
  const root = ReactDOM.createRoot(document.getElementById('root'));
7
  root.render(
 
9
  <App />
10
  </React.StrictMode>
11
  );
 
 
 
 
 
src/logo.svg DELETED
src/reportWebVitals.js DELETED
@@ -1,13 +0,0 @@
1
- const reportWebVitals = onPerfEntry => {
2
- if (onPerfEntry && onPerfEntry instanceof Function) {
3
- import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4
- getCLS(onPerfEntry);
5
- getFID(onPerfEntry);
6
- getFCP(onPerfEntry);
7
- getLCP(onPerfEntry);
8
- getTTFB(onPerfEntry);
9
- });
10
- }
11
- };
12
-
13
- export default reportWebVitals;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/setupTests.js DELETED
@@ -1,5 +0,0 @@
1
- // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
- // allows you to do things like:
3
- // expect(element).toHaveTextContent(/react/i)
4
- // learn more: https://github.com/testing-library/jest-dom
5
- import '@testing-library/jest-dom';