comparison static/javascript/typeface.js @ 248:53a5e100b497

Convert the frontpage, except for the download button.
author Steve Losh <steve@stevelosh.com>
date Wed, 23 Sep 2009 20:34:42 -0400
parents
children 84f2a6a16bd2
comparison
equal deleted inserted replaced
247:3e6869f76b8d 248:53a5e100b497
1 /*****************************************************************
2
3 typeface.js, version 0.11 | typefacejs.neocracy.org
4
5 Copyright (c) 2008, David Chester davidchester@gmx.net
6
7 Permission is hereby granted, free of charge, to any person
8 obtaining a copy of this software and associated documentation
9 files (the "Software"), to deal in the Software without
10 restriction, including without limitation the rights to use,
11 copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following
14 conditions:
15
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 OTHER DEALINGS IN THE SOFTWARE.
27
28 *****************************************************************/
29
30 (function() {
31
32 var _typeface_js = {
33
34 faces: {},
35
36 loadFace: function(typefaceData) {
37
38 var familyName = typefaceData.familyName.toLowerCase();
39
40 if (!this.faces[familyName]) {
41 this.faces[familyName] = {};
42 }
43 if (!this.faces[familyName][typefaceData.cssFontWeight]) {
44 this.faces[familyName][typefaceData.cssFontWeight] = {};
45 }
46
47 var face = this.faces[familyName][typefaceData.cssFontWeight][typefaceData.cssFontStyle] = typefaceData;
48 face.loaded = true;
49 },
50
51 log: {
52 debug: function(message) {
53 var typefaceConsole = document.getElementById('typeface-console');
54 if (typefaceConsole)
55 typefaceConsole.innerHTML += 'DEBUG: ' + message + "<br>";
56 },
57
58 error: function(message) {
59 var typefaceConsole = document.getElementById('typeface-console');
60 if (typefaceConsole)
61 typefaceConsole.innerHTML += 'ERROR: ' + message + "<br>";
62 }
63 },
64
65 pixelsFromPoints: function(face, style, points, dimension) {
66 var pixels = points * parseInt(style.fontSize) * 72 / (face.resolution * 100);
67 if (dimension == 'horizontal' && style.fontStretchPercent) {
68 pixels *= style.fontStretchPercent;
69 }
70 return pixels;
71 },
72
73 pointsFromPixels: function(face, style, pixels, dimension) {
74 var points = pixels * face.resolution / (parseInt(style.fontSize) * 72 / 100);
75 if (dimension == 'horizontal' && style.fontStretchPrecent) {
76 points *= style.fontStretchPercent;
77 }
78 return points;
79 },
80
81 cssFontWeightMap: {
82 normal: 'normal',
83 bold: 'bold',
84 400: 'normal',
85 700: 'bold'
86 },
87
88 cssFontStretchMap: {
89 'ultra-condensed': 0.55,
90 'extra-condensed': 0.77,
91 'condensed': 0.85,
92 'semi-condensed': 0.93,
93 'normal': 1,
94 'semi-expanded': 1.07,
95 'expanded': 1.15,
96 'extra-expanded': 1.23,
97 'ultra-expanded': 1.45,
98 'default': 1
99 },
100
101 fallbackCharacter: '.',
102
103 getTextExtents: function(face, style, text) {
104 var extentX = 0;
105 var extentY = 0;
106 var horizontalAdvance;
107
108 for (var i = 0; i < text.length; i++) {
109 var glyph = face.glyphs[text.charAt(i)] ? face.glyphs[text.charAt(i)] : face.glyphs[this.fallbackCharacter];
110 var letterSpacingAdjustment = this.pointsFromPixels(face, style, style.letterSpacing);
111 extentX += Math.max(glyph.ha, glyph.x_max) + letterSpacingAdjustment;
112 horizontalAdvance += glyph.ha + letterSpacingAdjustment;
113 }
114 return {
115 x: extentX,
116 y: extentY,
117 ha: horizontalAdvance
118
119 };
120 },
121
122 pixelsFromCssAmount: function(cssAmount, defaultValue) {
123
124 var matches = undefined;
125
126 if (cssAmount == 'normal') {
127 return defaultValue;
128
129 } else if (matches = cssAmount.match(/([\-\d+\.]+)px/)) {
130 return matches[1];
131
132 } else if (matches = cssAmount.match(/([\-\d\.]+)pt/)) {
133 return matches[1] * 100 / 75;
134 } else {
135 return defaultValue;
136 }
137 },
138
139 getRenderedText: function(e) {
140
141 var browserStyle = window.getComputedStyle ?
142 document.defaultView.getComputedStyle(e.parentNode, '') :
143 e.parentNode.currentStyle ?
144 e.parentNode.currentStyle :
145 { color: '#ff0000', fontSize: 12, fontFamily: 'arial' };
146
147 var inlineStyleAttribute = e.parentNode.getAttribute('style');
148 if (inlineStyleAttribute && typeof(inlineStyleAttribute) == 'object') {
149 inlineStyleAttribute = inlineStyleAttribute.cssText;
150 }
151
152 if (inlineStyleAttribute) {
153
154 var inlineStyleDeclarations = inlineStyleAttribute.split(/\s*\;\s*/);
155
156 var inlineStyle = {};
157 for (var i = 0; i < inlineStyleDeclarations.length; i++) {
158 var declaration = inlineStyleDeclarations[i];
159 var declarationOperands = declaration.split(/\s*\:\s*/);
160 inlineStyle[declarationOperands[0]] = declarationOperands[1];
161 }
162 }
163
164 var style = {
165 color: browserStyle.color,
166 fontFamily: browserStyle.fontFamily.split(/\s*,\s*/)[0].replace(/(^"|^'|'$|"$)/g, '').toLowerCase(),
167 fontSize: this.pixelsFromCssAmount(browserStyle.fontSize, 12),
168 fontWeight: this.cssFontWeightMap[browserStyle.fontWeight],
169 fontStyle: browserStyle.fontStyle ? browserStyle.fontStyle : 'normal',
170 fontStretchPercent: this.cssFontStretchMap[inlineStyle && inlineStyle['font-stretch'] ? inlineStyle['font-stretch'] : 'default'],
171 textDecoration: browserStyle.textDecoration,
172 lineHeight: this.pixelsFromCssAmount(browserStyle.lineHeight, 'normal'),
173 letterSpacing: this.pixelsFromCssAmount(browserStyle.letterSpacing, 0)
174 };
175
176 var face;
177 if (
178 this.faces[style.fontFamily] &&
179 this.faces[style.fontFamily][style.fontWeight]
180 ) {
181 face = this.faces[style.fontFamily][style.fontWeight][style.fontStyle];
182 }
183
184 if (!face) {
185 return;
186 }
187
188 var text = e.nodeValue.replace(/(?:^\s+|\s+$)/g, '');
189 text = text.replace(/\s+/g, ' ');
190 var words = text.split(/\s/);
191
192 var containerSpan = document.createElement('span');
193
194 for (var i = 0; i < words.length; i++) {
195 var word = words[i];
196 var delimiter = i == words.length - 1 ? '' : ' ';
197 var vectorElement = this.renderWord(face, style, word + delimiter);
198 if (vectorElement)
199 containerSpan.appendChild(vectorElement);
200 }
201
202 return containerSpan;
203 },
204
205 renderDocument: function(callback) { // args: onComplete
206
207 if (this.renderDocumentLock)
208 return;
209
210 this.renderDocumentLock = true;
211
212 if (!callback)
213 callback = function(e) { e.style.visibility = 'visible' };
214
215 var elements = document.getElementsByTagName('*');
216
217 var elementsLength = elements.length;
218 for (var i = 0; i < elements.length; i++) {
219 if (elements[i].className.match(/(^|\s)typeface-js(\s|$)/) || elements[i].tagName.match(/^(H1|H2|H3|H4|H5|H6)$/)) {
220 this.replaceText(elements[i]);
221 if (typeof callback == 'function') {
222 callback(elements[i]);
223 }
224 }
225 }
226 },
227
228 replaceText: function(e) {
229 if (e.hasChildNodes()) {
230 var childNodes = [];
231 for (var i = 0; i < e.childNodes.length; i++) {
232 childNodes[i] = e.childNodes[i];
233 }
234 for (var i = 0; i < childNodes.length; i++) {
235 this.replaceText(childNodes[i]);
236 }
237 }
238
239 if (e.nodeType == 3 && e.nodeValue.match(/\S/)) {
240 var parentNode = e.parentNode;
241
242 var renderedText = this.getRenderedText(e);
243
244 if (renderedText) {
245 parentNode.insertBefore(renderedText, e);
246 parentNode.removeChild(e);
247 }
248 }
249 },
250
251 applyElementVerticalMetrics: function(face, style, e) {
252
253 var boundingBoxAdjustmentTop = this.pixelsFromPoints(face, style, face.ascender - Math.max(face.boundingBox.yMax, face.ascender));
254 var boundingBoxAdjustmentBottom = this.pixelsFromPoints(face, style, Math.min(face.boundingBox.yMin, face.descender) - face.descender);
255
256 var cssLineHeightAdjustment = 0;
257 if (style.lineHeight != 'normal') {
258 cssLineHeightAdjustment = style.lineHeight - this.pixelsFromPoints(face, style, face.lineHeight);
259 }
260
261 var marginTop = Math.round(boundingBoxAdjustmentTop + cssLineHeightAdjustment / 2);
262 var marginBottom = Math.round(boundingBoxAdjustmentBottom + cssLineHeightAdjustment / 2);
263
264 e.style.marginTop = marginTop + 'px';
265 e.style.marginBottom = marginBottom + 'px';
266
267 },
268
269 vectorBackends: {
270
271 canvas: {
272
273 _initializeSurface: function(face, style, text) {
274
275 var extents = this.getTextExtents(face, style, text);
276
277 var canvas = document.createElement('canvas');
278 canvas.innerHTML = text;
279
280 this.applyElementVerticalMetrics(face, style, canvas);
281 canvas.height = Math.round(this.pixelsFromPoints(face, style, face.lineHeight));
282
283 canvas.width = Math.round(this.pixelsFromPoints(face, style, extents.x, 'horizontal'));
284
285 if (extents.x > extents.ha)
286 canvas.style.marginRight = Math.round(this.pixelsFromPoints(face, style, extents.x - extents.ha, 'horizontal')) + 'px';
287
288 var ctx = canvas.getContext('2d');
289
290 var pointScale = this.pixelsFromPoints(face, style, 1);
291 ctx.scale(pointScale * style.fontStretchPercent, -1 * pointScale);
292 ctx.translate(0, -1 * face.ascender);
293 ctx.fillStyle = style.color;
294
295 return { context: ctx, canvas: canvas };
296 },
297
298 _renderGlyph: function(ctx, face, char, style) {
299
300 var glyph = face.glyphs[char];
301
302 if (!glyph) {
303 //this.log.error("glyph not defined: " + char);
304 return this.renderGlyph(ctx, face, this.fallbackCharacter, style);
305 }
306
307 if (glyph.o) {
308
309 var outline;
310 if (glyph.cached_outline) {
311 outline = glyph.cached_outline;
312 } else {
313 outline = glyph.o.split(' ');
314 glyph.cached_outline = outline;
315 }
316
317 for (var i = 0; i < outline.length; ) {
318
319 var action = outline[i++];
320
321 switch(action) {
322 case 'm':
323 ctx.moveTo(outline[i++], outline[i++]);
324 break;
325 case 'l':
326 ctx.lineTo(outline[i++], outline[i++]);
327 break;
328
329 case 'q':
330 var cpx = outline[i++];
331 var cpy = outline[i++];
332 ctx.quadraticCurveTo(outline[i++], outline[i++], cpx, cpy);
333 break;
334 }
335 }
336 }
337 if (glyph.ha) {
338 var letterSpacingPoints =
339 style.letterSpacing && style.letterSpacing != 'normal' ?
340 this.pointsFromPixels(face, style, style.letterSpacing) :
341 0;
342
343 ctx.translate(glyph.ha + letterSpacingPoints, 0);
344 }
345 },
346
347 _renderWord: function(face, style, text) {
348 var surface = this.initializeSurface(face, style, text);
349 var ctx = surface.context;
350 var canvas = surface.canvas;
351 ctx.beginPath();
352 ctx.save();
353
354 var chars = text.split('');
355 for (var i = 0; i < chars.length; i++) {
356 var char = chars[i];
357 this.renderGlyph(ctx, face, char, style);
358 }
359
360 ctx.fill();
361
362 if (style.textDecoration == 'underline') {
363
364 ctx.beginPath();
365 ctx.moveTo(0, face.underlinePosition);
366 ctx.restore();
367 ctx.lineTo(0, face.underlinePosition);
368 ctx.strokeStyle = style.color;
369 ctx.lineWidth = face.underlineThickness;
370 ctx.stroke();
371 }
372
373 return ctx.canvas;
374 }
375 },
376
377 vml: {
378
379 _initializeSurface: function(face, style, text) {
380
381 var shape = document.createElement('v:shape');
382
383 var extents = this.getTextExtents(face, style, text);
384
385 shape.style.width = style.fontSize + 'px';
386 shape.style.height = style.fontSize + 'px';
387
388 if (extents.x > extents.ha) {
389 shape.style.marginRight = this.pixelsFromPoints(face, style, extents.x - extents.ha, 'horizontal') + 'px';
390 }
391
392 this.applyElementVerticalMetrics(face, style, shape);
393
394 shape.coordsize = (face.resolution * 100 / style.fontStretchPercent / 72 ) + "," + (face.resolution * 100 / 72);
395
396 shape.coordorigin = '0,' + face.ascender;
397 shape.style.flip = 'y';
398
399 shape.fillColor = style.color;
400 shape.stroked = false;
401
402 shape.path = 'hh m 0,' + face.ascender + ' l 0,' + face.descender + ' ';
403
404 return shape;
405 },
406
407 _renderGlyph: function(shape, face, char, offsetX, style) {
408
409 var glyph = face.glyphs[char];
410
411 if (!glyph) {
412 //this.log.error("glyph not defined: " + char);
413 this.renderGlyph(shape, face, this.fallbackCharacter, offsetX, style);
414 }
415
416 var vmlSegments = [];
417
418 if (glyph.o) {
419
420 var outline;
421 if (glyph.cached_outline) {
422 outline = glyph.cached_outline;
423 } else {
424 outline = glyph.o.split(' ');
425 glyph.cached_outline = outline;
426 }
427
428 var prevAction, prevX, prevY;
429
430 var i;
431 for (i = 0; i < outline.length;) {
432
433 var action = outline[i++];
434 var vmlSegment = '';
435
436 var x = Math.round(outline[i++]) + offsetX;
437 var y = Math.round(outline[i++]);
438
439 switch(action) {
440 case 'm':
441 vmlSegment = (vmlSegments.length ? 'x ' : '') + 'm ' + x + ',' + y;
442 break;
443
444 case 'l':
445 vmlSegment = 'l ' + x + ',' + y;
446 break;
447
448 case 'q':
449 var cpx = Math.round(outline[i++]) + offsetX;
450 var cpy = Math.round(outline[i++]);
451
452 var cp1x = Math.round(prevX + 2.0 / 3.0 * (cpx - prevX));
453 var cp1y = Math.round(prevY + 2.0 / 3.0 * (cpy - prevY));
454
455 var cp2x = Math.round(cp1x + (x - prevX) / 3.0);
456 var cp2y = Math.round(cp1y + (y - prevY) / 3.0);
457
458 vmlSegment = 'c ' + cp1x + ',' + cp1y + ',' + cp2x + ',' + cp2y + ',' + x + ',' + y;
459 break;
460 }
461
462 prevAction = action;
463 prevX = x;
464 prevY = y;
465
466 if (vmlSegment.length) {
467 vmlSegments.push(vmlSegment);
468 }
469 }
470 }
471
472 vmlSegments.push('x', 'e');
473 return vmlSegments.join(' ');
474 },
475
476 _renderWord: function(face, style, text) {
477 var offsetX = 0;
478 var shape = this.initializeSurface(face, style, text);
479
480 var letterSpacingPoints =
481 style.letterSpacing && style.letterSpacing != 'normal' ?
482 this.pointsFromPixels(face, style, style.letterSpacing) :
483 0;
484
485 letterSpacingPoints = Math.round(letterSpacingPoints);
486 var chars = text.split('');
487 for (var i = 0; i < chars.length; i++) {
488 var char = chars[i];
489 shape.path += this.renderGlyph(shape, face, char, offsetX, style) + ' ';
490 offsetX += face.glyphs[char].ha + letterSpacingPoints ;
491 }
492
493 shape.style.marginRight = this.pixelsFromPoints(face, style, face.glyphs[' '].ha) + 'px';
494 return shape;
495 }
496
497 }
498
499 },
500
501 setVectorBackend: function(backend) {
502
503 var backendFunctions = ['renderWord', 'initializeSurface', 'renderGlyph'];
504
505 for (var i = 0; i < backendFunctions.length; i++) {
506 var backendFunction = backendFunctions[i];
507 this[backendFunction] = this.vectorBackends[backend]['_' + backendFunction];
508 }
509 }
510 };
511
512 // IE won't accept real selectors...
513 var typefaceSelectors = ['.typeface-js', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
514
515 if (document.createStyleSheet) {
516 var styleSheet = document.createStyleSheet();
517 for (var i = 0; i < typefaceSelectors.length; i++) {
518 var selector = typefaceSelectors[i];
519 styleSheet.addRule(selector, 'visibility: hidden');
520 }
521
522 } else if (document.styleSheets && document.styleSheets.length) {
523 var styleSheet = document.styleSheets[0];
524 document.styleSheets[0].insertRule(typefaceSelectors.join(',') + ' { visibility: hidden; }', styleSheet.cssRules.length);
525 }
526
527 var backend = !!(window.attachEvent && !window.opera) ? 'vml' : window.CanvasRenderingContext2D || document.createElement('canvas').getContext ? 'canvas' : null;
528
529 if (backend == 'vml') {
530
531 document.namespaces.add("v");
532
533 var styleSheet = document.createStyleSheet();
534 styleSheet.addRule('v\\:*', "behavior: url(#default#VML); display: inline-block;");
535 }
536
537 _typeface_js.setVectorBackend(backend);
538
539 window._typeface_js = _typeface_js;
540
541 // based on code by Dean Edwards / Matthias Miller / John Resig
542
543 function typefaceInit() {
544
545 // quit if this function has already been called
546 if (arguments.callee.done) return;
547
548 // flag this function so we don't do the same thing twice
549 arguments.callee.done = true;
550
551 // kill the timer
552 if (window._typefaceTimer) clearInterval(_typefaceTimer);
553
554 _typeface_js.renderDocument( function(e) { e.style.visibility = 'visible' } );
555 };
556
557 if (/WebKit/i.test(navigator.userAgent)) {
558
559 var _typefaceTimer = setInterval(function() {
560 if (/loaded|complete/.test(document.readyState)) {
561 typefaceInit();
562 }
563 }, 10);
564 }
565
566 if (document.addEventListener) {
567 window.addEventListener('DOMContentLoaded', function() { typefaceInit() }, false);
568 }
569
570 /*@cc_on @*/
571 /*@if (@_win32)
572
573 document.write("<script id=__ie_onload_typeface defer src=javascript:void(0)><\/script>");
574 var script = document.getElementById("__ie_onload_typeface");
575 script.onreadystatechange = function() {
576 if (this.readyState == "complete") {
577 typefaceInit();
578 }
579 };
580
581 /*@end @*/
582
583 })();