├── .gitignore ├── .prettierignore ├── LICENSE.md ├── README.md ├── keymaps └── font-viewer.cson ├── lib ├── font-viewer-view.js ├── font-viewer.js └── main.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── screenshot.webp ├── spec.zip └── styles └── font-viewer.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json 3 | package-lock.json 4 | pnpm-lock.yaml 5 | changelog.md 6 | coverage 7 | build 8 | dist 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Eric Freese 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Font Viewer 2 | 3 | Preview font files in an editor in Atom. Support zooming via `CTRL+` `CTRL-`, and `CTRL0` (`⌘` on MacOS) 4 | 5 | Shows a preview of all mapped characters. Currently supports `.otf`, `.ttf`, and `.woff` 6 | 7 | ![Font Viewer](https://github.com/ericfreese/font-viewer/blob/master/screenshot.webp?raw=true) 8 | 9 | Based on [image-view](https://github.com/atom/image-view). Uses the [freetype2](https://www.npmjs.org/package/freetype2) module for font file parsing. 10 | 11 | https://atom.io/packages/font-viewer 12 | -------------------------------------------------------------------------------- /keymaps/font-viewer.cson: -------------------------------------------------------------------------------- 1 | '.platform-darwin .font-viewer': 2 | 'cmd-+': 'font-viewer:zoom-in' 3 | 'cmd-=': 'font-viewer:zoom-in' 4 | 'cmd--': 'font-viewer:zoom-out' 5 | 'cmd-_': 'font-viewer:zoom-out' 6 | 'cmd-0': 'font-viewer:reset-zoom' 7 | 8 | '.platform-win32 .font-viewer': 9 | 'ctrl-+': 'font-viewer:zoom-in' 10 | 'ctrl-=': 'font-viewer:zoom-in' 11 | 'ctrl--': 'font-viewer:zoom-out' 12 | 'ctrl-_': 'font-viewer:zoom-out' 13 | 'ctrl-0': 'font-viewer:reset-zoom' 14 | -------------------------------------------------------------------------------- /lib/font-viewer-view.js: -------------------------------------------------------------------------------- 1 | "use babel" 2 | import { each } from "underscore-plus" 3 | import { ScrollView } from "atom-space-pen-views-plus" 4 | import { Emitter, CompositeDisposable } from "atom" 5 | 6 | // View that renders a {FontViewer}. 7 | export default class FontViewerView extends ScrollView { 8 | static content() { 9 | this.div({ class: "font-viewer", tabindex: -1 }, () => { 10 | this.style({ outlet: "style" }) 11 | this.div({ class: "font-container", outlet: "container" }) 12 | }) 13 | } 14 | 15 | initialize(fontViewer) { 16 | this.fontViewer = fontViewer 17 | super.initialize(...arguments) 18 | this.emitter = new Emitter() 19 | } 20 | 21 | attached() { 22 | this.disposables = new CompositeDisposable() 23 | 24 | this.disposables.add( 25 | atom.commands.add(this.element, { 26 | "font-viewer:zoom-in": () => this.zoomIn(), 27 | "font-viewer:zoom-out": () => this.zoomOut(), 28 | "font-viewer:reset-zoom": () => this.resetZoom(), 29 | }) 30 | ) 31 | 32 | const { container } = this 33 | this.fontViewer.getAvailableCharacters((availableCharacters) => 34 | each(availableCharacters, (c) => { 35 | const cString = c.toString(16) 36 | container.append(`
&#x${cString};
U+${cString}
`) 37 | }) 38 | ) 39 | 40 | // load font in the view 41 | const fontViewerURI = this.fontViewer.getUri() 42 | this.style.append(` 43 | @font-face { 44 | font-family: "${fontViewerURI}"; 45 | src: url("${fontViewerURI}"); 46 | }`) 47 | this.container.css({ "font-family": `"${fontViewerURI}"` }) 48 | } 49 | 50 | detached() { 51 | this.disposables.dispose() 52 | } 53 | 54 | // Zooms the font preview out by 10%. 55 | zoomOut() { 56 | this.adjustSize(0.9) 57 | } 58 | 59 | // Zooms the font preview in by 10%. 60 | zoomIn() { 61 | this.adjustSize(1.1) 62 | } 63 | 64 | // Zooms the font preview to its normal size. 65 | resetZoom() { 66 | if (!this.isVisible()) { 67 | return 68 | } 69 | 70 | this.container.css({ "font-size": "" }) 71 | } 72 | 73 | // Adjust the size of the font preview by the given multiplying factor. 74 | // 75 | // factor - A {Number} to multiply against the current size. 76 | adjustSize(factor) { 77 | if (!this.isVisible()) { 78 | return 79 | } 80 | 81 | this.container.css({ "font-size": `${parseInt(this.container.css("font-size"), 10) * factor}px` }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/font-viewer.js: -------------------------------------------------------------------------------- 1 | "use babel" 2 | /* 3 | * decaffeinate suggestions: 4 | * DS102: Remove unnecessary code created because of implicit returns 5 | * DS206: Consider reworking classes to avoid initClass 6 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 7 | */ 8 | 9 | import path from "path" 10 | import fs from "fs-plus" 11 | import { File, CompositeDisposable } from "atom" 12 | import { NewMemoryFace } from "freetype2" 13 | import fileUrl from "file-url" 14 | 15 | // Editor model for a font file 16 | export default class FontViewer { 17 | static initClass() { 18 | atom.deserializers.add(this) 19 | } 20 | 21 | static deserialize({ filePath }) { 22 | if (fs.isFileSync(filePath)) { 23 | return new FontViewer(filePath) 24 | } else { 25 | console.warn(`Could not deserialize font viewer for path '${filePath}' because that file no longer exists`) 26 | } 27 | } 28 | 29 | constructor(filePath) { 30 | this.file = new File(filePath) 31 | this.subscriptions = new CompositeDisposable() 32 | } 33 | 34 | serialize() { 35 | return { filePath: this.getPath(), deserializer: this.constructor.name } 36 | } 37 | 38 | getViewClass() { 39 | return require("./font-viewer-view") 40 | } 41 | 42 | destroy() { 43 | this.subscriptions.dispose() 44 | } 45 | 46 | // Retrieves the filename of the open file. 47 | // 48 | // This is `'untitled'` if the file is new and not saved to the disk. 49 | // 50 | // Returns a {String}. 51 | getTitle() { 52 | const filePath = this.getPath() 53 | if (filePath) { 54 | return path.basename(filePath) 55 | } else { 56 | return "untitled" 57 | } 58 | } 59 | 60 | // Retrieves the URI of the font file. 61 | // 62 | // Returns a {String}. 63 | getUri() { 64 | return fileUrl(this.getPath()) 65 | } 66 | 67 | // Retrieves the absolute path to the font file. 68 | // 69 | // Returns a {String} path. 70 | getPath() { 71 | return this.file.getPath() 72 | } 73 | 74 | getAvailableCharacters(callback) { 75 | return fs.readFile(this.getPath(), function (err, buffer) { 76 | const chars = [] 77 | const face = NewMemoryFace(buffer, 0) 78 | 79 | let char = face.getFirstChar() 80 | while (char && char.glyphIndex !== 0) { 81 | chars.push(char.charCode) 82 | char = face.getNextChar(char.charCode) 83 | } 84 | 85 | return callback(chars) 86 | }) 87 | } 88 | 89 | // Compares two {FontViewer}s to determine equality. 90 | // 91 | // Equality is based on the condition that the two URIs are the same. 92 | // 93 | // Returns a {Boolean}. 94 | isEqual(other) { 95 | return other instanceof FontViewer && this.getUri() === other.getUri() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use babel" 2 | import path from "path" 3 | import { include } from "underscore-plus" 4 | import FontViewer from "./font-viewer" 5 | 6 | let openerDisposable 7 | 8 | export function activate() { 9 | FontViewer.initClass() 10 | 11 | // Files with these extensions will be opened as fonts 12 | const fontExtensions = [".otf", ".ttf", ".woff"] 13 | openerDisposable = atom.workspace.addOpener((uriToOpen) => { 14 | const uriExtension = path.extname(uriToOpen).toLowerCase() 15 | if (include(fontExtensions, uriExtension)) { 16 | return new FontViewer(uriToOpen) 17 | } 18 | }) 19 | } 20 | 21 | export function deactivate() { 22 | openerDisposable.dispose() 23 | } 24 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "font-viewer", 3 | "version": "1.0.5", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async": { 8 | "version": "1.5.2", 9 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 10 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 11 | }, 12 | "at-least-node": { 13 | "version": "1.0.0", 14 | "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", 15 | "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" 16 | }, 17 | "atom-space-pen-views-plus": { 18 | "version": "3.0.4", 19 | "resolved": "https://registry.npmjs.org/atom-space-pen-views-plus/-/atom-space-pen-views-plus-3.0.4.tgz", 20 | "integrity": "sha512-PfCBrD6RUN359P8Do3D3m2d1Ws2DyR7Jl1Ym97R2Gr9liM+5CYU5AvopJNL9m8pZqOBpu5ePcHjSrC/V1cL8oA==", 21 | "requires": { 22 | "fs-extra": "^9.0.1", 23 | "fuzzaldrin": "^2.1.0", 24 | "space-pen-plus": "^6.0.3" 25 | } 26 | }, 27 | "balanced-match": { 28 | "version": "1.0.0", 29 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 30 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 31 | }, 32 | "bindings": { 33 | "version": "1.5.0", 34 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 35 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 36 | "requires": { 37 | "file-uri-to-path": "1.0.0" 38 | } 39 | }, 40 | "brace-expansion": { 41 | "version": "1.1.11", 42 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 43 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 44 | "requires": { 45 | "balanced-match": "^1.0.0", 46 | "concat-map": "0.0.1" 47 | } 48 | }, 49 | "concat-map": { 50 | "version": "0.0.1", 51 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 52 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 53 | }, 54 | "file-uri-to-path": { 55 | "version": "1.0.0", 56 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 57 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 58 | }, 59 | "file-url": { 60 | "version": "3.0.0", 61 | "resolved": "https://registry.npmjs.org/file-url/-/file-url-3.0.0.tgz", 62 | "integrity": "sha512-g872QGsHexznxkIAdK8UiZRe7SkE6kvylShU4Nsj8NvfvZag7S0QuQ4IgvPDkk75HxgjIVDwycFTDAgIiO4nDA==" 63 | }, 64 | "freetype2": { 65 | "version": "1.0.6", 66 | "resolved": "https://registry.npmjs.org/freetype2/-/freetype2-1.0.6.tgz", 67 | "integrity": "sha512-jfVNUD9fecnST+bNIIfdtsAwy9aQwMGYgkHJVaxDg/P0mgiZCJauASiBAdRUQFDeh9flbG8W/ogyCe51VYCchA==", 68 | "requires": { 69 | "bindings": "^1.5.0", 70 | "node-addon-api": "^3.0.2", 71 | "node-gyp-build": "^4.2.3" 72 | } 73 | }, 74 | "fs-extra": { 75 | "version": "9.0.1", 76 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", 77 | "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", 78 | "requires": { 79 | "at-least-node": "^1.0.0", 80 | "graceful-fs": "^4.2.0", 81 | "jsonfile": "^6.0.1", 82 | "universalify": "^1.0.0" 83 | } 84 | }, 85 | "fs-plus": { 86 | "version": "3.1.1", 87 | "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz", 88 | "integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==", 89 | "requires": { 90 | "async": "^1.5.2", 91 | "mkdirp": "^0.5.1", 92 | "rimraf": "^2.5.2", 93 | "underscore-plus": "1.x" 94 | } 95 | }, 96 | "fs.realpath": { 97 | "version": "1.0.0", 98 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 99 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 100 | }, 101 | "fuzzaldrin": { 102 | "version": "2.1.0", 103 | "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", 104 | "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=" 105 | }, 106 | "glob": { 107 | "version": "7.1.6", 108 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 109 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 110 | "requires": { 111 | "fs.realpath": "^1.0.0", 112 | "inflight": "^1.0.4", 113 | "inherits": "2", 114 | "minimatch": "^3.0.4", 115 | "once": "^1.3.0", 116 | "path-is-absolute": "^1.0.0" 117 | } 118 | }, 119 | "graceful-fs": { 120 | "version": "4.2.4", 121 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 122 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" 123 | }, 124 | "inflight": { 125 | "version": "1.0.6", 126 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 127 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 128 | "requires": { 129 | "once": "^1.3.0", 130 | "wrappy": "1" 131 | } 132 | }, 133 | "inherits": { 134 | "version": "2.0.4", 135 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 136 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 137 | }, 138 | "jquery": { 139 | "version": "3.5.1", 140 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", 141 | "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==" 142 | }, 143 | "jsonfile": { 144 | "version": "6.0.1", 145 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", 146 | "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", 147 | "requires": { 148 | "graceful-fs": "^4.1.6", 149 | "universalify": "^1.0.0" 150 | } 151 | }, 152 | "minimatch": { 153 | "version": "3.0.4", 154 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 155 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 156 | "requires": { 157 | "brace-expansion": "^1.1.7" 158 | } 159 | }, 160 | "minimist": { 161 | "version": "1.2.5", 162 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 163 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 164 | }, 165 | "mkdirp": { 166 | "version": "0.5.5", 167 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 168 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 169 | "requires": { 170 | "minimist": "^1.2.5" 171 | } 172 | }, 173 | "node-addon-api": { 174 | "version": "3.0.2", 175 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.2.tgz", 176 | "integrity": "sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==" 177 | }, 178 | "node-gyp-build": { 179 | "version": "4.2.3", 180 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", 181 | "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" 182 | }, 183 | "once": { 184 | "version": "1.4.0", 185 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 186 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 187 | "requires": { 188 | "wrappy": "1" 189 | } 190 | }, 191 | "path-is-absolute": { 192 | "version": "1.0.1", 193 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 194 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 195 | }, 196 | "prettier": { 197 | "version": "2.2.1", 198 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", 199 | "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", 200 | "dev": true 201 | }, 202 | "rimraf": { 203 | "version": "2.7.1", 204 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 205 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 206 | "requires": { 207 | "glob": "^7.1.3" 208 | } 209 | }, 210 | "space-pen-plus": { 211 | "version": "6.0.3", 212 | "resolved": "https://registry.npmjs.org/space-pen-plus/-/space-pen-plus-6.0.3.tgz", 213 | "integrity": "sha512-iqPZAQYP3xPDGxT6MxIwm4GQks91p2H4QeUUcjjzPyr2FEmpaqVLX6cDwjzf8HWMQ0r9fa3hSB9CzMODXVBe6g==", 214 | "requires": { 215 | "jquery": "^3.5.1" 216 | } 217 | }, 218 | "underscore": { 219 | "version": "1.10.2", 220 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", 221 | "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==" 222 | }, 223 | "underscore-plus": { 224 | "version": "1.8.1", 225 | "resolved": "https://registry.npmjs.org/@aminya/underscore-plus/-/underscore-plus-1.8.1.tgz", 226 | "integrity": "sha512-MiGCwfNr6iYou5jaEICXGuQJBACyHt/RiCVjib136cjNmvrirXe4WIf7Px4YW4d2RPYxEHKiXV617CL26aa4tA==", 227 | "requires": { 228 | "underscore": "^1.10.2" 229 | } 230 | }, 231 | "universalify": { 232 | "version": "1.0.0", 233 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", 234 | "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" 235 | }, 236 | "wrappy": { 237 | "version": "1.0.2", 238 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 239 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "font-viewer", 3 | "main": "./lib/main", 4 | "version": "1.0.5", 5 | "description": "Preview font files in an editor in Atom.", 6 | "repository": "https://github.com/ericfreese/font-viewer", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": "*" 10 | }, 11 | "scripts": { 12 | "format": "prettier --write ." 13 | }, 14 | "dependencies": { 15 | "atom-space-pen-views-plus": "^3.0.4", 16 | "file-url": "^3.0.0", 17 | "freetype2": "1.0.6", 18 | "fs-plus": "~3.1.1", 19 | "underscore-plus": "^1.7" 20 | }, 21 | "activationHooks": [ 22 | "core:loaded-shell-environment" 23 | ], 24 | "devDependencies": { 25 | "prettier": "^2.2.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // Add to .prettierignore to ignore files and folders 2 | 3 | // This configuration all the formats including typescript, javascript, json, yaml, markdown 4 | module.exports = { 5 | tabWidth: 2, 6 | printWidth: 120, 7 | semi: false, 8 | singleQuote: false, 9 | overrides: [ 10 | { 11 | files: "{*.json}", 12 | options: { 13 | parser: "json", 14 | trailingComma: "es5", 15 | }, 16 | }, 17 | { 18 | files: "{*.md}", 19 | options: { 20 | parser: "markdown", 21 | proseWrap: "preserve", 22 | }, 23 | }, 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericfreese/font-viewer/f8f6042ef757221839cebb7aa571f81a5c620148/screenshot.webp -------------------------------------------------------------------------------- /spec.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericfreese/font-viewer/f8f6042ef757221839cebb7aa571f81a5c620148/spec.zip -------------------------------------------------------------------------------- /styles/font-viewer.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | // css containment to improve performance 4 | @contain_all: layout size paint style; 5 | @contain_but_size: layout paint style; 6 | 7 | .font-viewer { 8 | contain: @contain_all; 9 | .font-container { 10 | contain: @contain_all; 11 | padding: 0.2em; 12 | height: 100%; 13 | overflow: auto; 14 | font-size: 4em; 15 | color: @text-color-highlight; 16 | 17 | .font-glyph { 18 | contain: @contain_but_size; 19 | position: relative; 20 | display: inline-block; 21 | vertical-align: middle; 22 | border: 1px solid transparent; 23 | margin: 0.1em; 24 | min-width: 1.5em; 25 | text-align: center; 26 | white-space: pre; 27 | 28 | .font-glyph-id { 29 | contain: @contain_but_size; 30 | display: none; 31 | position: absolute; 32 | right: 0; 33 | bottom: 0; 34 | background: @text-color-highlight; 35 | color: @base-background-color; 36 | font-size: 0.2em; 37 | padding: 0 0.2em 0 0.2em; 38 | font-family: @font-family; 39 | } 40 | 41 | &:hover { 42 | border-color: @text-color-highlight; 43 | 44 | .font-glyph-id { 45 | contain: @contain_but_size; 46 | display: block; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | --------------------------------------------------------------------------------