* @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; use Com\Tecnick\Pdf\Graph\Exception as GraphException; /** * Com\Tecnick\Pdf\Graph\Transform * * @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 * * @phpstan-import-type TTMatrix from \Com\Tecnick\Pdf\Graph\Base */ abstract class Transform extends \Com\Tecnick\Pdf\Graph\Style { /** * Current ID for transformation matrix. */ protected int $ctmid = -1; /** * Array (stack) of Current Transformation Matrix (CTM), * which maps user space coordinates used within a PDF content stream into output device coordinates. * * @var array> */ protected array $ctm = []; /** * Returns the transformation stack. * * @return array> */ public function getTransformStack(): array { return $this->ctm; } /** * Returns the transformation stack index. */ public function getTransformIndex(): int { return $this->ctmid; } /** * Starts a 2D transformation saving current graphic state. * This function must be called before calling transformation methods */ public function getStartTransform(): string { $this->saveStyleStatus(); $this->ctm[++$this->ctmid] = []; return 'q' . "\n"; } /** * Stops a 2D tranformation restoring previous graphic state. * This function must be called after calling transformation methods. */ public function getStopTransform(): string { if (! isset($this->ctm[$this->ctmid])) { return ''; } unset($this->ctm[$this->ctmid]); --$this->ctmid; $this->restoreStyleStatus(); return 'Q' . "\n"; } /** * Get the tranformation matrix (CTM) PDF string * * @param TTMatrix $ctm Transformation matrix array. */ public function getTransformation(array $ctm): string { $this->ctm[$this->ctmid][] = $ctm; return sprintf('%F %F %F %F %F %F cm' . "\n", $ctm[0], $ctm[1], $ctm[2], $ctm[3], $ctm[4], $ctm[5]); } /** * Vertical and horizontal non-proportional Scaling. * * @param float $skx Horizontal scaling factor. * @param float $sky vertical scaling factor. * @param float $posx Abscissa of the scaling center. * @param float $posy Ordinate of the scaling center. * * @return string Transformation string */ public function getScaling(float $skx, float $sky, float $posx, float $posy): string { if (($skx == 0) || ($sky == 0)) { throw new GraphException('Scaling factors must be different than zero'); } $posy = (($this->pageh - $posy) * $this->kunit); $posx *= $this->kunit; $ctm = [$skx, 0, 0, $sky, ($posx * (1 - $skx)), ($posy * (1 - $sky))]; return $this->getTransformation($ctm); } /** * Horizontal Scaling. * * @param float $skx Horizontal scaling factor. * @param float $posx Abscissa of the scaling center. * @param float $posy Ordinate of the scaling center. * * @return string Transformation string */ public function getHorizScaling(float $skx, float $posx, float $posy): string { return $this->getScaling($skx, 1, $posx, $posy); } /** * Vertical Scaling. * * @param float $sky vertical scaling factor. * @param float $posx Abscissa of the scaling center. * @param float $posy Ordinate of the scaling center. * * @return string Transformation string */ public function getVertScaling(float $sky, float $posx, float $posy): string { return $this->getScaling(1, $sky, $posx, $posy); } /** * Vertical and horizontal proportional Scaling. * * @param float $skf Scaling factor. * @param float $posx Abscissa of the scaling center. * @param float $posy Ordinate of the scaling center. * * @return string Transformation string */ public function getPropScaling(float $skf, float $posx, float $posy): string { return $this->getScaling($skf, $skf, $posx, $posy); } /** * Rotation. * * @param float $angle Angle in degrees for counter-clockwise rotation. * @param float $posx Abscissa of the rotation center. * @param float $posy Ordinate of the rotation center. * * @return string Transformation string */ public function getRotation(float $angle, float $posx, float $posy): string { $posy = (($this->pageh - $posy) * $this->kunit); $posx *= $this->kunit; $ctm = []; $ctm[0] = cos($this->degToRad($angle)); $ctm[1] = sin($this->degToRad($angle)); $ctm[2] = -$ctm[1]; $ctm[3] = $ctm[0]; $ctm[4] = ($posx + ($ctm[1] * $posy) - ($ctm[0] * $posx)); $ctm[5] = ($posy - ($ctm[0] * $posy) - ($ctm[1] * $posx)); return $this->getTransformation($ctm); } /** * Horizontal Mirroring. * * @param float $posx Abscissa of the mirroring line. * * @return string Transformation string */ public function getHorizMirroring(float $posx): string { return $this->getScaling(-1, 1, $posx, 0); } /** * Verical Mirroring. * * @param float $posy Ordinate of the mirroring line. * * @return string Transformation string */ public function getVertMirroring(float $posy): string { return $this->getScaling(1, -1, 0, $posy); } /** * Point reflection mirroring. * * @param float $posx Abscissa of the mirroring point. * @param float $posy Ordinate of the mirroring point. * * @return string Transformation string */ public function getPointMirroring(float $posx, float $posy): string { return $this->getScaling(-1, -1, $posx, $posy); } /** * Reflection against a straight line through point (x, y) with the gradient angle (angle). * * @param float $ang Gradient angle in degrees of the straight line. * @param float $posx Abscissa of the mirroring point. * @param float $posy Ordinate of the mirroring point. * * @return string Transformation string */ public function getReflection(float $ang, float $posx, float $posy): string { return $this->getScaling(-1, 1, $posx, $posy) . $this->getRotation((-2 * ($ang - 90)), $posx, $posy); } /** * Translate graphic object horizontally and vertically. * * @param float $trx Movement to the right. * @param float $try Movement to the bottom. * * @return string Transformation string */ public function getTranslation(float $trx, float $try): string { //calculate elements of transformation matrix $ctm = [1, 0, 0, 1, ($trx * $this->kunit), (-$try * $this->kunit)]; return $this->getTransformation($ctm); } /** * Translate graphic object horizontally. * * @param float $trx Movement to the right. * * @return string Transformation string */ public function getHorizTranslation(float $trx): string { return $this->getTranslation($trx, 0); } /** * Translate graphic object vertically. * * @param float $try Movement to the bottom. * * @return string Transformation string */ public function getVertTranslation(float $try): string { return $this->getTranslation(0, $try); } /** * Skew. * * @param float $angx Angle in degrees between -90 (skew to the left) and 90 (skew to the right) * @param float $angy Angle in degrees between -90 (skew to the bottom) and 90 (skew to the top) * @param float $posx Abscissa of the skewing center. * @param float $posy Ordinate of the skewing center. * * @return string Transformation string */ public function getSkewing(float $angx, float $angy, float $posx, float $posy): string { if (($angx <= -90) || ($angx >= 90) || ($angy <= -90) || ($angy >= 90)) { throw new GraphException('Angle values must be beweeen -90 and +90 degrees.'); } $posy = (($this->pageh - $posy) * $this->kunit); $posx *= $this->kunit; $ctm = []; $ctm[0] = 1; $ctm[1] = tan($this->degToRad($angy)); $ctm[2] = tan($this->degToRad($angx)); $ctm[3] = 1; $ctm[4] = (-$ctm[2] * $posy); $ctm[5] = (-$ctm[1] * $posx); return $this->getTransformation($ctm); } /** * Skew horizontally. * * @param float $angx Angle in degrees between -90 (skew to the left) and 90 (skew to the right) * @param float $posx Abscissa of the skewing center. * @param float $posy Ordinate of the skewing center. * * @return string Transformation string */ public function getHorizSkewing(float $angx, float $posx, float $posy): string { return $this->getSkewing($angx, 0, $posx, $posy); } /** * Skew vertically. * * @param float $angy Angle in degrees between -90 (skew to the bottom) and 90 (skew to the top) * @param float $posx Abscissa of the skewing center. * @param float $posy Ordinate of the skewing center. * * @return string Transformation string */ public function getVertSkewing(float $angy, float $posx, float $posy): string { return $this->getSkewing(0, $angy, $posx, $posy); } /** * Get the product of two Tranformation Matrix. * * @param TTMatrix $tma First Tranformation Matrix. * @param TTMatrix $tmb Second Tranformation Matrix. * * @return TTMatrix CTM Transformation Matrix. */ public function getCtmProduct(array $tma, array $tmb): array { return [ (($tma[0] * $tmb[0]) + ($tma[2] * $tmb[1])), (($tma[1] * $tmb[0]) + ($tma[3] * $tmb[1])), (($tma[0] * $tmb[2]) + ($tma[2] * $tmb[3])), (($tma[1] * $tmb[2]) + ($tma[3] * $tmb[3])), (($tma[0] * $tmb[4]) + ($tma[2] * $tmb[5]) + $tma[4]), (($tma[1] * $tmb[4]) + ($tma[3] * $tmb[5]) + $tma[5]), ]; } /** * Converts the number in degrees to the radian equivalent. * We use this instead of $this->degToRad to avoid precision problems with hhvm. * * @param float $deg Angular value in degrees. * * @return float Angle in radiants */ public function degToRad(float $deg): float { return ($deg * self::MPI / 180); } }