comparison static/javascript/typeface.js @ 277:84f2a6a16bd2

Update typeface.js from version 0.11 to 0.13 Fixes issue #5 (Rendering problems in IE8 on Windows 7) reported by Maxim Khitrov.
author David Soria Parra <dsp@php.net>
date Sun, 01 Nov 2009 20:26:16 +0100
parents 53a5e100b497
children b9105ed958a4
comparison
equal deleted inserted replaced
276:6916a7e4fbf0 277:84f2a6a16bd2
1 /***************************************************************** 1 /*****************************************************************
2 2
3 typeface.js, version 0.11 | typefacejs.neocracy.org 3 typeface.js, version 0.13 | typefacejs.neocracy.org
4 4
5 Copyright (c) 2008, David Chester davidchester@gmx.net 5 Copyright (c) 2008 - 2009, David Chester davidchester@gmx.net
6 6
7 Permission is hereby granted, free of charge, to any person 7 Permission is hereby granted, free of charge, to any person
8 obtaining a copy of this software and associated documentation 8 obtaining a copy of this software and associated documentation
9 files (the "Software"), to deal in the Software without 9 files (the "Software"), to deal in the Software without
10 restriction, including without limitation the rights to use, 10 restriction, including without limitation the rights to use,
46 46
47 var face = this.faces[familyName][typefaceData.cssFontWeight][typefaceData.cssFontStyle] = typefaceData; 47 var face = this.faces[familyName][typefaceData.cssFontWeight][typefaceData.cssFontStyle] = typefaceData;
48 face.loaded = true; 48 face.loaded = true;
49 }, 49 },
50 50
51 log: { 51 log: function(message) {
52 debug: function(message) { 52
53 var typefaceConsole = document.getElementById('typeface-console'); 53 if (this.quiet) {
54 if (typefaceConsole) 54 return;
55 typefaceConsole.innerHTML += 'DEBUG: ' + message + "<br>"; 55 }
56 }, 56
57 57 message = "typeface.js: " + message;
58 error: function(message) { 58
59 var typefaceConsole = document.getElementById('typeface-console'); 59 if (this.customLogFn) {
60 if (typefaceConsole) 60 this.customLogFn(message);
61 typefaceConsole.innerHTML += 'ERROR: ' + message + "<br>"; 61
62 } 62 } else if (window.console && window.console.log) {
63 window.console.log(message);
64 }
65
63 }, 66 },
64 67
65 pixelsFromPoints: function(face, style, points, dimension) { 68 pixelsFromPoints: function(face, style, points, dimension) {
66 var pixels = points * parseInt(style.fontSize) * 72 / (face.resolution * 100); 69 var pixels = points * parseInt(style.fontSize) * 72 / (face.resolution * 100);
67 if (dimension == 'horizontal' && style.fontStretchPercent) { 70 if (dimension == 'horizontal' && style.fontStretchPercent) {
98 'default': 1 101 'default': 1
99 }, 102 },
100 103
101 fallbackCharacter: '.', 104 fallbackCharacter: '.',
102 105
106 configure: function(args) {
107 var configurableOptionNames = [ 'customLogFn', 'customClassNameRegex', 'customTypefaceElementsList', 'quiet', 'verbose', 'disableSelection' ];
108
109 for (var i = 0; i < configurableOptionNames.length; i++) {
110 var optionName = configurableOptionNames[i];
111 if (args[optionName]) {
112 if (optionName == 'customLogFn') {
113 if (typeof args[optionName] != 'function') {
114 throw "customLogFn is not a function";
115 } else {
116 this.customLogFn = args.customLogFn;
117 }
118 } else {
119 this[optionName] = args[optionName];
120 }
121 }
122 }
123 },
124
103 getTextExtents: function(face, style, text) { 125 getTextExtents: function(face, style, text) {
104 var extentX = 0; 126 var extentX = 0;
105 var extentY = 0; 127 var extentY = 0;
106 var horizontalAdvance; 128 var horizontalAdvance;
107 129
108 for (var i = 0; i < text.length; i++) { 130 var textLength = text.length;
131 for (var i = 0; i < textLength; i++) {
109 var glyph = face.glyphs[text.charAt(i)] ? face.glyphs[text.charAt(i)] : face.glyphs[this.fallbackCharacter]; 132 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); 133 var letterSpacingAdjustment = this.pointsFromPixels(face, style, style.letterSpacing);
111 extentX += Math.max(glyph.ha, glyph.x_max) + letterSpacingAdjustment; 134 extentX += Math.max(glyph.ha, glyph.x_max) + letterSpacingAdjustment;
112 horizontalAdvance += glyph.ha + letterSpacingAdjustment; 135 horizontalAdvance += glyph.ha + letterSpacingAdjustment;
113 } 136 }
117 ha: horizontalAdvance 140 ha: horizontalAdvance
118 141
119 }; 142 };
120 }, 143 },
121 144
122 pixelsFromCssAmount: function(cssAmount, defaultValue) { 145 pixelsFromCssAmount: function(cssAmount, defaultValue, element) {
123 146
124 var matches = undefined; 147 var matches = undefined;
125 148
126 if (cssAmount == 'normal') { 149 if (cssAmount == 'normal') {
127 return defaultValue; 150 return defaultValue;
128 151
129 } else if (matches = cssAmount.match(/([\-\d+\.]+)px/)) { 152 } else if (matches = cssAmount.match(/([\-\d+\.]+)px/)) {
130 return matches[1]; 153 return matches[1];
131 154
132 } else if (matches = cssAmount.match(/([\-\d\.]+)pt/)) {
133 return matches[1] * 100 / 75;
134 } else { 155 } else {
135 return defaultValue; 156 // thanks to Dean Edwards for this very sneaky way to get IE to convert
157 // relative values to pixel values
158
159 var pixelAmount;
160
161 var leftInlineStyle = element.style.left;
162 var leftRuntimeStyle = element.runtimeStyle.left;
163
164 element.runtimeStyle.left = element.currentStyle.left;
165
166 if (!cssAmount.match(/\d(px|pt)$/)) {
167 element.style.left = '1em';
168 } else {
169 element.style.left = cssAmount || 0;
170 }
171
172 pixelAmount = element.style.pixelLeft;
173
174 element.style.left = leftInlineStyle;
175 element.runtimeStyle.left = leftRuntimeStyle;
176
177 return pixelAmount || defaultValue;
178 }
179 },
180
181 capitalizeText: function(text) {
182 return text.replace(/(^|\s)[a-z]/g, function(match) { return match.toUpperCase() } );
183 },
184
185 getElementStyle: function(e) {
186 if (window.getComputedStyle) {
187 return window.getComputedStyle(e, '');
188
189 } else if (e.currentStyle) {
190 return e.currentStyle;
136 } 191 }
137 }, 192 },
138 193
139 getRenderedText: function(e) { 194 getRenderedText: function(e) {
140 195
141 var browserStyle = window.getComputedStyle ? 196 var browserStyle = this.getElementStyle(e.parentNode);
142 document.defaultView.getComputedStyle(e.parentNode, '') :
143 e.parentNode.currentStyle ?
144 e.parentNode.currentStyle :
145 { color: '#ff0000', fontSize: 12, fontFamily: 'arial' };
146 197
147 var inlineStyleAttribute = e.parentNode.getAttribute('style'); 198 var inlineStyleAttribute = e.parentNode.getAttribute('style');
148 if (inlineStyleAttribute && typeof(inlineStyleAttribute) == 'object') { 199 if (inlineStyleAttribute && typeof(inlineStyleAttribute) == 'object') {
149 inlineStyleAttribute = inlineStyleAttribute.cssText; 200 inlineStyleAttribute = inlineStyleAttribute.cssText;
150 } 201 }
162 } 213 }
163 214
164 var style = { 215 var style = {
165 color: browserStyle.color, 216 color: browserStyle.color,
166 fontFamily: browserStyle.fontFamily.split(/\s*,\s*/)[0].replace(/(^"|^'|'$|"$)/g, '').toLowerCase(), 217 fontFamily: browserStyle.fontFamily.split(/\s*,\s*/)[0].replace(/(^"|^'|'$|"$)/g, '').toLowerCase(),
167 fontSize: this.pixelsFromCssAmount(browserStyle.fontSize, 12), 218 fontSize: this.pixelsFromCssAmount(browserStyle.fontSize, 12, e.parentNode),
168 fontWeight: this.cssFontWeightMap[browserStyle.fontWeight], 219 fontWeight: this.cssFontWeightMap[browserStyle.fontWeight],
169 fontStyle: browserStyle.fontStyle ? browserStyle.fontStyle : 'normal', 220 fontStyle: browserStyle.fontStyle ? browserStyle.fontStyle : 'normal',
170 fontStretchPercent: this.cssFontStretchMap[inlineStyle && inlineStyle['font-stretch'] ? inlineStyle['font-stretch'] : 'default'], 221 fontStretchPercent: this.cssFontStretchMap[inlineStyle && inlineStyle['font-stretch'] ? inlineStyle['font-stretch'] : 'default'],
171 textDecoration: browserStyle.textDecoration, 222 textDecoration: browserStyle.textDecoration,
172 lineHeight: this.pixelsFromCssAmount(browserStyle.lineHeight, 'normal'), 223 lineHeight: this.pixelsFromCssAmount(browserStyle.lineHeight, 'normal', e.parentNode),
173 letterSpacing: this.pixelsFromCssAmount(browserStyle.letterSpacing, 0) 224 letterSpacing: this.pixelsFromCssAmount(browserStyle.letterSpacing, 0, e.parentNode),
225 textTransform: browserStyle.textTransform
174 }; 226 };
175 227
176 var face; 228 var face;
177 if ( 229 if (
178 this.faces[style.fontFamily] && 230 this.faces[style.fontFamily]
179 this.faces[style.fontFamily][style.fontWeight] 231 && this.faces[style.fontFamily][style.fontWeight]
180 ) { 232 ) {
181 face = this.faces[style.fontFamily][style.fontWeight][style.fontStyle]; 233 face = this.faces[style.fontFamily][style.fontWeight][style.fontStyle];
182 } 234 }
183 235
236 var text = e.nodeValue;
237
238 if (
239 e.previousSibling
240 && e.previousSibling.nodeType == 1
241 && e.previousSibling.tagName != 'BR'
242 && this.getElementStyle(e.previousSibling).display.match(/inline/)
243 ) {
244 text = text.replace(/^\s+/, ' ');
245 } else {
246 text = text.replace(/^\s+/, '');
247 }
248
249 if (
250 e.nextSibling
251 && e.nextSibling.nodeType == 1
252 && e.nextSibling.tagName != 'BR'
253 && this.getElementStyle(e.nextSibling).display.match(/inline/)
254 ) {
255 text = text.replace(/\s+$/, ' ');
256 } else {
257 text = text.replace(/\s+$/, '');
258 }
259
260 text = text.replace(/\s+/g, ' ');
261
262 if (style.textTransform && style.textTransform != 'none') {
263 switch (style.textTransform) {
264 case 'capitalize':
265 text = this.capitalizeText(text);
266 break;
267 case 'uppercase':
268 text = text.toUpperCase();
269 break;
270 case 'lowercase':
271 text = text.toLowerCase();
272 break;
273 }
274 }
275
184 if (!face) { 276 if (!face) {
277 var excerptLength = 12;
278 var textExcerpt = text.substring(0, excerptLength);
279 if (text.length > excerptLength) {
280 textExcerpt += '...';
281 }
282
283 var fontDescription = style.fontFamily;
284 if (style.fontWeight != 'normal') fontDescription += ' ' + style.fontWeight;
285 if (style.fontStyle != 'normal') fontDescription += ' ' + style.fontStyle;
286
287 this.log("couldn't find typeface font: " + fontDescription + ' for text "' + textExcerpt + '"');
185 return; 288 return;
186 } 289 }
187 290
188 var text = e.nodeValue.replace(/(?:^\s+|\s+$)/g, ''); 291 var words = text.split(/\b(?=\w)/);
189 text = text.replace(/\s+/g, ' ');
190 var words = text.split(/\s/);
191 292
192 var containerSpan = document.createElement('span'); 293 var containerSpan = document.createElement('span');
193 294 containerSpan.className = 'typeface-js-vector-container';
194 for (var i = 0; i < words.length; i++) { 295
296 var wordsLength = words.length
297 for (var i = 0; i < wordsLength; i++) {
195 var word = words[i]; 298 var word = words[i];
196 var delimiter = i == words.length - 1 ? '' : ' '; 299
197 var vectorElement = this.renderWord(face, style, word + delimiter); 300 var vector = this.renderWord(face, style, word);
198 if (vectorElement) 301
199 containerSpan.appendChild(vectorElement); 302 if (vector) {
303 containerSpan.appendChild(vector.element);
304
305 if (!this.disableSelection) {
306 var selectableSpan = document.createElement('span');
307 selectableSpan.className = 'typeface-js-selected-text';
308
309 var wordNode = document.createTextNode(word);
310 selectableSpan.appendChild(wordNode);
311
312 if (this.vectorBackend != 'vml') {
313 selectableSpan.style.marginLeft = -1 * (vector.width + 1) + 'px';
314 }
315 selectableSpan.targetWidth = vector.width;
316 //selectableSpan.style.lineHeight = 1 + 'px';
317
318 if (this.vectorBackend == 'vml') {
319 vector.element.appendChild(selectableSpan);
320 } else {
321 containerSpan.appendChild(selectableSpan);
322 }
323 }
324 }
200 } 325 }
201 326
202 return containerSpan; 327 return containerSpan;
203 }, 328 },
204 329
205 renderDocument: function(callback) { // args: onComplete 330 renderDocument: function(callback) {
206 331
207 if (this.renderDocumentLock)
208 return;
209
210 this.renderDocumentLock = true;
211
212 if (!callback) 332 if (!callback)
213 callback = function(e) { e.style.visibility = 'visible' }; 333 callback = function(e) { e.style.visibility = 'visible' };
214 334
215 var elements = document.getElementsByTagName('*'); 335 var elements = document.getElementsByTagName('*');
216 336
221 if (typeof callback == 'function') { 341 if (typeof callback == 'function') {
222 callback(elements[i]); 342 callback(elements[i]);
223 } 343 }
224 } 344 }
225 } 345 }
346
347 if (this.vectorBackend == 'vml') {
348 // lamely work around IE's quirky leaving off final dynamic shapes
349 var dummyShape = document.createElement('v:shape');
350 dummyShape.style.display = 'none';
351 document.body.appendChild(dummyShape);
352 }
226 }, 353 },
227 354
228 replaceText: function(e) { 355 replaceText: function(e) {
229 if (e.hasChildNodes()) { 356
230 var childNodes = []; 357 var childNodes = [];
231 for (var i = 0; i < e.childNodes.length; i++) { 358 var childNodesLength = e.childNodes.length;
232 childNodes[i] = e.childNodes[i]; 359
233 } 360 for (var i = 0; i < childNodesLength; i++) {
234 for (var i = 0; i < childNodes.length; i++) { 361 this.replaceText(e.childNodes[i]);
235 this.replaceText(childNodes[i]);
236 }
237 } 362 }
238 363
239 if (e.nodeType == 3 && e.nodeValue.match(/\S/)) { 364 if (e.nodeType == 3 && e.nodeValue.match(/\S/)) {
240 var parentNode = e.parentNode; 365 var parentNode = e.parentNode;
241 366
367 if (parentNode.className == 'typeface-js-selected-text') {
368 return;
369 }
370
242 var renderedText = this.getRenderedText(e); 371 var renderedText = this.getRenderedText(e);
243 372
373 if (
374 parentNode.tagName == 'A'
375 && this.vectorBackend == 'vml'
376 && this.getElementStyle(parentNode).display == 'inline'
377 ) {
378 // something of a hack, use inline-block to get IE to accept clicks in whitespace regions
379 parentNode.style.display = 'inline-block';
380 parentNode.style.cursor = 'pointer';
381 }
382
383 if (this.getElementStyle(parentNode).display == 'inline') {
384 parentNode.style.display = 'inline-block';
385 }
386
244 if (renderedText) { 387 if (renderedText) {
245 parentNode.insertBefore(renderedText, e); 388 if (parentNode.replaceChild) {
246 parentNode.removeChild(e); 389 parentNode.replaceChild(renderedText, e);
247 } 390 } else {
391 parentNode.insertBefore(renderedText, e);
392 parentNode.removeChild(e);
393 }
394 if (this.vectorBackend == 'vml') {
395 renderedText.innerHTML = renderedText.innerHTML;
396 }
397
398 var childNodesLength = renderedText.childNodes.length
399 for (var i; i < childNodesLength; i++) {
400
401 // do our best to line up selectable text with rendered text
402
403 var e = renderedText.childNodes[i];
404 if (e.hasChildNodes() && !e.targetWidth) {
405 e = e.childNodes[0];
406 }
407
408 if (e && e.targetWidth) {
409 var letterSpacingCount = e.innerHTML.length;
410 var wordSpaceDelta = e.targetWidth - e.offsetWidth;
411 var letterSpacing = wordSpaceDelta / (letterSpacingCount || 1);
412
413 if (this.vectorBackend == 'vml') {
414 letterSpacing = Math.ceil(letterSpacing);
415 }
416
417 e.style.letterSpacing = letterSpacing + 'px';
418 e.style.width = e.targetWidth + 'px';
419 }
420 }
421 }
248 } 422 }
249 }, 423 },
250 424
251 applyElementVerticalMetrics: function(face, style, e) { 425 applyElementVerticalMetrics: function(face, style, e) {
252 426
253 var boundingBoxAdjustmentTop = this.pixelsFromPoints(face, style, face.ascender - Math.max(face.boundingBox.yMax, face.ascender)); 427 if (style.lineHeight == 'normal') {
254 var boundingBoxAdjustmentBottom = this.pixelsFromPoints(face, style, Math.min(face.boundingBox.yMin, face.descender) - face.descender); 428 style.lineHeight = this.pixelsFromPoints(face, style, face.lineHeight);
255 429 }
256 var cssLineHeightAdjustment = 0; 430
257 if (style.lineHeight != 'normal') { 431 var cssLineHeightAdjustment = style.lineHeight - this.pixelsFromPoints(face, style, face.lineHeight);
258 cssLineHeightAdjustment = style.lineHeight - this.pixelsFromPoints(face, style, face.lineHeight); 432
259 } 433 e.style.marginTop = Math.round( cssLineHeightAdjustment / 2 ) + 'px';
260 434 e.style.marginBottom = Math.round( cssLineHeightAdjustment / 2) + 'px';
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 435
267 }, 436 },
268 437
269 vectorBackends: { 438 vectorBackends: {
270 439
273 _initializeSurface: function(face, style, text) { 442 _initializeSurface: function(face, style, text) {
274 443
275 var extents = this.getTextExtents(face, style, text); 444 var extents = this.getTextExtents(face, style, text);
276 445
277 var canvas = document.createElement('canvas'); 446 var canvas = document.createElement('canvas');
278 canvas.innerHTML = text; 447 if (this.disableSelection) {
279 448 canvas.innerHTML = text;
449 }
450
451 canvas.height = Math.round(this.pixelsFromPoints(face, style, face.lineHeight));
452 canvas.width = Math.round(this.pixelsFromPoints(face, style, extents.x, 'horizontal'));
453
280 this.applyElementVerticalMetrics(face, style, canvas); 454 this.applyElementVerticalMetrics(face, style, canvas);
281 canvas.height = Math.round(this.pixelsFromPoints(face, style, face.lineHeight)); 455
282
283 canvas.width = Math.round(this.pixelsFromPoints(face, style, extents.x, 'horizontal'));
284
285 if (extents.x > extents.ha) 456 if (extents.x > extents.ha)
286 canvas.style.marginRight = Math.round(this.pixelsFromPoints(face, style, extents.x - extents.ha, 'horizontal')) + 'px'; 457 canvas.style.marginRight = Math.round(this.pixelsFromPoints(face, style, extents.x - extents.ha, 'horizontal')) + 'px';
287 458
288 var ctx = canvas.getContext('2d'); 459 var ctx = canvas.getContext('2d');
289 460
312 } else { 483 } else {
313 outline = glyph.o.split(' '); 484 outline = glyph.o.split(' ');
314 glyph.cached_outline = outline; 485 glyph.cached_outline = outline;
315 } 486 }
316 487
317 for (var i = 0; i < outline.length; ) { 488 var outlineLength = outline.length;
489 for (var i = 0; i < outlineLength; ) {
318 490
319 var action = outline[i++]; 491 var action = outline[i++];
320 492
321 switch(action) { 493 switch(action) {
322 case 'm': 494 case 'm':
350 var canvas = surface.canvas; 522 var canvas = surface.canvas;
351 ctx.beginPath(); 523 ctx.beginPath();
352 ctx.save(); 524 ctx.save();
353 525
354 var chars = text.split(''); 526 var chars = text.split('');
355 for (var i = 0; i < chars.length; i++) { 527 var charsLength = chars.length;
356 var char = chars[i]; 528 for (var i = 0; i < charsLength; i++) {
357 this.renderGlyph(ctx, face, char, style); 529 this.renderGlyph(ctx, face, chars[i], style);
358 } 530 }
359 531
360 ctx.fill(); 532 ctx.fill();
361 533
362 if (style.textDecoration == 'underline') { 534 if (style.textDecoration == 'underline') {
368 ctx.strokeStyle = style.color; 540 ctx.strokeStyle = style.color;
369 ctx.lineWidth = face.underlineThickness; 541 ctx.lineWidth = face.underlineThickness;
370 ctx.stroke(); 542 ctx.stroke();
371 } 543 }
372 544
373 return ctx.canvas; 545 return { element: ctx.canvas, width: Math.floor(canvas.width) };
546
374 } 547 }
375 }, 548 },
376 549
377 vml: { 550 vml: {
378 551
380 553
381 var shape = document.createElement('v:shape'); 554 var shape = document.createElement('v:shape');
382 555
383 var extents = this.getTextExtents(face, style, text); 556 var extents = this.getTextExtents(face, style, text);
384 557
385 shape.style.width = style.fontSize + 'px'; 558 shape.style.width = shape.style.height = style.fontSize + 'px';
386 shape.style.height = style.fontSize + 'px'; 559 shape.style.marginLeft = '-1px'; // this seems suspect...
387 560
388 if (extents.x > extents.ha) { 561 if (extents.x > extents.ha) {
389 shape.style.marginRight = this.pixelsFromPoints(face, style, extents.x - extents.ha, 'horizontal') + 'px'; 562 shape.style.marginRight = this.pixelsFromPoints(face, style, extents.x - extents.ha, 'horizontal') + 'px';
390 } 563 }
391 564
392 this.applyElementVerticalMetrics(face, style, shape); 565 this.applyElementVerticalMetrics(face, style, shape);
393 566
394 shape.coordsize = (face.resolution * 100 / style.fontStretchPercent / 72 ) + "," + (face.resolution * 100 / 72); 567 var resolutionScale = face.resolution * 100 / 72;
568 shape.coordsize = (resolutionScale / style.fontStretchPercent) + "," + resolutionScale;
395 569
396 shape.coordorigin = '0,' + face.ascender; 570 shape.coordorigin = '0,' + face.ascender;
397 shape.style.flip = 'y'; 571 shape.style.flip = 'y';
398 572
399 shape.fillColor = style.color; 573 shape.fillColor = style.color;
402 shape.path = 'hh m 0,' + face.ascender + ' l 0,' + face.descender + ' '; 576 shape.path = 'hh m 0,' + face.ascender + ' l 0,' + face.descender + ' ';
403 577
404 return shape; 578 return shape;
405 }, 579 },
406 580
407 _renderGlyph: function(shape, face, char, offsetX, style) { 581 _renderGlyph: function(shape, face, char, offsetX, style, vmlSegments) {
408 582
409 var glyph = face.glyphs[char]; 583 var glyph = face.glyphs[char];
410 584
411 if (!glyph) { 585 if (!glyph) {
412 //this.log.error("glyph not defined: " + char); 586 this.log("glyph not defined: " + char);
413 this.renderGlyph(shape, face, this.fallbackCharacter, offsetX, style); 587 this.renderGlyph(shape, face, this.fallbackCharacter, offsetX, style);
588 return;
414 } 589 }
415 590
416 var vmlSegments = []; 591 vmlSegments.push('m');
417 592
418 if (glyph.o) { 593 if (glyph.o) {
419 594
420 var outline; 595 var outline, outlineLength;
596
421 if (glyph.cached_outline) { 597 if (glyph.cached_outline) {
422 outline = glyph.cached_outline; 598 outline = glyph.cached_outline;
599 outlineLength = outline.length;
423 } else { 600 } else {
424 outline = glyph.o.split(' '); 601 outline = glyph.o.split(' ');
602 outlineLength = outline.length;
603
604 for (var i = 0; i < outlineLength;) {
605
606 switch(outline[i++]) {
607 case 'q':
608 outline[i] = Math.round(outline[i++]);
609 outline[i] = Math.round(outline[i++]);
610 case 'm':
611 case 'l':
612 outline[i] = Math.round(outline[i++]);
613 outline[i] = Math.round(outline[i++]);
614 break;
615 }
616 }
617
425 glyph.cached_outline = outline; 618 glyph.cached_outline = outline;
426 } 619 }
427 620
428 var prevAction, prevX, prevY; 621 var prevX, prevY;
429 622
430 var i; 623 for (var i = 0; i < outlineLength;) {
431 for (i = 0; i < outline.length;) {
432 624
433 var action = outline[i++]; 625 var action = outline[i++];
434 var vmlSegment = ''; 626
435 627 var x = outline[i++] + offsetX;
436 var x = Math.round(outline[i++]) + offsetX; 628 var y = outline[i++];
437 var y = Math.round(outline[i++]);
438 629
439 switch(action) { 630 switch(action) {
440 case 'm': 631 case 'm':
441 vmlSegment = (vmlSegments.length ? 'x ' : '') + 'm ' + x + ',' + y; 632 vmlSegments.push('xm ', x, ',', y);
442 break; 633 break;
443 634
444 case 'l': 635 case 'l':
445 vmlSegment = 'l ' + x + ',' + y; 636 vmlSegments.push('l ', x, ',', y);
446 break; 637 break;
447 638
448 case 'q': 639 case 'q':
449 var cpx = Math.round(outline[i++]) + offsetX; 640 var cpx = outline[i++] + offsetX;
450 var cpy = Math.round(outline[i++]); 641 var cpy = outline[i++];
451 642
452 var cp1x = Math.round(prevX + 2.0 / 3.0 * (cpx - prevX)); 643 var cp1x = Math.round(prevX + 2.0 / 3.0 * (cpx - prevX));
453 var cp1y = Math.round(prevY + 2.0 / 3.0 * (cpy - prevY)); 644 var cp1y = Math.round(prevY + 2.0 / 3.0 * (cpy - prevY));
454 645
455 var cp2x = Math.round(cp1x + (x - prevX) / 3.0); 646 var cp2x = Math.round(cp1x + (x - prevX) / 3.0);
456 var cp2y = Math.round(cp1y + (y - prevY) / 3.0); 647 var cp2y = Math.round(cp1y + (y - prevY) / 3.0);
457 648
458 vmlSegment = 'c ' + cp1x + ',' + cp1y + ',' + cp2x + ',' + cp2y + ',' + x + ',' + y; 649 vmlSegments.push('c ', cp1x, ',', cp1y, ',', cp2x, ',', cp2y, ',', x, ',', y);
459 break; 650 break;
460 } 651 }
461 652
462 prevAction = action;
463 prevX = x; 653 prevX = x;
464 prevY = y; 654 prevY = y;
465
466 if (vmlSegment.length) {
467 vmlSegments.push(vmlSegment);
468 }
469 } 655 }
470 } 656 }
471 657
472 vmlSegments.push('x', 'e'); 658 vmlSegments.push('x e');
473 return vmlSegments.join(' '); 659 return vmlSegments;
474 }, 660 },
475 661
476 _renderWord: function(face, style, text) { 662 _renderWord: function(face, style, text) {
477 var offsetX = 0; 663 var offsetX = 0;
478 var shape = this.initializeSurface(face, style, text); 664 var shape = this.initializeSurface(face, style, text);
482 this.pointsFromPixels(face, style, style.letterSpacing) : 668 this.pointsFromPixels(face, style, style.letterSpacing) :
483 0; 669 0;
484 670
485 letterSpacingPoints = Math.round(letterSpacingPoints); 671 letterSpacingPoints = Math.round(letterSpacingPoints);
486 var chars = text.split(''); 672 var chars = text.split('');
673 var vmlSegments = [];
487 for (var i = 0; i < chars.length; i++) { 674 for (var i = 0; i < chars.length; i++) {
488 var char = chars[i]; 675 var char = chars[i];
489 shape.path += this.renderGlyph(shape, face, char, offsetX, style) + ' '; 676 vmlSegments = this.renderGlyph(shape, face, char, offsetX, style, vmlSegments);
490 offsetX += face.glyphs[char].ha + letterSpacingPoints ; 677 offsetX += face.glyphs[char].ha + letterSpacingPoints ;
491 } 678 }
492 679
493 shape.style.marginRight = this.pixelsFromPoints(face, style, face.glyphs[' '].ha) + 'px'; 680 // make sure to preserve trailing whitespace
494 return shape; 681 shape.path += vmlSegments.join('') + 'm ' + offsetX + ' 0 l ' + offsetX + ' ' + face.ascender;
682
683 return {
684 element: shape,
685 width: Math.floor(this.pixelsFromPoints(face, style, offsetX, 'horizontal'))
686 };
495 } 687 }
496 688
497 } 689 }
498 690
499 }, 691 },
500 692
501 setVectorBackend: function(backend) { 693 setVectorBackend: function(backend) {
502 694
695 this.vectorBackend = backend;
503 var backendFunctions = ['renderWord', 'initializeSurface', 'renderGlyph']; 696 var backendFunctions = ['renderWord', 'initializeSurface', 'renderGlyph'];
504 697
505 for (var i = 0; i < backendFunctions.length; i++) { 698 for (var i = 0; i < backendFunctions.length; i++) {
506 var backendFunction = backendFunctions[i]; 699 var backendFunction = backendFunctions[i];
507 this[backendFunction] = this.vectorBackends[backend]['_' + backendFunction]; 700 this[backendFunction] = this.vectorBackends[backend]['_' + backendFunction];
508 } 701 }
702 },
703
704 initialize: function() {
705
706 // quit if this function has already been called
707 if (arguments.callee.done) return;
708
709 // flag this function so we don't do the same thing twice
710 arguments.callee.done = true;
711
712 // kill the timer
713 if (window._typefaceTimer) clearInterval(_typefaceTimer);
714
715 this.renderDocument( function(e) { e.style.visibility = 'visible' } );
716
509 } 717 }
718
510 }; 719 };
511 720
512 // IE won't accept real selectors... 721 // IE won't accept real selectors...
513 var typefaceSelectors = ['.typeface-js', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; 722 var typefaceSelectors = ['.typeface-js', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
514 723
515 if (document.createStyleSheet) { 724 if (document.createStyleSheet) {
725
516 var styleSheet = document.createStyleSheet(); 726 var styleSheet = document.createStyleSheet();
517 for (var i = 0; i < typefaceSelectors.length; i++) { 727 for (var i = 0; i < typefaceSelectors.length; i++) {
518 var selector = typefaceSelectors[i]; 728 var selector = typefaceSelectors[i];
519 styleSheet.addRule(selector, 'visibility: hidden'); 729 styleSheet.addRule(selector, 'visibility: hidden');
520 } 730 }
521 731
732 styleSheet.addRule(
733 '.typeface-js-selected-text',
734 '-ms-filter: \
735 "Chroma(color=black) \
736 progid:DXImageTransform.Microsoft.MaskFilter(Color=white) \
737 progid:DXImageTransform.Microsoft.MaskFilter(Color=blue) \
738 alpha(opacity=30)" !important; \
739 color: black; \
740 font-family: Modern; \
741 position: absolute; \
742 white-space: pre; \
743 filter: alpha(opacity=0);'
744 );
745
746 styleSheet.addRule(
747 '.typeface-js-vector-container',
748 'position: relative'
749 );
750
522 } else if (document.styleSheets && document.styleSheets.length) { 751 } else if (document.styleSheets && document.styleSheets.length) {
752
523 var styleSheet = document.styleSheets[0]; 753 var styleSheet = document.styleSheets[0];
524 document.styleSheets[0].insertRule(typefaceSelectors.join(',') + ' { visibility: hidden; }', styleSheet.cssRules.length); 754 document.styleSheets[0].insertRule(typefaceSelectors.join(',') + ' { visibility: hidden; }', styleSheet.cssRules.length);
755
756 document.styleSheets[0].insertRule(
757 '.typeface-js-selected-text { \
758 color: rgba(128, 128, 128, 0); \
759 opacity: 0.30; \
760 position: absolute; \
761 font-family: Arial, sans-serif; \
762 white-space: pre \
763 }',
764 styleSheet.cssRules.length
765 );
766
767 try {
768 // set selection style for Mozilla / Firefox
769 document.styleSheets[0].insertRule(
770 '.typeface-js-selected-text::-moz-selection { background: blue; }',
771 styleSheet.cssRules.length
772 );
773
774 } catch(e) {};
775
776 try {
777 // set styles for browsers with CSS3 selectors (Safari, Chrome)
778 document.styleSheets[0].insertRule(
779 '.typeface-js-selected-text::selection { background: blue; }',
780 styleSheet.cssRules.length
781 );
782
783 } catch(e) {};
784
785 // most unfortunately, sniff for WebKit's quirky selection behavior
786 if (/WebKit/i.test(navigator.userAgent)) {
787 document.styleSheets[0].insertRule(
788 '.typeface-js-vector-container { position: relative }',
789 styleSheet.cssRules.length
790 );
791 }
792
525 } 793 }
526 794
527 var backend = !!(window.attachEvent && !window.opera) ? 'vml' : window.CanvasRenderingContext2D || document.createElement('canvas').getContext ? 'canvas' : null; 795 var backend = !!(window.attachEvent && !window.opera) ? 'vml' : window.CanvasRenderingContext2D || document.createElement('canvas').getContext ? 'canvas' : null;
528 796
529 if (backend == 'vml') { 797 if (backend == 'vml') {
530 798
531 document.namespaces.add("v"); 799 document.namespaces.add("v","urn:schemas-microsoft-com:vml","#default#VML");
532 800
533 var styleSheet = document.createStyleSheet(); 801 var styleSheet = document.createStyleSheet();
534 styleSheet.addRule('v\\:*', "behavior: url(#default#VML); display: inline-block;"); 802 styleSheet.addRule('v\\:shape', "display: inline-block;");
535 } 803 }
536 804
537 _typeface_js.setVectorBackend(backend); 805 _typeface_js.setVectorBackend(backend);
538
539 window._typeface_js = _typeface_js; 806 window._typeface_js = _typeface_js;
540 807
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)) { 808 if (/WebKit/i.test(navigator.userAgent)) {
558 809
559 var _typefaceTimer = setInterval(function() { 810 var _typefaceTimer = setInterval(function() {
560 if (/loaded|complete/.test(document.readyState)) { 811 if (/loaded|complete/.test(document.readyState)) {
561 typefaceInit(); 812 _typeface_js.initialize();
562 } 813 }
563 }, 10); 814 }, 10);
564 } 815 }
565 816
566 if (document.addEventListener) { 817 if (document.addEventListener) {
567 window.addEventListener('DOMContentLoaded', function() { typefaceInit() }, false); 818 window.addEventListener('DOMContentLoaded', function() { _typeface_js.initialize() }, false);
568 } 819 }
569 820
570 /*@cc_on @*/ 821 /*@cc_on @*/
571 /*@if (@_win32) 822 /*@if (@_win32)
572 823
573 document.write("<script id=__ie_onload_typeface defer src=javascript:void(0)><\/script>"); 824 document.write("<script id=__ie_onload_typeface defer src=//:><\/script>");
574 var script = document.getElementById("__ie_onload_typeface"); 825 var script = document.getElementById("__ie_onload_typeface");
575 script.onreadystatechange = function() { 826 script.onreadystatechange = function() {
576 if (this.readyState == "complete") { 827 if (this.readyState == "complete") {
577 typefaceInit(); 828 _typeface_js.initialize();
578 } 829 }
579 }; 830 };
580 831
581 /*@end @*/ 832 /*@end @*/
582 833
834 try { console.log('initializing typeface.js') } catch(e) {};
835
583 })(); 836 })();