Example #1
0
	/**
	 * Return the image type given the file name or array returned by getimagesize() function.
	 * @param $imgfile (string) image file name
	 * @param $iminfo (array) array of image information returned by getimagesize() function.
	 * @return string image type
	 * @since 4.8.017 (2009-11-27)
	 * @public static
	 */
	public static function getImageFileType($imgfile, $iminfo=array()) {
		$type = '';
		if (isset($iminfo['mime']) AND !empty($iminfo['mime'])) {
			$mime = explode('/', $iminfo['mime']);
			if ((count($mime) > 1) AND ($mime[0] == 'image') AND (!empty($mime[1]))) {
				$type = strtolower(trim($mime[1]));
			}
		}
		if (empty($type)) {
			$fileinfo = pathinfo($imgfile);
			if (isset($fileinfo['extension']) AND (!TCPDF_STATIC::empty_string($fileinfo['extension']))) {
				$type = strtolower(trim($fileinfo['extension']));
			}
		}
		if ($type == 'jpg') {
			$type = 'jpeg';
		}
		return $type;
	}
 /**
  * Add page if needed.
  *
  * @param float|int $h       cell height. Default value: 0
  * @param mixed     $y       starting y position, leave empty for current
  *                           position
  * @param boolean   $addpage if true add a page, otherwise only return
  *                           the true/false state
  *
  * @return boolean true in case of page break, false otherwise.
  */
 function checkPageBreak($h = 0, $y = '', $addpage = true)
 {
     if (TCPDF_STATIC::empty_string($y)) {
         $y = $this->y;
     }
     $current_page = $this->page;
     if ($y + $h > $this->PageBreakTrigger and !$this->InFooter and $this->AcceptPageBreak()) {
         if ($addpage) {
             //Automatic page break
             $x = $this->x;
             $this->AddPage($this->CurOrientation);
             $this->y = $this->dataY;
             $oldpage = $this->page - 1;
             $this_page_orm = $this->pagedim[$this->page]['orm'];
             $old_page_orm = $this->pagedim[$oldpage]['orm'];
             $this_page_olm = $this->pagedim[$this->page]['olm'];
             $old_page_olm = $this->pagedim[$oldpage]['olm'];
             if ($this->rtl) {
                 if ($this_page_orm != $old_page_orm) {
                     $this->x = $x - ($this_page_orm - $old_page_orm);
                 } else {
                     $this->x = $x;
                 }
             } else {
                 if ($this_page_olm != $old_page_olm) {
                     $this->x = $x + ($this_page_olm - $old_page_olm);
                 } else {
                     $this->x = $x;
                 }
             }
         }
         return true;
     }
     if ($current_page != $this->page) {
         // account for columns mode
         return true;
     }
     return false;
 }
