// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. 'use strict'; /** * @fileoverview The Advanced Font Settings Extension implementation. */ function $(id) { return document.getElementById(id); } /** * @namespace */ var advancedFonts = {}; /** * The ICU script code for the Common, or global, script, which is used as the * fallback when the script is undeclared. * @const */ advancedFonts.COMMON_SCRIPT = 'Zyyy'; /** * The scripts supported by the Font Settings Extension API. * @const */ advancedFonts.scripts = [ { scriptCode: advancedFonts.COMMON_SCRIPT, scriptName: 'Default'}, { scriptCode: 'Afak', scriptName: 'Afaka'}, { scriptCode: 'Arab', scriptName: 'Arabic'}, { scriptCode: 'Armi', scriptName: 'Imperial Aramaic'}, { scriptCode: 'Armn', scriptName: 'Armenian'}, { scriptCode: 'Avst', scriptName: 'Avestan'}, { scriptCode: 'Bali', scriptName: 'Balinese'}, { scriptCode: 'Bamu', scriptName: 'Bamum'}, { scriptCode: 'Bass', scriptName: 'Bassa Vah'}, { scriptCode: 'Batk', scriptName: 'Batak'}, { scriptCode: 'Beng', scriptName: 'Bengali'}, { scriptCode: 'Blis', scriptName: 'Blissymbols'}, { scriptCode: 'Bopo', scriptName: 'Bopomofo'}, { scriptCode: 'Brah', scriptName: 'Brahmi'}, { scriptCode: 'Brai', scriptName: 'Braille'}, { scriptCode: 'Bugi', scriptName: 'Buginese'}, { scriptCode: 'Buhd', scriptName: 'Buhid'}, { scriptCode: 'Cakm', scriptName: 'Chakma'}, { scriptCode: 'Cans', scriptName: 'Unified Canadian Aboriginal Syllabics'}, { scriptCode: 'Cari', scriptName: 'Carian'}, { scriptCode: 'Cham', scriptName: 'Cham'}, { scriptCode: 'Cher', scriptName: 'Cherokee'}, { scriptCode: 'Cirt', scriptName: 'Cirth'}, { scriptCode: 'Copt', scriptName: 'Coptic'}, { scriptCode: 'Cprt', scriptName: 'Cypriot'}, { scriptCode: 'Cyrl', scriptName: 'Cyrillic'}, { scriptCode: 'Cyrs', scriptName: 'Old Church Slavonic Cyrillic'}, { scriptCode: 'Deva', scriptName: 'Devanagari'}, { scriptCode: 'Dsrt', scriptName: 'Deseret'}, { scriptCode: 'Dupl', scriptName: 'Duployan shorthand'}, { scriptCode: 'Egyd', scriptName: 'Egyptian demotic'}, { scriptCode: 'Egyh', scriptName: 'Egyptian hieratic'}, { scriptCode: 'Egyp', scriptName: 'Egyptian hieroglyphs'}, { scriptCode: 'Elba', scriptName: 'Elbasan'}, { scriptCode: 'Ethi', scriptName: 'Ethiopic'}, { scriptCode: 'Geok', scriptName: 'Georgian Khutsuri'}, { scriptCode: 'Geor', scriptName: 'Georgian'}, { scriptCode: 'Glag', scriptName: 'Glagolitic'}, { scriptCode: 'Goth', scriptName: 'Gothic'}, { scriptCode: 'Gran', scriptName: 'Grantha'}, { scriptCode: 'Grek', scriptName: 'Greek'}, { scriptCode: 'Gujr', scriptName: 'Gujarati'}, { scriptCode: 'Guru', scriptName: 'Gurmukhi'}, { scriptCode: 'Hang', scriptName: 'Hangul'}, { scriptCode: 'Hani', scriptName: 'Han'}, { scriptCode: 'Hano', scriptName: 'Hanunoo'}, { scriptCode: 'Hans', scriptName: 'Simplified Han'}, { scriptCode: 'Hant', scriptName: 'Traditional Han'}, { scriptCode: 'Hebr', scriptName: 'Hebrew'}, { scriptCode: 'Hluw', scriptName: 'Anatolian Hieroglyphs'}, { scriptCode: 'Hmng', scriptName: 'Pahawh Hmong'}, { scriptCode: 'Hung', scriptName: 'Old Hungarian'}, { scriptCode: 'Inds', scriptName: 'Indus'}, { scriptCode: 'Ital', scriptName: 'Old Italic'}, { scriptCode: 'Java', scriptName: 'Javanese'}, { scriptCode: 'Jpan', scriptName: 'Japanese'}, { scriptCode: 'Jurc', scriptName: 'Jurchen'}, { scriptCode: 'Kali', scriptName: 'Kayah Li'}, { scriptCode: 'Khar', scriptName: 'Kharoshthi'}, { scriptCode: 'Khmr', scriptName: 'Khmer'}, { scriptCode: 'Khoj', scriptName: 'Khojki'}, { scriptCode: 'Knda', scriptName: 'Kannada'}, { scriptCode: 'Kpel', scriptName: 'Kpelle'}, { scriptCode: 'Kthi', scriptName: 'Kaithi'}, { scriptCode: 'Lana', scriptName: 'Lanna'}, { scriptCode: 'Laoo', scriptName: 'Lao'}, { scriptCode: 'Latf', scriptName: 'Fraktur Latin'}, { scriptCode: 'Latg', scriptName: 'Gaelic Latin'}, { scriptCode: 'Latn', scriptName: 'Latin'}, { scriptCode: 'Lepc', scriptName: 'Lepcha'}, { scriptCode: 'Limb', scriptName: 'Limbu'}, { scriptCode: 'Lina', scriptName: 'Linear A'}, { scriptCode: 'Linb', scriptName: 'Linear B'}, { scriptCode: 'Lisu', scriptName: 'Fraser'}, { scriptCode: 'Loma', scriptName: 'Loma'}, { scriptCode: 'Lyci', scriptName: 'Lycian'}, { scriptCode: 'Lydi', scriptName: 'Lydian'}, { scriptCode: 'Mand', scriptName: 'Mandaean'}, { scriptCode: 'Mani', scriptName: 'Manichaean'}, { scriptCode: 'Maya', scriptName: 'Mayan hieroglyphs'}, { scriptCode: 'Mend', scriptName: 'Mende'}, { scriptCode: 'Merc', scriptName: 'Meroitic Cursive'}, { scriptCode: 'Mero', scriptName: 'Meroitic'}, { scriptCode: 'Mlym', scriptName: 'Malayalam'}, { scriptCode: 'Mong', scriptName: 'Mongolian'}, { scriptCode: 'Moon', scriptName: 'Moon'}, { scriptCode: 'Mroo', scriptName: 'Mro'}, { scriptCode: 'Mtei', scriptName: 'Meitei Mayek'}, { scriptCode: 'Mymr', scriptName: 'Myanmar'}, { scriptCode: 'Narb', scriptName: 'Old North Arabian'}, { scriptCode: 'Nbat', scriptName: 'Nabataean'}, { scriptCode: 'Nkgb', scriptName: 'Naxi Geba'}, { scriptCode: 'Nkoo', scriptName: 'N’Ko'}, { scriptCode: 'Nshu', scriptName: 'Nüshu'}, { scriptCode: 'Ogam', scriptName: 'Ogham'}, { scriptCode: 'Olck', scriptName: 'Ol Chiki'}, { scriptCode: 'Orkh', scriptName: 'Orkhon'}, { scriptCode: 'Orya', scriptName: 'Oriya'}, { scriptCode: 'Osma', scriptName: 'Osmanya'}, { scriptCode: 'Palm', scriptName: 'Palmyrene'}, { scriptCode: 'Perm', scriptName: 'Old Permic'}, { scriptCode: 'Phag', scriptName: 'Phags-pa'}, { scriptCode: 'Phli', scriptName: 'Inscriptional Pahlavi'}, { scriptCode: 'Phlp', scriptName: 'Psalter Pahlavi'}, { scriptCode: 'Phlv', scriptName: 'Book Pahlavi'}, { scriptCode: 'Phnx', scriptName: 'Phoenician'}, { scriptCode: 'Plrd', scriptName: 'Pollard Phonetic'}, { scriptCode: 'Prti', scriptName: 'Inscriptional Parthian'}, { scriptCode: 'Rjng', scriptName: 'Rejang'}, { scriptCode: 'Roro', scriptName: 'Rongorongo'}, { scriptCode: 'Runr', scriptName: 'Runic'}, { scriptCode: 'Samr', scriptName: 'Samaritan'}, { scriptCode: 'Sara', scriptName: 'Sarati'}, { scriptCode: 'Sarb', scriptName: 'Old South Arabian'}, { scriptCode: 'Saur', scriptName: 'Saurashtra'}, { scriptCode: 'Sgnw', scriptName: 'SignWriting'}, { scriptCode: 'Shaw', scriptName: 'Shavian'}, { scriptCode: 'Shrd', scriptName: 'Sharada'}, { scriptCode: 'Sind', scriptName: 'Khudawadi'}, { scriptCode: 'Sinh', scriptName: 'Sinhala'}, { scriptCode: 'Sora', scriptName: 'Sora Sompeng'}, { scriptCode: 'Sund', scriptName: 'Sundanese'}, { scriptCode: 'Sylo', scriptName: 'Syloti Nagri'}, { scriptCode: 'Syrc', scriptName: 'Syriac'}, { scriptCode: 'Syre', scriptName: 'Estrangelo Syriac'}, { scriptCode: 'Syrj', scriptName: 'Western Syriac'}, { scriptCode: 'Syrn', scriptName: 'Eastern Syriac'}, { scriptCode: 'Tagb', scriptName: 'Tagbanwa'}, { scriptCode: 'Takr', scriptName: 'Takri'}, { scriptCode: 'Tale', scriptName: 'Tai Le'}, { scriptCode: 'Talu', scriptName: 'New Tai Lue'}, { scriptCode: 'Taml', scriptName: 'Tamil'}, { scriptCode: 'Tang', scriptName: 'Tangut'}, { scriptCode: 'Tavt', scriptName: 'Tai Viet'}, { scriptCode: 'Telu', scriptName: 'Telugu'}, { scriptCode: 'Teng', scriptName: 'Tengwar'}, { scriptCode: 'Tfng', scriptName: 'Tifinagh'}, { scriptCode: 'Tglg', scriptName: 'Tagalog'}, { scriptCode: 'Thaa', scriptName: 'Thaana'}, { scriptCode: 'Thai', scriptName: 'Thai'}, { scriptCode: 'Tibt', scriptName: 'Tibetan'}, { scriptCode: 'Tirh', scriptName: 'Tirhuta'}, { scriptCode: 'Ugar', scriptName: 'Ugaritic'}, { scriptCode: 'Vaii', scriptName: 'Vai'}, { scriptCode: 'Visp', scriptName: 'Visible Speech'}, { scriptCode: 'Wara', scriptName: 'Varang Kshiti'}, { scriptCode: 'Wole', scriptName: 'Woleai'}, { scriptCode: 'Xpeo', scriptName: 'Old Persian'}, { scriptCode: 'Xsux', scriptName: 'Sumero-Akkadian Cuneiform'}, { scriptCode: 'Yiii', scriptName: 'Yi'}, { scriptCode: 'Zmth', scriptName: 'Mathematical Notation'}, { scriptCode: 'Zsym', scriptName: 'Symbols'} ]; /** * The generic font families supported by the Font Settings Extension API. * @const */ advancedFonts.FAMILIES = ['standard', 'sansserif', 'serif', 'fixed', 'cursive', 'fantasy']; /** * Sample texts. * @const */ advancedFonts.SAMPLE_TEXTS = { // "Cyrllic script". Cyrl: 'Кириллица', Hang: '정 참판 양반댁 규수 큰 교자 타고 혼례 치른 날.', Hans: '床前明月光,疑是地上霜。举头望明月,低头思故乡。', Hant: '床前明月光,疑是地上霜。舉頭望明月,低頭思故鄉。', Jpan: '吾輩は猫である。名前はまだ無い。', // "Khmer language". Khmr: '\u1797\u17B6\u179F\u17B6\u1781\u17D2\u1798\u17C2\u179A', Zyyy: 'The quick brown fox jumps over the lazy dog.' }; /** * Controller of pending changes. * @const */ advancedFonts.pendingChanges = new PendingChanges(); /** * Map from |genericFamily| to UI controls and data for its font setting. */ advancedFonts.fontSettings = null; /** * Map from |fontSizeKey| to UI contols and data for its font size setting. */ advancedFonts.fontSizeSettings = null; /** * Gets the font size used for |fontSizeKey|, including pending changes. Calls * |callback| with the result. * * @param {string} fontSizeKey The font size setting key. See * PendingChanges.getFontSize(). * @param {function(number, boolean)} callback The callback of form * function(size, controllable). |size| is the effective setting, * |controllable| is whether the setting can be set. */ advancedFonts.getEffectiveFontSize = function(fontSizeKey, callback) { advancedFonts.fontSizeSettings[fontSizeKey].getter({}, function(details) { var controllable = advancedFonts.isControllableLevel( details.levelOfControl); var size = details.pixelSize; var pendingFontSize = advancedFonts.pendingChanges.getFontSize(fontSizeKey); // If the setting is not controllable, we can have no pending change. if (!controllable) { if (pendingFontSize != null) { advancedFonts.pendingChanges.setFontSize(fontSizeKey, null); $('apply-settings').disabled = advancedFonts.pendingChanges.isEmpty(); pendingFontSize = null; } } // If we have a pending change, it overrides the current setting. if (pendingFontSize != null) size = pendingFontSize; callback(size, controllable); }); }; /** * Gets the font used for |script| and |genericFamily|, including pending * changes. Calls |callback| with the result. * * @param {string} script The script code. * @param {string} genericFamily The generic family. * @param {function(string, boolean, string)} callback The callback of form * function(font, controllable, effectiveFont). |font| is the setting * (pending or not), |controllable| is whether the setting can be set, * |effectiveFont| is the font used taking fallback into consideration. */ advancedFonts.getEffectiveFont = function(script, genericFamily, callback) { var pendingChanges = advancedFonts.pendingChanges; var details = { script: script, genericFamily: genericFamily }; chrome.fontSettings.getFont(details, function(result) { var setting = {}; setting.font = result.fontId; setting.controllable = advancedFonts.isControllableLevel(result.levelOfControl); var pendingFont = pendingChanges.getFont(details.script, details.genericFamily); // If the setting is not controllable, we can have no pending change. if (!setting.controllable) { if (pendingFont != null) { pendingChanges.setFont(script, genericFamily, null); $('apply-settings').disabled = advancedFonts.pendingChanges.isEmpty(); pendingFont = null; } } // If we have a pending change, it overrides the current setting. if (pendingFont != null) setting.font = pendingFont; // If we have a font, we're done. if (setting.font) { callback(setting.font, setting.controllable, setting.font); return; } // If we're still here, we have to fallback to common script, unless this // already is common script. if (script == advancedFonts.COMMON_SCRIPT) { callback('', setting.controllable, ''); return; } advancedFonts.getEffectiveFont( advancedFonts.COMMON_SCRIPT, genericFamily, callback.bind(null, setting.font, setting.controllable)); }); }; /** * Refreshes the UI controls related to a font setting. * * @param {{fontList: HTMLSelectElement, samples: Array.}} * fontSetting The setting object (see advancedFonts.fontSettings). * @param {string} font The value of the font setting. * @param {boolean} controllable Whether the font setting can be controlled * by this extension. * @param {string} effectiveFont The font used, including fallback to Common * script. */ advancedFonts.refreshFont = function( fontSetting, font, controllable, effectiveFont) { for (var i = 0; i < fontSetting.samples.length; ++i) fontSetting.samples[i].style.fontFamily = effectiveFont; advancedFonts.setSelectedFont(fontSetting.fontList, font); fontSetting.fontList.disabled = !controllable; }; /** * Refreshes the UI controls related to a font size setting. * * @param {{label: HTMLElement, slider: Slider, samples: Array.}} * fontSizeSetting The setting object (see advancedFonts.fontSizeSettings). * @param size The value of the font size setting. * @param controllable Whether the setting can be controlled by this extension. */ advancedFonts.refreshFontSize = function(fontSizeSetting, size, controllable) { fontSizeSetting.label.textContent = 'Size: ' + size + 'px'; advancedFonts.setFontSizeSlider(fontSizeSetting.slider, size, controllable); for (var i = 0; i < fontSizeSetting.samples.length; ++i) fontSizeSetting.samples[i].style.fontSize = size + 'px'; }; /** * Refreshes all UI controls to reflect the current settings, including pending * changes. */ advancedFonts.refresh = function() { var script = advancedFonts.getSelectedScript(); var sample; if (advancedFonts.SAMPLE_TEXTS[script]) sample = advancedFonts.SAMPLE_TEXTS[script]; else sample = advancedFonts.SAMPLE_TEXTS[advancedFonts.COMMON_SCRIPT]; var sampleTexts = document.querySelectorAll('.sample-text-span'); for (var i = 0; i < sampleTexts.length; i++) sampleTexts[i].textContent = sample; var setting; var callback; for (var genericFamily in advancedFonts.fontSettings) { setting = advancedFonts.fontSettings[genericFamily]; callback = advancedFonts.refreshFont.bind(null, setting); advancedFonts.getEffectiveFont(script, genericFamily, callback); } for (var fontSizeKey in advancedFonts.fontSizeSettings) { setting = advancedFonts.fontSizeSettings[fontSizeKey]; callback = advancedFonts.refreshFontSize.bind(null, setting); advancedFonts.getEffectiveFontSize(fontSizeKey, callback); } $('apply-settings').disabled = advancedFonts.pendingChanges.isEmpty(); }; /** * @return {string} The currently selected script code. */ advancedFonts.getSelectedScript = function() { var scriptList = $('scriptList'); return scriptList.options[scriptList.selectedIndex].value; }; /** * @param {HTMLSelectElement} fontList The element for a font setting. * @param {string} genericFamily The generic family for the font setting. * @param {Event} event The change event. */ advancedFonts.handleFontListChange = function(genericFamily, event) { var script = advancedFonts.getSelectedScript(); var font = advancedFonts.getSelectedFont(event.target); advancedFonts.pendingChanges.setFont(script, genericFamily, font); advancedFonts.refresh(); }; /** * Sets the selected value of |fontList| to |fontId|. * @param {HTMLSelectElement} fontList The