├── .gitignore ├── .bowerrc ├── .awsbox.json ├── README.md ├── LICENSE ├── server ├── views │ ├── partials │ │ ├── package-info.html │ │ ├── set-example-text.html │ │ └── family-info.html │ ├── family-list.html │ ├── family-detail.html │ ├── layout.html │ ├── font-detail.html │ └── index.html ├── lib │ ├── util.js │ ├── font-installer.js │ └── font-packs.js └── start.js ├── bower.json ├── .jshintrc ├── package.json └── client └── css └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | client/bower_components/ 3 | 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.awsbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "processes": [ "./server/start.js" ] 3 | } 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # connectfonts.org 2 | 3 | The website for [connect-fonts](https://github.com/shane-tomlinson/connect-fonts) Connect/Express middleware. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This Source Code Form is subject to the terms of the Mozilla Public 2 | License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | You can obtain one at http://mozilla.org/MPL/2.0/. 4 | -------------------------------------------------------------------------------- /server/views/partials/package-info.html: -------------------------------------------------------------------------------- 1 | {% if packConfig.package.name %} 2 |

from: {{ packConfig.package.name }} 4 |

5 | {% endif %} 6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-fonts.org", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/shane-tomlinson/connect-fonts.org", 5 | "authors": [ 6 | "Shane Tomlinson " 7 | ], 8 | "license": "MPL2.0", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "client/src/bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "rum-diary-js-client": "0.0.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "passfail": false, 3 | "maxerr": 100, 4 | "node": true, 5 | "forin": false, 6 | "boss": true, 7 | "noarg": true, 8 | "undef": true, 9 | "unused": true, 10 | "browser": true, 11 | "laxbreak": true, 12 | "laxcomma": true, 13 | "eqeqeq": true, 14 | "eqnull": true, 15 | "expr": true, 16 | "indent": 2, 17 | "white": false, 18 | "predef": [ 19 | "exports", 20 | "require", 21 | "process" 22 | ], 23 | "es5": true, 24 | "esnext": true, 25 | "shadow": false, 26 | "supernew": false, 27 | "strict": false 28 | } 29 | -------------------------------------------------------------------------------- /server/views/partials/set-example-text.html: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /server/lib/util.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | exports.asyncForEach = function(array, cb, done) { 6 | next(null); 7 | 8 | var index = 0; 9 | function next(err) { 10 | if (err) return done && done(err); 11 | 12 | var item = array.shift(); 13 | if (!item) return done && done(null); 14 | var idx = index; 15 | index++; 16 | cb(item, idx, next); 17 | } 18 | }; 19 | 20 | exports.deepCopy = function(toCopy) { 21 | return JSON.parse(JSON.stringify(toCopy)); 22 | }; 23 | 24 | exports.split = function(toSplit, splitOn) { 25 | return toSplit.split(splitOn); 26 | }; 27 | 28 | exports.print = function(object) { 29 | console.log(JSON.stringify(object, null, 2)); 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "Author": "Shane Tomlinson (https://shanetomlinson.com)", 3 | "name": "connectfonts.org", 4 | "description": "connectfonts.org homepage", 5 | "keywords": [ 6 | "font", 7 | "font-face", 8 | "CSS", 9 | "connectfonts.org", 10 | "connect-fonts" 11 | ], 12 | "homepage": "https://github.com/shane-tomlinson/connectfonts.org", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/shane-tomlinson/connectfonts.org.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/shane-tomlinson/connectfonts.org/issues" 19 | }, 20 | "version": "0.0.2", 21 | "engines": { 22 | "node": ">= 0.6.2" 23 | }, 24 | "main": "index", 25 | "dependencies": { 26 | "bower": "1.3.8", 27 | "connect-fonts": "2.0.2", 28 | "connect-fonts-roboto": "0.0.4", 29 | "connect-fonts-quicksand": "0.0.1", 30 | "express": "3.4.7", 31 | "globule": "0.2.0", 32 | "helmet": "0.3.2", 33 | "npm": "1.4.20", 34 | "rum-diary-endpoint": "0.0.2", 35 | "spdy": "1.27.0", 36 | "swig": "1.4.1", 37 | "timebot": "0.0.1" 38 | }, 39 | "devDependencies": { 40 | "nodeunit": "~0.9.0" 41 | }, 42 | "scripts": { 43 | "postinstall": "bower install", 44 | "start": "node server/start.js", 45 | "test": "nodeunit test" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/views/family-list.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block title %}connect-fonts - family list{% endblock %} 4 | 5 | {% block head %} 6 | {% parent %} 7 | 8 | 17 | {% endblock %} 18 | 19 | 20 | {% block navitems %} 21 |
  • 22 | Set example text 23 |
  • 24 | {% endblock %} 25 | 26 | 27 | {% block content %} 28 |

    Family list

    29 | 30 | 45 | 46 | {% include "partials/set-example-text.html" %} 47 | {% endblock %} 48 | 49 | 50 | -------------------------------------------------------------------------------- /server/views/family-detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block title %}connect-fonts - {{ packConfig.font_common.family }}{% endblock %} 4 | 5 | {% block head %} 6 | {% parent %} 7 | 8 | 17 | {% endblock %} 18 | 19 | 20 | {% block navitems %} 21 | {% parent %} 22 | {% if packConfig.font_common || packConfig.package || packConfig.author %} 23 |
  • 24 | View extended info 25 |
  • 26 | {% endif %} 27 |
  • 28 | Set example text 29 |
  • 30 | {% endblock %} 31 | 32 | 33 | {% block content %} 34 |

    {{ packConfig.font_common.family }}

    35 | {% include "partials/package-info.html" %} 36 | 37 | 48 | 49 | {% include "partials/family-info.html" %} 50 | {% include "partials/set-example-text.html" %} 51 | {% endblock %} 52 | 53 | 54 | -------------------------------------------------------------------------------- /server/views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block title %}connect-fonts - font serving middleware for Connect/Express{% endblock %} 7 | 8 | {% block head %} 9 | 10 | 11 | {% endblock %} 12 | 13 | 14 |
    15 |

    Connect-fonts

    16 |

    Connect/Express Font Serving Middleware

    17 |
    18 | 19 | 33 | 34 |
    35 | {% block content %}{% endblock %} 36 |
    37 | 38 |
    39 | Site created by Shane Tomlinson, 41 | licensed under the MPL 2.0 - Source available @ GitHub. 44 |
    45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /server/views/font-detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block title %}connect-fonts - {{ font.name }}{% endblock %} 4 | 5 | {% block head %} 6 | {% parent %} 7 | 8 | 15 | {% endblock %} 16 | 17 | {% block navitems %} 18 | {% if font.packConfig.count > 1 %} 19 |
  • 20 | {{ font.fontFamily }} 21 |
  • 22 | {% endif %} 23 | {% parent %} 24 | {% if font.packConfig.font_common || font.packConfig.package || font.packConfig.author %} 25 |
  • 26 | View extended info 27 |
  • 28 | {% endif %} 29 |
  • 30 | Set example text 31 |
  • 32 | {% endblock %} 33 | 34 | 35 | {% block content %} 36 | 37 |

    38 | {{ font.name }} 39 |

    40 | {% include "partials/package-info.html" with font %} 41 | 42 | 43 |
    44 | 45 |

    36px

    46 |

    47 | {{ sampletext }} 48 |

    49 | 50 |

    24px

    51 |

    52 | {{ sampletext }} 53 |

    54 | 55 |

    18px

    56 |

    57 | {{ sampletext }} 58 |

    59 | 60 |

    12px

    61 |

    62 | {{ sampletext }} 63 |

    64 | 65 |
    66 | 67 | {% include "partials/family-info.html" with font %} 68 | {% include "partials/set-example-text.html" %} 69 | 70 | 71 | {% endblock %} 72 | 73 | 74 | -------------------------------------------------------------------------------- /server/lib/font-installer.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const npm = require("npm"); 6 | const path = require("path"); 7 | const globule = require("globule"); 8 | const util = require("./util"); 9 | const timebot = require("timebot"); 10 | const dependencies = require(path.join(__dirname, "..", "..", "package.json")).dependencies; 11 | 12 | const PACKAGE_QUERY = "connect-fonts-"; 13 | 14 | /** 15 | * A list of packages with the connect-fonts prefix that should 16 | * not be installed 17 | */ 18 | const PACKAGE_BLACKLIST = [ 19 | "connect-fonts", 20 | "connect-fonts-tools", 21 | "connect-fonts-example" 22 | ]; 23 | 24 | var fontMiddleware; 25 | 26 | function shouldInstallPackage(packageName) { 27 | return !(dependencies[packageName] || PACKAGE_BLACKLIST.indexOf(packageName) > -1); 28 | } 29 | 30 | function getUpdateMinutes(updateIntervalMins) { 31 | updateIntervalMins = updateIntervalMins || 10; 32 | var updateTimes = []; 33 | 34 | for (var i = 0; i < 60; ++i) { 35 | if ((i % updateIntervalMins) === 0) { 36 | updateTimes.push(i); 37 | } 38 | } 39 | 40 | return updateTimes.join(','); 41 | } 42 | 43 | exports.setup = function(options, done) { 44 | options = options || {}; 45 | fontMiddleware = options.fontMiddleware; 46 | 47 | exports.loadInstalled(function(err) { 48 | if (err) return done(err); 49 | exports.loadNew(function(err) { 50 | if (err) { 51 | console.error('error loading fonts: %s', String(err)); 52 | return done(err); 53 | } 54 | 55 | // Refresh the font list from npm every updateIntervalMins 56 | timebot.set({ 57 | path: 'load new fonts', 58 | minute: getUpdateMinutes(options.updateIntervalMins) 59 | }, { 60 | cron: function() { 61 | console.log("refreshing font list"); 62 | exports.loadNew(function (err) { 63 | if (err) console.error('error loading fonts: %s', String(err)); 64 | }); 65 | } 66 | }); 67 | }); 68 | }); 69 | }; 70 | 71 | function registerFontPack(packageName, done) { 72 | try { 73 | var fontPack = require(packageName); 74 | dependencies[packageName] = true; 75 | fontMiddleware.registerFontPack(fontPack, done); 76 | } catch(e) { 77 | console.log("could not install", packageName, ":", String(e)); 78 | done(null); 79 | } 80 | } 81 | 82 | /** 83 | * Load font packs already installed in the node_modules directory 84 | * @method loadInstalled 85 | * @param {function} done 86 | */ 87 | exports.loadInstalled = function(done) { 88 | console.log("searching for installed fonts"); 89 | var packageNames = globule.find('connect*', { 90 | srcBase: path.join(__dirname, '..', '..', 'node_modules') 91 | }); 92 | console.log(packageNames.length + " potential installed font packs found"); 93 | 94 | if (! packageNames.length) { 95 | return done(); 96 | } 97 | 98 | util.asyncForEach(packageNames, function(packageName, index, next) { 99 | console.log(packageName); 100 | if ( ! shouldInstallPackage(packageName)) return next(); 101 | 102 | registerFontPack(packageName, next); 103 | }, done); 104 | }; 105 | 106 | /** 107 | * Load new fonts from the npmjs.org repository 108 | * @method loadNew 109 | * @param {function} done 110 | */ 111 | exports.loadNew = function(done) { 112 | console.log("searching for new fonts on npm"); 113 | npm.load(function(err) { 114 | if (err) return done(err); 115 | 116 | npm.commands.search(PACKAGE_QUERY, true, function(err, packages) { 117 | if (err) return done(err); 118 | 119 | var packageNames = Object.keys(packages); 120 | 121 | console.log(packageNames.length + " potential npm font packs found"); 122 | 123 | if (! packageNames.length) { 124 | return done(); 125 | } 126 | 127 | util.asyncForEach(packageNames, function(packageName, index, next) { 128 | console.log(packageName); 129 | if ( ! shouldInstallPackage(packageName)) return next(); 130 | 131 | npm.commands.install([packageName], function(err) { 132 | if (err) { 133 | console.error(String(err)); 134 | return next(); 135 | } 136 | 137 | registerFontPack(packageName, next); 138 | }); 139 | }, done); 140 | }); 141 | }); 142 | }; 143 | 144 | -------------------------------------------------------------------------------- /server/lib/font-packs.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | const util = require('./util'); 6 | 7 | /** 8 | * Font pack config access functions 9 | */ 10 | 11 | var fontConfigs; 12 | var families; 13 | 14 | exports.setup = function(config, done) { 15 | fontConfigs = config.fontConfigs; 16 | 17 | done && done(null); 18 | }; 19 | 20 | exports.update = function(fontConfigs, done) { 21 | fontConfigs = fontConfigs; 22 | families = null; 23 | 24 | done(null); 25 | }; 26 | 27 | /** 28 | * Get all font configurations for all families. Each family will contain an array of 29 | * fonts. Calls done with a copy of all font configs that is safe to modify. 30 | */ 31 | exports.getAllFontsByFamily = function(done) { 32 | if (families) return done(null, util.deepCopy(families)); 33 | 34 | // Sort fonts into families 35 | families = {}; 36 | for (var fontName in fontConfigs) { 37 | var fontConfig = util.deepCopy(fontConfigs[fontName]); 38 | fontConfig.name = fontName; 39 | 40 | var family = fontConfig.fontFamily; 41 | 42 | if (!families[family]) 43 | families[family] = {}; 44 | 45 | families[family][fontName] = cleanupFontConfig(fontConfig); 46 | } 47 | 48 | done(null, util.deepCopy(families)); 49 | }; 50 | 51 | /** 52 | * Get the configuration for a single family 53 | */ 54 | exports.getFamily = function(familyName, done) { 55 | exports.getAllFontsByFamily(function(err, families) { 56 | if (err) return done(err); 57 | var family = families[familyName]; 58 | if (!family) return done(null, null); 59 | 60 | var sortedFonts = {}; 61 | Object.keys(family).sort().forEach(function(fontName) { 62 | sortedFonts[fontName] = family[fontName]; 63 | }); 64 | 65 | var cssNames = Object.keys(family); 66 | var familyInfo = { 67 | fonts: sortedFonts, 68 | cssNames: cssNames, 69 | packConfig: family[cssNames[0]].packConfig 70 | }; 71 | done(null, familyInfo); 72 | }); 73 | }; 74 | 75 | /** 76 | * Get a list of all installed font families. 77 | * Each family will contain a sample font, which is normally 78 | * the "regular" font. 79 | */ 80 | exports.getFamilies = function(done) { 81 | // Find the "regular" font for each family 82 | exports.getAllFontsByFamily(function(err, families) { 83 | if (err) return done(err); 84 | 85 | var bestRegulars = {}; 86 | Object.keys(families).sort().forEach(function(familyName) { 87 | var family = families[familyName]; 88 | var bestRegularName = getRegularFontForFamily(family); 89 | var bestRegular = family[bestRegularName]; 90 | 91 | bestRegular.cssname = bestRegularName; 92 | bestRegular.familyName = familyName; 93 | bestRegular.count = Object.keys(family).length; 94 | 95 | bestRegulars[bestRegularName] = bestRegular; 96 | }); 97 | 98 | done(null, bestRegulars); 99 | }); 100 | }; 101 | 102 | /** 103 | * Get the configuration for a single font 104 | */ 105 | exports.getFont = function(fontName, done) { 106 | var fontConfig = fontConfigs[fontName]; 107 | if (!fontConfig) return done(null, null); 108 | 109 | fontConfig = cleanupFontConfig(util.deepCopy(fontConfig)); 110 | fontConfig.packConfig.count = Object.keys(fontConfig.packConfig.fonts).length; 111 | fontConfig.name = fontName; 112 | done(null, fontConfig); 113 | }; 114 | 115 | 116 | function cleanupFontConfig(fontConfig) { 117 | try { 118 | fontConfig.packConfig.author.urls = fontConfig.packConfig.author.urls.split(','); 119 | } catch(e) {} 120 | try { 121 | fontConfig.packConfig.author.emails = fontConfig.packConfig.author.emails.split(','); 122 | } catch(e) {} 123 | try { 124 | fontConfig.packConfig.author.githubs = fontConfig.packConfig.author.githubs.split(','); 125 | } catch(e) {} 126 | 127 | return fontConfig; 128 | } 129 | 130 | function getRegularFontForFamily(family) { 131 | var fontNames = Object.keys(family); 132 | // try to find the best match. 133 | var bestRegular = fontNames.reduce(function(bestRegular, fontName) { 134 | if (fontName === bestRegular) return bestRegular; 135 | 136 | var fontConfig = family[fontName]; 137 | 138 | if (fontConfig.fontWeight === 400 && fontConfig.fontStyle === "normal") { 139 | // If an exact match. 140 | return fontName; 141 | } 142 | else if (Math.abs(fontConfig.fontWeight - 400) < Math.abs(bestRegular.fontWeight - 400)) { 143 | // Otherwise, look for the closest font weight. 144 | return fontName; 145 | } 146 | 147 | return bestRegular; 148 | }, fontNames[0]); 149 | 150 | return bestRegular; 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /server/views/partials/family-info.html: -------------------------------------------------------------------------------- 1 | {% if packConfig.font_common || packConfig.package || packConfig.author %} 2 |
    3 | View extended info 4 |
    5 | 6 |
     
    7 | {% endif %} 8 | 9 | {% if packConfig.font_common %} 10 |
    11 |

    Family Information

    12 | 13 | 14 | {% if packConfig.font_common.description %} 15 | 16 | 17 | 18 | 19 | {% endif %} 20 | {% if packConfig.font_common.copyright %} 21 | 22 | 23 | 24 | 25 | {% endif %} 26 | {% if packConfig.font_common.trademark %} 27 | 28 | 29 | 30 | 31 | {% endif %} 32 | {% if packConfig.font_common.manufacturer %} 33 | 34 | 35 | 36 | 37 | {% endif %} 38 | {% if packConfig.font_common.url_vendor %} 39 | 40 | 41 | 46 | 47 | {% endif %} 48 | {% if packConfig.font_common.designer %} 49 | 50 | 51 | 52 | 53 | {% endif %} 54 | {% if packConfig.font_common.url_designer %} 55 | 56 | 57 | 62 | 63 | {% endif %} 64 | {% if packConfig.license.name %} 65 | 66 | 67 | 78 | 79 | {% endif %} 80 |
    Description{{ packConfig.font_common.description }}
    Copyright{{ packConfig.font_common.copyright }}
    Trademark{{ packConfig.font_common.trademark }}
    Manufacturer{{ packConfig.font_common.manufacturer }}
    Manufacturer URL 42 | 43 | {{ packConfig.font_common.url_vendor }} 44 | 45 |
    Designer{{ packConfig.font_common.designer }}
    Designer URL 58 | 59 | {{ packConfig.font_common.url_designer }} 60 | 61 |
    License 68 | {% if packConfig.license.url %} 69 | 70 | {{ packConfig.license.name }} 71 | {{ packConfig.license.version }} 72 | 73 | {% else %} 74 | {{ packConfig.license.name }} 75 | {{ packConfig.license.version }} 76 | {% endif %} 77 |
    81 |
    82 | {% endif %} 83 | 84 | {% if packConfig.package %} 85 |
    86 |

    Package Information

    87 | 88 | {% if packConfig.package.name %} 89 | 90 | 91 | 92 | 93 | {% endif %} 94 | {% if packConfig.package.homepage %} 95 | 96 | 97 | 102 | 103 | {% endif %} 104 | {% if packConfig.package.repourl %} 105 | 106 | 107 | 112 | 113 | {% endif %} 114 | {% if packConfig.package.bugsurl %} 115 | 116 | 117 | 122 | 123 | {% endif %} 124 |
    Name{{ packConfig.package.name }}
    Homepage 98 | 99 | {{ packConfig.package.homepage }} 100 | 101 |
    Repo 108 | 109 | {{ packConfig.package.repourl }} 110 | 111 |
    Issue Tracker 118 | 119 | {{ packConfig.package.bugsurl }} 120 | 121 |
    125 |
    126 | {% endif %} 127 | 128 | 129 | {% if packConfig.author %} 130 |
    131 |

    Author Information

    132 | 133 | {% if packConfig.author.name %} 134 | 135 | 136 | 137 | 138 | {% endif %} 139 | {% if packConfig.author.urls%} 140 | 141 | 142 | 151 | 152 | {% endif %} 153 | {% if packConfig.author.emails %} 154 | 155 | 156 | 163 | 164 | {% endif %} 165 | {% if packConfig.author.githubs %} 166 | 167 | 168 | 177 | 178 | {% endif %} 179 | {% if packConfig.author.twitter %} 180 | 181 | 182 | 187 | 188 | {% endif %} 189 |
    Name{{ packConfig.author.name }}
    Homepage 143 | {% for url in packConfig.author.urls %} 144 |
  • 145 | 146 | {{ url }} 147 | 148 |
  • 149 | {% endfor %} 150 |
    Emails 157 |
      158 | {% for email in packConfig.author.emails %} 159 |
    • {{ email }}
    • 160 | {% endfor %} 161 |
    162 |
    GitHub Repos 169 | {% for github in packConfig.author.githubs %} 170 |
  • 171 | 172 | {{ github }} 173 | 174 |
  • 175 | {% endfor %} 176 |
    Twitter 183 | 184 | {{ packConfig.author.twitter }} 185 | 186 |
    190 |
    191 | {% endif %} 192 | 193 | -------------------------------------------------------------------------------- /server/start.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const swig = require('swig'); 3 | const url = require('url'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const http = require('http'); 7 | const spdy = require('spdy'); 8 | const helmet = require('helmet'); 9 | const rum_diary_endpoint 10 | = require('rum-diary-endpoint'); 11 | const connect_fonts = require('connect-fonts'); 12 | const fontpack_quicksand 13 | = require('connect-fonts-quicksand'); 14 | const fontpack_roboto 15 | = require('connect-fonts-roboto'); 16 | const fontpack_installer 17 | = require('./lib/font-installer'); 18 | const font_packs = require('./lib/font-packs'); 19 | 20 | const app = express(); 21 | 22 | const IP_ADDRESS = process.env.IP_ADDRESS || "127.0.0.1"; 23 | const HTTP_PORT = process.env.HTTP_PORT || 3000; 24 | const SSL_PORT = process.env.SSL_PORT || 3433; 25 | 26 | const TEMPLATE_PATH = path.join(__dirname, 'views'); 27 | const STATIC_PATH = path.join(__dirname, '..', 'client'); 28 | 29 | const SSL_CERT_PATH = path.join(__dirname, '..', 'cert'); 30 | 31 | const DEFAULT_SAMPLE_TEXT 32 | = "Grumpy wizards make toxic brew for the evil Queen and Jack."; 33 | 34 | process.on('uncaughtException', function (err) { 35 | console.error((new Date).toUTCString() + ' uncaughtException:', err.message); 36 | console.error(err.stack); 37 | }); 38 | 39 | app.engine('html', swig.renderFile); 40 | app.set('view engine', 'html'); 41 | app.set('views', TEMPLATE_PATH); 42 | 43 | // Swig will cache templates for you, but you can disable 44 | // that and use Express's caching instead, if you like: 45 | app.set('view cache', false); 46 | 47 | swig.setDefaults({ 48 | cache: false 49 | }); 50 | 51 | 52 | app.set('views', TEMPLATE_PATH); 53 | app.set('view cache', false); 54 | 55 | // Redirect all http traffic to https 56 | app.use(function(req, res, next) { 57 | console.log("received request: ", req.url); 58 | if (!req.secure) { 59 | var urlObj = url.parse(req.protocol + "://" + req.get('Host') + req.url); 60 | if (urlObj.host.indexOf(":" + HTTP_PORT) > -1) { 61 | urlObj.host = urlObj.host.replace(":" + HTTP_PORT, ":" + SSL_PORT); 62 | } 63 | urlObj.protocol = "https"; 64 | 65 | var redirectTo = url.format(urlObj); 66 | res.redirect(redirectTo, 301); 67 | } else { 68 | next(); 69 | } 70 | }); 71 | 72 | var fontMiddleware = connect_fonts.setup({ 73 | fonts: [ fontpack_quicksand, fontpack_roboto ], 74 | allow_origin: "http://connect-fonts.org", 75 | maxage: 180 * 24 * 60 * 60 * 1000, // 180 days 76 | compress: true, 77 | ua: 'all' 78 | }); 79 | 80 | app.use(fontMiddleware); 81 | 82 | font_packs.setup({ 83 | fontConfigs: fontMiddleware.fontConfigs 84 | }); 85 | 86 | // Force a refresh of the font list. 87 | fontpack_installer.setup({ 88 | fontMiddleware: fontMiddleware, 89 | updateIntervalMins: 10 90 | }, function(err) { 91 | if (err) return err; 92 | 93 | font_packs.update(fontMiddleware.fontConfigs); 94 | }); 95 | 96 | app.use(express.cookieParser()); 97 | app.use(express.bodyParser()); 98 | app.use(helmet()); 99 | 100 | 101 | const httpCollector = new rum_diary_endpoint.collectors.Http({ 102 | collectorUrl: 'https://rum-diary.org/metrics', 103 | maxCacheSize: 1 104 | }); 105 | 106 | const metricsMiddleware = rum_diary_endpoint.setup({ 107 | endpoint: '/metrics', 108 | collectors: [ httpCollector ] 109 | }); 110 | 111 | app.use(metricsMiddleware); 112 | 113 | app.use(express.static(STATIC_PATH)); 114 | 115 | app.get('/', function (req, res) { 116 | res.render('index.html', {}); 117 | }); 118 | 119 | app.get('/families', function (req, res) { 120 | font_packs.getFamilies(function(err, families) { 121 | var cssNames = Object.keys(families).join(","); 122 | 123 | res.render('family-list.html', { 124 | cssNames: cssNames, 125 | fonts: families, 126 | sampletext: getSampleText(req) 127 | }); 128 | }); 129 | }); 130 | 131 | app.get('/family/:name', function (req, res) { 132 | var familyName = req.params.name; 133 | font_packs.getFamily(familyName, function(err, familyConfig) { 134 | if (err || !familyConfig) { 135 | return res.send("Unknown font", 404); 136 | } 137 | 138 | var cssNames = familyConfig.cssNames; 139 | if (cssNames.length === 1) { 140 | // if there is only one font in the family, redirect 141 | // straight to that font. 142 | res.redirect('/font/' + cssNames[0]); 143 | } 144 | else { 145 | familyConfig.sampletext = getSampleText(req); 146 | res.render('family-detail.html', familyConfig); 147 | } 148 | }); 149 | }); 150 | 151 | app.get('/font/:name', function (req, res) { 152 | var fontName = req.params.name; 153 | font_packs.getFont(fontName, function(err, fontConfig) { 154 | if (err || !fontConfig) return res.send("unknown font", 404); 155 | 156 | res.render('font-detail.html', { 157 | font: fontConfig, 158 | sampletext: getSampleText(req) 159 | }); 160 | }); 161 | }); 162 | 163 | app.get('/reload', function(req, res) { 164 | fontpack_installer.loadNew(); 165 | res.redirect('/fonts'); 166 | }); 167 | 168 | 169 | // Set the users sample text. 170 | app.post('/sampletext', function(req, res) { 171 | var sampleText = req.body.sampletext; 172 | if (sampleText) { 173 | res.cookie('sampletext', sampleText, { 174 | expires: new Date(Date.now() + 900000), 175 | httpOnly: true 176 | }); 177 | res.redirect(303, req.headers.referer); 178 | } 179 | }); 180 | 181 | app.post('/delete-sampletext', function(req, res) { 182 | res.clearCookie('sampletext', {}); 183 | res.redirect(303, req.headers.referer); 184 | }); 185 | 186 | 187 | function getSampleText(req) { 188 | var sampleText = req.cookies && req.cookies.sampletext; 189 | if (!sampleText) sampleText = DEFAULT_SAMPLE_TEXT; 190 | return sampleText; 191 | } 192 | 193 | var spdy_options = { 194 | key: fs.readFileSync(path.join(SSL_CERT_PATH, 'connect-fonts.org.key')), 195 | cert: fs.readFileSync(path.join(SSL_CERT_PATH, 'connect-fonts.org.crt')), 196 | ca: fs.readFileSync(path.join(SSL_CERT_PATH, 'connect-fonts.org.csr')) 197 | }; 198 | 199 | console.log("HTTP Server listening on", IP_ADDRESS + ":" + HTTP_PORT); 200 | var httpServer = http.createServer(app); 201 | httpServer.listen(HTTP_PORT); 202 | 203 | console.log("HTTPS Server listening on", IP_ADDRESS + ":" + SSL_PORT); 204 | var spdyServer = spdy.createServer(spdy_options, app); 205 | spdyServer.listen(SSL_PORT); 206 | 207 | -------------------------------------------------------------------------------- /client/css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | 5 | -webkit-box-sizing: border-box; 6 | -moz-box-sizing: border-box; 7 | -o-box-sizing: border-box; 8 | box-sizing: border-box; 9 | } 10 | 11 | html, body { 12 | width: 100%; 13 | height: 100%; 14 | font: 20px/1.5 "Quicksand", "Helvetica", Sans-Serif, Serif; 15 | color: #000; 16 | } 17 | 18 | p { 19 | margin-bottom: 1rem; 20 | } 21 | 22 | .left { 23 | float: left; 24 | } 25 | 26 | .right { 27 | float: right; 28 | } 29 | 30 | nav { 31 | width: 700px; 32 | margin: 150px auto 50px; 33 | text-align: center; 34 | } 35 | 36 | nav li { 37 | display: inline; 38 | } 39 | 40 | 41 | nav ul, nav li { 42 | list-style-type: none; 43 | } 44 | 45 | ol li { 46 | margin-bottom: 1em; 47 | } 48 | 49 | a { 50 | color: #333; 51 | } 52 | 53 | a:hover { 54 | color: #339; 55 | border-color: #339; 56 | } 57 | 58 | .underline { 59 | text-decoration: none; 60 | border-bottom: 1px dotted; 61 | } 62 | 63 | 64 | 65 | .widescreen { 66 | display: none; 67 | } 68 | 69 | @media screen and (min-width: 1220px) { 70 | nav { 71 | position: fixed; 72 | top: 0; 73 | right: 50px; 74 | display: block; 75 | width: 200px; 76 | text-align: right; 77 | } 78 | 79 | nav li { 80 | display: block; 81 | } 82 | 83 | .widescreen { 84 | display: block; 85 | } 86 | } 87 | 88 | nav a { 89 | font-size: 14px; 90 | } 91 | 92 | main { 93 | display: block; 94 | width: 700px; 95 | margin: 0 auto 150px; 96 | } 97 | 98 | @media screen and (min-width: 1220px) { 99 | main { 100 | padding-top: 150px; 101 | } 102 | } 103 | 104 | @media screen and (max-width: 720px) { 105 | main, nav { 106 | width: 90%; 107 | } 108 | } 109 | 110 | h1 { 111 | font-weight: 600; 112 | } 113 | 114 | h2, h3, h4, h5, h6 { 115 | margin-top: 1.3em; 116 | margin-bottom: 0.75em; 117 | } 118 | 119 | header, footer { 120 | background-color: #000; 121 | color: #fff; 122 | } 123 | 124 | header { 125 | padding: 0 0 0 150px; 126 | position: fixed; 127 | top: 0; 128 | right: 0; 129 | left: 0; 130 | } 131 | 132 | footer { 133 | font-size: .8em; 134 | padding: 0.5em 0; 135 | text-align: center; 136 | position: fixed; 137 | bottom: 0; 138 | left: 0; 139 | right: 0; 140 | } 141 | 142 | @media screen and (max-width: 1000px) { 143 | header, footer { 144 | padding-left: 50px; 145 | } 146 | } 147 | 148 | @media screen and (max-width: 800px) { 149 | header, footer { 150 | padding-left: 10px; 151 | } 152 | } 153 | 154 | 155 | header h1, header h2 { 156 | display: inline; 157 | } 158 | 159 | 160 | header h2:before { 161 | content: '-'; 162 | margin: 0 .5em; 163 | } 164 | 165 | @media screen and (max-width: 690px) { 166 | header h1, header h2 { 167 | display: block; 168 | } 169 | 170 | header h2 { 171 | margin-top: 0; 172 | } 173 | 174 | header h2:before { 175 | content: none; 176 | } 177 | } 178 | 179 | 180 | header h2 { 181 | font-size: .9em; 182 | } 183 | 184 | header a { 185 | color: #fff; 186 | text-decoration: none; 187 | } 188 | 189 | footer a { 190 | color: #999; 191 | } 192 | 193 | pre, code { 194 | font: 20px/1.5 "Roboto", Sans-Serif, Serif; 195 | } 196 | 197 | pre { 198 | background-color: #ddd; 199 | padding: 1em; 200 | border-radius: 3px; 201 | border: 1px solid #999; 202 | margin: .5em 0; 203 | } 204 | 205 | code { 206 | font-style: italic; 207 | } 208 | 209 | dt { 210 | float: left; 211 | width: 25%; 212 | font-weight: bold; 213 | } 214 | 215 | dd { 216 | float: left; 217 | width: 75%; 218 | } 219 | 220 | .nomargin { 221 | margin: 0; 222 | } 223 | 224 | .fontlist, .fontlist li { 225 | list-style-type: none; 226 | margin-top: .3em; 227 | } 228 | 229 | .samples { 230 | margin: 1em 0; 231 | } 232 | 233 | .sampletext { 234 | padding: 20px; 235 | margin: .75em 0; 236 | color: #000; 237 | font-size: 24px; 238 | line-height: 1.5em; 239 | background-color: #f7f7f7; 240 | box-shadow: inset 0 0 2px black; 241 | border-radius: 10px; 242 | text-rendering: optimizeLegibility; 243 | 244 | -ms-word-break: break-all; 245 | word-break: break-all; 246 | 247 | // Non standard for webkit 248 | word-break: break-word; 249 | 250 | -webkit-hyphens: auto; 251 | -moz-hyphens: auto; 252 | hyphens: auto; 253 | } 254 | 255 | h2.sampletext { 256 | font-size: 56px; 257 | margin: 0; 258 | line-height: 1.2em; 259 | } 260 | 261 | .sampletext.large { 262 | font-size: 36px; 263 | } 264 | 265 | .sampletext.medium { 266 | font-size: 18px; 267 | } 268 | 269 | .sampletext.small { 270 | font-size: 12px; 271 | } 272 | 273 | /** 274 | * Push the first bit of fontinfo off the screen. This allows readers to still 275 | * see it, but it does not clutter up the initial viewing of the font 276 | */ 277 | 278 | .viewextended { 279 | margin: 3em 0 100% 0; 280 | text-align: center; 281 | } 282 | 283 | .viewextended a { 284 | font-size: .8em; 285 | color: #333; 286 | text-decoration: none; 287 | border-bottom: 1px dotted #333; 288 | } 289 | 290 | .viewextended a:hover { 291 | color: #339; 292 | border-bottom: 1px dotted #339; 293 | } 294 | 295 | .fontinfo { 296 | font-size: 18px; 297 | } 298 | 299 | .fontinfo td:first-child { 300 | min-width: 175px; 301 | } 302 | 303 | .lightbox { 304 | display: none; 305 | } 306 | 307 | .lightbox:target { 308 | position: fixed; 309 | top: 0; 310 | left: 0; 311 | right: 0; 312 | bottom: 0; 313 | z-index: 999; 314 | background-color: rgba(255,255,255,.4); 315 | display: block; 316 | } 317 | 318 | .lightbox-centerer { 319 | /** center the content! */ 320 | width: 100%; 321 | height: 100%; 322 | 323 | display: -webkit-box; 324 | -webkit-box-align: center; 325 | -webkit-box-pack: center; 326 | 327 | display: -moz-box; 328 | -moz-box-align: center; 329 | -moz-box-pack: center; 330 | 331 | display: -ms-flexbox; 332 | -ms-flex-align: center; 333 | -ms-flex-pack: center; 334 | 335 | display: box; 336 | box-align: center; 337 | box-pack: center; 338 | } 339 | 340 | .lightbox-title { 341 | margin: 0; 342 | } 343 | 344 | .lightbox-close { 345 | font-size: .7em; 346 | text-transform: lowercase; 347 | color: #555; 348 | } 349 | 350 | .lightbox-content { 351 | padding: 10px; 352 | border: 3px solid; 353 | background-color: #fff; 354 | } 355 | 356 | .lightbox-submit { 357 | line-height: 25px; 358 | font-size: 16px; 359 | } 360 | 361 | .lightbox-textarea { 362 | margin: 0.5em 0 1em 0; 363 | width: 400px; 364 | height: 3em; 365 | display: block; 366 | font-size: 18px; 367 | -webkit-transition: width .25s ease, height .25s ease; 368 | -moz-transition: width .25s ease, height .25s ease; 369 | -ms-transition: width .25s ease, height .25s ease; 370 | -o-transition: width .25s ease, height .25s ease; 371 | transition: width .25s ease, height .25s ease; 372 | } 373 | 374 | @media screen and (max-width: 430px) { 375 | .lightbox-textarea { 376 | width: 300px; 377 | height: 7em; 378 | } 379 | 380 | .lightbox-submit { 381 | width: 100%; 382 | margin-bottom: .5em; 383 | } 384 | } 385 | 386 | @media screen and (max-width: 330px) { 387 | .lightbox-textarea { 388 | width: 200px; 389 | } 390 | } 391 | 392 | -------------------------------------------------------------------------------- /server/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'layout.html' %} 3 | 4 | {% block head %} 5 | {% parent %} 6 | {% endblock %} 7 | 8 | {% block navitems %} 9 | {% parent %} 10 |
  • 11 | Usage 12 |
  • 13 |
  • 14 | Locale Optimised Fonts 15 |
  • 16 |
  • 17 | Generate CSS Programatically 18 |
  • 19 |
  • 20 | Creating a Fontpack 21 |
  • 22 |
  • 23 | Code 24 |
  • 25 |
  • 26 | Author 27 |
  • 28 |
  • 29 | Getting involved 30 |
  • 31 |
  • 32 | License 33 |
  • 34 | {% endblock %} 35 | 36 | 37 | {% block content %} 38 |

    39 | Connect-fonts is a font serving middleware for 41 | Connect/Express. 42 |

    43 | 44 |

    45 | The middleware looks for requests of the form (expressed in Express terminology): 46 |

     47 | /:font-list/fonts.css
    48 |

    49 | 50 | 51 |

    52 | An example is: 53 |

     54 | /opensans-regular,opensans-italics/fonts.css
    55 |

    56 | 57 |

    58 | When a match is made, connect-fonts generates a CSS response with @font-face declarations tailored to the user's browser. 59 |

    60 | 61 |

    Usage

    62 |
      63 |
    1. 64 | Install the middleware 65 |
       66 | npm install connect-fonts
      67 |
    2. 68 |
    3. 69 | Install a font-pack 70 |
       71 | npm install connect-fonts-opensans
      72 |
    4. 73 | Include connect-fonts in your server code. 74 |
       75 | const font_middleware = require("connect-fonts");
      76 |
    5. 77 | 78 |
    6. 79 | Include the font packs. 80 |
       81 | const opensans = require("connect-fonts-opensans");
      82 |
    7. 83 | 84 |
    8. 85 | Add a middleware by calling the setup function. 86 |
       87 | app.use(font_middleware.setup({
       88 |   fonts: [ opensans ],
       89 |   allow_origin: "https://exampledomain.com",
       90 |   maxage: 180 * 24 * 60 * 60 * 1000,   // 180 days
       91 |   compress: true
       92 | }));
      93 | 94 |
      95 |
      fonts
      96 |
      array of font packs
      97 |
      allow_origin
      98 |
      origin to set in the Access-Control-Allow-Origin header
      99 |
      maxage
      100 |
      provide a max-age in milliseconds for http caching, defaults to 0.
      101 |
      compress
      102 |
      If true, compresses CSS and compressable fonts
      103 |
      ua
      104 |
      (optional) force a user-agent. "all" means serve up all font types to all 105 | users. If not specified, the user's user-agent header will be used to 106 | send the user only the fonts that their user-agent support.
      107 |
      108 |
    9. 109 | 110 |
    10. 111 | Add a link tag to include the font CSS. 112 | To serve a default, non-locale specific font, include a CSS link that contains the name of the font: 113 |
      114 | <link href="/opensans-regular/fonts.css" type="text/css"
      115 |     rel="stylesheet"/ >
      116 |
    11. 117 | 118 |
    12. 119 | Set your CSS up to use the new font by using the correct font-family. 120 |
      121 | body {
      122 |   font-family: 'Open Sans', 'sans-serif', 'serif';
      123 | }
      124 |
    13. 125 | 126 |
    127 | 128 |

    Advanced Usage

    129 | 130 |

    Locale optimised fonts

    131 | Locale optimised fonts can be requested by prepending 132 | the locale name before the font list in the fonts.css request. 133 |
    134 | <link href="/en/opensans-regular/fonts.css" type="text/css"
    135 |     rel="stylesheet"/ >
    136 | 137 | Locale optimised fonts are included with some font packs, but if not, scripts/subset from connect-fonts-tools can be used to create them. 138 | 139 | 140 |

    Programatically generate CSS for use in build steps

    141 | One of the easiest ways to speed up your site is to minimize the number of 142 | external resources that are requested. The @font-face CSS provided by 143 | fonts.css can be generated programatically and concatinated with other site CSS during a build step. 144 |
    145 | // font_middleware.setup has already been called.
    146 | // `ua` - user agent. Use 'all' for a CSS bundle that
    147 | //     is compatible with all browsers.
    148 | // `lang` - language. generate_css can be called once
    149 | //     for each served language, or "default" can be
    150 | //     specified
    151 | // `fonts` - array of font names -
    152 | //     e.g. ["opensans-regular", "opensans-italics"]
    153 | font_middleware.generate_css(ua, lang, fonts, function(err, css) {
    154 |   var css_output_path = path.join(output_dir, dep);
    155 |   var css_output_dir = path.dirname(css_output_path);
    156 | 
    157 |   // create any missing directories.
    158 |   mkdirp.sync(css_output_dir);
    159 | 
    160 |   // finally, write out the file.
    161 |   fs.writeFileSync(css_output_path, css.css, "utf8");
    162 | });
    163 | 164 |

    Direct access to font files

    165 | After the middleware's setup function is called, a map of font URLs to paths can be 166 | retreived using font_middleware.urlToPaths. This information is useful to tools like connect-cachify that need access to the font file to create a caching hash. 168 | 169 | 170 |

    Creating a Font Pack

    171 | A font pack is an npm module like any other node library. Creating a new font pack is similar to creating any npm module. 172 | 173 |
      174 |
    1. 175 | Install connect-fonts-tools and run its scripts/setup utility. 176 |
      177 | npm install connect-fonts-tools
      178 | cd node_modules/connect-fonts-tools
      179 | ./scripts/setup
      180 |
    2. 181 | 182 |
    3. 183 | Call scripts/create_fontpack from connect-font-tools with the source directory, the target directory, and the pack name. 184 |
      185 | connect-fonts-tools/scripts/create_fontpack --pn <pack_name> \
      186 |     --sp <source_path> --tp <target_path>
      187 | If the font pack is for public use, specify the additional parameters to be placed inside the font pack's package.json and README.md files. 188 |
      189 | connect-fonts-tools/scripts/create_fontpack --pn <pack_name> \
      190 |     --ph <pack_homepage_url> --pr <pack_repo_url> \
      191 |     --pb <pack_bugtracker_url> --sp <source_path> --tp <target_path>
      192 |
    4. 193 | 194 |
    5. 195 | Check whether the font pack configuration is sane and if all font files are available by calling the built in linter, script/check_font_pack.js. To use it, call check_font_pack.js with the absolute path to the font pack's configuration file. 196 |
      197 | script/check_font_pack.js ~/development/connect-fonts-opensans/index.js
      198 |
    6. 199 | 200 |
    7. 201 |

      202 | If the font pack is for public use, publish it to the npm repository 203 |

      204 | cd <target_path>
      205 | npm publish
      206 |

      207 |
    8. 208 | 209 |
    9. 210 |

      211 | Install the pack using npm into your project: 212 |

      213 | npm install <pack_name>
      214 |

      215 |

      216 | Local font packs can be installed to another local project directory: 217 |

      218 | cd <target_project_dir>
      219 | npm install <font_pack_directory>
      220 |

      221 |
    10. 222 |
    223 | 224 |

    Code

    225 | The source is available on GitHub at https://github.com/shane-tomlinson/connect-fonts. The npm module can be installed using npm install connect-fonts. 226 | 227 |

    Author

    228 | 238 | 239 |

    Getting involved

    240 | MOAR font packs! See connect-fonts-tools for tools to make font pack creation 243 | easy. connect-fonts-opensans is an example of a finished font pack. 245 | 246 | Any updates to connect-fonts are appreciated. All submissions will be reviewed and considered for merge. 247 | 248 |

    License

    249 | This software is available under version 2.0 of the MPL: 250 | 251 |

    252 | https://www.mozilla.org/MPL/ 253 |

    254 | 255 | 256 | {% endblock %} 257 | 258 | --------------------------------------------------------------------------------