Example #3
0
 /**
  * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
  * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
  * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
  * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
  * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
  * @author Nicola Asuni
  * @since 5.0.000 (2010-05-02)
  * @protected
  */
 protected function startSVGElementHandler($parser, $name, $attribs, $ctm = array())
 {
     // check if we are in clip mode
     if ($this->svgclipmode) {
         $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
         return;
     }
     if ($this->svgdefsmode and !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
         if (!isset($attribs['id'])) {
             $attribs['id'] = 'DF_' . (count($this->svgdefs) + 1);
         }
         $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
         return;
     }
     $clipping = false;
     if ($parser == 'clip-path') {
         // set clipping mode
         $clipping = true;
     }
     // get styling properties
     $prev_svgstyle = $this->svgstyles[count($this->svgstyles) - 1];
     // previous style
     $svgstyle = $this->svgstyles[0];
     // set default style
     if ($clipping and !isset($attribs['fill']) and (!isset($attribs['style']) or !preg_match('/[;\\"\\s]{1}fill[\\s]*:[\\s]*([^;\\"]*)/si', $attribs['style'], $attrval))) {
         // default fill attribute for clipping
         $attribs['fill'] = 'none';
     }
     if (isset($attribs['style']) and !TCPDF_STATIC::empty_string($attribs['style'])) {
         // fix style for regular expression
         $attribs['style'] = ';' . $attribs['style'];
     }
     foreach ($prev_svgstyle as $key => $val) {
         if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
             // inherit previous value
             $svgstyle[$key] = $val;
         }
         if (isset($attribs[$key]) and !TCPDF_STATIC::empty_string($attribs[$key])) {
             // specific attribute settings
             if ($attribs[$key] == 'inherit') {
                 $svgstyle[$key] = $val;
             } else {
                 $svgstyle[$key] = $attribs[$key];
             }
         } elseif (isset($attribs['style']) and !TCPDF_STATIC::empty_string($attribs['style'])) {
             // CSS style syntax
             $attrval = array();
             if (preg_match('/[;\\"\\s]{1}' . $key . '[\\s]*:[\\s]*([^;\\"]*)/si', $attribs['style'], $attrval) and isset($attrval[1])) {
                 if ($attrval[1] == 'inherit') {
                     $svgstyle[$key] = $val;
                 } else {
                     $svgstyle[$key] = $attrval[1];
                 }
             }
         }
     }
     // transformation matrix
     if (!empty($ctm)) {
         $tm = $ctm;
     } else {
         //$tm = $this->svgstyles[(count($this->svgstyles) - 1)]['transfmatrix'];
         $tm = array(1, 0, 0, 1, 0, 0);
     }
     if (isset($attribs['transform']) and !empty($attribs['transform'])) {
         $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
     }
     $svgstyle['transfmatrix'] = $tm;
     $invisible = false;
     if ($svgstyle['visibility'] == 'hidden' or $svgstyle['visibility'] == 'collapse' or $svgstyle['display'] == 'none') {
         // the current graphics element is invisible (nothing is painted)
         $invisible = true;
     }
     // process tag
     switch ($name) {
         case 'defs':
             $this->svgdefsmode = true;
             break;
             // clipPath
         // clipPath
         case 'clipPath':
             if ($invisible) {
                 break;
             }
             $this->svgclipmode = true;
             if (!isset($attribs['id'])) {
                 $attribs['id'] = 'CP_' . (count($this->svgcliptm) + 1);
             }
             $this->svgclipid = $attribs['id'];
             $this->svgclippaths[$this->svgclipid] = array();
             $this->svgcliptm[$this->svgclipid] = $tm;
             break;
         case 'svg':
             // start of SVG object
             break;
         case 'g':
             // group together related graphics elements
             array_push($this->svgstyles, $svgstyle);
             $this->StartTransform();
             $this->SVGTransform($tm);
             $this->setSVGStyles($svgstyle, $prev_svgstyle);
             break;
         case 'linearGradient':
             if ($this->pdfa_mode) {
                 break;
             }
             if (!isset($attribs['id'])) {
                 $attribs['id'] = 'GR_' . (count($this->svggradients) + 1);
             }
             $this->svggradientid = $attribs['id'];
             $this->svggradients[$this->svggradientid] = array();
             $this->svggradients[$this->svggradientid]['type'] = 2;
             $this->svggradients[$this->svggradientid]['stops'] = array();
             if (isset($attribs['gradientUnits'])) {
                 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
             } else {
                 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
             }
             //$attribs['spreadMethod']
             if (!isset($attribs['x1']) and !isset($attribs['y1']) and !isset($attribs['x2']) and !isset($attribs['y2']) or (isset($attribs['x1']) and substr($attribs['x1'], -1) == '%' or isset($attribs['y1']) and substr($attribs['y1'], -1) == '%' or isset($attribs['x2']) and substr($attribs['x2'], -1) == '%' or isset($attribs['y2']) and substr($attribs['y2'], -1) == '%')) {
                 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
             } else {
                 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
             }
             $x1 = isset($attribs['x1']) ? $attribs['x1'] : '0';
             $y1 = isset($attribs['y1']) ? $attribs['y1'] : '0';
             $x2 = isset($attribs['x2']) ? $attribs['x2'] : '100';
             $y2 = isset($attribs['y2']) ? $attribs['y2'] : '0';
             if (isset($attribs['gradientTransform'])) {
                 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
             }
             $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
             if (isset($attribs['xlink:href']) and !empty($attribs['xlink:href'])) {
                 // gradient is defined on another place
                 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
             }
             break;
         case 'radialGradient':
             if ($this->pdfa_mode) {
                 break;
             }
             if (!isset($attribs['id'])) {
                 $attribs['id'] = 'GR_' . (count($this->svggradients) + 1);
             }
             $this->svggradientid = $attribs['id'];
             $this->svggradients[$this->svggradientid] = array();
             $this->svggradients[$this->svggradientid]['type'] = 3;
             $this->svggradients[$this->svggradientid]['stops'] = array();
             if (isset($attribs['gradientUnits'])) {
                 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
             } else {
                 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
             }
             //$attribs['spreadMethod']
             if (!isset($attribs['cx']) and !isset($attribs['cy']) or (isset($attribs['cx']) and substr($attribs['cx'], -1) == '%' or isset($attribs['cy']) and substr($attribs['cy'], -1) == '%')) {
                 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
             } else {
                 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
             }
             $cx = isset($attribs['cx']) ? $attribs['cx'] : 0.5;
             $cy = isset($attribs['cy']) ? $attribs['cy'] : 0.5;
             $fx = isset($attribs['fx']) ? $attribs['fx'] : $cx;
             $fy = isset($attribs['fy']) ? $attribs['fy'] : $cy;
             $r = isset($attribs['r']) ? $attribs['r'] : 0.5;
             if (isset($attribs['gradientTransform'])) {
                 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
             }
             $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
             if (isset($attribs['xlink:href']) and !empty($attribs['xlink:href'])) {
                 // gradient is defined on another place
                 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
             }
             break;
         case 'stop':
             // gradient stops
             if (substr($attribs['offset'], -1) == '%') {
                 $offset = floatval(substr($attribs['offset'], -1)) / 100;
             } else {
                 $offset = floatval($attribs['offset']);
                 if ($offset > 1) {
                     $offset /= 100;
                 }
             }
             $stop_color = isset($svgstyle['stop-color']) ? TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors) : 'black';
             $opacity = isset($svgstyle['stop-opacity']) ? $svgstyle['stop-opacity'] : 1;
             $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
             break;
             // paths
         // paths
         case 'path':
             if ($invisible) {
                 break;
             }
             if (isset($attribs['d'])) {
                 $d = trim($attribs['d']);
                 if (!empty($d)) {
                     if ($clipping) {
                         $this->SVGTransform($tm);
                         $this->SVGPath($d, 'CNZ');
                     } else {
                         $this->StartTransform();
                         $this->SVGTransform($tm);
                         $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, 0, 0, 1, 1, 'SVGPath', array($d, 'CNZ'));
                         if (!empty($obstyle)) {
                             $this->SVGPath($d, $obstyle);
                         }
                         $this->StopTransform();
                     }
                 }
             }
             break;
             // shapes
         // shapes
         case 'rect':
             if ($invisible) {
                 break;
             }
             $x = isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0;
             $y = isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0;
             $w = isset($attribs['width']) ? $this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false) : 0;
             $h = isset($attribs['height']) ? $this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false) : 0;
             $rx = isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0;
             $ry = isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : $rx;
             if ($clipping) {
                 $this->SVGTransform($tm);
                 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
             } else {
                 $this->StartTransform();
                 $this->SVGTransform($tm);
                 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
                 if (!empty($obstyle)) {
                     $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
                 }
                 $this->StopTransform();
             }
             break;
         case 'circle':
             if ($invisible) {
                 break;
             }
             $r = isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0;
             $cx = isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0);
             $cy = isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0);
             $x = $cx - $r;
             $y = $cy - $r;
             $w = 2 * $r;
             $h = $w;
             if ($clipping) {
                 $this->SVGTransform($tm);
                 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
             } else {
                 $this->StartTransform();
                 $this->SVGTransform($tm);
                 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
                 if (!empty($obstyle)) {
                     $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
                 }
                 $this->StopTransform();
             }
             break;
         case 'ellipse':
             if ($invisible) {
                 break;
             }
             $rx = isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0;
             $ry = isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0;
             $cx = isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0);
             $cy = isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0);
             $x = $cx - $rx;
             $y = $cy - $ry;
             $w = 2 * $rx;
             $h = 2 * $ry;
             if ($clipping) {
                 $this->SVGTransform($tm);
                 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
             } else {
                 $this->StartTransform();
                 $this->SVGTransform($tm);
                 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
                 if (!empty($obstyle)) {
                     $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
                 }
                 $this->StopTransform();
             }
             break;
         case 'line':
             if ($invisible) {
                 break;
             }
             $x1 = isset($attribs['x1']) ? $this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false) : 0;
             $y1 = isset($attribs['y1']) ? $this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false) : 0;
             $x2 = isset($attribs['x2']) ? $this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false) : 0;
             $y2 = isset($attribs['y2']) ? $this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false) : 0;
             $x = $x1;
             $y = $y1;
             $w = abs($x2 - $x1);
             $h = abs($y2 - $y1);
             if (!$clipping) {
                 $this->StartTransform();
                 $this->SVGTransform($tm);
                 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
                 $this->Line($x1, $y1, $x2, $y2);
                 $this->StopTransform();
             }
             break;
         case 'polyline':
         case 'polygon':
             if ($invisible) {
                 break;
             }
             $points = isset($attribs['points']) ? $attribs['points'] : '0 0';
             $points = trim($points);
             // note that point may use a complex syntax not covered here
             $points = preg_split('/[\\,\\s]+/si', $points);
             if (count($points) < 4) {
                 break;
             }
             $p = array();
             $xmin = 2147483647;
             $xmax = 0;
             $ymin = 2147483647;
             $ymax = 0;
             foreach ($points as $key => $val) {
                 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
                 if ($key % 2 == 0) {
                     // X coordinate
                     $xmin = min($xmin, $p[$key]);
                     $xmax = max($xmax, $p[$key]);
                 } else {
                     // Y coordinate
                     $ymin = min($ymin, $p[$key]);
                     $ymax = max($ymax, $p[$key]);
                 }
             }
             $x = $xmin;
             $y = $ymin;
             $w = $xmax - $xmin;
             $h = $ymax - $ymin;
             if ($name == 'polyline') {
                 $this->StartTransform();
                 $this->SVGTransform($tm);
                 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
                 if (!empty($obstyle)) {
                     $this->PolyLine($p, $obstyle, array(), array());
                 }
                 $this->StopTransform();
             } else {
                 // polygon
                 if ($clipping) {
                     $this->SVGTransform($tm);
                     $this->Polygon($p, 'CNZ', array(), array(), true);
                 } else {
                     $this->StartTransform();
                     $this->SVGTransform($tm);
                     $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
                     if (!empty($obstyle)) {
                         $this->Polygon($p, $obstyle, array(), array(), true);
                     }
                     $this->StopTransform();
                 }
             }
             break;
             // image
         // image
         case 'image':
             if ($invisible) {
                 break;
             }
             if (!isset($attribs['xlink:href']) or empty($attribs['xlink:href'])) {
                 break;
             }
             $x = isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0;
             $y = isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0;
             $w = isset($attribs['width']) ? $this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false) : 0;
             $h = isset($attribs['height']) ? $this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false) : 0;
             $img = $attribs['xlink:href'];
             if (!$clipping) {
                 $this->StartTransform();
                 $this->SVGTransform($tm);
                 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
                 if (preg_match('/^data:image\\/[^;]+;base64,/', $img, $m) > 0) {
                     // embedded image encoded as base64
                     $img = '@' . base64_decode(substr($img, strlen($m[0])));
                 } else {
                     // fix image path
                     if (!TCPDF_STATIC::empty_string($this->svgdir) and ($img[0] == '.' or basename($img) == $img)) {
                         // replace relative path with full server path
                         $img = $this->svgdir . '/' . $img;
                     }
                     if ($img[0] == '/' and !empty($_SERVER['DOCUMENT_ROOT']) and $_SERVER['DOCUMENT_ROOT'] != '/') {
                         $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
                         if ($findroot === false or $findroot > 1) {
                             if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
                                 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1) . $img;
                             } else {
                                 $img = $_SERVER['DOCUMENT_ROOT'] . $img;
                             }
                         }
                     }
                     $img = urldecode($img);
                     $testscrtype = @parse_url($img);
                     if (!isset($testscrtype['query']) or empty($testscrtype['query'])) {
                         // convert URL to server path
                         $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
                     }
                 }
                 // get image type
                 $imgtype = TCPDF_IMAGES::getImageFileType($img);
                 if ($imgtype == 'eps' or $imgtype == 'ai') {
                     $this->ImageEps($img, $x, $y, $w, $h);
                 } elseif ($imgtype == 'svg') {
                     $this->ImageSVG($img, $x, $y, $w, $h);
                 } else {
                     $this->Image($img, $x, $y, $w, $h);
                 }
                 $this->StopTransform();
             }
             break;
             // text
         // text
         case 'text':
         case 'tspan':
             // only basic support - advanced features must be implemented
             $this->svgtextmode['invisible'] = $invisible;
             if ($invisible) {
                 break;
             }
             array_push($this->svgstyles, $svgstyle);
             if (isset($attribs['x'])) {
                 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
             } elseif ($name == 'tspan') {
                 $x = $this->x;
             } else {
                 $x = 0;
             }
             if (isset($attribs['dx'])) {
                 $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
             }
             if (isset($attribs['y'])) {
                 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
             } elseif ($name == 'tspan') {
                 $y = $this->y;
             } else {
                 $y = 0;
             }
             if (isset($attribs['dy'])) {
                 $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
             }
             $svgstyle['text-color'] = $svgstyle['fill'];
             $this->svgtext = '';
             if (isset($svgstyle['text-anchor'])) {
                 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
             } else {
                 $this->svgtextmode['text-anchor'] = 'start';
             }
             if (isset($svgstyle['direction'])) {
                 if ($svgstyle['direction'] == 'rtl') {
                     $this->svgtextmode['rtl'] = true;
                 } else {
                     $this->svgtextmode['rtl'] = false;
                 }
             } else {
                 $this->svgtextmode['rtl'] = false;
             }
             if (isset($svgstyle['stroke']) and $svgstyle['stroke'] != 'none' and isset($svgstyle['stroke-width']) and $svgstyle['stroke-width'] > 0) {
                 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
             } else {
                 $this->svgtextmode['stroke'] = false;
             }
             $this->StartTransform();
             $this->SVGTransform($tm);
             $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
             $this->x = $x;
             $this->y = $y;
             break;
             // use
         // use
         case 'use':
             if (isset($attribs['xlink:href']) and !empty($attribs['xlink:href'])) {
                 $svgdefid = substr($attribs['xlink:href'], 1);
                 if (isset($this->svgdefs[$svgdefid])) {
                     $use = $this->svgdefs[$svgdefid];
                     if (isset($attribs['xlink:href'])) {
                         unset($attribs['xlink:href']);
                     }
                     if (isset($attribs['id'])) {
                         unset($attribs['id']);
                     }
                     $attribs = array_merge($attribs, $use['attribs']);
                     $this->startSVGElementHandler($parser, $use['name'], $attribs);
                 }
             }
             break;
         default:
             break;
     }
     // end of switch
 }
	/**
	 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
	 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
	 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
	 * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
	 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
	 * @author Nicola Asuni
	 * @since 5.0.000 (2010-05-02)
	 * @protected
	 */
	protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
		$name = $this->removeTagNamespace($name);
		// check if we are in clip mode
		if ($this->svgclipmode) {
			$this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
			return;
		}
		if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
			if (isset($attribs['id'])) {
				$attribs['child_elements'] = array();
				$this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
				return;
			}
			if (end($this->svgdefs) !== FALSE) {
				$last_svgdefs_id = key($this->svgdefs);
				if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
					$attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
					$this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
					return;
				}
			}
			return;
		}
		$clipping = false;
		if ($parser == 'clip-path') {
			// set clipping mode
			$clipping = true;
		}
		// get styling properties
		$prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
		$svgstyle = $this->svgstyles[0]; // set default style
		if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
			// default fill attribute for clipping
			$attribs['fill'] = 'none';
		}
		if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
			// fix style for regular expression
			$attribs['style'] = ';'.$attribs['style'];
		}
		foreach ($prev_svgstyle as $key => $val) {
			if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
				// inherit previous value
				$svgstyle[$key] = $val;
			}
			if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
				// specific attribute settings
				if ($attribs[$key] == 'inherit') {
					$svgstyle[$key] = $val;
				} else {
					$svgstyle[$key] = $attribs[$key];
				}
			} elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
				// CSS style syntax
				$attrval = array();
				if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
					if ($attrval[1] == 'inherit') {
						$svgstyle[$key] = $val;
					} else {
						$svgstyle[$key] = $attrval[1];
					}
				}
			}
		}
		// transformation matrix
		if (!empty($ctm)) {
			$tm = $ctm;
		} else {
			$tm = array(1,0,0,1,0,0);
		}
		if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
			$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
		}
		$svgstyle['transfmatrix'] = $tm;
		$invisible = false;
		if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
			// the current graphics element is invisible (nothing is painted)
			$invisible = true;
		}
		// process tag
		switch($name) {
			case 'defs': {
				$this->svgdefsmode = true;
				break;
			}
			// clipPath
			case 'clipPath': {
				if ($invisible) {
					break;
				}
				$this->svgclipmode = true;
				if (!isset($attribs['id'])) {
					$attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
				}
				$this->svgclipid = $attribs['id'];
				$this->svgclippaths[$this->svgclipid] = array();
				$this->svgcliptm[$this->svgclipid] = $tm;
				break;
			}
			case 'svg': {
				// start of SVG object
				if(++$this->svg_tag_depth <= 1) {
					break;
				}
				// inner SVG
				array_push($this->svgstyles, $svgstyle);
				$this->StartTransform();
				$svgX = (isset($attribs['x'])?$attribs['x']:0);
				$svgY = (isset($attribs['y'])?$attribs['y']:0);
				$svgW = (isset($attribs['width'])?$attribs['width']:0);
				$svgH = (isset($attribs['height'])?$attribs['height']:0);
				// set x, y position using transform matrix
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
				$this->SVGTransform($tm);
				// set clipping for width and height
				$x = 0;
				$y = 0;
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
				// draw clipping rect
				$this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
				// parse viewbox, calculate extra transformation matrix
				if (isset($attribs['viewBox'])) {
					$tmp = array();
					preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
					$tmp = $tmp[0];
					if (sizeof($tmp) == 4) {
						$vx = $tmp[0];
						$vy = $tmp[1];
						$vw = $tmp[2];
						$vh = $tmp[3];
						// get aspect ratio
						$tmp = array();
						$aspectX = 'xMid';
						$aspectY = 'YMid';
						$fit = 'meet';
						if (isset($attribs['preserveAspectRatio'])) {
							if($attribs['preserveAspectRatio'] == 'none') {
								$fit = 'none';
							} else {
								preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
								$tmp = $tmp[0];
								if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
									$aspectX = substr($tmp[0], 0, 4);
									$aspectY = substr($tmp[0], 4, 4);
									$fit = $tmp[1];
								}
							}
						}
						$wr = ($svgW / $vw);
						$hr = ($svgH / $vh);
						$ax = $ay = 0;
						if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
							if ($aspectX == 'xMax') {
								$ax = (($vw * ($wr / $hr)) - $vw);
							}
							if ($aspectX == 'xMid') {
								$ax = ((($vw * ($wr / $hr)) - $vw) / 2);
							}
							$wr = $hr;
						} elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
							if ($aspectY == 'YMax') {
								$ay = (($vh * ($hr / $wr)) - $vh);
							}
							if ($aspectY == 'YMid') {
								$ay = ((($vh * ($hr / $wr)) - $vh) / 2);
							}
							$hr = $wr;
						}
						$newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
						$this->SVGTransform($tm);
					}
				}
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
				break;
			}
			case 'g': {
				// group together related graphics elements
				array_push($this->svgstyles, $svgstyle);
				$this->StartTransform();
				$x = (isset($attribs['x'])?$attribs['x']:0);
				$y = (isset($attribs['y'])?$attribs['y']:0);
				$w = 1;//(isset($attribs['width'])?$attribs['width']:1);
				$h = 1;//(isset($attribs['height'])?$attribs['height']:1);
				$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
				$this->SVGTransform($tm);
				$this->setSVGStyles($svgstyle, $prev_svgstyle);
				break;
			}
			case 'linearGradient': {
				if ($this->pdfa_mode) {
					break;
				}
				if (!isset($attribs['id'])) {
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
				}
				$this->svggradientid = $attribs['id'];
				$this->svggradients[$this->svggradientid] = array();
				$this->svggradients[$this->svggradientid]['type'] = 2;
				$this->svggradients[$this->svggradientid]['stops'] = array();
				if (isset($attribs['gradientUnits'])) {
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
				} else {
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
				}
				//$attribs['spreadMethod']
				if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
					OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
						OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
						OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
						OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
				} else {
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
				}
				$x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
				$y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
				$x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
				$y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
				if (isset($attribs['gradientTransform'])) {
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
				}
				$this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
					// gradient is defined on another place
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
				}
				break;
			}
			case 'radialGradient': {
				if ($this->pdfa_mode) {
					break;
				}
				if (!isset($attribs['id'])) {
					$attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
				}
				$this->svggradientid = $attribs['id'];
				$this->svggradients[$this->svggradientid] = array();
				$this->svggradients[$this->svggradientid]['type'] = 3;
				$this->svggradients[$this->svggradientid]['stops'] = array();
				if (isset($attribs['gradientUnits'])) {
					$this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
				} else {
					$this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
				}
				//$attribs['spreadMethod']
				if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
					OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
					OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
					$this->svggradients[$this->svggradientid]['mode'] = 'percentage';
				} elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
					$this->svggradients[$this->svggradientid]['mode'] = 'ratio';
				} else {
					$this->svggradients[$this->svggradientid]['mode'] = 'measure';
				}
				$cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
				$cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
				$fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
				$fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
				$r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
				if (isset($attribs['gradientTransform'])) {
					$this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
				}
				$this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
					// gradient is defined on another place
					$this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
				}
				break;
			}
			case 'stop': {
				// gradient stops
				if (substr($attribs['offset'], -1) == '%') {
					$offset = floatval(substr($attribs['offset'], -1)) / 100;
				} else {
					$offset = floatval($attribs['offset']);
					if ($offset > 1) {
						$offset /= 100;
					}
				}
				$stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
				$opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
				$this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
				break;
			}
			// paths
			case 'path': {
				if ($invisible) {
					break;
				}
				if (isset($attribs['d'])) {
					$d = trim($attribs['d']);
					if (!empty($d)) {
						$x = (isset($attribs['x'])?$attribs['x']:0);
						$y = (isset($attribs['y'])?$attribs['y']:0);
						$w = (isset($attribs['width'])?$attribs['width']:1);
						$h = (isset($attribs['height'])?$attribs['height']:1);
						$tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
						if ($clipping) {
							$this->SVGTransform($tm);
							$this->SVGPath($d, 'CNZ');
						} else {
							$this->StartTransform();
							$this->SVGTransform($tm);
							$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
							if (!empty($obstyle)) {
								$this->SVGPath($d, $obstyle);
							}
							$this->StopTransform();
						}
					}
				}
				break;
			}
			// shapes
			case 'rect': {
				if ($invisible) {
					break;
				}
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
				$rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
				$ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
				if ($clipping) {
					$this->SVGTransform($tm);
					$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
				} else {
					$this->StartTransform();
					$this->SVGTransform($tm);
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
					if (!empty($obstyle)) {
						$this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
					}
					$this->StopTransform();
				}
				break;
			}
			case 'circle': {
				if ($invisible) {
					break;
				}
				$r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
				$x = ($cx - $r);
				$y = ($cy - $r);
				$w = (2 * $r);
				$h = $w;
				if ($clipping) {
					$this->SVGTransform($tm);
					$this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
				} else {
					$this->StartTransform();
					$this->SVGTransform($tm);
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
					if (!empty($obstyle)) {
						$this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
					}
					$this->StopTransform();
				}
				break;
			}
			case 'ellipse': {
				if ($invisible) {
					break;
				}
				$rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
				$ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
				$cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
				$cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
				$x = ($cx - $rx);
				$y = ($cy - $ry);
				$w = (2 * $rx);
				$h = (2 * $ry);
				if ($clipping) {
					$this->SVGTransform($tm);
					$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
				} else {
					$this->StartTransform();
					$this->SVGTransform($tm);
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
					if (!empty($obstyle)) {
						$this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
					}
					$this->StopTransform();
				}
				break;
			}
			case 'line': {
				if ($invisible) {
					break;
				}
				$x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
				$y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
				$x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
				$y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
				$x = $x1;
				$y = $y1;
				$w = abs($x2 - $x1);
				$h = abs($y2 - $y1);
				if (!$clipping) {
					$this->StartTransform();
					$this->SVGTransform($tm);
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
					$this->Line($x1, $y1, $x2, $y2);
					$this->StopTransform();
				}
				break;
			}
			case 'polyline':
			case 'polygon': {
				if ($invisible) {
					break;
				}
				$points = (isset($attribs['points'])?$attribs['points']:'0 0');
				$points = trim($points);
				// note that point may use a complex syntax not covered here
				$points = preg_split('/[\,\s]+/si', $points);
				if (count($points) < 4) {
					break;
				}
				$p = array();
				$xmin = 2147483647;
				$xmax = 0;
				$ymin = 2147483647;
				$ymax = 0;
				foreach ($points as $key => $val) {
					$p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
					if (($key % 2) == 0) {
						// X coordinate
						$xmin = min($xmin, $p[$key]);
						$xmax = max($xmax, $p[$key]);
					} else {
						// Y coordinate
						$ymin = min($ymin, $p[$key]);
						$ymax = max($ymax, $p[$key]);
					}
				}
				$x = $xmin;
				$y = $ymin;
				$w = ($xmax - $xmin);
				$h = ($ymax - $ymin);
				if ($name == 'polyline') {
					$this->StartTransform();
					$this->SVGTransform($tm);
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
					if (!empty($obstyle)) {
						$this->PolyLine($p, $obstyle, array(), array());
					}
					$this->StopTransform();
				} else { // polygon
					if ($clipping) {
						$this->SVGTransform($tm);
						$this->Polygon($p, 'CNZ', array(), array(), true);
					} else {
						$this->StartTransform();
						$this->SVGTransform($tm);
						$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
						if (!empty($obstyle)) {
							$this->Polygon($p, $obstyle, array(), array(), true);
						}
						$this->StopTransform();
					}
				}
				break;
			}
			// image
			case 'image': {
				if ($invisible) {
					break;
				}
				if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
					break;
				}
				$x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
				$y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
				$w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
				$h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
				$img = $attribs['xlink:href'];
				if (!$clipping) {
					$this->StartTransform();
					$this->SVGTransform($tm);
					$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
					if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
						// embedded image encoded as base64
						$img = '@'.base64_decode(substr($img, strlen($m[0])));
					} else {
						// fix image path
						if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
							// replace relative path with full server path
							$img = $this->svgdir.'/'.$img;
						}
						if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
							$findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
							if (($findroot === false) OR ($findroot > 1)) {
								if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
									$img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
								} else {
									$img = $_SERVER['DOCUMENT_ROOT'].$img;
								}
							}
						}
						$img = urldecode($img);
						$testscrtype = @parse_url($img);
						if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
							// convert URL to server path
							$img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
						}
					}
					// get image type
					$imgtype = TCPDF_IMAGES::getImageFileType($img);
					if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
						$this->ImageEps($img, $x, $y, $w, $h);
					} elseif ($imgtype == 'svg') {
						// store SVG vars
						$svggradients = $this->svggradients;
						$svggradientid = $this->svggradientid;
						$svgdefsmode = $this->svgdefsmode;
						$svgdefs = $this->svgdefs;
						$svgclipmode = $this->svgclipmode;
						$svgclippaths = $this->svgclippaths;
						$svgcliptm = $this->svgcliptm;
						$svgclipid = $this->svgclipid;
						$svgtext = $this->svgtext;
						$svgtextmode = $this->svgtextmode;
						$this->ImageSVG($img, $x, $y, $w, $h);
						// restore SVG vars
						$this->svggradients = $svggradients;
						$this->svggradientid = $svggradientid;
						$this->svgdefsmode = $svgdefsmode;
						$this->svgdefs = $svgdefs;
						$this->svgclipmode = $svgclipmode;
						$this->svgclippaths = $svgclippaths;
						$this->svgcliptm = $svgcliptm;
						$this->svgclipid = $svgclipid;
						$this->svgtext = $svgtext;
						$this->svgtextmode = $svgtextmode;
					} else {
						$this->Image($img, $x, $y, $w, $h);
					}
					$this->StopTransform();
				}
				break;
			}
			// text
			case 'text':
			case 'tspan': {
				if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
					// @TODO: unsupported feature
				}
				// only basic support - advanced features must be implemented
				$this->svgtextmode['invisible'] = $invisible;
				if ($invisible) {
					break;
				}
				array_push($this->svgstyles, $svgstyle);
				if (isset($attribs['x'])) {
					$x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
				} elseif ($name == 'tspan') {
					$x = $this->x;
				} else {
					$x = 0;
				}
				if (isset($attribs['dx'])) {
					$x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
				}
				if (isset($attribs['y'])) {
					$y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
				} elseif ($name == 'tspan') {
					$y = $this->y;
				} else {
					$y = 0;
				}
				if (isset($attribs['dy'])) {
					$y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
				}
				$svgstyle['text-color'] = $svgstyle['fill'];
				$this->svgtext = '';
				if (isset($svgstyle['text-anchor'])) {
					$this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
				} else {
					$this->svgtextmode['text-anchor'] = 'start';
				}
				if (isset($svgstyle['direction'])) {
					if ($svgstyle['direction'] == 'rtl') {
						$this->svgtextmode['rtl'] = true;
					} else {
						$this->svgtextmode['rtl'] = false;
					}
				} else {
					$this->svgtextmode['rtl'] = false;
				}
				if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
					$this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
				} else {
					$this->svgtextmode['stroke'] = false;
				}
				$this->StartTransform();
				$this->SVGTransform($tm);
				$obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
				$this->x = $x;
				$this->y = $y;
				break;
			}
			// use
			case 'use': {
				if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
					$svgdefid = substr($attribs['xlink:href'], 1);
					if (isset($this->svgdefs[$svgdefid])) {
						$use = $this->svgdefs[$svgdefid];
						if (isset($attribs['xlink:href'])) {
							unset($attribs['xlink:href']);
						}
						if (isset($attribs['id'])) {
							unset($attribs['id']);
						}
						if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
							$attribs['x'] += $use['attribs']['x'];
						}
						if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
							$attribs['y'] += $use['attribs']['y'];
						}
						if (empty($attribs['style'])) {
							$attribs['style'] = '';
						}
						if (!empty($use['attribs']['style'])) {
							// merge styles
							$attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
						}
						$attribs = array_merge($use['attribs'], $attribs);
						$this->startSVGElementHandler($parser, $use['name'], $attribs);
						return;
					}
				}
				break;
			}
			default: {
				break;
			}
		} // end of switch
		// process child elements
		if (!empty($attribs['child_elements'])) {
			$child_elements = $attribs['child_elements'];
			unset($attribs['child_elements']);
			foreach($child_elements as $child_element) {
				if (empty($child_element['attribs']['closing_tag'])) {
					$this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
				} else {
					if (isset($child_element['attribs']['content'])) {
						$this->svgtext = $child_element['attribs']['content'];
					}
					$this->endSVGElementHandler('child-tag', $child_element['name']);
				}
			}
		}
	}
 /**
  * Returns an array of hyphenation patterns.
  * @param $file (string) TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
  * @return array of hyphenation patterns
  * @author Nicola Asuni
  * @since 4.9.012 (2010-04-12)
  * @public static
  */
 public static function getHyphenPatternsFromTEX($file)
 {
     // TEX patterns are available at:
     // http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
     $data = file_get_contents($file);
     $patterns = array();
     // remove comments
     $data = preg_replace('/\\%[^\\n]*/', '', $data);
     // extract the patterns part
     preg_match('/\\\\patterns\\{([^\\}]*)\\}/i', $data, $matches);
     $data = trim(substr($matches[0], 10, -1));
     // extract each pattern
     $patterns_array = preg_split('/[\\s]+/', $data);
     // create new language array of patterns
     $patterns = array();
     foreach ($patterns_array as $val) {
         if (!TCPDF_STATIC::empty_string($val)) {
             $val = trim($val);
             $val = str_replace('\'', '\\\'', $val);
             $key = preg_replace('/[0-9]+/', '', $val);
             $patterns[$key] = $val;
         }
     }
     return $patterns;
 }
	/**
	 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
	 * @param $ta (array) array of characters composing the string.
	 * @param $str (string) string to process
	 * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
	 * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
	 * @param $currentfont (array) Reference to current font array.
	 * @return array of unicode chars
	 * @author Nicola Asuni
	 * @since 2.4.000 (2008-03-06)
	 * @public static
	 */
	public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) {
		// paragraph embedding level
		$pel = 0;
		// max level
		$maxlevel = 0;
		if (TCPDF_STATIC::empty_string($str)) {
			// create string from array
			$str = self::UTF8ArrSubString($ta, '', '', $isunicode);
		}
		// check if string contains arabic text
		if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) {
			$arabic = true;
		} else {
			$arabic = false;
		}
		// check if string contains RTL text
		if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) {
			return $ta;
		}

		// get number of chars
		$numchars = count($ta);

		if ($forcertl == 'R') {
			$pel = 1;
		} elseif ($forcertl == 'L') {
			$pel = 0;
		} else {
			// P2. In each paragraph, find the first character of type L, AL, or R.
			// P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero.
			for ($i=0; $i < $numchars; ++$i) {
				$type = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
				if ($type == 'L') {
					$pel = 0;
					break;
				} elseif (($type == 'AL') OR ($type == 'R')) {
					$pel = 1;
					break;
				}
			}
		}

		// Current Embedding Level
		$cel = $pel;
		// directional override status
		$dos = 'N';
		$remember = array();
		// start-of-level-run
		$sor = $pel % 2 ? 'R' : 'L';
		$eor = $sor;

		// Array of characters data
		$chardata = Array();

		// X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase.
		// In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
		for ($i=0; $i < $numchars; ++$i) {
			if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) {
				// X2. With each RLE, compute the least greater odd embedding level.
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
				$next_level = $cel + ($cel % 2) + 1;
				if ($next_level < 62) {
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
					$cel = $next_level;
					$dos = 'N';
					$sor = $eor;
					$eor = $cel % 2 ? 'R' : 'L';
				}
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) {
				// X3. With each LRE, compute the least greater even embedding level.
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral.
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
				$next_level = $cel + 2 - ($cel % 2);
				if ( $next_level < 62 ) {
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
					$cel = $next_level;
					$dos = 'N';
					$sor = $eor;
					$eor = $cel % 2 ? 'R' : 'L';
				}
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) {
				// X4. With each RLO, compute the least greater odd embedding level.
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left.
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
				$next_level = $cel + ($cel % 2) + 1;
				if ($next_level < 62) {
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
					$cel = $next_level;
					$dos = 'R';
					$sor = $eor;
					$eor = $cel % 2 ? 'R' : 'L';
				}
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) {
				// X5. With each LRO, compute the least greater even embedding level.
				//	a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right.
				//	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
				$next_level = $cel + 2 - ($cel % 2);
				if ( $next_level < 62 ) {
					$remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
					$cel = $next_level;
					$dos = 'L';
					$sor = $eor;
					$eor = $cel % 2 ? 'R' : 'L';
				}
			} elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) {
				// X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override.
				if (count($remember)) {
					$last = count($remember ) - 1;
					if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR
						($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) {
						$match = array_pop($remember);
						$cel = $match['cel'];
						$dos = $match['dos'];
						$sor = $eor;
						$eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
					}
				}
			} elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND
							 ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) {
				// X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
				//	a. Set the level of the current character to the current embedding level.
				//	b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
				if ($dos != 'N') {
					$chardir = $dos;
				} else {
					if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) {
						$chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]];
					} else {
						$chardir = 'L';
					}
				}
				// stores string characters and other information
				$chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
			}
		} // end for each char

		// X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
		// X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
		// X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L.

		// 3.3.3 Resolving Weak Types
		// Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used.
		// Nonspacing marks are now resolved based on the previous characters.
		$numchars = count($chardata);

		// W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor.
		$prevlevel = -1; // track level changes
		$levcount = 0; // counts consecutive chars at the same level
		for ($i=0; $i < $numchars; ++$i) {
			if ($chardata[$i]['type'] == 'NSM') {
				if ($levcount) {
					$chardata[$i]['type'] = $chardata[$i]['sor'];
				} elseif ($i > 0) {
					$chardata[$i]['type'] = $chardata[($i-1)]['type'];
				}
			}
			if ($chardata[$i]['level'] != $prevlevel) {
				$levcount = 0;
			} else {
				++$levcount;
			}
			$prevlevel = $chardata[$i]['level'];
		}

		// W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number.
		$prevlevel = -1;
		$levcount = 0;
		for ($i=0; $i < $numchars; ++$i) {
			if ($chardata[$i]['char'] == 'EN') {
				for ($j=$levcount; $j >= 0; $j--) {
					if ($chardata[$j]['type'] == 'AL') {
						$chardata[$i]['type'] = 'AN';
					} elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
						break;
					}
				}
			}
			if ($chardata[$i]['level'] != $prevlevel) {
				$levcount = 0;
			} else {
				++$levcount;
			}
			$prevlevel = $chardata[$i]['level'];
		}

		// W3. Change all ALs to R.
		for ($i=0; $i < $numchars; ++$i) {
			if ($chardata[$i]['type'] == 'AL') {
				$chardata[$i]['type'] = 'R';
			}
		}

		// W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type.
		$prevlevel = -1;
		$levcount = 0;
		for ($i=0; $i < $numchars; ++$i) {
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
				if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
					$chardata[$i]['type'] = 'EN';
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
					$chardata[$i]['type'] = 'EN';
				} elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
					$chardata[$i]['type'] = 'AN';
				}
			}
			if ($chardata[$i]['level'] != $prevlevel) {
				$levcount = 0;
			} else {
				++$levcount;
			}
			$prevlevel = $chardata[$i]['level'];
		}

		// W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
		$prevlevel = -1;
		$levcount = 0;
		for ($i=0; $i < $numchars; ++$i) {
			if ($chardata[$i]['type'] == 'ET') {
				if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
					$chardata[$i]['type'] = 'EN';
				} else {
					$j = $i+1;
					while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
						if ($chardata[$j]['type'] == 'EN') {
							$chardata[$i]['type'] = 'EN';
							break;
						} elseif ($chardata[$j]['type'] != 'ET') {
							break;
						}
						++$j;
					}
				}
			}
			if ($chardata[$i]['level'] != $prevlevel) {
				$levcount = 0;
			} else {
				++$levcount;
			}
			$prevlevel = $chardata[$i]['level'];
		}

		// W6. Otherwise, separators and terminators change to Other Neutral.
		$prevlevel = -1;
		$levcount = 0;
		for ($i=0; $i < $numchars; ++$i) {
			if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
				$chardata[$i]['type'] = 'ON';
			}
			if ($chardata[$i]['level'] != $prevlevel) {
				$levcount = 0;
			} else {
				++$levcount;
			}
			$prevlevel = $chardata[$i]['level'];
		}

		//W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L.
		$prevlevel = -1;
		$levcount = 0;
		for ($i=0; $i < $numchars; ++$i) {
			if ($chardata[$i]['char'] == 'EN') {
				for ($j=$levcount; $j >= 0; $j--) {
					if ($chardata[$j]['type'] == 'L') {
						$chardata[$i]['type'] = 'L';
					} elseif ($chardata[$j]['type'] == 'R') {
						break;
					}
				}
			}
			if ($chardata[$i]['level'] != $prevlevel) {
				$levcount = 0;
			} else {
				++$levcount;
			}
			$prevlevel = $chardata[$i]['level'];
		}

		// N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries.
		$prevlevel = -1;
		$levcount = 0;
		for ($i=0; $i < $numchars; ++$i) {
			if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
					$chardata[$i]['type'] = 'L';
				} elseif (($chardata[$i]['type'] == 'N') AND
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
					$chardata[$i]['type'] = 'R';
				} elseif ($chardata[$i]['type'] == 'N') {
					// N2. Any remaining neutrals take the embedding direction
					$chardata[$i]['type'] = $chardata[$i]['sor'];
				}
			} elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
				// first char
				if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
					$chardata[$i]['type'] = 'L';
				} elseif (($chardata[$i]['type'] == 'N') AND
				 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
				 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
					$chardata[$i]['type'] = 'R';
				} elseif ($chardata[$i]['type'] == 'N') {
					// N2. Any remaining neutrals take the embedding direction
					$chardata[$i]['type'] = $chardata[$i]['sor'];
				}
			} elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
				//last char
				if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
					$chardata[$i]['type'] = 'L';
				} elseif (($chardata[$i]['type'] == 'N') AND
				 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
				 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
					$chardata[$i]['type'] = 'R';
				} elseif ($chardata[$i]['type'] == 'N') {
					// N2. Any remaining neutrals take the embedding direction
					$chardata[$i]['type'] = $chardata[$i]['sor'];
				}
			} elseif ($chardata[$i]['type'] == 'N') {
				// N2. Any remaining neutrals take the embedding direction
				$chardata[$i]['type'] = $chardata[$i]['sor'];
			}
			if ($chardata[$i]['level'] != $prevlevel) {
				$levcount = 0;
			} else {
				++$levcount;
			}
			$prevlevel = $chardata[$i]['level'];
		}

		// I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels.
		// I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
		for ($i=0; $i < $numchars; ++$i) {
			$odd = $chardata[$i]['level'] % 2;
			if ($odd) {
				if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
					$chardata[$i]['level'] += 1;
				}
			} else {
				if ($chardata[$i]['type'] == 'R') {
					$chardata[$i]['level'] += 1;
				} elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
					$chardata[$i]['level'] += 2;
				}
			}
			$maxlevel = max($chardata[$i]['level'],$maxlevel);
		}

		// L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
		//	1. Segment separators,
		//	2. Paragraph separators,
		//	3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
		//	4. Any sequence of white space characters at the end of the line.
		for ($i=0; $i < $numchars; ++$i) {
			if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
				$chardata[$i]['level'] = $pel;
			} elseif ($chardata[$i]['type'] == 'WS') {
				$j = $i+1;
				while ($j < $numchars) {
					if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
						(($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
						$chardata[$i]['level'] = $pel;
						break;
					} elseif ($chardata[$j]['type'] != 'WS') {
						break;
					}
					++$j;
				}
			}
		}

		// Arabic Shaping
		// Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run.
		if ($arabic) {
			$endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
			$alfletter = array(1570,1571,1573,1575);
			$chardata2 = $chardata;
			$laaletter = false;
			$charAL = array();
			$x = 0;
			for ($i=0; $i < $numchars; ++$i) {
				if ((TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
					$charAL[$x] = $chardata[$i];
					$charAL[$x]['i'] = $i;
					$chardata[$i]['x'] = $x;
					++$x;
				}
			}
			$numAL = $x;
			for ($i=0; $i < $numchars; ++$i) {
				$thischar = $chardata[$i];
				if ($i > 0) {
					$prevchar = $chardata[($i-1)];
				} else {
					$prevchar = false;
				}
				if (($i+1) < $numchars) {
					$nextchar = $chardata[($i+1)];
				} else {
					$nextchar = false;
				}
				if (TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') {
					$x = $thischar['x'];
					if ($x > 0) {
						$prevchar = $charAL[($x-1)];
					} else {
						$prevchar = false;
					}
					if (($x+1) < $numAL) {
						$nextchar = $charAL[($x+1)];
					} else {
						$nextchar = false;
					}
					// if laa letter
					if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
						$arabicarr = TCPDF_FONT_DATA::$uni_laa_array;
						$laaletter = true;
						if ($x > 1) {
							$prevchar = $charAL[($x-2)];
						} else {
							$prevchar = false;
						}
					} else {
						$arabicarr = TCPDF_FONT_DATA::$uni_arabicsubst;
						$laaletter = false;
					}
					if (($prevchar !== false) AND ($nextchar !== false) AND
						((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
						((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
						($prevchar['type'] == $thischar['type']) AND
						($nextchar['type'] == $thischar['type']) AND
						($nextchar['char'] != 1567)) {
						if (in_array($prevchar['char'], $endedletter)) {
							if (isset($arabicarr[$thischar['char']][2])) {
								// initial
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
							}
						} else {
							if (isset($arabicarr[$thischar['char']][3])) {
								// medial
								$chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
							}
						}
					} elseif (($nextchar !== false) AND
						((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND
						($nextchar['type'] == $thischar['type']) AND
						($nextchar['char'] != 1567)) {
						if (isset($arabicarr[$chardata[$i]['char']][2])) {
							// initial
							$chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
						}
					} elseif ((($prevchar !== false) AND
						((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND
						($prevchar['type'] == $thischar['type'])) OR
						(($nextchar !== false) AND ($nextchar['char'] == 1567))) {
						// final
						if (($i > 1) AND ($thischar['char'] == 1607) AND
							($chardata[$i-1]['char'] == 1604) AND
							($chardata[$i-2]['char'] == 1604)) {
							//Allah Word
							// mark characters to delete with false
							$chardata2[$i-2]['char'] = false;
							$chardata2[$i-1]['char'] = false;
							$chardata2[$i]['char'] = 65010;
						} else {
							if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
								if (isset($arabicarr[$thischar['char']][0])) {
									// isolated
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
								}
							} else {
								if (isset($arabicarr[$thischar['char']][1])) {
									// final
									$chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
								}
							}
						}
					} elseif (isset($arabicarr[$thischar['char']][0])) {
						// isolated
						$chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
					}
					// if laa letter
					if ($laaletter) {
						// mark characters to delete with false
						$chardata2[($charAL[($x-1)]['i'])]['char'] = false;
					}
				} // end if AL (Arabic Letter)
			} // end for each char
			/*
			 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
			 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
			 */
			for ($i = 0; $i < ($numchars-1); ++$i) {
				if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]))) {
					// check if the subtitution font is defined on current font
					if (isset($currentfont['cw'][(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])])])) {
						$chardata2[$i]['char'] = false;
						$chardata2[$i+1]['char'] = TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])];
					}
				}
			}
			// remove marked characters
			foreach ($chardata2 as $key => $value) {
				if ($value['char'] === false) {
					unset($chardata2[$key]);
				}
			}
			$chardata = array_values($chardata2);
			$numchars = count($chardata);
			unset($chardata2);
			unset($arabicarr);
			unset($laaletter);
			unset($charAL);
		}

		// L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher.
		for ($j=$maxlevel; $j > 0; $j--) {
			$ordarray = Array();
			$revarr = Array();
			$onlevel = false;
			for ($i=0; $i < $numchars; ++$i) {
				if ($chardata[$i]['level'] >= $j) {
					$onlevel = true;
					if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) {
						// L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true.
						$chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']];
					}
					$revarr[] = $chardata[$i];
				} else {
					if ($onlevel) {
						$revarr = array_reverse($revarr);
						$ordarray = array_merge($ordarray, $revarr);
						$revarr = Array();
						$onlevel = false;
					}
					$ordarray[] = $chardata[$i];
				}
			}
			if ($onlevel) {
				$revarr = array_reverse($revarr);
				$ordarray = array_merge($ordarray, $revarr);
			}
			$chardata = $ordarray;
		}
		$ordarray = array();
		foreach ($chardata as $cd) {
			$ordarray[] = $cd['char'];
			// store char values for subsetting
			$currentfont['subsetchars'][$cd['char']] = true;
		}
		return $ordarray;
	}
