1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/lib/domplate",
  7     "firebug/lib/locale",
  8     "firebug/lib/xpcom",
  9     "firebug/lib/events",
 10     "firebug/chrome/window",
 11     "firebug/lib/css",
 12     "firebug/lib/dom",
 13     "firebug/lib/string",
 14     "firebug/lib/fonts",
 15     "firebug/lib/url",
 16     "firebug/lib/http",
 17     "firebug/net/netUtils",
 18     "firebug/lib/options"
 19 ],
 20 function(Obj, Firebug, Domplate, Locale, Xpcom, Events, Win, Css, Dom, Str, Fonts, Url, Http,
 21     NetUtils, Options) {
 22 
 23 // ********************************************************************************************* //
 24 
 25 // List of font content types
 26 var contentTypes =
 27 [
 28     "application/x-woff",
 29     "application/x-font-woff",
 30     "application/x-ttf",
 31     "application/x-font-ttf",
 32     "font/ttf",
 33     "application/x-otf",
 34     "application/x-font-otf"
 35 ];
 36 
 37 // ********************************************************************************************* //
 38 // Model implementation
 39 
 40 Firebug.FontViewerModel = Obj.extend(Firebug.Module,
 41 {
 42     dispatchName: "fontViewer",
 43     contentTypes: contentTypes,
 44 
 45     initialize: function()
 46     {
 47         Firebug.TabCacheModel.addListener(this);
 48         Firebug.NetMonitor.NetInfoBody.addListener(this);
 49         Firebug.registerUIListener(this);
 50     },
 51 
 52     shutdown: function()
 53     {
 54         Firebug.TabCacheModel.removeListener(this);
 55         Firebug.NetMonitor.NetInfoBody.removeListener(this);
 56         Firebug.unregisterUIListener(this);
 57     },
 58 
 59     /**
 60      * Checks whether the given file name and content are a valid font file
 61      * 
 62      * @param contentType: MIME type of the file
 63      * @param url: URL of the file
 64      * @param data: File contents
 65      * @return True, if the given data outlines a font file, otherwise false
 66      */
 67     isFont: function(contentType, url, data)
 68     {
 69         if (!contentType)
 70             return false;
 71 
 72         if (NetUtils.matchesContentType(contentType, contentTypes))
 73         {
 74             if (FBTrace.DBG_FONTS)
 75             {
 76                 FBTrace.sysout("fontviewer.isFont; content type: "+contentType,
 77                     {url: url, data: data});
 78             }
 79 
 80             return true;
 81         }
 82 
 83         // Workaround for font responses without proper content type
 84         // Let's consider all responses starting with "wOFF" as font. In the worst
 85         // case there will be an exception when parsing. This means that no-font
 86         // responses (and post data) (with "wOFF") can be parsed unnecessarily,
 87         // which represents a little overhead, but this happens only if the request
 88         // is actually expanded by the user in the UI (Net & Console panel).
 89         var extension = Url.getFileExtension(url);
 90         var validExtension = /woff|otf|ttf/.exec(extension);
 91         if (validExtension && (!data || Str.hasPrefix(data, "wOFF") || Str.hasPrefix(data, "OTTO")))
 92         {
 93             if (FBTrace.DBG_FONTS)
 94             {
 95                 FBTrace.sysout("fontviewer.isFont; Font without proper content type",
 96                     {url: url, data: data});
 97             }
 98 
 99             return true;
100         }
101 
102         contentType = contentType.split(";")[0];
103         contentType = Str.trim(contentType);
104         return contentTypes[contentType];
105     },
106 
107     /**
108      * Parses the file and returns information about the font
109      * 
110      * @param file: File to parse
111      * @return Font related information
112      */
113     parseFont: function(file)
114     {
115         return Fonts.getFontInfo(Firebug.currentContext, null, file.href);
116     },
117 
118     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
119     // TabCacheModel listener
120 
121     shouldCacheRequest: function(request)
122     {
123         if (this.isFont(request.contentType, request.name))
124             return true;
125 
126         return false;
127     },
128 
129     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
130     // NetInfoBody listener
131 
132     updateResponse: function(netInfoBox, file, context)
133     {
134         // Let listeners parse the font
135         Events.dispatch(this.fbListeners, "onParseFont", [file]);
136 
137         // The font is still not there, try to parse most common cases
138         if (!file.fontObject)
139         {
140             if (this.isFont(Http.safeGetContentType(file.request), file.href, file.responseText))
141                 file.fontObject = this.parseFont(file);
142         }
143 
144         if (file.fontObject)
145         {
146             var responseTextBox = netInfoBox.getElementsByClassName("netInfoResponseText").item(0);
147             this.Preview.render(responseTextBox, file, context);
148             netInfoBox.responsePresented = true;
149 
150             if (FBTrace.DBG_FONTS)
151                 FBTrace.sysout("fontviewer.updateResponse", file);
152         }
153     }
154 });
155 
156 // ********************************************************************************************* //
157 
158 with (Domplate) {
159 Firebug.FontViewerModel.Preview = domplate(
160 {
161     bodyTag:
162         DIV({"class": "fontInfo", _repObject: "$fontObject"},
163             DIV({"class": "fontInfoGroup fontInfoGeneralInfoTitle"},
164                 SPAN(Locale.$STR("fontviewer.General Info"))
165             ),
166             TABLE({cellpadding: 0, cellspacing: 0},
167                 TBODY({"class": "fontInfoGeneralInfoBody", "role": "list",
168                     "aria-label": Locale.$STR("fontviewer.General Info")})
169             ),
170             DIV({"class": "fontInfoGroup fontInfoMetaDataTitle",
171                 $collapsed: "$fontObject|noMetaData"},
172                 SPAN(Locale.$STR("fontviewer.Meta Data")),
173                 SPAN({"class": "fontInfoToggleView", onclick: "$onToggleView",
174                     _sourceDisplayed: false, _rowName: "MetaData"},
175                     Locale.$STR("fontviewer.view source")
176                 )
177             ),
178             TABLE({cellpadding: 0, cellspacing: 0},
179                 TBODY({"class": "fontInfoMetaDataBody", "role": "list",
180                     "aria-label": Locale.$STR("fontviewer.Meta Data")})
181             ),
182             DIV({"class": "fontInfoGroup fontInfoPreviewTitle"},
183                 SPAN(Locale.$STR("fontviewer.Preview")),
184                 SPAN({"class": "fontInfoToggleView", onclick: "$onToggleView",
185                   _lettersDisplayed: false, _rowName: "Preview"},
186                   Locale.$STR("fontviewer.view characters")
187                 )
188             ),
189             DIV({"class": "fontInfoPreview"},
190                 STYLE({"class": "fontInfoPreviewStyle"}),
191                 DIV({"class": "fontInfoPreviewSample"},
192                     FOR("style", "$styles",
193                         P({"class": "$fontObject.CSSFamilyName|getFontFaceClass",
194                             "style": "font-size: $style|getFontSize"},
195                             SPAN({"class": "fontViewerFontSize"}, "$style"),
196                             SPAN(Locale.$STR("fontviewer.pangram"))
197                         )
198                     )
199                 ),
200                 DIV({"class": "fontInfoPreviewCharacters"},
201                     FOR("charType", "$charTypes",
202                         P({"class": "$fontObject.CSSFamilyName|getFontFaceClass"},
203                             "$charType|getCharacters"
204                         )
205                     )
206                 )
207             )
208         ),
209 
210     propDataTag:
211         FOR("prop", "$props",
212             TR({"role": "listitem", _repObject: "$prop.node"},
213                 TD({"class": "fontInfoPropName", "role": "presentation"},
214                     SPAN("$prop|getPropName")
215                 ),
216                 TD({"class": "fontInfoPropValue", "role": "list", "aria-label": "$prop.name"},
217                     TAG("$prop|getTag", {node: "$prop.node"})
218                 )
219             )
220         ),
221 
222     sourceTag:
223         TR({"role": "presentation"},
224             TD({colspan: 2, "role": "presentation"},
225                 PRE({"class": "source"})
226             )
227         ),
228 
229     translatedInfoTag:
230         DIV({"class": "fontInfoTranslatedInfo"},
231             DIV({"class": "fontInfoLangInfo"},
232                 FOR("lang", "$node|getLanguages",
233                     A({"class": "fontInfoLangTab", $selected: "$lang.selected", role: "tab",
234                         onclick: "$onTranslatedLangChange"}, "$lang.name")
235                 )
236             ),
237             DIV({"class": "fontInfoTranslatedContent"},
238                 "$node|getTranslatedText"
239             )
240         ),
241 
242     vendorTag:
243         TAG("$node|getLinkedTextTag", {node: "$node"}),
244 
245     licenseTag:
246         DIV({"class": "fontInfoLicense"},
247             TAG("$node|getLinkedTextTag", {node: "$node"}),
248             TAG("$translatedInfoTag", {node: "$node"})
249         ),
250 
251     creditsTag:
252         UL({"class": "fontInfoCredits"},
253             FOR("credit", "$node|getCredits",
254                 LI(
255                     TAG("$credit|getLinkedTextTag", {node: "$credit"}),
256                     " ",
257                     SPAN({"class": "fontInfoCreditsRole"}, "$credit|getRole")
258                 )
259             )
260         ),
261 
262     linkTag:
263         A({"class": "fontInfoLink", href: "$node|getUrl", onclick: "$onOpenUrl"},
264             "$node|getLinkName"),
265 
266     textTag:
267         SPAN("$node|getText"),
268 
269     /**
270      * Handles toggling of the font information display
271      * 
272      * @param event: Click event
273      */
274     onToggleView: function(event)
275     {
276         var target = event.target;
277         var fontInfo = Dom.getAncestorByClass(target, "fontInfo");
278         var fontObject = fontInfo.repObject;
279 
280         switch (target.rowName)
281         {
282             case "MetaData":
283                 if (target.sourceDisplayed)
284                 {
285                     this.insertMetaDataFormatted(fontInfo, fontObject.metadata);
286                     target.textContent = Locale.$STR("fontviewer.view source");
287                 }
288                 else
289                 {
290                     this.insertMetaDataSource(fontInfo, fontObject.metadata);
291                     target.textContent = Locale.$STR("fontviewer.pretty print");
292                 }
293                 target.sourceDisplayed = !target.sourceDisplayed;
294                 break;
295 
296             case "Preview":
297                 var sample = fontInfo.getElementsByClassName("fontInfoPreviewSample").item(0);
298                 var chars = fontInfo.getElementsByClassName("fontInfoPreviewCharacters").item(0);
299                 if (target.lettersDisplayed)
300                 {
301                     sample.style.display = "block";
302                     chars.style.display = "none";
303                     target.textContent = Locale.$STR("fontviewer.view characters");
304                 }
305                 else
306                 {
307                     sample.style.display = "none";
308                     chars.style.display = "block";
309                     target.textContent = Locale.$STR("fontviewer.view sample");
310                 }
311                 target.lettersDisplayed = !target.lettersDisplayed;
312                 break;
313         }
314     },
315 
316     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
317     // Meta data
318 
319     /**
320      * Checks, whether the font contains meta data
321      * 
322      * @param fontObject: Font related information
323      * @return True, if font contains meta data, otherwise false
324      */
325     noMetaData: function(fontObject)
326     {
327         return fontObject.metadata == "";
328     },
329 
330     /**
331      * Selects the corresponding Domplate template related to a given meta data property
332      * 
333      * @param prop: Meta data property
334      * @return Domplate template related to the property
335      */
336     getTag: function(prop)
337     {
338         return prop.tag ? prop.tag : Firebug.FontViewerModel.Preview.textTag;
339     },
340 
341     /**
342      * Returns the translated property name
343      * 
344      * @param prop: Meta data property
345      * @return Translated name of the property
346      */
347     getPropName: function(prop)
348     {
349         return prop.name ? prop.name : Locale.$STR("fontviewer."+prop.node.nodeName);
350     },
351 
352     /**
353      * Returns the text of a meta data property or a string
354      * 
355      * @param value: Meta data property or string
356      * @return String representing the text related to the property
357      */
358     getText: function(value)
359     {
360         if (typeof(value) == "string")
361             return value;
362 
363         var name = value.getAttribute("id") || value.getAttribute("name");
364         if (name)
365             return name;
366 
367         return value.textContent;
368     },
369 
370     /**
371      * Opens a URL in a new browser tab
372      * 
373      * @param event: Click event
374      */
375     onOpenUrl: function(event)
376     {
377         Win.openNewTab(event.target.getAttribute("href"));
378         Events.cancelEvent(event);
379     },
380 
381     /**
382      * Returns either text or a linked text depending on a URL to be present of a
383      * meta data property
384      * 
385      * @param node: Meta data property node
386      * @return Text or linked text
387      */
388     getLinkedTextTag: function(node)
389     {
390         if (this.getUrl(node))
391             return Firebug.FontViewerModel.Preview.linkTag;
392         else
393             return Firebug.FontViewerModel.Preview.textTag;
394     },
395 
396     /**
397      * Returns the URL of a meta data property
398      * 
399      * @param node: Meta data property node
400      * @return URL of the meta data property
401      */
402     getUrl: function(node)
403     {
404         return node.getAttribute("url");
405     },
406 
407     /**
408      * Returns the name of a link related to a meta data property
409      * 
410      * @param node: Meta data property node
411      * @return Link name of the meta data property
412      */
413     getLinkName: function(node)
414     {
415         return node.getAttribute("id") || node.getAttribute("name") || node.getAttribute("url");
416     },
417 
418     /**
419      * Returns an array of font credits
420      * 
421      * @param node: Meta data property node
422      * @return Credits
423      */
424     getCredits: function(node)
425     {
426         return Array.prototype.slice.call(node.children);
427     },
428 
429     /**
430      * Returns the role of a contributor of the font
431      * 
432      * @param node: Meta data property node
433      * @return Contributor role
434      */
435     getRole: function(node)
436     {
437         var role = node.getAttribute("role");
438         return role ? "("+role+")" : "";
439     },
440 
441     /**
442      * Returns the available languages of a translated meta data property text
443      * 
444      * @param node: Meta data property node
445      * @return Array of languages
446      */
447     getLanguages: function(node)
448     {
449         var texts = Array.prototype.slice.call(node.getElementsByTagName("text"));
450         var langs = [];
451         var defaultLang = this.getDefaultLang(node);
452         texts.forEach(function(e, i, a) {
453             var lang = e.getAttribute("lang");
454             langs.push({name: lang, selected: lang == defaultLang});
455         });
456 
457         return langs;
458     },
459 
460     /**
461      * Returns the default language of a meta data property
462      * 
463      * @param node: Meta data property node
464      * @return Language
465      */
466     getDefaultLang: function(node)
467     {
468         var localeDomain = "general.useragent";
469         var localeName = "locale";
470         var localeValue = Options.getPref(localeDomain, localeName);
471 
472         if (node.querySelector("text[lang="+localeValue+"]"))
473             return localeValue;
474 
475         if (node.querySelector("text[lang=en]"))
476             return "en";
477 
478         if (node.firstElementChild)
479             return node.firstElementChild.getAttribute("lang");
480 
481         return null;
482     },
483 
484     /**
485      * Returns the translated text of meta data property
486      * 
487      * @param node: Meta data property node
488      * @param lang: Language of the text
489      * @return Translated text
490      */
491     getTranslatedText: function(node, lang)
492     {
493         if (!lang)
494             lang = this.getDefaultLang(node);
495 
496         if (lang)
497         {
498             var element = node.querySelector("text[lang="+lang+"]");
499             if (element)
500                 return element.textContent;
501         }
502 
503         return "";
504     },
505 
506     /**
507      * Displays the XML source of the meta data
508      * 
509      * @param fontInfo: Font related information
510      * @param source: XML source of the meta data
511      */
512     insertMetaDataSource: function(fontInfo, source)
513     {
514         var tbody = fontInfo.getElementsByClassName("fontInfoMetaDataBody").item(0);
515         var node = this.sourceTag.replace({}, tbody);
516         var sourceNode = node.getElementsByClassName("source").item(0);
517         Str.insertWrappedText(source, sourceNode);
518     },
519 
520     /**
521      * Displays the meta data information formatted
522      * 
523      * @param fontInfo: Font related information
524      * @param source: XML source of the meta data
525      */
526     insertMetaDataFormatted: function(fontInfo, source)
527     {
528         var tbody = fontInfo.getElementsByClassName("fontInfoMetaDataBody").item(0);
529         var parser = Xpcom.CCIN("@mozilla.org/xmlextras/domparser;1", "nsIDOMParser");
530         var doc = parser.parseFromString(source, "text/xml");
531         var root = doc.documentElement;
532 
533         if (FBTrace.DBG_FONTS)
534             FBTrace.sysout("fontviewer.insertMetaDataFormatted; XML", doc);
535 
536         Dom.clearNode(tbody);
537 
538         var props = [];
539         var propValueTemplates = {
540             vendor: Firebug.FontViewerModel.Preview.vendorTag,
541             credits: Firebug.FontViewerModel.Preview.creditsTag,
542             description: Firebug.FontViewerModel.Preview.translatedInfoTag,
543             copyright: Firebug.FontViewerModel.Preview.translatedInfoTag,
544             trademark: Firebug.FontViewerModel.Preview.translatedInfoTag,
545             license: Firebug.FontViewerModel.Preview.licenseTag
546         }
547 
548         for (var i=0; i<root.children.length; i++)
549         {
550             var child = root.children[i];
551             props.push({tag: propValueTemplates[child.nodeName], node: child});
552         }
553 
554         if (FBTrace.DBG_FONTS)
555             FBTrace.sysout("fontviewer.insertMetaDataFormatted; props", props);
556 
557         tbody.repObject = root;
558         Firebug.FontViewerModel.Preview.propDataTag.insertRows({props: props}, tbody);
559     },
560 
561     /**
562      * Handles text language changes
563      * 
564      * @param event: Click event
565      */
566     onTranslatedLangChange: function(event)
567     {
568         var target = event.target;
569         var translatedInfo = Dom.getAncestorByClass(target, "fontInfoTranslatedInfo");
570         var selected = translatedInfo.getElementsByClassName("selected").item(0);
571         Css.removeClass(selected, "selected");
572         Css.setClass(target, "selected");
573 
574         var content = translatedInfo.getElementsByClassName("fontInfoTranslatedContent").item(0);
575         content.textContent = this.getTranslatedText(Firebug.getRepObject(target),
576             target.textContent);
577     },
578 
579     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
580     // Preview
581 
582     /**
583      * Returns the CSS font-face class name
584      * 
585      * @param cssFamilyName: CSS name of the font family
586      * @return Name of the font-face class
587      */
588     getFontFaceClass: function(cssFamilyName)
589     {
590         return "fontFacePreview"+cssFamilyName.replace(/[^a-z0-9_]/ig, "");
591     },
592 
593     /**
594      * Returns the font size CSS
595      * 
596      * @param size: Font size
597      * @return Font size CSS
598      */
599     getFontSize: function(size)
600     {
601         return size+"pt";
602     },
603 
604     /**
605      * Returns the characters used for the font preview
606      * 
607      * @param charType: Type of characters to return
608      * @return Preview characters
609      */
610     getCharacters: function(charType)
611     {
612         switch (charType)
613         {
614             case "lowercase":
615                 return "abcdefghijklmnopqrstuvwxyz";
616 
617             case "uppercase":
618                 return "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
619 
620             case "numbersAndSpecialChars":
621                 return "0123456789.:,;(\"*!?')";
622         }
623     },
624 
625     /**
626      * Returns the CSS for the @font-face CSS
627      * 
628      * @param fontObject: Font related information
629      * @return @font-face CSS
630      */
631     getFontFaceCss: function(fontObject)
632     {
633         var fontFaceClass = this.getFontFaceClass(fontObject.CSSFamilyName);
634         return fontObject.rule.cssText.replace(/url\(.*?\)/g, "url("+fontObject.URI+")")+
635             " ."+fontFaceClass+" {font-family: \""+fontObject.CSSFamilyName+"\";}";
636     },
637 
638     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
639 
640     /**
641      * Displays general information about the font
642      * 
643      * @param body: Node containing the font information display
644      * @param fontObject: Font related information
645      */
646     insertGeneralInfo: function(body, fontObject)
647     {
648 
649         var fontInfo = body.getElementsByClassName("fontInfo").item(0);
650         var tbody = fontInfo.getElementsByClassName("fontInfoGeneralInfoBody").item(0);
651 
652         Dom.clearNode(tbody);
653 
654         if (fontObject)
655         {
656             var props =
657             [
658                 {name: Locale.$STR("fontviewer.Name"), node: fontObject.name}, 
659                 {name: Locale.$STR("fontviewer.CSS Family Name"), node: fontObject.CSSFamilyName},
660                 {name: Locale.$STR("fontviewer.Format"), node: fontObject.format}
661             ];
662             Firebug.FontViewerModel.Preview.propDataTag.insertRows({props: props}, tbody);
663         }
664     },
665 
666     /**
667      * Renders the font display
668      * 
669      * @param body: Node containing the font information display
670      * @param file: Font file to be displayed
671      * @param context: Related context
672      */
673     render: function(body, file, context)
674     {
675         var fontObject = file.fontObject;
676         if (!fontObject)
677             return;
678 
679         var styles = [10, 14, 18];
680         var charTypes = ["lowercase", "uppercase", "numbersAndSpecialChars"];
681 
682         var node = this.bodyTag.replace({fontObject: fontObject, styles: styles,
683             charTypes: charTypes}, body, this);
684 
685         var styleNode = node.getElementsByClassName("fontInfoPreviewStyle").item(0);
686         styleNode.innerHTML = this.getFontFaceCss(fontObject);
687 
688         this.insertGeneralInfo(body, file.fontObject);
689 
690         if (fontObject.metadata != "")
691             this.insertMetaDataFormatted(body, fontObject.metadata);
692     }
693 })};
694 
695 // ********************************************************************************************* //
696 // Registration
697 
698 Firebug.registerModule(Firebug.FontViewerModel);
699 
700 return Firebug.FontViewerModel;
701 
702 // ********************************************************************************************* //
703 });
704