Mercurial > hg-website
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 })(); |