Example #7
0
 /**
  * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
  * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
  * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
  * @param $h (float) Cell height. Default value: 0.
  * @param $txt (string) String to print. Default value: empty string.
  * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
  * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
  * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
  * @param $link (mixed) URL or identifier returned by AddLink().
  * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
  * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
  * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
  * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
  * @return string containing cell code
  * @protected
  * @since 1.0
  * @see Cell()
  */
 protected function getCellCode($w, $h = 0, $txt = '', $border = 0, $ln = 0, $align = '', $fill = false, $link = '', $stretch = 0, $ignore_min_height = false, $calign = 'T', $valign = 'M')
 {
     // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
     $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
     $prev_cell_margin = $this->cell_margin;
     $prev_cell_padding = $this->cell_padding;
     $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
     $rs = '';
     //string to be returned
     $this->adjustCellPadding($border);
     if (!$ignore_min_height) {
         $min_cell_height = $this->getCellHeight($this->FontSize);
         if ($h < $min_cell_height) {
             $h = $min_cell_height;
         }
     }
     $k = $this->k;
     // check page for no-write regions and adapt page margins if necessary
     list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
     if ($this->rtl) {
         $x = $this->x - $this->cell_margin['R'];
     } else {
         $x = $this->x + $this->cell_margin['L'];
     }
     $y = $this->y + $this->cell_margin['T'];
     $prev_font_stretching = $this->font_stretching;
     $prev_font_spacing = $this->font_spacing;
     // cell vertical alignment
     switch ($calign) {
         case 'A':
             // font top
             switch ($valign) {
                 case 'T':
                     // top
                     $y -= $this->cell_padding['T'];
                     break;
                 case 'B':
                     // bottom
                     $y -= $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
                     break;
                 default:
                 case 'C':
                 case 'M':
                     // center
                     $y -= ($h - $this->FontAscent - $this->FontDescent) / 2;
                     break;
             }
             break;
         case 'L':
             // font baseline
             switch ($valign) {
                 case 'T':
                     // top
                     $y -= $this->cell_padding['T'] + $this->FontAscent;
                     break;
                 case 'B':
                     // bottom
                     $y -= $h - $this->cell_padding['B'] - $this->FontDescent;
                     break;
                 default:
                 case 'C':
                 case 'M':
                     // center
                     $y -= ($h + $this->FontAscent - $this->FontDescent) / 2;
                     break;
             }
             break;
         case 'D':
             // font bottom
             switch ($valign) {
                 case 'T':
                     // top
                     $y -= $this->cell_padding['T'] + $this->FontAscent + $this->FontDescent;
                     break;
                 case 'B':
                     // bottom
                     $y -= $h - $this->cell_padding['B'];
                     break;
                 default:
                 case 'C':
                 case 'M':
                     // center
                     $y -= ($h + $this->FontAscent + $this->FontDescent) / 2;
                     break;
             }
             break;
         case 'B':
             // cell bottom
             $y -= $h;
             break;
         case 'C':
         case 'M':
             // cell center
             $y -= $h / 2;
             break;
         default:
         case 'T':
             // cell top
             break;
     }
     // text vertical alignment
     switch ($valign) {
         case 'T':
             // top
             $yt = $y + $this->cell_padding['T'];
             break;
         case 'B':
             // bottom
             $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
             break;
         default:
         case 'C':
         case 'M':
             // center
             $yt = $y + ($h - $this->FontAscent - $this->FontDescent) / 2;
             break;
     }
     $basefonty = $yt + $this->FontAscent;
     if (TCPDF_STATIC::empty_string($w) or $w <= 0) {
         if ($this->rtl) {
             $w = $x - $this->lMargin;
         } else {
             $w = $this->w - $this->rMargin - $x;
         }
     }
     $s = '';
     // fill and borders
     if (is_string($border) and strlen($border) == 4) {
         // full border
         $border = 1;
     }
     if ($fill or $border == 1) {
         if ($fill) {
             $op = $border == 1 ? 'B' : 'f';
         } else {
             $op = 'S';
         }
         if ($this->rtl) {
             $xk = ($x - $w) * $k;
         } else {
             $xk = $x * $k;
         }
         $s .= sprintf('%F %F %F %F re %s ', $xk, ($this->h - $y) * $k, $w * $k, -$h * $k, $op);
     }
     // draw borders
     $s .= $this->getCellBorder($x, $y, $w, $h, $border);
     if ($txt != '') {
         $txt2 = $txt;
         if ($this->isunicode) {
             $txt2 = $this->UTF8ToLatin2($txt2, $this->isunicode);
         }
         $txt2 = TCPDF_STATIC::_escape($txt2);
         // get current text width (considering general font stretching and spacing)
         $txwidth = $this->GetStringWidth($txt);
         $width = $txwidth;
         // check for stretch mode
         if ($stretch > 0) {
             // calculate ratio between cell width and text width
             if ($width <= 0) {
                 $ratio = 1;
             } else {
                 $ratio = ($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width;
             }
             // check if stretching is required
             if ($ratio < 1 or $ratio > 1 and $stretch % 2 == 0) {
                 // the text will be stretched to fit cell width
                 if ($stretch > 2) {
                     // set new character spacing
                     $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max($this->GetNumChars($txt) - 1, 1) * ($this->font_stretching / 100));
                 } else {
                     // set new horizontal stretching
                     $this->font_stretching *= $ratio;
                 }
                 // recalculate text width (the text fills the entire cell)
                 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
                 // reset alignment
                 $align = '';
             }
         }
         if ($this->font_stretching != 100) {
             // apply font stretching
             $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
         }
         if ($this->font_spacing != 0) {
             // increase/decrease font spacing
             $rs .= sprintf('BT %F Tc ET ', $this->font_spacing * $this->k);
         }
         if ($this->ColorFlag and $this->textrendermode < 4) {
             $s .= 'q ' . $this->TextColor . ' ';
         }
         // rendering mode
         $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, $this->textstrokewidth * $this->k);
         // count number of spaces
         $ns = substr_count($txt, chr(32));
         // Justification
         $spacewidth = 0;
         if ($align == 'J' and $ns > 0) {
             if ($this->isUnicodeFont()) {
                 // get string width without spaces
                 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
                 // calculate average space width
                 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns ? $ns : 1) / ($this->FontSize ? $this->FontSize : 1);
                 if ($this->font_stretching != 100) {
                     // word spacing is affected by stretching
                     $spacewidth /= $this->font_stretching / 100;
                 }
                 // set word position to be used with TJ operator
                 $txt2 = str_replace(chr(0) . chr(32), ') ' . sprintf('%F', $spacewidth) . ' (', $txt2);
                 $unicode_justification = true;
             } else {
                 // get string width
                 $width = $txwidth;
                 // new space width
                 $spacewidth = ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns ? $ns : 1) * $this->k;
                 if ($this->font_stretching != 100) {
                     // word spacing (Tw) is affected by stretching
                     $spacewidth /= $this->font_stretching / 100;
                 }
                 // set word spacing
                 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
             }
             $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
         }
         // replace carriage return characters
         $txt2 = str_replace("\r", ' ', $txt2);
         switch ($align) {
             case 'C':
                 $dx = ($w - $width) / 2;
                 break;
             case 'R':
                 if ($this->rtl) {
                     $dx = $this->cell_padding['R'];
                 } else {
                     $dx = $w - $width - $this->cell_padding['R'];
                 }
                 break;
             case 'L':
                 if ($this->rtl) {
                     $dx = $w - $width - $this->cell_padding['L'];
                 } else {
                     $dx = $this->cell_padding['L'];
                 }
                 break;
             case 'J':
             default:
                 if ($this->rtl) {
                     $dx = $this->cell_padding['R'];
                 } else {
                     $dx = $this->cell_padding['L'];
                 }
                 break;
         }
         if ($this->rtl) {
             $xdx = $x - $dx - $width;
         } else {
             $xdx = $x + $dx;
         }
         $xdk = $xdx * $k;
         // print text
         $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, ($this->h - $basefonty) * $k, $txt2);
         if (isset($uniblock)) {
             // print overlapping characters as separate string
             $xshift = 0;
             // horizontal shift
             $ty = ($this->h - $basefonty + 0.2 * $this->FontSize) * $k;
             $spw = ($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns ? $ns : 1);
             foreach ($uniblock as $uk => $uniarr) {
                 if ($uk % 2 == 0) {
                     // x space to skip
                     if ($spacewidth != 0) {
                         // justification shift
                         $xshift += count(array_keys($uniarr, 32)) * $spw;
                     }
                     $xshift += $this->GetArrStringWidth($uniarr);
                     // + shift justification
                 } else {
                     // character to print
                     $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
                     $topchr = TCPDF_STATIC::_escape($topchr);
                     $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', $xdk + $xshift * $k, $ty, $topchr);
                 }
             }
         }
         if ($this->underline) {
             $s .= ' ' . $this->_dounderlinew($xdx, $basefonty, $width);
         }
         if ($this->linethrough) {
             $s .= ' ' . $this->_dolinethroughw($xdx, $basefonty, $width);
         }
         if ($this->overline) {
             $s .= ' ' . $this->_dooverlinew($xdx, $basefonty, $width);
         }
         if ($this->ColorFlag and $this->textrendermode < 4) {
             $s .= ' Q';
         }
         if ($link) {
             $this->Link($xdx, $yt, $width, $this->FontAscent + $this->FontDescent, $link, $ns);
         }
     }
     // output cell
     if ($s) {
         // output cell
         $rs .= $s;
         if ($this->font_spacing != 0) {
             // reset font spacing mode
             $rs .= ' BT 0 Tc ET';
         }
         if ($this->font_stretching != 100) {
             // reset font stretching mode
             $rs .= ' BT 100 Tz ET';
         }
     }
     // reset word spacing
     if (!$this->isUnicodeFont() and $align == 'J') {
         $rs .= ' BT 0 Tw ET';
     }
     // reset stretching and spacing
     $this->font_stretching = $prev_font_stretching;
     $this->font_spacing = $prev_font_spacing;
     $this->lasth = $h;
     if ($ln > 0) {
         //Go to the beginning of the next line
         $this->y = $y + $h + $this->cell_margin['B'];
         if ($ln == 1) {
             if ($this->rtl) {
                 $this->x = $this->w - $this->rMargin;
             } else {
                 $this->x = $this->lMargin;
             }
         }
     } else {
         // go left or right by case
         if ($this->rtl) {
             $this->x = $x - $w - $this->cell_margin['L'];
         } else {
             $this->x = $x + $w + $this->cell_margin['R'];
         }
     }
     $gstyles = '' . $this->linestyleWidth . ' ' . $this->linestyleCap . ' ' . $this->linestyleJoin . ' ' . $this->linestyleDash . ' ' . $this->DrawColor . ' ' . $this->FillColor . "\n";
     $rs = $gstyles . $rs;
     $this->cell_padding = $prev_cell_padding;
     $this->cell_margin = $prev_cell_margin;
     return $rs;
 }
