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