├── .compilerc ├── .gitignore ├── .nvmrc ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── CNAME ├── Info.plist ├── README.md ├── fixtures └── foo.ttf ├── lib ├── __tests__ │ └── font.test.ts ├── css.ts ├── font-collection.ts ├── font.ts ├── ttf.ts ├── woff.ts └── woff2.ts ├── package-lock.json ├── package.json ├── src ├── app.tsx ├── assets │ ├── build │ │ ├── dmg-background-combined.tiff │ │ ├── dmg-background.tiff │ │ └── dmg-background@2x.tiff │ └── system-icons │ │ ├── icon.icns │ │ ├── icon.png │ │ ├── icon_64x64.png │ │ └── icon_old.icns ├── checkupdates.js ├── index.html ├── index.ts ├── process-fonts.ts └── styles │ └── app.css ├── tsconfig.json └── tslint.json /.compilerc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "development": { 4 | "text/typescript": { 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "declaration": true, 8 | "noImplicitAny": true, 9 | "noImplicitReturns": true, 10 | "suppressImplicitAnyIndexErrors": true, 11 | "strictNullChecks": true, 12 | "noUnusedLocals": true, 13 | "noImplicitThis": true, 14 | "noUnusedParameters": true, 15 | "inlineSourceMap": true, 16 | "inlineSources": true, 17 | "importHelpers": true, 18 | "noEmitHelpers": true, 19 | "experimentalDecorators": true, 20 | "target": "es2015", 21 | "module": "commonjs", 22 | "jsx": "react" 23 | } 24 | }, 25 | "production": { 26 | "text/typescript": { 27 | "removeComments": false, 28 | "preserveConstEnums": true, 29 | "declaration": true, 30 | "noImplicitAny": true, 31 | "noImplicitReturns": true, 32 | "suppressImplicitAnyIndexErrors": true, 33 | "strictNullChecks": true, 34 | "noUnusedLocals": true, 35 | "noImplicitThis": true, 36 | "noUnusedParameters": true, 37 | "sourceMap": false, 38 | "importHelpers": true, 39 | "noEmitHelpers": true, 40 | "experimentalDecorators": true, 41 | "target": "es2015", 42 | "jsx": "react" 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /out 3 | /build 4 | *.log 5 | .DS_Store -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.6.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | 5 | install: 6 | - npm install 7 | 8 | script: 9 | - npm run lint 10 | - npm run test 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Main Process", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron", 10 | "windows": { 11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd" 12 | }, 13 | "args" : ["."] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.format.enable": false 3 | } 4 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | www.fontplop.com 2 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDocumentTypes 6 | 7 | 8 | CFBundleTypeExtensions 9 | 10 | ttf 11 | woff 12 | eot 13 | 14 | CFBundleTypeIconFile 15 | 16 | CFBundleTypeName 17 | Font files 18 | CFBundleTypeRole 19 | Editor 20 | LSHandlerRank 21 | Alternate 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | fontplop logo 4 |

5 | 6 | --- 7 | 8 | [![Backers on Open Collective](https://opencollective.com/fontplop/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/fontplop/sponsors/badge.svg)](#sponsors) [![build status](https://img.shields.io/travis/reactjs/redux/master.svg?style=flat-square)](https://travis-ci.org/matthewgonzalez/fontplop) 9 | 10 | FontPlop is an OSX/macOS application which takes `ttf` and `otf` files and outputs a webfont bundle: `woff2`, `woff`, `ttf`/`otf`. It is the simpler, faster, free successor to [FontPrep](http://www.fontprep.com), which is no longer maintained. 11 | 12 | ### Demo 13 | 14 | fontplop demo 15 | 16 | 17 | ### Installation 18 | 19 | `brew cask install fontplop` 20 | 21 | 22 | ### Testing 23 | 24 | Run lint (tslint) and tests (Jest) like so: 25 | 26 | ```sh 27 | npm run lint 28 | npm run test 29 | ``` 30 | 31 | ### Generated CSS (recommended) 32 | 33 | _Stolen directly from the Typekit site 💂💰_ 34 | 35 | ```css 36 | 37 | @font-face { 38 | font-family:"My Font"; 39 | src:url("my-font.woff2") format("woff2"),url("my-font.woff") format("woff"),url("my-font.otf") format("opentype"); 40 | font-style:normal;font-weight:400; 41 | } 42 | ``` 43 | 44 | **Why not EOT or SVG?** Well, they're essentially no longer needed and/or dying off. See 45 | [this issue](https://github.com/matthewgonzalez/fontplop/issues/17). If you _really_ need EOT/SVG 46 | fonts, you can download version [1.1.0](https://github.com/matthewgonzalez/fontplop/releases/tag/v1.1.0) 47 | where those formats were last supported. 48 | 49 | ### Donations 50 | 51 | Help us keep FontPlop free and open source by making a donation. Thanks – it helps us continue 52 | to build awesome stuff. 53 | 54 | Via OpenCollective: https://opencollective.com/fontplop 55 | 56 | Via Bitcoin: `182JQcPACPh3bf6iQE73KB4Dvv5na6zep4` 57 | 58 | 59 | ### Building fontplop.com 60 | 61 | fontplop.com is hosted on Github pages. Publish changes like so: 62 | 63 | 1. Checkout `site` branch, which is a simple `create-react-app` app 64 | 2. Make changes 65 | 3. `npm run deploy` to publish to github 66 | 67 | 68 | ### Generating Installation Background 69 | 70 | To accomodate for retina/non-retina screens, a multipage `TIFF` should be generated from two files. 71 | 72 | ```$ cd src/assets/build``` 73 | 74 | ```$ tiffutil -catnosizecheck dmg-background.tiff dmg-background@2x.tiff -out dmg-background-combined.tiff``` 75 | 76 | ### Credits 77 | 78 | #### Contributors 79 | 80 | This project exists thanks to all the people who contribute. 81 | 82 | 83 | 84 | #### Backers 85 | 86 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/fontplop#backer)] 87 | 88 | 89 | 90 | 91 | #### Sponsors 92 | 93 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/fontplop#sponsor)] 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | ### License 109 | 110 | MIT 111 | -------------------------------------------------------------------------------- /fixtures/foo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/fixtures/foo.ttf -------------------------------------------------------------------------------- /lib/__tests__/font.test.ts: -------------------------------------------------------------------------------- 1 | import { FontCollection } from '../font-collection' 2 | import { WOFF2 } from '../woff2' 3 | import * as path from 'path' 4 | import * as fs from 'fs' 5 | 6 | const fontPath = path.resolve(__dirname, '../../fixtures/foo.ttf') 7 | const fc = new FontCollection(fontPath) 8 | 9 | it('should create an output path', () => { 10 | expect(fs.existsSync(fc.outputPath)).toBeTruthy() 11 | fc.cleanup() 12 | }) 13 | 14 | it('should create an output path', () => { 15 | expect(fc.isTTF).toBeTruthy() 16 | expect(fc.isOTF).not.toBeTruthy() 17 | }) 18 | 19 | it('should have an extension', () => { 20 | const svg = new WOFF2(fontPath) 21 | expect(svg.ext).toBe('woff2') 22 | }) 23 | 24 | it('should have a human font name', () => { 25 | expect(fc.fontNameHuman).not.toBeUndefined() 26 | expect(fc.fontNameHuman).not.toEqual(null) 27 | fc.cleanup() 28 | }) 29 | -------------------------------------------------------------------------------- /lib/css.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Font 3 | } from './font' 4 | import * as fs from 'fs' 5 | 6 | export class CSS extends Font { 7 | 8 | export () { 9 | const nameWithoutExt = this.nameWithoutExt 10 | const fontNameHuman = this.fontNameHuman 11 | 12 | const cssOutput = ` 13 | @font-face { 14 | font-family:"${fontNameHuman}"; 15 | src:url("${nameWithoutExt}.woff2") format("woff2"),url("${nameWithoutExt}.woff") format("woff"),url("${nameWithoutExt}.ttf") format("truetype"); 16 | font-style:normal;font-weight:400; 17 | } 18 | ` 19 | fs.writeFileSync(this.outFile, cssOutput) 20 | } 21 | 22 | get ext () { 23 | return 'css' 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /lib/font-collection.ts: -------------------------------------------------------------------------------- 1 | import { Font } from './font' 2 | import { TTF } from './ttf' 3 | import { WOFF } from './woff' 4 | import { WOFF2 } from './woff2' 5 | import { CSS } from './css' 6 | 7 | import * as rimraf from 'rimraf' 8 | 9 | export class FontCollection extends Font { 10 | 11 | paths: Array 12 | 13 | export () { 14 | const path = this.ttfPath 15 | 16 | if (this.isOTF) { 17 | this.createOrphanTTF() 18 | this.copySelf() 19 | } 20 | 21 | new TTF(path).export() 22 | new WOFF(path).export() 23 | new WOFF2(path).export() 24 | new CSS(path).export() 25 | 26 | this.cleanupOrphansIfNecessary() 27 | } 28 | 29 | cleanup () { 30 | rimraf.sync(this.outputPath) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /lib/font.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as replaceExt from 'replace-ext' 3 | import * as FontEditorCore from 'fonteditor-core' 4 | 5 | import * as fs from 'fs' 6 | import * as rimraf from 'rimraf' 7 | import { execSync } from 'child_process' 8 | 9 | export class Font { 10 | 11 | filePath: string 12 | hasOrphanTTF: boolean 13 | 14 | constructor (filePath: string) { 15 | this.filePath = filePath 16 | this.createOutputPath() 17 | } 18 | 19 | createOutputPath () { 20 | if (!fs.existsSync(this.outputPath)) { 21 | fs.mkdirSync(this.outputPath) 22 | } 23 | } 24 | 25 | readFont () { 26 | const inBuffer = fs.readFileSync(this.ttfPath) 27 | return FontEditorCore.Font.create(inBuffer, { 28 | type: 'ttf' 29 | }) 30 | } 31 | 32 | export () { 33 | 34 | const font = this.readFont() 35 | 36 | const outBuffer = font.write({ 37 | type: this.ext, 38 | hinting: true 39 | }) 40 | 41 | fs.writeFileSync(this.outFile, outBuffer) 42 | } 43 | 44 | get inputExt (): string { 45 | return path.extname(this.filePath).replace('.', '') 46 | } 47 | 48 | get ext () { 49 | return 'ttf' 50 | } 51 | 52 | get outFile () { 53 | return `${this.outputPath}/${this.nameWithoutExt}.${this.ext}` 54 | } 55 | 56 | get outputPath () { 57 | const output = path.resolve(this.dir, `${this.nameWithoutExt}-export`) 58 | return output 59 | } 60 | 61 | get dir () { 62 | return path.dirname(this.filePath) 63 | } 64 | 65 | get basename () { 66 | return path.basename(this.filePath) 67 | } 68 | 69 | get nameWithoutExt () { 70 | return path.parse(this.basename).name 71 | } 72 | 73 | get fontNameHuman () { 74 | return this.readFont().data.name.fullName || this.nameWithoutExt 75 | } 76 | 77 | get isTTF () { 78 | return path.parse(this.filePath).ext === '.ttf' 79 | } 80 | 81 | get isOTF () { 82 | return path.parse(this.filePath).ext === '.otf' 83 | } 84 | 85 | hasTTF (): boolean { 86 | return fs.existsSync(this.ttfPath) 87 | } 88 | 89 | get ttfPath () { 90 | return replaceExt(this.filePath, '.ttf') 91 | } 92 | 93 | copySelf () { 94 | this.createOutputPath() 95 | execSync(` 96 | cp "${this.filePath}" "${this.outputPath}" 97 | `) 98 | } 99 | 100 | createOrphanTTF () { 101 | const inBuffer = fs.readFileSync(this.filePath) 102 | 103 | const font = FontEditorCore.Font.create(inBuffer, { 104 | type: this.inputExt 105 | }) 106 | 107 | const outBuffer = font.write({ 108 | type: this.ext, 109 | hinting: true 110 | }) 111 | 112 | this.hasOrphanTTF = true 113 | fs.writeFileSync(this.ttfPath, outBuffer) 114 | } 115 | 116 | cleanupOrphansIfNecessary () { 117 | if (this.hasOrphanTTF) rimraf.sync(this.ttfPath) 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /lib/ttf.ts: -------------------------------------------------------------------------------- 1 | import { Font } from './font' 2 | 3 | export class TTF extends Font { 4 | 5 | get ext () { 6 | return 'ttf' 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /lib/woff.ts: -------------------------------------------------------------------------------- 1 | import { Font } from './font' 2 | 3 | export class WOFF extends Font { 4 | 5 | get ext () { 6 | return 'woff' 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /lib/woff2.ts: -------------------------------------------------------------------------------- 1 | import { Font } from './font' 2 | import * as ttf2woff2 from 'ttf2woff2' 3 | import * as fs from 'fs' 4 | 5 | export class WOFF2 extends Font { 6 | 7 | get ext () { 8 | return 'woff2' 9 | } 10 | 11 | export () { 12 | const input = fs.readFileSync(this.ttfPath) 13 | fs.writeFileSync(this.outFile, ttf2woff2(input)) 14 | return this.outFile 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fontplop", 3 | "productName": "fontplop", 4 | "version": "1.4.0", 5 | "description": "Drag'n'drop font converter.", 6 | "main": "src/index.ts", 7 | "scripts": { 8 | "start": "electron-forge start", 9 | "package": "electron-forge package", 10 | "make": "electron-forge make", 11 | "lint": "tslint --project tsconfig.json --type-check --force --fix", 12 | "publish": "electron-forge publish", 13 | "test": "jest", 14 | "postinstall": "opencollective postinstall" 15 | }, 16 | "keywords": [], 17 | "contributors": [ 18 | { 19 | "name": "Matthew Gonzalez", 20 | "email": "me@matthewgonzalez.me", 21 | "url": "http://matthewgonzalez.me" 22 | }, 23 | { 24 | "name": "Brian Gonzalez", 25 | "email": "me@briangonzalez.org", 26 | "url": "http://www.briangonzalez.org" 27 | } 28 | ], 29 | "license": "MIT", 30 | "config": { 31 | "forge": { 32 | "make_targets": { 33 | "win32": [ 34 | "squirrel" 35 | ], 36 | "darwin": [ 37 | "dmg" 38 | ], 39 | "linux": [ 40 | "deb", 41 | "rpm" 42 | ] 43 | }, 44 | "electronPackagerConfig": { 45 | "packageManager": "npm", 46 | "extendInfo": "Info.plist", 47 | "icon": "src/assets/system-icons/icon.icns", 48 | "name": "fontplop" 49 | }, 50 | "electronWinstallerConfig": { 51 | "name": "fontplop" 52 | }, 53 | "electronInstallerDMG": { 54 | "background": "src/assets/build/dmg-background-combined.tiff", 55 | "icon": "src/assets/system-icons/icon.icns" 56 | }, 57 | "electronInstallerDebian": {}, 58 | "electronInstallerRedhat": {}, 59 | "github_repository": { 60 | "owner": "matthewgonzalez", 61 | "name": "fontplop" 62 | }, 63 | "windowsStoreConfig": { 64 | "packageName": "", 65 | "name": "fontplop" 66 | } 67 | } 68 | }, 69 | "dependencies": { 70 | "electron-compile": "^6.4.2", 71 | "electron-devtools-installer": "^2.2.0", 72 | "electron-json-storage": "^3.2.0", 73 | "fonteditor-core": "0.0.37", 74 | "github-version-checker": "github:matthewgonzalez/github-version-checker", 75 | "opencollective": "^1.0.3", 76 | "react": "^16.0.0", 77 | "react-dom": "^16.0.0", 78 | "react-dropzone": "^4.1.3", 79 | "react-hot-loader": "^3.0.0-beta.7", 80 | "replace-ext": "^1.0.0", 81 | "rimraf": "^2.6.2", 82 | "tslib": "^1.7.1", 83 | "ttf2woff2": "^2.0.3" 84 | }, 85 | "devDependencies": { 86 | "@types/electron": "^1.6.10", 87 | "@types/electron-devtools-installer": "^2.0.2", 88 | "@types/jest": "^21.1.2", 89 | "@types/react": "16.0.10", 90 | "@types/react-dom": "^16.0.1", 91 | "@types/react-dropzone": "^4.1.0", 92 | "@types/replace-ext": "0.0.27", 93 | "@types/rimraf": "^2.0.2", 94 | "babel-plugin-transform-async-to-generator": "^6.24.1", 95 | "babel-preset-env": "^1.6.0", 96 | "babel-preset-react": "^6.24.1", 97 | "electron-forge": "^4.1.2", 98 | "electron-prebuilt-compile": "1.7.8", 99 | "jest": "^21.2.1", 100 | "ts-jest": "^21.1.0", 101 | "tslint": "^5.7.0", 102 | "tslint-config-standard": "^7.0.0", 103 | "typescript": "^2.5.3" 104 | }, 105 | "jest": { 106 | "transform": { 107 | "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" 108 | }, 109 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 110 | "moduleFileExtensions": [ 111 | "ts", 112 | "tsx", 113 | "js", 114 | "jsx", 115 | "json" 116 | ] 117 | }, 118 | "collective": { 119 | "type": "opencollective", 120 | "url": "https://opencollective.com/fontplop", 121 | "logo": "https://opencollective.com/fontplop/logo.txt" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron' 2 | import * as React from 'react' 3 | import * as Dropzone from 'react-dropzone' 4 | 5 | export const validExtensions = ['.otf', '.ttf'] 6 | 7 | export class App extends React.Component { 8 | 9 | validExtensions: Array 10 | 11 | constructor (props: any) { 12 | super(props) 13 | 14 | this.validExtensions = validExtensions 15 | this.state = { 16 | dragOver: false 17 | } 18 | } 19 | 20 | onDrop (acceptedFiles: Array /* rejected: any */) { 21 | const acceptedFilePaths = acceptedFiles.map(f => f.path) 22 | ipcRenderer.send('process-fonts', acceptedFilePaths) 23 | this.setState({ dragOver: false }) 24 | } 25 | 26 | onDragOver () { 27 | this.setState({ dragOver: true }) 28 | } 29 | 30 | onDragLeave () { 31 | this.setState({ dragOver: false }) 32 | } 33 | 34 | public render () { 35 | const dzStyle = { 36 | position: 'fixed', 37 | top: '1px', 38 | left: '1px', 39 | right: '1px', 40 | bottom: '1px', 41 | borderRadius: '3px', 42 | borderWidth: '1px', 43 | borderStyle: 'solid', 44 | borderColor: '#087D9B', 45 | justifyContent: 'center', 46 | display: 'flex', 47 | alignItems: 'center', 48 | textAlign: 'center', 49 | fontSize: '24px', 50 | lineHeight: '1em', 51 | userSelect: 'none', 52 | cursor: 'default' 53 | } 54 | const dzActiveStyle = { 55 | backgroundColor: 'green', 56 | borderColor: 'green' 57 | } 58 | 59 | const titleStyle = { 60 | fontSize: 20, 61 | marginBottom: 10 62 | } 63 | 64 | const subtitleStyle = { 65 | fontSize: 10, 66 | clear: 'both', 67 | color: 'rgba(255,255,255,0.5)', 68 | padding: '0px 20px', 69 | lineHeight: '1.75em' 70 | } 71 | 72 | const snippetStyle = { 73 | backgroundColor: this.state.dragOver ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.1)', 74 | borderRadius: 2, 75 | padding: '2px 5px', 76 | display: 'inline block', 77 | marginRight: '2px' 78 | } 79 | 80 | return ( 81 | 90 |
91 |
Plop here
92 |
93 | Must be of type {(() => this.validExtensions.map((ext, index) => {ext}))()} 94 |
95 |
96 |
97 | ) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/assets/build/dmg-background-combined.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/build/dmg-background-combined.tiff -------------------------------------------------------------------------------- /src/assets/build/dmg-background.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/build/dmg-background.tiff -------------------------------------------------------------------------------- /src/assets/build/dmg-background@2x.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/build/dmg-background@2x.tiff -------------------------------------------------------------------------------- /src/assets/system-icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon.icns -------------------------------------------------------------------------------- /src/assets/system-icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon.png -------------------------------------------------------------------------------- /src/assets/system-icons/icon_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon_64x64.png -------------------------------------------------------------------------------- /src/assets/system-icons/icon_old.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthewgonzalez/fontplop/d6fde1b56fd3fa0dc684a9eb7ebd5577d5602cae/src/assets/system-icons/icon_old.icns -------------------------------------------------------------------------------- /src/checkupdates.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const {app, dialog, shell} = require('electron') 3 | const path = require('path') 4 | const version = app.getVersion() 5 | const versionCheck = require('github-version-checker') 6 | const storage = require('electron-json-storage') 7 | 8 | const repo = 'matthewgonzalez/fontplop' 9 | const iconPath = 'assets/system-icons/icon_64x64.png' 10 | const updateURL = 'http://fontplop.com/' 11 | 12 | function showUpdateMessageBox (update) { 13 | let message = 'Check it. ' + update.tag_name + ' is hot off the press. You are currently rocking v' + version + '. You might want to go and grab it.' 14 | 15 | // Ask user to update the app 16 | dialog.showMessageBox({ 17 | type: 'question', 18 | icon: path.join(__dirname, iconPath), 19 | buttons: [ 20 | 'Take me there. Right now.', 'Remind me.', 'No, thanks. I don\'t want this update.' 21 | ], 22 | defaultId: 0, 23 | cancelId: 1, 24 | message: 'The new ' + app.getName() + ' is available 😃', 25 | detail: message 26 | }, response => { 27 | if (response === 0) { 28 | shell.openExternal(updateURL) 29 | } else if (response === 1) { 30 | storage.has('ignoreVersion', (error, hasKey) => { 31 | if (error) throw error 32 | if (hasKey) storage.remove('ignoreVersion', (error) => { 33 | if (error) throw error 34 | }) 35 | }) 36 | } else if (response === 2) { 37 | storage.set('ignoreVersion', update.tag_name, (error) => { 38 | if (error) throw error 39 | }) 40 | } 41 | }) 42 | } 43 | 44 | function showNoUpdateMessageBox () { 45 | let message = `Continue ploppin'.` 46 | dialog.showMessageBox({ 47 | type: 'info', 48 | icon: path.join(__dirname, iconPath), 49 | buttons: [ 50 | '¡Muchisimas gracias!' 51 | ], 52 | defaultId: 0, 53 | message: 'You\'re all up to date.', 54 | detail: message 55 | }) 56 | } 57 | 58 | function startVersionCheck (options) { 59 | versionCheck(options.versionCheck, function (update) { // callback function 60 | if (update) { 61 | showUpdateMessageBox(update) 62 | } else if (options.forceCheck) { 63 | showNoUpdateMessageBox() 64 | } 65 | }) 66 | } 67 | 68 | function checkUpdates () { 69 | const options = Object.assign({ 70 | versionCheck: { 71 | // repo: 'atom/atom', // for test purposes 72 | repo, 73 | currentVersion: version 74 | }, 75 | forceCheck: false 76 | }, arguments[0]) 77 | 78 | if (options.forceCheck) { 79 | startVersionCheck(options) 80 | } else { 81 | storage.get('ignoreVersion', (error, data) => { 82 | if (error || typeof data !== 'string') return startVersionCheck(options) 83 | 84 | options.versionCheck.currentVersion = data.replace(/[^0-9$.,]/g, '') 85 | 86 | startVersionCheck(options) 87 | }) 88 | } 89 | } 90 | 91 | module.exports = checkUpdates 92 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, ipcMain, Menu, shell, dialog } from 'electron' 2 | import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer' 3 | import { enableLiveReload } from 'electron-compile' 4 | import processFonts from './process-fonts' 5 | import * as path from 'path' 6 | 7 | const checkUpdates = require('./checkupdates') 8 | 9 | // Keep a global reference of the window object, if you don't, the window will 10 | // be closed automatically when the JavaScript object is garbage collected. 11 | let mainWindow: Electron.BrowserWindow | null = null 12 | const isDevMode = process.execPath.match(/[\\/]electron/) 13 | 14 | if (isDevMode) { 15 | enableLiveReload({ strategy: 'react-hmr' }) 16 | } 17 | 18 | const createWindow = async () => { 19 | mainWindow = new BrowserWindow({ 20 | width: isDevMode ? 800 : 200, 21 | height: isDevMode ? 800 : 200, 22 | resizable: !!isDevMode, 23 | show: false, 24 | backgroundColor: '#000', 25 | titleBarStyle: 'hiddenInset', 26 | fullscreen: false, 27 | fullscreenable: false, 28 | maximizable: false, 29 | icon: path.join(__dirname, 'src/fontplop-app-icon.png') 30 | }) 31 | 32 | // and load the index.html of the app. 33 | mainWindow.loadURL(`file://${__dirname}/index.html`) 34 | 35 | // Open the DevTools. 36 | if (isDevMode) { 37 | await installExtension(REACT_DEVELOPER_TOOLS) 38 | mainWindow.webContents.openDevTools() 39 | } 40 | 41 | mainWindow.once('ready-to-show', () => { 42 | if (mainWindow) { mainWindow.show() } 43 | }) 44 | 45 | // Emitted when the window is closed. 46 | mainWindow.on('closed', () => { 47 | // Dereference the window object, usually you would store windows 48 | // in an array if your app supports multi windows, this is the time 49 | // when you should delete the corresponding element. 50 | mainWindow = null 51 | }) 52 | 53 | // Call auto-updater 54 | mainWindow.webContents.on('did-frame-finish-load', () => { 55 | // checkUpdates() 56 | if (!isDevMode) { 57 | checkUpdates() 58 | } 59 | }) 60 | 61 | const showOpen = function () { 62 | dialog.showOpenDialog({ 63 | properties: ['openFile', 'openDirectory', 'multiSelections'], 64 | filters: [{ name: 'fontplop', extensions: ['ttf', 'otf'] }] 65 | }, (filePaths) => { 66 | processFonts(filePaths) 67 | }) 68 | } 69 | 70 | const menu = Menu.buildFromTemplate([ 71 | { 72 | submenu: [ 73 | { 74 | label: 'About fontplop', 75 | click: () => { shell.openExternal('http://www.fontplop.com') } 76 | }, 77 | { 78 | label: 'Check for Updates...', 79 | click: () => checkUpdates({ forceCheck: true }) 80 | }, 81 | { type: 'separator' }, 82 | { role: 'quit' } 83 | ] 84 | }, 85 | { 86 | label: 'File', 87 | submenu: [ 88 | { 89 | label: 'Open', 90 | click: function () { showOpen() } 91 | } 92 | ] 93 | } 94 | ]) 95 | Menu.setApplicationMenu(menu) 96 | 97 | } 98 | 99 | // This method will be called when Electron has finished 100 | // initialization and is ready to create browser windows. 101 | // Some APIs can only be used after this event occurs. 102 | app.on('ready', createWindow) 103 | 104 | // Quit when all windows are closed. 105 | app.on('window-all-closed', () => { 106 | // On OS X it is common for applications and their menu bar 107 | // to stay active until the user quits explicitly with Cmd + Q 108 | if (process.platform !== 'darwin') { 109 | app.quit() 110 | } 111 | }) 112 | 113 | app.on('activate', () => { 114 | // On OS X it's common to re-create a window in the app when the 115 | // dock icon is clicked and there are no other windows open. 116 | if (mainWindow === null) { 117 | createWindow() 118 | } 119 | }) 120 | 121 | /* 122 | The following creates a listener to handle files dropped onto the window 123 | and files dropped onto app icon in dock or in Finder 124 | */ 125 | ipcMain.on('process-fonts', (event: any, files: Array) => { 126 | event.preventDefault() 127 | processFonts(files) 128 | }) 129 | 130 | app.on('open-file', function (event: any, filePaths: Array) { 131 | event.preventDefault() 132 | 133 | if (app.isReady()) { 134 | processFonts(filePaths) 135 | } else { 136 | app.once('ready', () => { 137 | processFonts(filePaths) 138 | }) 139 | } 140 | 141 | }) 142 | -------------------------------------------------------------------------------- /src/process-fonts.ts: -------------------------------------------------------------------------------- 1 | import { FontCollection } from '../lib/font-collection' 2 | 3 | function processFonts (files: Array) { 4 | if (!files) return 5 | files = [].concat(files) 6 | files.forEach(file => { 7 | new FontCollection(file).export() 8 | }) 9 | } 10 | 11 | export default processFonts 12 | -------------------------------------------------------------------------------- /src/styles/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #fff; 3 | font-family: -apple-system, BlinkMacSystemFont, sans-serif; 4 | font-weight: 100; 5 | letter-spacing: .5px; 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "removeComments": false, 4 | "preserveConstEnums": true, 5 | "sourceMap": true, 6 | "declaration": true, 7 | "noImplicitAny": true, 8 | "noImplicitReturns": true, 9 | "suppressImplicitAnyIndexErrors": true, 10 | "strictNullChecks": true, 11 | "noUnusedLocals": true, 12 | "noImplicitThis": true, 13 | "noUnusedParameters": true, 14 | "importHelpers": true, 15 | "noEmitHelpers": true, 16 | "module": "commonjs", 17 | "moduleResolution": "node", 18 | "pretty": true, 19 | "target": "es2015", 20 | "jsx": "react" 21 | }, 22 | "formatCodeOptions": { 23 | "indentSize": 2, 24 | "tabSize": 2 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-standard" 3 | } 4 | --------------------------------------------------------------------------------