Example #8
0
 /**
  * Output a Table Of Content Index (TOC) using HTML templates.
  * This method must be called after all Bookmarks were set.
  * Before calling this method you have to open the page using the addTOCPage() method.
  * After calling this method you have to call endTOCPage() to close the TOC page.
  * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
  * @param $toc_name (string) name to use for TOC bookmark.
  * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
  * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
  * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
  * @param $color (array) RGB color array for title (values from 0 to 255).
  * @param $afterContent (string) Content to add after the TOC
  * @public
  * @author Nicola Asuni
  * @since 5.0.001 (2010-05-06)
  * @see addTOCPage(), endTOCPage(), addTOC()
  */
 public function addHTMLTOC($page = '', $toc_name = 'TOC', $templates = array(), $correct_align = true, $style = '', $color = array(0, 0, 0), $afterContent = '')
 {
     $filler = ' ';
     $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
     $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
     // set new style for link
     $this->htmlLinkColorArray = array();
     $this->htmlLinkFontStyle = '';
     $page_first = $this->getPage();
     $page_fill_start = false;
     $page_fill_end = false;
     // get the font type used for numbers in each template
     $current_font = $this->FontFamily;
     foreach ($templates as $level => $html) {
         $dom = $this->getHtmlDomArray($html);
         foreach ($dom as $key => $value) {
             if ($value['value'] == '#TOC_PAGE_NUMBER#') {
                 $this->SetFont($dom[$key - 1]['fontname']);
                 $templates['F' . $level] = $this->isUnicodeFont();
             }
         }
     }
     $this->SetFont($current_font);
     $maxpage = 0;
     //used for pages on attached documents
     foreach ($this->outlines as $key => $outline) {
         // get HTML template
         $row = $templates[$outline['l']];
         if (\TCPDF_STATIC::empty_string($page)) {
             $pagenum = $outline['p'];
         } else {
             // placemark to be replaced with the correct number
             $pagenum = '{#' . $outline['p'] . '}';
             if ($templates['F' . $outline['l']]) {
                 $pagenum = '{' . $pagenum . '}';
             }
             $maxpage = max($maxpage, $outline['p']);
         }
         // replace templates with current values
         $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
         $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
         $row = str_replace('#TOC_CHAPTERNUMBER#', $outline['cn'], $row);
         $row = str_replace('#TOC_CSSCLASS#', $outline['cssClass'], $row);
         $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
         // add link to page
         $row = '<a href="#' . $outline['p'] . ',' . $outline['y'] . '">' . $row . '</a>';
         // write bookmark entry
         $this->writeHTML($row, false, false, true, false, '');
     }
     // restore link styles
     $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
     $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
     // move TOC page and replace numbers
     $page_last = $this->getPage();
     $numpages = $page_last - $page_first + 1;
     // account for booklet mode
     if ($this->booklet) {
         // check if a blank page is required before TOC
         $page_fill_start = ($page_first % 2 == 0 xor $page % 2 == 0);
         $page_fill_end = !($numpages % 2 == 0 xor $page_fill_start);
         if ($page_fill_start) {
             // add a page at the end (to be moved before TOC)
             $this->addPage();
             ++$page_last;
             ++$numpages;
         }
         if ($page_fill_end) {
             // add a page at the end
             $this->addPage();
             ++$page_last;
             ++$numpages;
         }
     }
     $maxpage = max($maxpage, $page_last);
     if (!\TCPDF_STATIC::empty_string($page)) {
         for ($p = $page_first; $p <= $page_last; ++$p) {
             // get page data
             $temppage = $this->getPageBuffer($p);
             for ($n = 1; $n <= $maxpage; ++$n) {
                 // update page numbers
                 $a = '{#' . $n . '}';
                 // get page number aliases
                 $pnalias = $this->getInternalPageNumberAliases($a);
                 // calculate replacement number
                 if ($n >= $page) {
                     $np = $n + $numpages;
                 } else {
                     $np = $n;
                 }
                 $na = \TCPDF_STATIC::formatTOCPageNumber($this->starting_page_number + $np - 1);
                 $nu = \TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
                 // replace aliases with numbers
                 foreach ($pnalias['u'] as $u) {
                     if ($correct_align) {
                         $sfill = str_repeat($filler, strlen($u) - strlen($nu . ' '));
                         if ($this->rtl) {
                             $nr = $nu . \TCPDF_FONTS::UTF8ToUTF16BE(' ' . $sfill, false, $this->isunicode, $this->CurrentFont);
                         } else {
                             $nr = \TCPDF_FONTS::UTF8ToUTF16BE($sfill . ' ', false, $this->isunicode, $this->CurrentFont) . $nu;
                         }
                     } else {
                         $nr = $nu;
                     }
                     $temppage = str_replace($u, $nr, $temppage);
                 }
                 foreach ($pnalias['a'] as $a) {
                     if ($correct_align) {
                         $sfill = str_repeat($filler, strlen($a) - strlen($na . ' '));
                         if ($this->rtl) {
                             $nr = $na . ' ' . $sfill;
                         } else {
                             $nr = $sfill . ' ' . $na;
                         }
                     } else {
                         $nr = $na;
                     }
                     $temppage = str_replace($a, $nr, $temppage);
                 }
             }
             // save changes
             $this->setPageBuffer($p, $temppage);
         }
         // append afterContent if set
         if (!empty($afterContent)) {
             $this->writeHTML($afterContent, TRUE, FALSE, TRUE);
         }
         // move pages
         $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
         if ($page_fill_start) {
             $this->movePage($page_last, $page_first);
         }
         for ($i = 0; $i < $numpages; ++$i) {
             $this->movePage($page_last, $page);
         }
     }
 }