* @copyright 2011-2024 Nicola Asuni - Tecnick.com LTD * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) * @link https://github.com/tecnickcom/tc-lib-pdf-graph * * This file is part of tc-lib-pdf-graph software library. */ namespace Com\Tecnick\Pdf\Graph; /** * Com\Tecnick\Pdf\Graph\Raw * * @since 2011-05-23 * @category Library * @package PdfGraph * @author Nicola Asuni * @copyright 2011-2024 Nicola Asuni - Tecnick.com LTD * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) * @link https://github.com/tecnickcom/tc-lib-pdf-graph */ abstract class Raw extends \Com\Tecnick\Pdf\Graph\Transform { /** * Begin a new subpath by moving the current point to the specified coordinates, * omitting any connecting line segment. * * @param float $posx Abscissa of point. * @param float $posy Ordinate of point. * * @return string PDF command */ public function getRawPoint(float $posx, float $posy): string { return sprintf( '%F %F m' . "\n", ($posx * $this->kunit), (($this->pageh - $posy) * $this->kunit) ); } /** * Append a straight line segment from the current point to the specified one. * The new current point shall be the one specified. * * @param float $posx Abscissa of end point. * @param float $posy Ordinate of end point. * * @return string PDF command */ public function getRawLine(float $posx, float $posy): string { return sprintf( '%F %F l' . "\n", ($posx * $this->kunit), (($this->pageh - $posy) * $this->kunit) ); } /** * Append a rectangle to the current path as a complete subpath, * with lower-left corner in the specified point and dimensions width and height in user units. * * @param float $posx Abscissa of upper-left corner. * @param float $posy Ordinate of upper-left corner. * @param float $width Width. * @param float $height Height. * @param string $mode Mode of rendering. @see getPathPaintOp() * * @return string PDF command */ public function getRawRect( float $posx, float $posy, float $width, float $height, string $mode = '' ): string { return sprintf( '%F %F %F %F re' . "\n" . $this->getPathPaintOp($mode, ''), ($posx * $this->kunit), (($this->pageh - $posy) * $this->kunit), ($width * $this->kunit), (-$height * $this->kunit) ); } /** * Append a cubic Bezier curve to the current path. * The curve shall extend from the current point to the point (posx3, posy3), * using (posx1, posy1) and (posx2, posy2) as the Bezier control points. * The new current point shall be (posx3, posy3). * * @param float $posx1 Abscissa of control point 1. * @param float $posy1 Ordinate of control point 1. * @param float $posx2 Abscissa of control point 2. * @param float $posy2 Ordinate of control point 2. * @param float $posx3 Abscissa of end point. * @param float $posy3 Ordinate of end point. * * @return string PDF command */ public function getRawCurve( float $posx1, float $posy1, float $posx2, float $posy2, float $posx3, float $posy3 ): string { return sprintf( '%F %F %F %F %F %F c' . "\n", ($posx1 * $this->kunit), (($this->pageh - $posy1) * $this->kunit), ($posx2 * $this->kunit), (($this->pageh - $posy2) * $this->kunit), ($posx3 * $this->kunit), (($this->pageh - $posy3) * $this->kunit) ); } /** * Append a cubic Bezier curve to the current path. * The curve shall extend from the current point to the point (posx3, posy3), * using the current point and (posx2, posy2) as the Bezier control points. * The new current point shall be (posx3, posy3). * * @param float $posx2 Abscissa of control point 2. * @param float $posy2 Ordinate of control point 2. * @param float $posx3 Abscissa of end point. * @param float $posy3 Ordinate of end point. * * @return string PDF command */ public function getRawCurveV(float $posx2, float $posy2, float $posx3, float $posy3): string { return sprintf( '%F %F %F %F v' . "\n", ($posx2 * $this->kunit), (($this->pageh - $posy2) * $this->kunit), ($posx3 * $this->kunit), (($this->pageh - $posy3) * $this->kunit) ); } /** * Append a cubic Bezier curve to the current path. * The curve shall extend from the current point to the point (posx3, posy3), * using (posx1, posy1) and (posx3, posy3) as the Bezier control points. * The new current point shall be (posx3, posy3). * * @param float $posx1 Abscissa of control point 1. * @param float $posy1 Ordinate of control point 1. * @param float $posx3 Abscissa of end point. * @param float $posy3 Ordinate of end point. * * @return string PDF command */ public function getRawCurveY(float $posx1, float $posy1, float $posx3, float $posy3): string { return sprintf( '%F %F %F %F y' . "\n", ($posx1 * $this->kunit), (($this->pageh - $posy1) * $this->kunit), ($posx3 * $this->kunit), (($this->pageh - $posy3) * $this->kunit) ); } /** * Initialize angles for the elliptical arc. * * @param float $ags Angle in degrees at which starting drawing. * @param float $agf Angle in degrees at which stop drawing. * @param float $rdh Horizontal radius. * @param float $rdv Vertical radius (if = 0 then it is a circle). * @param bool $ccw If true draws in counter-clockwise direction. * @param bool $svg If true the angles are in svg mode (already calculated). */ protected function setRawEllipticalArcAngles( float &$ags, float &$agf, float $rdv, float $rdh, bool $ccw, bool $svg ): void { $ags = $this->degToRad($ags); $agf = $this->degToRad($agf); if (! $svg) { $ags = atan2((sin($ags) / $rdv), (cos($ags) / $rdh)); $agf = atan2((sin($agf) / $rdv), (cos($agf) / $rdh)); } if ($ags < 0) { $ags += (2 * self::MPI); } if ($agf < 0) { $agf += (2 * self::MPI); } if ($ccw && ($ags > $agf)) { // reverse rotation $ags -= (2 * self::MPI); } elseif (! $ccw && ($ags < $agf)) { // reverse rotation $agf -= (2 * self::MPI); } } /** * Append an elliptical arc to the current path. * An ellipse is formed from n Bezier curves. * * @param float $posxc Abscissa of center point. * @param float $posyc Ordinate of center point. * @param float $rdh Horizontal radius. * @param float $rdv Vertical radius (if = 0 then it is a circle). * @param float $posxang Angle between the X-axis and the major axis of the ellipse. * @param float $angs Angle in degrees at which starting drawing. * @param float $angf Angle in degrees at which stop drawing. * @param bool $pie If true do not mark the border point (used to draw pie sectors). * @param float $ncv Number of curves used to draw a 90 degrees portion of ellipse. * @param bool $startpoint If true output a starting point. * @param bool $ccw If true draws in counter-clockwise direction. * @param bool $svg If true the angles are in svg mode (already calculated). * @param array $bbox If provided, it will be filled with the bounding box coordinates * (x min, y min, x max, y max). * * @return string PDF command * * @SuppressWarnings("PHPMD.ExcessiveParameterList") * @SuppressWarnings("PHPMD.ExcessiveMethodLength") */ public function getRawEllipticalArc( float $posxc, float $posyc, float $rdh, float $rdv, float $posxang = 0.0, float $angs = 0.0, float $angf = 360.0, bool $pie = false, float $ncv = 2, bool $startpoint = true, bool $ccw = true, bool $svg = false, array &$bbox = [] ): string { $out = ''; if (($rdh <= 0) || ($rdv < 0)) { return ''; } $bbox = [PHP_INT_MAX, PHP_INT_MAX, 0, 0]; if ($pie) { $out .= $this->getRawPoint($posxc, $posyc); // center of the arc } $posxang = $this->degToRad($posxang); $ags = $angs; $agf = $angf; $this->setRawEllipticalArcAngles($ags, $agf, $rdv, $rdh, $ccw, $svg); $total_angle = ($agf - $ags); $ncv = max(2, $ncv); $ncv *= (2 * abs($total_angle) / self::MPI); // total arcs to draw $ncv = round($ncv) + 1; $arcang = ($total_angle / $ncv); // angle of each arc $posx0 = $posxc; // X center point in PDF coordinates $posy0 = ($this->pageh - $posyc); // Y center point in PDF coordinates $ang = $ags; // starting angle $alpha = sin($arcang) * ((sqrt(4 + (3 * tan(($arcang) / 2) ** 2)) - 1) / 3); $cos_xang = cos($posxang); $sin_xang = sin($posxang); $cos_ang = cos($ang); $sin_ang = sin($ang); // first arc point $px1 = $posx0 + ($rdh * $cos_xang * $cos_ang) - ($rdv * $sin_xang * $sin_ang); $py1 = $posy0 + ($rdh * $sin_xang * $cos_ang) + ($rdv * $cos_xang * $sin_ang); // first Bezier control point $qx1 = ($alpha * ((-$rdh * $cos_xang * $sin_ang) - ($rdv * $sin_xang * $cos_ang))); $qy1 = ($alpha * ((-$rdh * $sin_xang * $sin_ang) + ($rdv * $cos_xang * $cos_ang))); if ($pie) { $out .= $this->getRawLine($px1, ($this->pageh - $py1)); // line from center to arc starting point } elseif ($startpoint) { $out .= $this->getRawPoint($px1, ($this->pageh - $py1)); // arc starting point } // draw arcs for ($idx = 1; $idx <= $ncv; ++$idx) { $ang = $ags + ($idx * $arcang); // starting angle if ($idx == $ncv) { $ang = $agf; } $cos_ang = cos($ang); $sin_ang = sin($ang); // second arc point $px2 = $posx0 + ($rdh * $cos_xang * $cos_ang) - ($rdv * $sin_xang * $sin_ang); $py2 = $posy0 + ($rdh * $sin_xang * $cos_ang) + ($rdv * $cos_xang * $sin_ang); // second Bezier control point $qx2 = ($alpha * ((-$rdh * $cos_xang * $sin_ang) - ($rdv * $sin_xang * $cos_ang))); $qy2 = ($alpha * ((-$rdh * $sin_xang * $sin_ang) + ($rdv * $cos_xang * $cos_ang))); // draw arc $cx1 = ($px1 + $qx1); $cy1 = ($this->pageh - ($py1 + $qy1)); $cx2 = ($px2 - $qx2); $cy2 = ($this->pageh - ($py2 - $qy2)); $cx3 = $px2; $cy3 = ($this->pageh - $py2); $out .= $this->getRawCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3); // get bounding box coordinates $bbox = [ min($bbox[0], (int) $cx1, (int) $cx2, (int) $cx3), min($bbox[1], (int) $cy1, (int) $cy2, (int) $cy3), max($bbox[2], (int) $cx1, (int) $cx2, (int) $cx3), max($bbox[3], (int) $cy1, (int) $cy2, (int) $cy3), ]; // move to next point $px1 = $px2; $py1 = $py2; $qx1 = $qx2; $qy1 = $qy2; } if ($pie) { $out .= $this->getRawLine($posxc, $posyc); // get bounding box coordinates $bbox = [ min($bbox[0], (int) $posxc), min($bbox[1], (int) $posyc), max($bbox[2], (int) $posxc), max($bbox[3], (int) $posyc), ]; } return $out; } /** * Returns the angle in radiants between two vectors with the same origin point. * Angles are counted counter-clock wise. * * @param float $posx1 X coordinate of first vector point. * @param float $posy1 Y coordinate of first vector point. * @param float $posx2 X coordinate of second vector point. * @param float $posy2 Y coordinate of second vector point. * * @return float Angle in radiants */ public function getVectorsAngle( float $posx1, float $posy1, float $posx2, float $posy2, ): float { $dprod = (($posx1 * $posx2) + ($posy1 * $posy2)); $dist1 = sqrt(($posx1 * $posx1) + ($posy1 * $posy1)); $dist2 = sqrt(($posx2 * $posx2) + ($posy2 * $posy2)); $distprod = ($dist1 * $dist2); if ($distprod == 0) { return 0; } $angle = acos(min(1, max(-1, ($dprod / $distprod)))); if ((($posx1 * $posy2) - ($posx2 * $posy1)) < 0) { $angle *= -1; } return $angle; } }