edmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType(); // @import url media_type [media_type...] foreach ($arr as $type) { if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) { $accept = true; break; } } } else { // unconditional import $accept = true; } if ($accept) { // Store our current base url properties in case the new url is elsewhere $protocol = $this->_protocol; $host = $this->_base_host; $path = $this->_base_path; // $url = str_replace(array('"',"url", "(", ")"), "", $url); // If the protocol is php, assume that we will import using file:// // $url = Helpers::build_url($protocol === "php://" ? "file://" : $protocol, $host, $path, $url); // Above does not work for subfolders and absolute urls. // Todo: As above, do we need to replace php or file to an empty protocol for local files? if (($url = $this->resolve_url($url)) !== "none") { $this->load_css_file($url); } // Restore the current base url $this->_protocol = $protocol; $this->_base_host = $host; $this->_base_path = $path; } } /** * parse @font-face{} sections * http://www.w3.org/TR/css3-fonts/#the-font-face-rule * * @param string $str CSS @font-face rules */ private function _parse_font_face($str) { $descriptors = $this->_parse_properties($str); preg_match_all("/(url|local)\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\)\s*(format\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\))?/i", $descriptors->src, $src); $valid_sources = []; foreach ($src[0] as $i => $value) { $source = [ "local" => strtolower($src[1][$i]) === "local", "uri" => $src[2][$i], "format" => strtolower($src[4][$i]), "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]), ]; if (!$source["local"] && in_array($source["format"], ["", "truetype"]) && $source["path"] !== null) { $valid_sources[] = $source; } } // No valid sources if (empty($valid_sources)) { return; } $style = [ "family" => $descriptors->get_font_family_raw(), "weight" => $descriptors->font_weight, "style" => $descriptors->font_style, ]; $this->getFontMetrics()->registerFont($style, $valid_sources[0]["path"], $this->_dompdf->getHttpContext()); } /** * parse regular CSS blocks * * _parse_properties() creates a new Style object based on the provided * CSS rules. * * @param string $str CSS rules * @return Style */ private function _parse_properties($str) { $properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str); $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); if ($DEBUGCSS) { print '[_parse_properties'; } // Create the style $style = new Style($this, Stylesheet::ORIG_AUTHOR); foreach ($properties as $prop) { // If the $prop contains an url, the regex may be wrong // @todo: fix the regex so that it works every time /*if (strpos($prop, "url(") === false) { if (preg_match("/([a-z-]+)\s*:\s*[^:]+$/i", $prop, $m)) $prop = $m[0]; }*/ //A css property can have " ! important" appended (whitespace optional) //strip this off to decode core of the property correctly. /* Instead of short code, prefer the typical case with fast code $important = preg_match("/(.*?)!\s*important/",$prop,$match); if ( $important ) { $prop = $match[1]; } $prop = trim($prop); */ if ($DEBUGCSS) print '('; $important = false; $prop = trim($prop); if (substr($prop, -9) === 'important') { $prop_tmp = rtrim(substr($prop, 0, -9)); if (substr($prop_tmp, -1) === '!') { $prop = rtrim(substr($prop_tmp, 0, -1)); $important = true; } } if ($prop === "") { if ($DEBUGCSS) print 'empty)'; continue; } $i = mb_strpos($prop, ":"); if ($i === false) { if ($DEBUGCSS) print 'novalue' . $prop . ')'; continue; } $prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i))); $value = ltrim(mb_substr($prop, $i + 1)); if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')'; $style->set_prop($prop_name, $value, $important, false); } if ($DEBUGCSS) print '_parse_properties]'; return $style; } /** * parse selector + rulesets * * @param string $str CSS selectors and rulesets * @param array $media_queries */ private function _parse_sections($str, $media_queries = []) { // Pre-process selectors: collapse all whitespace and strip whitespace // around '>', '.', ':', '+', '~', '#' $patterns = ["/\s+/", "/\s+([>.:+~#])\s+/"]; $replacements = [" ", "\\1"]; $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); $sections = explode("}", $str); if ($DEBUGCSS) print '[_parse_sections'; foreach ($sections as $sect) { $i = mb_strpos($sect, "{"); if ($i === false) { continue; } if ($DEBUGCSS) print '[section'; $selector_str = preg_replace($patterns, $replacements, mb_substr($sect, 0, $i)); $selectors = preg_split("/,(?![^\(]*\))/", $selector_str, 0, PREG_SPLIT_NO_EMPTY); $style = $this->_parse_properties(trim(mb_substr($sect, $i + 1))); // Assign it to the selected elements foreach ($selectors as $selector) { $selector = trim($selector); if ($selector === "") { if ($DEBUGCSS) print '#empty#'; continue; } if ($DEBUGCSS) print '#' . $selector . '#'; //if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; } //FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here if (count($media_queries) > 0) { $style->set_media_queries($media_queries); } $this->add_style($selector, $style); } if ($DEBUGCSS) { print 'section]'; } } if ($DEBUGCSS) { print "_parse_sections]\n"; } } /** * @return string */ public function getDefaultStylesheet() { $options = $this->_dompdf->getOptions(); $rootDir = realpath($options->getRootDir()); return Helpers::build_url("file://", "", $rootDir, $rootDir . self::DEFAULT_STYLESHEET); } /** * @param FontMetrics $fontMetrics * @return $this */ public function setFontMetrics(FontMetrics $fontMetrics) { $this->fontMetrics = $fontMetrics; return $this; } /** * @return FontMetrics */ public function getFontMetrics() { return $this->fontMetrics; } /** * dumps the entire stylesheet as a string * * Generates a string of each selector and associated style in the * Stylesheet. Useful for debugging. * * @return string */ function __toString() { $str = ""; foreach ($this->_styles as $selector => $selector_styles) { foreach ($selector_styles as $style) { $str .= "$selector => " . $style->__toString() . "\n"; } } return $str; } }