array(), 'advagg_font_storage' => variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE), 'advagg_font_cookie' => variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE), 'advagg_font_no_fout' => variable_get('advagg_font_no_fout', ADVAGG_FONT_NO_FOUT), ), array('type' => 'setting')); // Add inline script for reading the cookies and adding the fonts already // loaded to the html class. if (variable_get('advagg_font_cookie', ADVAGG_FONT_COOKIE) || variable_get('advagg_font_storage', ADVAGG_FONT_STORAGE)) { $inline_script_min = 'for(var fonts=document.cookie.split("advaggf"),i=0;i=current_time&&(window.document.documentElement.className+=" "+key.replace(/[^a-zA-Z0-9\-]/g,""))}'; drupal_add_js($inline_script_min, array( 'type' => 'inline', 'group' => JS_LIBRARY - 1, 'weight' => -50000, 'scope' => 'above_css', 'scope_lock' => TRUE, 'movable' => FALSE, 'no_defer' => TRUE, )); } // Get library data for fontfaceobserver. $library = advagg_get_library('fontfaceobserver', 'advagg_font'); // If libraries_load() does not exist load library externally. if (!is_callable('libraries_load')) { $advagg_font_ffo = 6; } // Add fontfaceobserver.js. if ($advagg_font_ffo != 6 && empty($library['installed'])) { // The fontfaceobserver library is not installed; use external variant. $advagg_font_ffo = 6; } if ($advagg_font_ffo == 6) { // Use the external variant. foreach ($library['variants']['external']['files']['js'] as $data => $options) { drupal_add_js($data, $options); } } else { // Load the fontfaceobserver library. if ($advagg_font_ffo == 2) { // Use the inline variant. libraries_load('fontfaceobserver', 'inline'); } else { libraries_load('fontfaceobserver'); } } // Add advagg_font.js; sets cookie and changes the class of the top level // element once a font has been downloaded. $file_path = drupal_get_path('module', 'advagg_font') . '/advagg_font.js'; drupal_add_js($file_path, array( 'async' => TRUE, 'defer' => TRUE, )); } /** * Implements hook_menu(). */ function advagg_font_menu() { $file_path = drupal_get_path('module', 'advagg_font'); $config_path = advagg_admin_config_root_path(); $items[$config_path . '/advagg/font'] = array( 'title' => 'Async Font Loader', 'description' => 'Load external fonts in a non blocking manner.', 'page callback' => 'drupal_get_form', 'page arguments' => array('advagg_font_admin_settings_form'), 'type' => MENU_LOCAL_TASK, 'access arguments' => array('administer site configuration'), 'file path' => $file_path, 'file' => 'advagg_font.admin.inc', 'weight' => 10, ); return $items; } /** * @} End of "addtogroup hooks". */ /** * @addtogroup 3rd_party_hooks * @{ */ /** * Implements hook_libraries_info(). */ function advagg_font_libraries_info() { $libraries['fontfaceobserver'] = array( // Only used in administrative UI of Libraries API. 'name' => 'fontfaceobserver', 'vendor url' => 'https://github.com/bramstein/fontfaceobserver', 'download url' => 'https://github.com/bramstein/fontfaceobserver/archive/master.zip', 'version arguments' => array( 'file' => 'package.json', // 1.50. : "version": "1.5.0". 'pattern' => '/"version":\\s+"([0-9\.]+)"/', 'lines' => 10, ), 'remote' => array( 'callback' => 'advagg_get_github_version_json', 'url' => 'https://rawgit.com/bramstein/fontfaceobserver/master/package.json', ), 'files' => array( 'js' => array( 'fontfaceobserver.js' => array( 'type' => 'file', 'group' => JS_LIBRARY, 'async' => TRUE, 'defer' => TRUE, ), ), ), 'variants' => array(), ); // Get the latest tagged version for external file loading. $version = advagg_get_remote_libraries_version('fontfaceobserver', $libraries['fontfaceobserver']); $libraries['fontfaceobserver']['variants'] += array( 'external' => array( 'files' => array( 'js' => array( "https://cdn.rawgit.com/bramstein/fontfaceobserver/v{$version}/fontfaceobserver.js" => array( 'type' => 'external', 'data' => "https://cdn.rawgit.com/bramstein/fontfaceobserver/v{$version}/fontfaceobserver.js", 'async' => TRUE, 'defer' => TRUE, ), ), ), ), ); // Inline if local js is there. $libraries_paths = array(); if (is_callable('libraries_get_libraries')) { $libraries_paths = libraries_get_libraries(); } if (!empty($libraries_paths['fontfaceobserver']) && is_readable($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js')) { $libraries['fontfaceobserver']['variants'] += array( 'inline' => array( 'files' => array( 'js' => array( 'loadCSS_inline' => array( 'type' => 'inline', 'data' => (string) @advagg_file_get_contents($libraries_paths['fontfaceobserver'] . '/fontfaceobserver.js'), 'no_defer' => TRUE, ), ), ), ), ); } return $libraries; } /** * @} End of "addtogroup 3rd_party_hooks". */ /** * @addtogroup advagg_hooks * @{ */ /** * Implements hook_advagg_current_hooks_hash_array_alter(). */ function advagg_font_advagg_current_hooks_hash_array_alter(&$aggregate_settings) { $aggregate_settings['variables']['advagg_font_fontfaceobserver'] = variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER); } /** * @} End of "addtogroup advagg_hooks". */ /** * Get the replacements array for the css. * * @param string $css_string * String of CSS. * * @return array * An array containing the replacemnts and the font class name. */ function advagg_font_get_replacements_array($css_string) { // Get the CSS that contains a font-family rule. $length = strlen($css_string); $property_position = 0; $property = 'font'; $property_alt = 'font-family'; $replacements = array(); $fonts_with_no_replacements = array(); $lower = strtolower($css_string); $safe_fonts_list = array( 'georgia' => TRUE, 'palatino' => TRUE, 'times new roman' => TRUE, 'times' => TRUE, 'arial' => TRUE, 'helvetica' => TRUE, 'gadget' => TRUE, 'verdana' => TRUE, 'geneva' => TRUE, 'tahoma' => TRUE, 'garamond' => TRUE, 'bookman' => TRUE, 'comic sans ms' => TRUE, 'cursive' => TRUE, 'trebuchet ms' => TRUE, 'arial black' => TRUE, 'impact' => TRUE, 'charcoal' => TRUE, 'courier new' => TRUE, 'courier' => TRUE, 'monaco' => TRUE, 'system' => TRUE, ); while (($property_position = strpos($lower, $property, $property_position)) !== FALSE) { // Find the start of the values for the property. $start_of_values = strpos($css_string, ':', $property_position); // Get the property at this location of the css. $property_in_loop = trim(substr($css_string, $property_position, ($start_of_values - $property_position))); // Make sure this property is one of the ones we're looking for. if ($property_in_loop !== $property && $property_in_loop !== $property_alt) { $property_position += strlen($property); continue; } // Get position of the last closing bracket plus 1 (start of this section). $start = strrpos($css_string, '}', -($length - $property_position)); if ($start === FALSE) { // Property is in the first selector and a declaration block (full rule // set). $start = 0; } else { // Add one to start after the }. $start++; } // Get closing bracket (end of this section). $end = strpos($css_string, '}', $property_position); if ($end === FALSE) { // The end is the end of this file. $end = $length; } // Get closing ; in order to get the end of the declaration of the property. $declaration_end_a = strpos($css_string, ';', $property_position); $declaration_end_b = strpos($css_string, '}', $property_position); if ($declaration_end_a === FALSE) { $declaration_end = $declaration_end_b; } else { $declaration_end = min($declaration_end_a, $declaration_end_b); } if ($declaration_end > $end) { $declaration_end = $end; } // Add one in order to capture the } when we ge the full rule set. $end++; // Advance position for the next run of the while loop. $property_position = $end; // Get values assigned to this property. $values_string = substr($css_string, $start_of_values + 1, $declaration_end - ($start_of_values + 1)); // Parse values string into an array of values. $values_array = explode(',', $values_string); if (empty($values_array)) { continue; } // Values array, first element is a quoted string. $dq = strpos($values_array[0], '"'); $sq = strpos($values_array[0], "'"); $quote_pos = ($sq !== FALSE) ? $sq : $dq; // Skip if the first font is not quoted. if ($quote_pos === FALSE) { continue; } $values_array[0] = trim($values_array[0]); // Skip if only one font is listed. if (count($values_array) === 1) { $fonts_with_no_replacements[$values_array[0]] = ''; continue; } // Save the first value to a variable; starting at the quote. $removed_value_original = substr($values_array[0], max($quote_pos - 1, 0)); // Resave first value. if ($quote_pos > 1) { $values_array[0] = trim(substr($values_array[0], 0, $quote_pos - 1)); } // Get value as a classname. Remove quotes, trim, lowercase, and replace // spaces with dashes. $removed_value_classname = strtolower(trim(str_replace(array('"', "'"), '', $removed_value_original))); $removed_value_classname = str_replace(' ', '-', $removed_value_classname); // Remove value if it contains a quote. $values_array_copy = $values_array; foreach ($values_array as $key => $value) { if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { unset($values_array[$key]); } elseif ($key !== 0) { break; } } if (empty($values_array)) { // See if there's a "safe" fallback that is quoted. $values_array = $values_array_copy; foreach ($values_array as $key => $value) { if (strpos($value, '"') !== FALSE || strpos($value, "'") !== FALSE) { if ($key !== 0) { $lower_key = trim(trim(strtolower(trim($value)), '"'), "'"); if (!empty($safe_fonts_list[$lower_key])) { break; } } unset($values_array[$key]); } elseif ($key !== 0) { break; } } if (empty($values_array)) { // No unquoted values left; do not modify the css. $key = array_shift($values_array_copy); $fonts_with_no_replacements[$key] = implode(',', $values_array_copy); continue; } } $extra = ''; if (isset($values_array[0])) { $extra = $values_array[0] . ' '; unset($values_array[0]); } // Rezero the keys. $values_array = array_values($values_array); // Save next value. $next_value_original = trim($values_array[0]); // Create the values string. $new_values_string = $extra . implode(',', $values_array); // Get all selectors. $end_of_selectors = strpos($css_string, '{', $start); $selectors = substr($css_string, $start, $end_of_selectors - $start); // Ensure selectors is not a media query. if (stripos($selectors, "@media") !== FALSE) { // Move the start to the end of the media query. $start = $end_of_selectors + 1; // Get the selectors again. $end_of_selectors = strpos($css_string, '{', $start); $selectors = substr($css_string, $start, $end_of_selectors - $start); } // From advagg_load_stylesheet_content(). // Perform some safe CSS optimizations. // Regexp to match comment blocks. // Regexp to match double quoted strings. // Regexp to match single quoted strings. $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; // Strip all comment blocks, but keep double/single quoted strings. $selectors_stripped = preg_replace( "<($double_quot|$single_quot)|$comment>Ss", "$1", $selectors ); // Add css class to all the selectors. $selectors_array = explode(',', $selectors_stripped); foreach ($selectors_array as &$selector) { $selector = ' .' . $removed_value_classname . ' ' . $selector; } $new_selectors = implode(',', $selectors_array); // Get full rule set. $full_rule_set = substr($css_string, $start, $end - $start); // Replace values. $new_values_full_rule_set = str_replace($values_string, $new_values_string, $full_rule_set); // Add in old rule set with new selectors. $new_selectors_full_rule_set = $new_selectors . '{' . $property_in_loop . ': ' . $values_string . ';}'; // Record info. $replacements[] = array( $full_rule_set, $new_values_full_rule_set, $new_selectors_full_rule_set, $removed_value_original, $removed_value_classname, $next_value_original, ); } return array($replacements, $fonts_with_no_replacements); }