(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 |
16 | Connect/Express Font Serving Middleware
17 |
18 |
19 |
20 | {% block nav %}
21 |
22 | {% block navitems %}
23 |
24 | Families List
25 |
26 | {% endblock %}
27 |
28 | Top
29 |
30 |
31 | {% endblock %}
32 |
33 |
34 |
35 | {% block content %}{% endblock %}
36 |
37 |
38 |
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 |
5 |
6 |
7 | {% endif %}
8 |
9 | {% if packConfig.font_common %}
10 |
11 | Family Information
12 |
13 |
14 | {% if packConfig.font_common.description %}
15 |
16 | Description
17 | {{ packConfig.font_common.description }}
18 |
19 | {% endif %}
20 | {% if packConfig.font_common.copyright %}
21 |
22 | Copyright
23 | {{ packConfig.font_common.copyright }}
24 |
25 | {% endif %}
26 | {% if packConfig.font_common.trademark %}
27 |
28 | Trademark
29 | {{ packConfig.font_common.trademark }}
30 |
31 | {% endif %}
32 | {% if packConfig.font_common.manufacturer %}
33 |
34 | Manufacturer
35 | {{ packConfig.font_common.manufacturer }}
36 |
37 | {% endif %}
38 | {% if packConfig.font_common.url_vendor %}
39 |
40 | Manufacturer URL
41 |
42 |
43 | {{ packConfig.font_common.url_vendor }}
44 |
45 |
46 |
47 | {% endif %}
48 | {% if packConfig.font_common.designer %}
49 |
50 | Designer
51 | {{ packConfig.font_common.designer }}
52 |
53 | {% endif %}
54 | {% if packConfig.font_common.url_designer %}
55 |
56 | Designer URL
57 |
58 |
59 | {{ packConfig.font_common.url_designer }}
60 |
61 |
62 |
63 | {% endif %}
64 | {% if packConfig.license.name %}
65 |
66 | License
67 |
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 |
78 |
79 | {% endif %}
80 |
81 |
82 | {% endif %}
83 |
84 | {% if packConfig.package %}
85 |
86 | Package Information
87 |
125 |
126 | {% endif %}
127 |
128 |
129 | {% if packConfig.author %}
130 |
131 | Author Information
132 |
133 | {% if packConfig.author.name %}
134 |
135 | Name
136 | {{ packConfig.author.name }}
137 |
138 | {% endif %}
139 | {% if packConfig.author.urls%}
140 |
141 | Homepage
142 |
143 | {% for url in packConfig.author.urls %}
144 |
145 |
146 | {{ url }}
147 |
148 |
149 | {% endfor %}
150 |
151 |
152 | {% endif %}
153 | {% if packConfig.author.emails %}
154 |
155 | Emails
156 |
157 |
158 | {% for email in packConfig.author.emails %}
159 | {{ email }}
160 | {% endfor %}
161 |
162 |
163 |
164 | {% endif %}
165 | {% if packConfig.author.githubs %}
166 |
167 | GitHub Repos
168 |
169 | {% for github in packConfig.author.githubs %}
170 |
171 |
172 | {{ github }}
173 |
174 |
175 | {% endfor %}
176 |
177 |
178 | {% endif %}
179 | {% if packConfig.author.twitter %}
180 |
181 | Twitter
182 |
183 |
184 | {{ packConfig.author.twitter }}
185 |
186 |
187 |
188 | {% endif %}
189 |
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 |
64 | Install the middleware
65 |
66 | npm install connect-fonts
67 |
68 |
69 | Install a font-pack
70 |
71 | npm install connect-fonts-opensans
72 |
73 | Include connect-fonts in your server code.
74 |
75 | const font_middleware = require("connect-fonts");
76 |
77 |
78 |
79 | Include the font packs.
80 |
81 | const opensans = require("connect-fonts-opensans");
82 |
83 |
84 |
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 |
109 |
110 |
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 |
117 |
118 |
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 |
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 |
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 |
181 |
182 |
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 |
193 |
194 |
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 |
199 |
200 |
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 |
208 |
209 |
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 |
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 |
--------------------------------------------------------------------------------