diff --git a/lib/MPDF/composer.json b/lib/MPDF/composer.json new file mode 100644 index 0000000..49fa66e --- /dev/null +++ b/lib/MPDF/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "mpdf/mpdf": "^8.0" + } +} diff --git a/lib/MPDF/composer.lock b/lib/MPDF/composer.lock new file mode 100644 index 0000000..52264c0 --- /dev/null +++ b/lib/MPDF/composer.lock @@ -0,0 +1,288 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4eb8b6908db5ba53816b92c15345ed52", + "packages": [ + { + "name": "mpdf/mpdf", + "version": "v8.0.3", + "source": { + "type": "git", + "url": "https://github.com/mpdf/mpdf.git", + "reference": "6dd4285aff21f013554ff752046bb44ab1240f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/6dd4285aff21f013554ff752046bb44ab1240f5f", + "reference": "6dd4285aff21f013554ff752046bb44ab1240f5f", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "myclabs/deep-copy": "^1.7", + "paragonie/random_compat": "^1.4|^2.0|9.99.99", + "php": "^5.6 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", + "psr/log": "^1.0", + "setasign/fpdi": "^2.1" + }, + "require-dev": { + "mockery/mockery": "^0.9.5", + "mpdf/qrcode": "^1.0.0", + "phpunit/phpunit": "^5.0", + "squizlabs/php_codesniffer": "^3.5.0", + "tracy/tracy": "^2.4" + }, + "suggest": { + "ext-bcmath": "Needed for generation of some types of barcodes", + "ext-xml": "Needed mainly for SVG manipulation", + "ext-zlib": "Needed for compression of embedded resources, such as fonts" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-development": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Mpdf\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Matěj Humpál", + "role": "Developer, maintainer" + }, + { + "name": "Ian Back", + "role": "Developer (retired)" + } + ], + "description": "PHP library generating PDF files from UTF-8 encoded HTML", + "homepage": "https://mpdf.github.io", + "keywords": [ + "pdf", + "php", + "utf-8" + ], + "time": "2019-10-25T17:54:07+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2019-08-09T12:45:53+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "psr/log", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2019-11-01T11:05:21+00:00" + }, + { + "name": "setasign/fpdi", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDI.git", + "reference": "3c266002f8044f61b17329f7cd702d44d73f0f7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/3c266002f8044f61b17329f7cd702d44d73f0f7f", + "reference": "3c266002f8044f61b17329f7cd702d44d73f0f7f", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.7", + "setasign/fpdf": "~1.8", + "setasign/tfpdf": "1.25", + "tecnickcom/tcpdf": "~6.2" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured.", + "setasign/fpdi-fpdf": "Use this package to automatically evaluate dependencies to FPDF.", + "setasign/fpdi-tcpdf": "Use this package to automatically evaluate dependencies to TCPDF.", + "setasign/fpdi-tfpdf": "Use this package to automatically evaluate dependencies to tFPDF." + }, + "type": "library", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ], + "time": "2019-01-30T14:11:19+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/lib/MPDF/vendor/autoload.php b/lib/MPDF/vendor/autoload.php new file mode 100644 index 0000000..5dab4cf --- /dev/null +++ b/lib/MPDF/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/lib/MPDF/vendor/composer/LICENSE b/lib/MPDF/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/lib/MPDF/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/lib/MPDF/vendor/composer/autoload_classmap.php b/lib/MPDF/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/lib/MPDF/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', +); diff --git a/lib/MPDF/vendor/composer/autoload_namespaces.php b/lib/MPDF/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/lib/MPDF/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/setasign/fpdi/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Mpdf\\' => array($vendorDir . '/mpdf/mpdf/src'), + 'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'), +); diff --git a/lib/MPDF/vendor/composer/autoload_real.php b/lib/MPDF/vendor/composer/autoload_real.php new file mode 100644 index 0000000..78cb1ab --- /dev/null +++ b/lib/MPDF/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit464b8cc4726849d52f4325e9114ac125::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit464b8cc4726849d52f4325e9114ac125::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire464b8cc4726849d52f4325e9114ac125($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire464b8cc4726849d52f4325e9114ac125($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/lib/MPDF/vendor/composer/autoload_static.php b/lib/MPDF/vendor/composer/autoload_static.php new file mode 100644 index 0000000..d855abc --- /dev/null +++ b/lib/MPDF/vendor/composer/autoload_static.php @@ -0,0 +1,59 @@ + __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', + ); + + public static $prefixLengthsPsr4 = array ( + 's' => + array ( + 'setasign\\Fpdi\\' => 14, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + ), + 'M' => + array ( + 'Mpdf\\' => 5, + ), + 'D' => + array ( + 'DeepCopy\\' => 9, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'setasign\\Fpdi\\' => + array ( + 0 => __DIR__ . '/..' . '/setasign/fpdi/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Mpdf\\' => + array ( + 0 => __DIR__ . '/..' . '/mpdf/mpdf/src', + ), + 'DeepCopy\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit464b8cc4726849d52f4325e9114ac125::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit464b8cc4726849d52f4325e9114ac125::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/lib/MPDF/vendor/composer/installed.json b/lib/MPDF/vendor/composer/installed.json new file mode 100644 index 0000000..25093fe --- /dev/null +++ b/lib/MPDF/vendor/composer/installed.json @@ -0,0 +1,282 @@ +[ + { + "name": "mpdf/mpdf", + "version": "v8.0.3", + "version_normalized": "8.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/mpdf/mpdf.git", + "reference": "6dd4285aff21f013554ff752046bb44ab1240f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mpdf/mpdf/zipball/6dd4285aff21f013554ff752046bb44ab1240f5f", + "reference": "6dd4285aff21f013554ff752046bb44ab1240f5f", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "myclabs/deep-copy": "^1.7", + "paragonie/random_compat": "^1.4|^2.0|9.99.99", + "php": "^5.6 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", + "psr/log": "^1.0", + "setasign/fpdi": "^2.1" + }, + "require-dev": { + "mockery/mockery": "^0.9.5", + "mpdf/qrcode": "^1.0.0", + "phpunit/phpunit": "^5.0", + "squizlabs/php_codesniffer": "^3.5.0", + "tracy/tracy": "^2.4" + }, + "suggest": { + "ext-bcmath": "Needed for generation of some types of barcodes", + "ext-xml": "Needed mainly for SVG manipulation", + "ext-zlib": "Needed for compression of embedded resources, such as fonts" + }, + "time": "2019-10-25T17:54:07+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-development": "7.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Mpdf\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-only" + ], + "authors": [ + { + "name": "Matěj Humpál", + "role": "Developer, maintainer" + }, + { + "name": "Ian Back", + "role": "Developer (retired)" + } + ], + "description": "PHP library generating PDF files from UTF-8 encoded HTML", + "homepage": "https://mpdf.github.io", + "keywords": [ + "pdf", + "php", + "utf-8" + ] + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.3", + "version_normalized": "1.9.3.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "time": "2019-08-09T12:45:53+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.99", + "version_normalized": "9.99.99.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "shasum": "" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2018-07-02T15:55:56+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ] + }, + { + "name": "psr/log", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2019-11-01T11:05:21+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "setasign/fpdi", + "version": "v2.2.0", + "version_normalized": "2.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/Setasign/FPDI.git", + "reference": "3c266002f8044f61b17329f7cd702d44d73f0f7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Setasign/FPDI/zipball/3c266002f8044f61b17329f7cd702d44d73f0f7f", + "reference": "3c266002f8044f61b17329f7cd702d44d73f0f7f", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "~5.7", + "setasign/fpdf": "~1.8", + "setasign/tfpdf": "1.25", + "tecnickcom/tcpdf": "~6.2" + }, + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured.", + "setasign/fpdi-fpdf": "Use this package to automatically evaluate dependencies to FPDF.", + "setasign/fpdi-tcpdf": "Use this package to automatically evaluate dependencies to TCPDF.", + "setasign/fpdi-tfpdf": "Use this package to automatically evaluate dependencies to tFPDF." + }, + "time": "2019-01-30T14:11:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "homepage": "https://www.setasign.com/fpdi", + "keywords": [ + "fpdf", + "fpdi", + "pdf" + ] + } +] diff --git a/lib/MPDF/vendor/mpdf/mpdf/.gitignore b/lib/MPDF/vendor/mpdf/mpdf/.gitignore new file mode 100644 index 0000000..0bfe817 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/.gitignore @@ -0,0 +1,2 @@ +vendor/* +composer.lock diff --git a/lib/MPDF/vendor/mpdf/mpdf/.travis.yml b/lib/MPDF/vendor/mpdf/mpdf/.travis.yml new file mode 100644 index 0000000..7bac3e6 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/.travis.yml @@ -0,0 +1,53 @@ +language: php + +dist: xenial + +group: edge + +php: + - 5.6 + - 7.0 + - 7.1 + - 7.2 + - 7.3 + - 7.4snapshot + - nightly + +matrix: + + include: + + - env: LINT=1 + php: 7.1 + #- env: COVERAGE=1 + # php: 7.1 + + allow_failures: + - php: nightly + +install: + - composer self-update + - echo "${TRAVIS_PHP_VERSION:0:3}" + - | + if [[ "${TRAVIS_PHP_VERSION:0:7}" == "nightly" ]]; then + composer install --ignore-platform-reqs + elif [[ "${TRAVIS_PHP_VERSION:0:11}" == "7.4snapshot" ]]; then + composer install --ignore-platform-reqs + else + composer install + fi + +script: + - | + if [[ "$LINT" == "1" ]]; then + composer cs + elif [[ "$COVERAGE" == "1" ]]; then + composer coverage + else + composer test + fi + +notifications: + email: + on_success: change + on_failure: always diff --git a/lib/MPDF/vendor/mpdf/mpdf/CHANGELOG.md b/lib/MPDF/vendor/mpdf/mpdf/CHANGELOG.md new file mode 100644 index 0000000..a31d16d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/CHANGELOG.md @@ -0,0 +1,741 @@ +mPDF 8.1.x +=========================== + +* Add proxy support to curl +* Fixed date and time format in the informations dictionary (#1083, @peterdevpl) +* Checking allowed stream wrappers in CssManager +* PHP 7.4 support (until final 7.4 release with composer --ignore-platform-reqs) +* Improve debugging of remote content issues (@ribeirobreno) + +mPDF 8.0.x +=========================== + +* Added the check for JPEG SOF header 0xFF 0xC1 (extended) (@jamiejones85) +* Allows setting `none` as zoom mode in `SetDisplayMode` method, so that OpenAction is not written (#602) +* Allowed image stream whitelist to be customised (#1005, thanks @jakejackson) +* Fixed parsing of top-left-bottom-right CSS rules with !important (#1009) +* Fixed skipping ordered list numbering with page-break-inside: avoid (#339) +* Compound classes selector support, like `.one.two` or `div.message.special` (#538, @peterdevpl) + +mPDF 8.0.0 +=========================== + +### 15/03/2019 + +* Updated FPDI dependency to version 2 (thanks a lot, @JanSlabon) + - removed `SetImportUse` method + - case of `ImportPage` method changed to `importPage` + - similarly, case of `setSourceFile` and `useTemplate` was changed to a lowercase first letter. + - signature of `importPage` changed + - returned value of `useTemplate` changed +* Moved QRCode generating code portions to external package _mpdf/qrcode_ + - This reduced package size considerably (ca 6MB) +* Fraction sizes without leading zeros allowed for font sizes (#973, thanks @peterdevpl) +* WriteHTML is now strict about used `$mode` parameter (#915, thanks, @tomtomau) +* Fixed regression in nested tables (#860, thanks, @machour) +* Scientific notation handling in CSS font sizes (#753, thanks, @peterdevpl) + + +mPDF 7.1.x +=========================== + +* PHAR security issue fixed (thanks, @jakejackson) +* Font temporary data saved as JSON instead of generating PHP files (thanks, @jakejackson) +* cURL handling enhancements (thanks, @jakejackson) +* SVG parsing fixes (thanks, @achretien) +* Write PDF content with *Writer service classes +* PHP 7.3 is supported +* Added myclabs/deepcopy dependency, fixed TOC page numbering (thanks, @jakejackson) +* Custom color for QR codes +* Added support for orientation config key +* Code and tests cleanups and enhancements + - PHPUnit dedicated assertions (thanks, @carusogabriel) + - WriteHTML part constants (thanks, @tomtomau) + - Various notice fixes (kudos to all respective authors) + +mPDF 7.0.x +=========================== + +* Allow passing file content or file path to `SetAssociatedFiles` (#558) +* Allowed ^1.4 and ^2.0 of paragon/random_compat to allow wider usage +* Fix of undefined _getImage function (#539) +* Code cleanup +* Better writable rights for temp dir validation (#534) +* Fix displaying dollar character in footer with core fonts (#520) +* Fixed missed code2utf call (#531) +* Refactored and cleaned-up classes and subnamespaces + + +mPDF 7.0.0 +=========================== + +### 19/10/2017 + +Backward incompatible changes +----------------------------- + +- PHP `^5.6 || ~7.0.0 || ~7.1.0 || ~7.2.0` is required. +- Entire project moved under `Mpdf` namespace + - Practically all classes renamed to use `PascalCase` and named to be more verbose + - Changed directory structure to comply to `PSR-4` +- Removed explicit require calls, replaced with Composer autoloading +- Removed configuration files + - All configuration now done via `__construct` parameter (see below) +- Changed `\Mpdf\Mpdf` constructor signature + - Class now accepts only single array `$config` parameter + - Array keys are former `config.php` and `config_fonts.php` properties + - Additionally, former constructor parameters can be used as keys +- `tempDir` directory now must be writable, otherwise an exception is thrown +- ICC profile is loaded as entire path to file (to prevent a need to write inside vendor directory) +- Moved examples to separate repository +- Moved `TextVars` constants to separate class +- Moved border constants to separate class +- `scriptToLang` and `langToFont` in separate interfaced class methods +- Will now throw an exception when `mbstring.func_overload` is set +- Moved Glyph operator `GF_` constants in separate `\Mpdf\Fonts\GlyphOperator` class +- All methods in Barcode class renamed to camelCase including public `dec_to_hex` and `hex_to_dec` +- Decimal conversion methods (to roman, cjk, etc.) were moved to classes in `\Mpdf\Conversion` namespace +- Images in PHP variables (``) were moved from direct Mpdf properties to `Mpdf::$imageVars` public property array +- Removed global `_SVG_AUTOFONT` and `_SVG_CLASSES` constants in favor of `svgAutoFont` and `svgClasses` configuration keys +- Moved global `_testIntersect`, `_testIntersectCircle` and `calc_bezier_bbox` fucntions inside `Svg` class as private methods. + - Changed names to camelCase without underscores and to `computeBezierBoundingBox` +- Security: Embedded files via `` custom tag must be explicitly allowed via `allowAnnotationFiles` configuration key +- `fontDir` property of Mpdf class is private and must be accessed via configuration variable with array of paths or `AddFontDirectory` method +- QR code `` element now treats `\r\n` and `\n` as actual line breaks +- cURL is prefered over socket when downloading images. +- Removed globally defined functions from `functions.php` in favor of `\Mpdf\Utils` classes `PdfDate` and `UtfString`. + - Unused global functions were removed entirely. + + +Removed features +---------------- + +- Progressbar support +- JpGraph support +- `error_reporting` changes +- Timezone changes +- `compress.php` utility +- `_MPDF_PATH` and `_MPDF_URI` constants +- `_MPDF_TEMP_PATH` constant in favor of `tempDir` configuration variable +- `_MPDF_TTFONTDATAPATH` in favor of `tempDir` configuration variable +- `_MPDFK` constant in favor of `\Mpdf\Mpdf::SCALE` class constant +- `FONT_DESCRIPTOR` constant in favor of `fontDescriptor` configuration variable +- `_MPDF_SYSTEM_TTFONTS` constant in favor of `fontDir` configuration variable with array of paths or `AddFontDirectory` method +- HTML output of error messages and debugs +- Formerly deprecated methods + + +Fixes and code enhancements +---------------------------- + +- Fixed joining arab letters +- Fixed redeclared `unicode_hex` function +- Converted arrays to short syntax +- Refactored and tested color handling with potential conversion fixes in `hsl*()` color definitions +- Refactored `Barcode` class with separate class in `Mpdf\Barcode` namespace for each barcode type +- Fixed colsum calculation for different locales (by @flow-control in #491) +- Image type guessing from content separated to its own class + + +New features +------------ + +- Refactored caching (custom `Cache` and `FontCache` classes) +- Implemented `Psr\Log\LoggerAware` interface + - All debug and additional messages are now sent to the logger + - Messages can be filtered based on `\Mpdf\Log\Context` class constants +- `FontFileFinder` class allowing to specify multiple paths to search for fonts +- `MpdfException` now extends `ErrorException` to allow specifying place in code where error occured +- Generating font metrics moved to separate class +- Added `\Mpdf\Output\Destination` class with verbose output destination constants +- Availability to set custom default CSS file +- Availability to set custom hyphenation dictionary file +- Refactored code portions to new "separate" classes: + - `Mpdf\Color\*` classes + - `ColorConvertor` + - `ColorModeConvertor` + - `ColorSpaceRestrictor` + - `Mpdf\SizeConvertor` + - `Mpdf\Hyphenator` + - `Mpdf\Image\ImageProcessor` + - `Mpdf\Image\ImageTypeGuesser` + - `Mpdf\Conversion\*` classes +- Custom watermark angle with `watermarkAngle` configuration variable +- Custom document properties (idea by @zarubik in #142) +- PDF/A-3 associated files + additional xmp rdf (by @chab in #130) +- Additional font directories can be added via `addFontDir` method +- Introduced `cleanup` method which restores original `mb_` encoding settings (see #421) +- QR code `` element now treats `\r\n` and `\n` as actual line breaks +- Customizable following of 3xx HTTP redirects, validation of SSL certificates, cURL timeout. + - `curlFollowLocation` + - `curlAllowUnsafeSslRequests` + - `curlTimeout` +- QR codes can be generated without a border using `disableborder="1"` HTML attribute in `` tag + + +Git repository enhancements +--------------------------- + +- Added contributing guidelines +- Added Issue template + + +mPDF 6.1.0 +=========================== + +### 26/04/2016 + +- Composer updates + - First release officially supporting Composer + - Updated license in composer.json + - Chmod 777 on dirs `ttfontdata`, `tmp`, `graph_cache` after composer install +- Requiring PHP 5.4.0+ with Composer +- Code style + - Reformated (almost) all PHP files to keep basic code style + - Removed trailing whitespaces + - Converted all txt, php, css, and htm files to utf8 + - Removed closing PHP tags + - Change all else if calls to elseif +- Added base PHPUnit tests +- Added Travis CI integration with unit tests +- Changed all `mPDF::Error` and `die()` calls to throwing `MpdfException` +- PDF Import changes + - FPDI updated to 1.6.0 to fix incompatible licenses + - FPDI loaded from Composer or manually only +- Removed iccprofiles/CMYK directory +- Renamed example files: change spaces to underscores to make scripting easier +- Fixed `LEDGER` and `TABLOID` paper sizes +- Implemented static cache for mpdf function `ConvertColor`. +- Removed PHP4 style constructors +- Work with HTML tags separated to `Tag` class +- Fixed most Strict standards PHP errors +- Add config constant so we can define custom font data +- HTML + - fax & tel support in href attribute + - Check $html in `$mpdf->WriteHTML()` to see if it is an integer, float, string, boolean or + a class with `__toString()` and cast to a string, otherwise throw exception. +- PHP 7 + - Fix getting image from internal variable in PHP7 (4dcc2b4) + - Fix PHP7 Fatal error: `'break' not in the 'loop' or 'switch' context` (002bb8a) +- Fixed output file name for `D` and `I` output modes (issue #105, f297546) + +mPDF 6.0 +=========================== + +### 20/12/2014 + +New features / Improvements +--------------------------- +- Support for OpenTypeLayout tables / features for complex scripts and Advances Typography. +- Improved bidirectional text handling. +- Improved line-breaking, including for complex scripts e.g. Lao, Thai and Khmer. +- Updated page-breaking options. +- Automatic language mark-up and font selection using autoScriptToLang and autoLangToFont. +- Kashida for text-justification in arabic scripts. +- Index collation for non-ASCII characters. +- Index mark-up allowing control over layout using CSS. +- `{PAGENO}` and `{nbpg}` can use any of the number types as in list-style e.g. set in `` using pagenumstyle. +- CSS support for lists. +- Default stylesheet - `mpdf.css` - updated. + +Added CSS support +----------------- +- lang attribute selector e.g. :lang(fr), [lang="fr"] +- font-variant-position +- font-variant-caps +- font-variant-ligatures +- font-variant-numeric +- font-variant-alternates - Only [normal | historical-forms] supported (i.e. most are NOT supported) +- font-variant - as above, and except for: east-asian-variant-values, east-asian-width-values, ruby +- font-language-override +- font-feature-settings +- text-outline is now supported on TD/TH tags +- hebrew, khmer, cambodian, lao, and cjk-decimal recognised as values for "list-style-type" in numbered lists and page numbering. +- list-style-image and list-style-position +- transform (on `` only) +- text-decoration:overline +- image-rendering +- unicode-bidi (also `` tag) +- vertical-align can use lengths e.g. 0.5em +- line-stacking-strategy +- line-stacking-shift + +mPDF 5.7.4 +================ + +### 15/12/2014 + +Bug Fixes & Minor Additions +--------------------------- +- SVG images now support embedded images e.g. `` +- SVG images now supports `` element e.g. ``, and also `` +- SVG images now can use Autofont (see top of `classes/svg.php` file) +- SVG images now has limited support for CSS classes (see top of `classes/svg.php` file) +- SVG images - style inheritance improved +- SVG images - improved handling of comments and other extraneous code +- SVG images - fix to ensure opacity is reset before another element +- SVG images - font-size not resetting after a `` element +- SVG radial gradients bug (if the focus [fx,fy] lies outside circle defined by [cx,cy] and r) cf. pservers-grad-15-b.svg +- SVG allows spaces in attribute definitions in `` or `` e.g. `` +- SVG text which contains a `<` sign, it will break the text - now processed as `<` (despite the fact that this does not conform to XML spec) +- SVG images - support automatic font selection and (minimal) use of CSS classes - cf. the defined constants at top of svg.php file +- SVG images - text-anchor now supported as a CSS style, as well as an HTML attribute +- CSS support for :nth-child() selector improved to fully support the draft CSS3 spec - http://www.w3.org/TR/selectors/#nth-child-pseudo + [NB only works on table columns or rows] +- text-indent when set as "em" - incorrectly calculated if last text in line in different font size than for block +- CSS not applying cascaded styles on `` elements - [changed MergeCSS() type to INLINE for 'A', LEGEND, METER and PROGRESS] +- fix for underline/strikethrough/overline so that line position(s) are based correctly on font-size/font in nested situations +- Error: Strict warning: Only variables should be passed by reference - in PHP5.5.9 +- bug accessing images from some servers (HTTP 403 Forbidden whn accessed using fopen etc.) +- Setting page format incorrectly set default twice and missed some options +- bug fixed in Overwrite() when specifying replacement as a string +- barcode C93 - updated C93 code from TCPDF because of bug - incorrect checksum character for "153-2-4" +- Tables - bug when using colspan across columns which may have a cell width specified + cf. http://www.mpdf1.com/forum/discussion/2221/colspan-bug +- Tables - cell height (when specified) is not resized when table is shrunk +- Tables - if table width specified, but narrower than minimum cell wdith, and less than page width - table will expand to + minimum cell width(s) as long as $keep_table_proportions = true +- Tables - if using packTableData, and borders-collapse, wider border is overwriting content of adjacent cell + Test case: + ``` + + + +
Hallo world
Hallo world
+ ``` +- Images - image height is reset proportional to original if width is set to maximum e.g. `` +- URL handling changed to work with special characters in path fragments; affects `
` links, `` images and + CSS url() e.g background-image + - also to ignore `../` included as a query value +- Barcodes with bottom numerals e.g. EAN-13 - incorrect numeral size when using core fonts + +-------------------------------- + +NB Spec. for embedded SVG images: +as per http://www.w3.org/TR/2003/REC-SVG11-20030114/struct.html#ImageElement +Attributes supported: +- x +- y +- xlink:href (required) - can be jpeg, png or gif image - not vector (SVG or WMF) image +- width (required) +- height (required) +- preserveAspectRatio + +Note: all attribute names and values are case-sensitive +width and height cannot be assigned by CSS - must be attributes + +mPDF 5.7.3 +================ + +### 24/8/2014 + +Bug Fixes & Minor Additions +--------------------------- + +- Tables - cellSpacing and cellPadding taking preference over CSS stylesheet +- Tables - background images in table inside HTML Footer incorrectly positioned +- Tables - cell in a nested table with a specified width, should determine width of parent table cell + (cf. http://www.mpdf1.com/forum/discussion/1648/nested-table-bug-) +- Tables - colspan (on a row after first row) exceeds number of columns in table +- Gradients in Imported documents (mPDFI) causing error in some browsers +- Fatal error after page-break-after:always on root level block element +- Support for 'https/SSL' if file_get_contents_by_socket required (e.g. getting images with allow_url_fopen turned off) +- Improved support for specified ports when getting external CSS stylesheets e.g. www.domain.com:80 +- error accessing local .css files with dummy queries (cache-busting) e.g. mpdfstyleA4.css?v=2.0.18.9 +- start of end tag in PRE incorrectly changed to < +- error thrown when open.basedir restriction in effect (deleting temporary files) +- image which forces pagebreak incorrectly positioned at top of page +- [changes to avoid warning notices by checking if (isset(x)) before referencing it] +- text with letter-spacing set inside table which needs to be resixed (shrunk) - letter-spacing was not adjusted +- nested table incorrectly calculating width and unnecessarily wrapping text +- vertical-align:super|sub can be nested using `` elements +- inline elements can be nested e.g. text `text13text` text +- CSS vertical-align:0.5em (or %) now supported +- underline and strikethrough now use the parent inline block baseline/fontsize/color for child inline elements *** change in behaviour + (Adjusts line height to take account of superscript and subscript except in tables) +- nested table incorrectly calculating width and unnecessarily wrapping text +- tables - font size carrying over from one nested table to the next nested table +- tables - border set as attribute on `` overrides border set as CSS on `` not printing at all (since v5.7) +- list-style incorrectly overriding list-style-type in cascading CSS +- page-break-after:avoid not taking into account bottom padding and margin when estimating if next line can fit on page +- images not displayed when using "https://" if images are referenced by src="//domain.com/image" +- +aCJK incorrectly parsed when instantiating class e.g. new mpDF('ja+aCJK') +- line-breaking - zero-width object at end of line (e.g. index entry) causing a space left untrimmed at end of line +- ToC since v5.7 incorrectly handling non-ascii characters, entities or tags +- cell height miscalculated when using hard-hyphenate +- border colors set with transparency not working +- transparency settings for stroke and fill interfering with one another +- 'float' inside a HTML header/footer - not clearing the float before first line of text +- error if script run across date change at midnight +- temporary file name collisions (e.g. when processing images) if numerous users +- `` position attribute not working +- `<` (less-than sign) inside a PRE element, and NOT start of a valid tag, was incorrectly removed +- file attachments not opening in Reader XI +- JPG images not recognised if not containing JFIF or Exif markers +- instance of preg_replace with /e modifier causing error in PHP 5.5 +- correctly handle CSS URLs with no scheme +- Index entries causing errors when repeat entries are used within page-break-inside:avoid, rotated tables etc. +- table with fixed width column and long word in cell set to colspan across this column (adding spare width to all columns) +- incorrect hyphenation if multiple soft-hyphens on line before break +- SVG images - objects contained in `` being displayed +- SVG images - multiple, or quoted fonts e.g. style="font-family:'lucida grande', verdana" not recognised +- SVG images - line with opacity=0 still visible (only in some PDF viewers/browsers) +- text in an SVG image displaying with incorrect font in some PDF viewers/browsers +- SVG images - fill:RGB(0,0,0) not recognised when uppercase +- background images using data:image\/(jpeg|gif|png);base64 format - error when reading in stylesheet + +New CSS support +--------------- + +- added support for style="opacity:0.6;" in SVG images - previously only supported style="fill-opacity:0.6; stroke-opacity: 0.6;" +- improved PNG image handling for some cases of alpha channel transparency +- khmer, cambodian and lao recognised as list-style-type for numbered lists + +SVG Images +---------- + +- Limited support for `` and `` + +mPDF 5.7.1 +================ +## 01/09/2013 + +1) FILES: mpdf.php + +Bug fix; Dollar sign enclosed by `
` tag causing error.
+Test e.g.: `
Test $1.00 Test
Test $2.00 Test
Test $3.00 Test
Test $4.00 Test
` + +----------------------------- + +2) FILES: includes/functions.php AND mpdf.php + +Changes to `preg_replace` with `/e` modifier to use `preg_replace_callback` +(/e depracated from PHP 5.5) + +----------------------------- + +3) FILES: classes/barcode.php + +Small change to function `barcode_c128()` which allows ASCII 0 - 31 to be used in C128A e.g. chr(13) in: +`` + +----------------------------- + +4) FILES: mpdf.php + +Using $use_kwt ("keep-[heading]-with-table") if `

` before table is on 2 lines and pagebreak occurs after first line +the first line is displayed at the bottom of the 2nd page. +Edited so that $use_kwt only works if the HEADING is only one line. Else ignores (but prints correctly) + +----------------------------- + +5) FILES: mpdf.php + +Clearing old temporary files from `_MPDF_TEMP_PATH` will now ignore "hidden" files e.g. starting with a "`.`" `.htaccess`, `.gitignore` etc. +and also leave `dummy.txt` alone + + +mPDF 5.7 +=========================== + +### 14/07/2013 + +Files changed +------------- +- config.php +- mpdf.php +- classes/tocontents.php +- classes/cssmgr.php +- classes/svg.php +- includes/functions.php +- includes/out.php +- examples/formsubmit.php [Important - Security update] + +Updated Example Files in /examples/ +----------------------------------- + +- All example files +- mpdfstyleA4.css + +config.php +---------- + +Removed: +- $this->hyphenateTables +- $this->hyphenate +- $this->orphansAllowed +Edited: +- "hyphens: manual" - Added to $this->defaultCSS +- $this->allowedCSStags now includes '|TEXTCIRCLE|DOTTAB' +New: +- $this->decimal_align = array('DP'=>'.', 'DC'=>',', 'DM'=>"\xc2\xb7", 'DA'=>"\xd9\xab", 'DD'=>'-'); +- $this->h2toc = array('H1'=>0, 'H2'=>1, 'H3'=>2); +- $this->h2bookmarks = array('H1'=>0, 'H2'=>1, 'H3'=>2); +- $this->CJKforceend = false; // Forces overflowng punctuation to hang outside right margin (used with CJK script) + + +Backwards compatability +----------------------- + +Changes in mPDF 5.7 may cause some changes to the way your documents appear. There are two main differences: +1) Hyphenation. To retain appearance compatible with earlier versions, set the CSS property "hyphens: auto" whenever + you previously used $mpdf->hyphenate=true; +2) Table of Contents - appearance can now be controlled with CSS styles. By default, in mPDF 5.7, no styling is applied so you will get: + - No indent (previous default of 5mm) - ($tocindent is ignored) + - Any font, font-size set ($tocfont or $tocfontsize) will not work + - HyperLinks will appear with your default appearance - usually blue and underlined + - line spacing will be narrower (can use line-height or margin-top in CSS) + +New features / Improvements +--------------------------- +- Layout of Table of Content ToC now controlled using CSS styles +- Text alignment on decimal mark inside tables +- Automatically generated bookmarks and/or ToC entries from H1 - H6 tags +- Support for unit of "rem" as size e.g. font-size: 1rem; +- Origin and clipping for background images and gradients controlled by CSS i.e. background-origin, background-size, background-clip +- Text-outline controlled by CSS (compatible with CSS3 spec.) +- Use of `` enhanced by custom CSS "outdent" property +- Image HTML attributes `` added: max-height, max-width, min-height and min-width +- Spotcolor can now be defined as it is used e.g. color: spot(PANTONE 534 EC, 100%, 85, 65, 47, 9); +- Lists - added support for "start" attribute in `
    ` e.g. `
      ` +- Hyphenation controlled using CSS, consistent with CSS3 spec. +- Line breaking improved to avoid breaks within words where HTML tags are used e.g. H20 +- Line breaking in CJK scripts improved (and ability to force hanging punctuation) +- Numerals in a CJK script are kept together +- RTL improved support for phrases containing numerals and \ and / +- Bidi override codes supported - Right-to-Left Embedding [RLE] U+202B, Left-to-Right Embedding [LRE] U+202A, + U+202C POP DIRECTIONAL FORMATTING (PDF) +- Support for `` in HTML - uses it to SetBasePath for relative URLs. +- HTML tag - added support for `` or `` - converted to a soft-hyphen +- CSS now takes precedence over HTML attribute e.g. `
` +- tables - if table width set to 100% and one cell/column is empty with no padding/border, sizing incorrectly + (http://www.mpdf1.com/forum/discussion/1886/td-fontsize-in-nested-table-bug-#Item_5) +- `
` added as recognised tag +- CSS style transform supported on `` element (only) + All transform functions are supported except matrix() i.e. translate(), translateX(), translateY(), skew(), skewX(), skewY(), + scale(), scaleX(), scaleY(), rotate() + NB When using Columns or Keep-with-table (use_kwt), cannot use transform +- CSS background-color now supported on `` element +- @page :first not recognised unless @page {} has styles set +- left/right margins not allowed on @page :first + +mPDF 5.7.2 +================ + +### 28/12/2013 + +Bug Fixes +--------- + +- `
` + +Added CSS support +----------------- +- max-height, max-width, min-height and min-width for images `` +- "hyphens: none|manual|auto" as per CSS3 spec. +- Decimal mark alignment e.g. text-align: "." center; +- "rem" accepted as a valid (font)size in CSS e.g. font-size: 1.5rem +- text-outline, text-outline-width and text-outline-color supported everywhere except in tables (blur not supported) +- background-origin, background-size, background-clip are now supported everywhere except in tables +- "visibility: hidden|visible|printonly|screenonly" for inline elements e.g. `` +- Colors: device-cmyk(c,m,y,k) as per CSS3 spec. For consistency, device-cmyka also supported (not CSS3 spec) +- "z-index" can be used to utilise layers in the PDF document +- Custom CSS property added: "outdent" - opposite of indent + +The HTML elements `` and `` can now have CSS properties applied to them. + +Bug fixes +--------- +- SVG images - path including e.g. 1.234E-15 incorrectly parsed (not recognising capital E) +- Tables - if a table starts when the Y position on page is below bottom margin caused endless loop +- Float-ing DIVs - starting a float at bottom of page and it causes page break before anything output, second new page is forced +- Tables - Warning notice now given in Table footer or header if `` placed after `` and table spans page +- Columns - block with border-width wider than the length of the border line, line overflows +- Columns - block with no padding containing a block with borders but no backgound colour, borders not printed +- Table in Columns - when background color set by surrounding block element - colour missing for height of half bottom border. +- TOCpagebreakByArray() when called by function was not adding the pagebreak +- Border around block element - dashed not showing correctly (not resetting linewidth between different edges) +- Double border in table - when background colour set in surrounding block element - shows as black line between the 2 bits of double +- Borders around DIVs - "double" border problem if not all 4 sides equally - fixed +- Borders around DIVs - solid (and double) borders overlap as in tables - now fixed so mitred joins as in browser + [Inadvertently improves borders in Columns because of change in LineCap] +- Page numbering - $mpdf->pagenumSuffix etc not suppressed in HTML headers/footers if number suppressed +- Page numbering - Page number total {nbpg} incorrect - e.g. showing decreasing numbers through document, when ToC present +- RTL numerals - incorrectly reversing a number followed by a comma +- Transform to uppercase/lowercase not working for chars > ASCII 128 when using core fonts +- TOCpagebreak - Not setting TOC-FOOTER +- TOCpagebreak - toc-even-header-name etc. not working +- Parsing some relative URLs incorrectly +- Textcircle - when moved to next page by "page-break-inside: avoid" +- Bookmarks will now work if jump more than one level e.g. 0,2,1 Inserts a new blank entry at level 1 +- Paths to img or stylesheets - incorrectly reading "//www.domain.com" i.e. when starting with two / +- data:image as background url() - incorrectly adjusting path on server if MPDF_PATH not specified (included in release mPDF 5.6.1) +- Image problem if spaces or commas in path using http:// URL (included in release mPDF 5.6.1) +- Image URL parsing rewritten to handle both urlencoded URLs and not urlencoded (included in release mPDF 5.6.1) +- `` fixed to allow color, font-size and font-family to be correctly used, avoid dots being moved to new page, and to work in RTL +- Table {colsum} summed figures in table header +- list-style-type (custom) colour not working +- `` toc-preHTML and toc-postHTML can now contain quotes + +mPDF 5.6 +=========================== + +### 20/01/2013 + +Files changed +------------- +- mpdf.php +- config.php +- includes/functions.php +- classes/meter.php +- classes/directw.php + + +config.php changes +------------------ + +- $this->allowedCSStags - added HTML5 tags + textcircle AND +- $this->outerblocktags - added HTML5 tags +- $this->defaultCSS - added default CSS properties + +New features / Improvements +--------------------------- +CSS support added for for min-height, min-width, max-height and max-width in `` + +Images embedded in CSS +- `` improved to make it more robust, and background: `url(data:image...` now added to work + +HTML5 tags supported +- as generic block elements: `
must precede in a table"); + } else { + return; + } + } + + + // Advance down page by half width of top border + if ($horf == 'H') { // Only if header + if ($table['borders_separate']) { + $adv = $table['border_spacing_V'] / 2 + $table['border_details']['T']['w'] + $table['padding']['T']; + } else { + $adv = $table['max_cell_border_width']['T'] / 2; + } + if ($adv) { + if ($this->table_rotate) { + $this->y += ($adv); + } else { + $this->DivLn($adv, $this->blklvl, true); + } + } + } + + $topy = $content[$firstrow][0]['y'] - $this->y; + + for ($i = $firstrow; $i <= $lastrow; $i++) { + $y = $this->y; + + /* -- COLUMNS -- */ + // If outside columns, this is done in PaintDivBB + if ($this->ColActive) { + // OUTER FILL BGCOLOR of DIVS + if ($this->blklvl > 0) { + $firstblockfill = $this->GetFirstBlockFill(); + if ($firstblockfill && $this->blklvl >= $firstblockfill) { + $divh = $content[$i][0]['h']; + $bak_x = $this->x; + $this->DivLn($divh, -3, false); + // Reset current block fill + $bcor = $this->blk[$this->blklvl]['bgcolorarray']; + $this->SetFColor($bcor); + $this->x = $bak_x; + } + } + } + /* -- END COLUMNS -- */ + + $colctr = 0; + foreach ($content[$i] as $tablehf) { + $colctr++; + $y = Arrays::get($tablehf, 'y', null) - $topy; + $this->y = $y; + // Set some cell values + $x = Arrays::get($tablehf, 'x', null); + if (($this->mirrorMargins) && ($tablestartpage == 'ODD') && (($this->page) % 2 == 0)) { // EVEN + $x = $x + $this->MarginCorrection; + } elseif (($this->mirrorMargins) && ($tablestartpage == 'EVEN') && (($this->page) % 2 == 1)) { // ODD + $x = $x + $this->MarginCorrection; + } + /* -- COLUMNS -- */ + // Added to correct for Columns + if ($this->ColActive) { + if ($this->directionality == 'rtl') { // *OTL* + $x -= ($this->CurrCol - $tablestartcolumn) * ($this->ColWidth + $this->ColGap); // *OTL* + } // *OTL* + else { // *OTL* + $x += ($this->CurrCol - $tablestartcolumn) * ($this->ColWidth + $this->ColGap); + } // *OTL* + } + /* -- END COLUMNS -- */ + + if ($colctr == 1) { + $x0 = $x; + } + + // mPDF ITERATION + if ($this->iterationCounter) { + foreach ($tablehf['textbuffer'] as $k => $t) { + if (!is_array($t[0]) && preg_match('/{iteration ([a-zA-Z0-9_]+)}/', $t[0], $m)) { + $vname = '__' . $m[1] . '_'; + if (!isset($this->$vname)) { + $this->$vname = 1; + } else { + $this->$vname++; + } + $tablehf['textbuffer'][$k][0] = preg_replace('/{iteration ' . $m[1] . '}/', $this->$vname, $tablehf['textbuffer'][$k][0]); + } + } + } + + $w = Arrays::get($tablehf, 'w', null); + $h = Arrays::get($tablehf, 'h', null); + $va = Arrays::get($tablehf, 'va', null); + $R = Arrays::get($tablehf, 'R', null); + $direction = Arrays::get($tablehf, 'direction', null); + $mih = Arrays::get($tablehf, 'mih', null); + $border = Arrays::get($tablehf, 'border', null); + $border_details = Arrays::get($tablehf, 'border_details', null); + $padding = Arrays::get($tablehf, 'padding', null); + $this->tabletheadjustfinished = true; + + $textbuffer = Arrays::get($tablehf, 'textbuffer', null); + + // Align + $align = Arrays::get($tablehf, 'a', null); + $this->cellTextAlign = $align; + + $this->cellLineHeight = Arrays::get($tablehf, 'cellLineHeight', null); + $this->cellLineStackingStrategy = Arrays::get($tablehf, 'cellLineStackingStrategy', null); + $this->cellLineStackingShift = Arrays::get($tablehf, 'cellLineStackingShift', null); + + $this->x = $x; + + if ($this->ColActive) { + if ($table['borders_separate']) { + $tablefill = isset($table['bgcolor'][-1]) ? $table['bgcolor'][-1] : 0; + if ($tablefill) { + $color = $this->colorConverter->convert($tablefill, $this->PDFAXwarnings); + if ($color) { + $xadj = ($table['border_spacing_H'] / 2); + $yadj = ($table['border_spacing_V'] / 2); + $wadj = $table['border_spacing_H']; + $hadj = $table['border_spacing_V']; + if ($i == $firstrow && $horf == 'H') { // Top + $yadj += $table['padding']['T'] + $table['border_details']['T']['w']; + $hadj += $table['padding']['T'] + $table['border_details']['T']['w']; + } + if (($i == ($lastrow) || (isset($tablehf['rowspan']) && ($i + $tablehf['rowspan']) == ($lastrow + 1)) || (!isset($tablehf['rowspan']) && ($i + 1) == ($lastrow + 1))) && $horf == 'F') { // Bottom + $hadj += $table['padding']['B'] + $table['border_details']['B']['w']; + } + if ($colctr == 1) { // Left + $xadj += $table['padding']['L'] + $table['border_details']['L']['w']; + $wadj += $table['padding']['L'] + $table['border_details']['L']['w']; + } + if ($colctr == count($content[$i])) { // Right + $wadj += $table['padding']['R'] + $table['border_details']['R']['w']; + } + $this->SetFColor($color); + $this->Rect($x - $xadj, $y - $yadj, $w + $wadj, $h + $hadj, 'F'); + } + } + } + } + + if ($table['empty_cells'] != 'hide' || !empty($textbuffer) || !$table['borders_separate']) { + $paintcell = true; + } else { + $paintcell = false; + } + + // Vertical align + if ($R && intval($R) > 0 && isset($va) && $va != 'B') { + $va = 'B'; + } + + if (!isset($va) || empty($va) || $va == 'M') { + $this->y += ($h - $mih) / 2; + } elseif (isset($va) && $va == 'B') { + $this->y += $h - $mih; + } + + + // TABLE ROW OR CELL FILL BGCOLOR + $fill = 0; + if (isset($tablehf['bgcolor']) && $tablehf['bgcolor'] && $tablehf['bgcolor'] != 'transparent') { + $fill = $tablehf['bgcolor']; + $leveladj = 6; + } elseif (isset($content[$i][0]['trbgcolor']) && $content[$i][0]['trbgcolor'] && $content[$i][0]['trbgcolor'] != 'transparent') { // Row color + $fill = $content[$i][0]['trbgcolor']; + $leveladj = 3; + } + if ($fill && $paintcell) { + $color = $this->colorConverter->convert($fill, $this->PDFAXwarnings); + if ($color) { + if ($table['borders_separate']) { + if ($this->ColActive) { + $this->SetFColor($color); + $this->Rect($x + ($table['border_spacing_H'] / 2), $y + ($table['border_spacing_V'] / 2), $w - $table['border_spacing_H'], $h - $table['border_spacing_V'], 'F'); + } else { + $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => ($x + ($table['border_spacing_H'] / 2)), 'y' => ($y + ($table['border_spacing_V'] / 2)), 'w' => ($w - $table['border_spacing_H']), 'h' => ($h - $table['border_spacing_V']), 'col' => $color]; + } + } else { + if ($this->ColActive) { + $this->SetFColor($color); + $this->Rect($x, $y, $w, $h, 'F'); + } else { + $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'col' => $color]; + } + } + } + } + + + /* -- BACKGROUNDS -- */ + if (isset($tablehf['gradient']) && $tablehf['gradient'] && $paintcell) { + $g = $this->gradient->parseBackgroundGradient($tablehf['gradient']); + if ($g) { + if ($table['borders_separate']) { + $px = $x + ($table['border_spacing_H'] / 2); + $py = $y + ($table['border_spacing_V'] / 2); + $pw = $w - $table['border_spacing_H']; + $ph = $h - $table['border_spacing_V']; + } else { + $px = $x; + $py = $y; + $pw = $w; + $ph = $h; + } + if ($this->ColActive) { + $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']); + } else { + $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } + + if (isset($tablehf['background-image']) && $paintcell) { + if ($tablehf['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $tablehf['background-image']['gradient'])) { + $g = $this->gradient->parseMozGradient($tablehf['background-image']['gradient']); + if ($g) { + if ($table['borders_separate']) { + $px = $x + ($table['border_spacing_H'] / 2); + $py = $y + ($table['border_spacing_V'] / 2); + $pw = $w - $table['border_spacing_H']; + $ph = $h - $table['border_spacing_V']; + } else { + $px = $x; + $py = $y; + $pw = $w; + $ph = $h; + } + if ($this->ColActive) { + $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']); + } else { + $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } elseif ($tablehf['background-image']['image_id']) { // Background pattern + $n = count($this->patterns) + 1; + if ($table['borders_separate']) { + $px = $x + ($table['border_spacing_H'] / 2); + $py = $y + ($table['border_spacing_V'] / 2); + $pw = $w - $table['border_spacing_H']; + $ph = $h - $table['border_spacing_V']; + } else { + $px = $x; + $py = $y; + $pw = $w; + $ph = $h; + } + if ($this->ColActive) { + list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($tablehf['background-image']['orig_w'], $tablehf['background-image']['orig_h'], $pw, $ph, $tablehf['background-image']['resize'], $tablehf['background-image']['x_repeat'], $tablehf['background-image']['y_repeat']); + $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $tablehf['background-image']['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $tablehf['background-image']['x_pos'], 'y_pos' => $tablehf['background-image']['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'itype' => $tablehf['background-image']['itype']]; + if ($tablehf['background-image']['opacity'] > 0 && $tablehf['background-image']['opacity'] < 1) { + $opac = $this->SetAlpha($tablehf['background-image']['opacity'], 'Normal', true); + } else { + $opac = ''; + } + $this->writer->write(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE)); + } else { + $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $tablehf['background-image']['image_id'], 'orig_w' => $tablehf['background-image']['orig_w'], 'orig_h' => $tablehf['background-image']['orig_h'], 'x_pos' => $tablehf['background-image']['x_pos'], 'y_pos' => $tablehf['background-image']['y_pos'], 'x_repeat' => $tablehf['background-image']['x_repeat'], 'y_repeat' => $tablehf['background-image']['y_repeat'], 'clippath' => '', 'resize' => $tablehf['background-image']['resize'], 'opacity' => $tablehf['background-image']['opacity'], 'itype' => $tablehf['background-image']['itype']]; + } + } + } + /* -- END BACKGROUNDS -- */ + + // Cell Border + if ($table['borders_separate'] && $paintcell && $border) { + $this->_tableRect($x + ($table['border_spacing_H'] / 2) + ($border_details['L']['w'] / 2), $y + ($table['border_spacing_V'] / 2) + ($border_details['T']['w'] / 2), $w - $table['border_spacing_H'] - ($border_details['L']['w'] / 2) - ($border_details['R']['w'] / 2), $h - $table['border_spacing_V'] - ($border_details['T']['w'] / 2) - ($border_details['B']['w'] / 2), $border, $border_details, false, $table['borders_separate']); + } elseif ($paintcell && $border) { + $this->_tableRect($x, $y, $w, $h, $border, $border_details, true, $table['borders_separate']); // true causes buffer + } + + // Print cell content + if (!empty($textbuffer)) { + if ($horf == 'F' && preg_match('/{colsum([0-9]*)[_]*}/', $textbuffer[0][0], $m)) { + $rep = sprintf("%01." . intval($m[1]) . "f", $this->colsums[$colctr - 1]); + $textbuffer[0][0] = preg_replace('/{colsum[0-9_]*}/', $rep, $textbuffer[0][0]); + } + + if ($R) { + $cellPtSize = $textbuffer[0][11] / $this->shrin_k; + if (!$cellPtSize) { + $cellPtSize = $this->default_font_size; + } + $cellFontHeight = ($cellPtSize / Mpdf::SCALE); + $opx = $this->x; + $opy = $this->y; + $angle = intval($R); + + // Only allow 45 - 90 degrees (when bottom-aligned) or -90 + if ($angle > 90) { + $angle = 90; + } elseif ($angle > 0 && (isset($va) && $va != 'B')) { + $angle = 90; + } elseif ($angle > 0 && $angle < 45) { + $angle = 45; + } elseif ($angle < 0) { + $angle = -90; + } + + $offset = ((sin(deg2rad($angle))) * 0.37 * $cellFontHeight); + if (isset($align) && $align == 'R') { + $this->x += ($w) + ($offset) - ($cellFontHeight / 3) - ($padding['R'] + $border_details['R']['w']); + } elseif (!isset($align) || $align == 'C') { + $this->x += ($w / 2) + ($offset); + } else { + $this->x += ($offset) + ($cellFontHeight / 3) + ($padding['L'] + $border_details['L']['w']); + } + $str = ''; + foreach ($tablehf['textbuffer'] as $t) { + $str .= $t[0] . ' '; + } + $str = rtrim($str); + + if (!isset($va) || $va == 'M') { + $this->y -= ($h - $mih) / 2; // Undo what was added earlier VERTICAL ALIGN + if ($angle > 0) { + $this->y += (($h - $mih) / 2) + ($padding['T'] + $border_details['T']['w']) + ($mih - ($padding['T'] + $border_details['T']['w'] + $border_details['B']['w'] + $padding['B'])); + } elseif ($angle < 0) { + $this->y += (($h - $mih) / 2) + ($padding['T'] + $border_details['T']['w']); + } + } elseif (isset($va) && $va == 'B') { + $this->y -= $h - $mih; // Undo what was added earlier VERTICAL ALIGN + if ($angle > 0) { + $this->y += $h - ($border_details['B']['w'] + $padding['B']); + } elseif ($angle < 0) { + $this->y += $h - $mih + ($padding['T'] + $border_details['T']['w']); + } + } elseif (isset($va) && $va == 'T') { + if ($angle > 0) { + $this->y += $mih - ($border_details['B']['w'] + $padding['B']); + } elseif ($angle < 0) { + $this->y += ($padding['T'] + $border_details['T']['w']); + } + } + + $this->Rotate($angle, $this->x, $this->y); + $s_fs = $this->FontSizePt; + $s_f = $this->FontFamily; + $s_st = $this->FontStyle; + if (!empty($textbuffer[0][3])) { // Font Color + $cor = $textbuffer[0][3]; + $this->SetTColor($cor); + } + $this->SetFont($textbuffer[0][4], $textbuffer[0][2], $cellPtSize, true, true); + + $this->magic_reverse_dir($str, $this->directionality, $textbuffer[0][18]); + $this->Text($this->x, $this->y, $str, $textbuffer[0][18], $textbuffer[0][8]); // textvar + $this->Rotate(0); + $this->SetFont($s_f, $s_st, $s_fs, true, true); + $this->SetTColor(0); + $this->x = $opx; + $this->y = $opy; + } else { + if ($table['borders_separate']) { // NB twice border width + $xadj = $border_details['L']['w'] + $padding['L'] + ($table['border_spacing_H'] / 2); + $wadj = $border_details['L']['w'] + $border_details['R']['w'] + $padding['L'] + $padding['R'] + $table['border_spacing_H']; + $yadj = $border_details['T']['w'] + $padding['T'] + ($table['border_spacing_H'] / 2); + } else { + $xadj = $border_details['L']['w'] / 2 + $padding['L']; + $wadj = ($border_details['L']['w'] + $border_details['R']['w']) / 2 + $padding['L'] + $padding['R']; + $yadj = $border_details['T']['w'] / 2 + $padding['T']; + } + + $this->divwidth = $w - ($wadj); + $this->x += $xadj; + $this->y += $yadj; + $this->printbuffer($textbuffer, '', true, false, $direction); + } + } + $textbuffer = []; + + /* -- BACKGROUNDS -- */ + if (!$this->ColActive) { + if (isset($content[$i][0]['trgradients']) && ($colctr == 1 || $table['borders_separate'])) { + $g = $this->gradient->parseBackgroundGradient($content[$i][0]['trgradients']); + if ($g) { + $gx = $x0; + $gy = $y; + $gh = $h; + $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + if ($table['borders_separate']) { + $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']); + $clx = $x + ($table['border_spacing_H'] / 2); + $cly = $y + ($table['border_spacing_V'] / 2); + $clw = $w - $table['border_spacing_H']; + $clh = $h - $table['border_spacing_V']; + // Set clipping path + $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6 + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s]; + } else { + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } + + if (isset($content[$i][0]['trbackground-images']) && ($colctr == 1 || $table['borders_separate'])) { + if ($content[$i][0]['trbackground-images']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $content[$i][0]['trbackground-images']['gradient'])) { + $g = $this->gradient->parseMozGradient($content[$i][0]['trbackground-images']['gradient']); + if ($g) { + $gx = $x0; + $gy = $y; + $gh = $h; + $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + if ($table['borders_separate']) { + $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']); + $clx = $x + ($table['border_spacing_H'] / 2); + $cly = $y + ($table['border_spacing_V'] / 2); + $clw = $w - $table['border_spacing_H']; + $clh = $h - $table['border_spacing_V']; + // Set clipping path + $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6 + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s]; + } else { + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } else { + $image_id = $content[$i][0]['trbackground-images']['image_id']; + $orig_w = $content[$i][0]['trbackground-images']['orig_w']; + $orig_h = $content[$i][0]['trbackground-images']['orig_h']; + $x_pos = $content[$i][0]['trbackground-images']['x_pos']; + $y_pos = $content[$i][0]['trbackground-images']['y_pos']; + $x_repeat = $content[$i][0]['trbackground-images']['x_repeat']; + $y_repeat = $content[$i][0]['trbackground-images']['y_repeat']; + $resize = $content[$i][0]['trbackground-images']['resize']; + $opacity = $content[$i][0]['trbackground-images']['opacity']; + $itype = $content[$i][0]['trbackground-images']['itype']; + + $clippath = ''; + $gx = $x0; + $gy = $y; + $gh = $h; + $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + if ($table['borders_separate']) { + $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']); + $clx = $x + ($table['border_spacing_H'] / 2); + $cly = $y + ($table['border_spacing_V'] / 2); + $clw = $w - $table['border_spacing_H']; + $clh = $h - $table['border_spacing_V']; + // Set clipping path + $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6 + $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype]; + } else { + $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype]; + } + } + } + } + /* -- END BACKGROUNDS -- */ + + // TABLE BORDER - if separate OR collapsed and only table border + if (($table['borders_separate'] || ($this->simpleTables && !$table['simple']['border'])) && $table['border']) { + $halfspaceL = $table['padding']['L'] + ($table['border_spacing_H'] / 2); + $halfspaceR = $table['padding']['R'] + ($table['border_spacing_H'] / 2); + $halfspaceT = $table['padding']['T'] + ($table['border_spacing_V'] / 2); + $halfspaceB = $table['padding']['B'] + ($table['border_spacing_V'] / 2); + $tbx = $x; + $tby = $y; + $tbw = $w; + $tbh = $h; + $tab_bord = 0; + $corner = ''; + if ($i == $firstrow && $horf == 'H') { // Top + $tby -= $halfspaceT + ($table['border_details']['T']['w'] / 2); + $tbh += $halfspaceT + ($table['border_details']['T']['w'] / 2); + $this->setBorder($tab_bord, Border::TOP); + $corner .= 'T'; + } + if (($i == ($lastrow) || (isset($tablehf['rowspan']) && ($i + $tablehf['rowspan']) == ($lastrow + 1))) && $horf == 'F') { // Bottom + $tbh += $halfspaceB + ($table['border_details']['B']['w'] / 2); + $this->setBorder($tab_bord, Border::BOTTOM); + $corner .= 'B'; + } + if ($colctr == 1 && $firstSpread) { // Left + $tbx -= $halfspaceL + ($table['border_details']['L']['w'] / 2); + $tbw += $halfspaceL + ($table['border_details']['L']['w'] / 2); + $this->setBorder($tab_bord, Border::LEFT); + $corner .= 'L'; + } + if ($colctr == count($content[$i]) && $finalSpread) { // Right + $tbw += $halfspaceR + ($table['border_details']['R']['w'] / 2); + $this->setBorder($tab_bord, Border::RIGHT); + $corner .= 'R'; + } + $this->_tableRect($tbx, $tby, $tbw, $tbh, $tab_bord, $table['border_details'], false, $table['borders_separate'], 'table', $corner, $table['border_spacing_V'], $table['border_spacing_H']); + } + }// end column $content + $this->y = $y + $h; // Update y coordinate + }// end row $i + unset($table); + $this->colsums = []; + } + } + + /* -- END TABLES -- */ + + function SetHTMLHeader($header = '', $OE = '', $write = false) + { + + $height = 0; + if (is_array($header) && isset($header['html']) && $header['html']) { + $Hhtml = $header['html']; + if ($this->setAutoTopMargin) { + if (isset($header['h'])) { + $height = $header['h']; + } else { + $height = $this->_getHtmlHeight($Hhtml); + } + } + } elseif (!is_array($header) && $header) { + $Hhtml = $header; + if ($this->setAutoTopMargin) { + $height = $this->_getHtmlHeight($Hhtml); + } + } else { + $Hhtml = ''; + } + + if ($OE !== 'E') { + $OE = 'O'; + } + + if ($OE === 'E') { + if ($Hhtml) { + $this->HTMLHeaderE = []; + $this->HTMLHeaderE['html'] = $Hhtml; + $this->HTMLHeaderE['h'] = $height; + } else { + $this->HTMLHeaderE = ''; + } + } else { + if ($Hhtml) { + $this->HTMLHeader = []; + $this->HTMLHeader['html'] = $Hhtml; + $this->HTMLHeader['h'] = $height; + } else { + $this->HTMLHeader = ''; + } + } + + if (!$this->mirrorMargins && $OE == 'E') { + return; + } + if ($Hhtml == '') { + return; + } + + if ($this->setAutoTopMargin == 'pad') { + $this->tMargin = $this->margin_header + $height + $this->orig_tMargin; + if (isset($this->saveHTMLHeader[$this->page][$OE]['mt'])) { + $this->saveHTMLHeader[$this->page][$OE]['mt'] = $this->tMargin; + } + } elseif ($this->setAutoTopMargin == 'stretch') { + $this->tMargin = max($this->orig_tMargin, $this->margin_header + $height + $this->autoMarginPadding); + if (isset($this->saveHTMLHeader[$this->page][$OE]['mt'])) { + $this->saveHTMLHeader[$this->page][$OE]['mt'] = $this->tMargin; + } + } + if ($write && $this->state != 0 && (($this->mirrorMargins && $OE == 'E' && ($this->page) % 2 == 0) || ($this->mirrorMargins && $OE != 'E' && ($this->page) % 2 == 1) || !$this->mirrorMargins)) { + $this->writeHTMLHeaders(); + } + } + + function SetHTMLFooter($footer = '', $OE = '') + { + $height = 0; + if (is_array($footer) && isset($footer['html']) && $footer['html']) { + $Fhtml = $footer['html']; + if ($this->setAutoBottomMargin) { + if (isset($footer['h'])) { + $height = $footer['h']; + } else { + $height = $this->_getHtmlHeight($Fhtml); + } + } + } elseif (!is_array($footer) && $footer) { + $Fhtml = $footer; + if ($this->setAutoBottomMargin) { + $height = $this->_getHtmlHeight($Fhtml); + } + } else { + $Fhtml = ''; + } + + if ($OE !== 'E') { + $OE = 'O'; + } + + if ($OE === 'E') { + if ($Fhtml) { + $this->HTMLFooterE = []; + $this->HTMLFooterE['html'] = $Fhtml; + $this->HTMLFooterE['h'] = $height; + } else { + $this->HTMLFooterE = ''; + } + } else { + if ($Fhtml) { + $this->HTMLFooter = []; + $this->HTMLFooter['html'] = $Fhtml; + $this->HTMLFooter['h'] = $height; + } else { + $this->HTMLFooter = ''; + } + } + + if (!$this->mirrorMargins && $OE == 'E') { + return; + } + + if ($Fhtml == '') { + return false; + } + + if ($this->setAutoBottomMargin == 'pad') { + $this->bMargin = $this->margin_footer + $height + $this->orig_bMargin; + $this->PageBreakTrigger = $this->h - $this->bMargin; + if (isset($this->saveHTMLHeader[$this->page][$OE]['mb'])) { + $this->saveHTMLHeader[$this->page][$OE]['mb'] = $this->bMargin; + } + } elseif ($this->setAutoBottomMargin == 'stretch') { + $this->bMargin = max($this->orig_bMargin, $this->margin_footer + $height + $this->autoMarginPadding); + $this->PageBreakTrigger = $this->h - $this->bMargin; + if (isset($this->saveHTMLHeader[$this->page][$OE]['mb'])) { + $this->saveHTMLHeader[$this->page][$OE]['mb'] = $this->bMargin; + } + } + } + + function _getHtmlHeight($html) + { + $save_state = $this->state; + if ($this->state == 0) { + $this->AddPage($this->CurOrientation); + } + $this->state = 2; + $this->Reset(); + $this->pageoutput[$this->page] = []; + $save_x = $this->x; + $save_y = $this->y; + $this->x = $this->lMargin; + $this->y = $this->margin_header; + $html = str_replace('{PAGENO}', $this->pagenumPrefix . $this->docPageNum($this->page) . $this->pagenumSuffix, $html); + $html = str_replace($this->aliasNbPgGp, $this->nbpgPrefix . $this->docPageNumTotal($this->page) . $this->nbpgSuffix, $html); + $html = str_replace($this->aliasNbPg, $this->page, $html); + $html = preg_replace_callback('/\{DATE\s+(.*?)\}/', [$this, 'date_callback'], $html); // mPDF 5.7 + $this->HTMLheaderPageLinks = []; + $this->HTMLheaderPageAnnots = []; + $this->HTMLheaderPageForms = []; + $savepb = $this->pageBackgrounds; + $this->writingHTMLheader = true; + $this->WriteHTML($html, HTMLParserMode::HTML_HEADER_BUFFER); + $this->writingHTMLheader = false; + $h = ($this->y - $this->margin_header); + $this->Reset(); + // mPDF 5.7.2 - Clear in case Float used in Header/Footer + $this->blk[0]['blockContext'] = 0; + $this->blk[0]['float_endpos'] = 0; + + $this->pageoutput[$this->page] = []; + $this->headerbuffer = ''; + $this->pageBackgrounds = $savepb; + $this->x = $save_x; + $this->y = $save_y; + $this->state = $save_state; + if ($save_state == 0) { + unset($this->pages[1]); + $this->page = 0; + } + return $h; + } + + // Called internally from Header + function writeHTMLHeaders() + { + + if ($this->mirrorMargins && ($this->page) % 2 == 0) { + $OE = 'E'; + } else { + $OE = 'O'; + } + + if ($OE === 'E') { + $this->saveHTMLHeader[$this->page][$OE]['html'] = $this->HTMLHeaderE['html']; + } else { + $this->saveHTMLHeader[$this->page][$OE]['html'] = $this->HTMLHeader['html']; + } + + if ($this->forcePortraitHeaders && $this->CurOrientation == 'L' && $this->CurOrientation != $this->DefOrientation) { + $this->saveHTMLHeader[$this->page][$OE]['rotate'] = true; + $this->saveHTMLHeader[$this->page][$OE]['ml'] = $this->tMargin; + $this->saveHTMLHeader[$this->page][$OE]['mr'] = $this->bMargin; + $this->saveHTMLHeader[$this->page][$OE]['mh'] = $this->margin_header; + $this->saveHTMLHeader[$this->page][$OE]['mf'] = $this->margin_footer; + $this->saveHTMLHeader[$this->page][$OE]['pw'] = $this->h; + $this->saveHTMLHeader[$this->page][$OE]['ph'] = $this->w; + } else { + $this->saveHTMLHeader[$this->page][$OE]['ml'] = $this->lMargin; + $this->saveHTMLHeader[$this->page][$OE]['mr'] = $this->rMargin; + $this->saveHTMLHeader[$this->page][$OE]['mh'] = $this->margin_header; + $this->saveHTMLHeader[$this->page][$OE]['mf'] = $this->margin_footer; + $this->saveHTMLHeader[$this->page][$OE]['pw'] = $this->w; + $this->saveHTMLHeader[$this->page][$OE]['ph'] = $this->h; + } + } + + function writeHTMLFooters() + { + + if ($this->mirrorMargins && ($this->page) % 2 == 0) { + $OE = 'E'; + } else { + $OE = 'O'; + } + + if ($OE === 'E') { + $this->saveHTMLFooter[$this->page][$OE]['html'] = $this->HTMLFooterE['html']; + } else { + $this->saveHTMLFooter[$this->page][$OE]['html'] = $this->HTMLFooter['html']; + } + + if ($this->forcePortraitHeaders && $this->CurOrientation == 'L' && $this->CurOrientation != $this->DefOrientation) { + $this->saveHTMLFooter[$this->page][$OE]['rotate'] = true; + $this->saveHTMLFooter[$this->page][$OE]['ml'] = $this->tMargin; + $this->saveHTMLFooter[$this->page][$OE]['mr'] = $this->bMargin; + $this->saveHTMLFooter[$this->page][$OE]['mt'] = $this->rMargin; + $this->saveHTMLFooter[$this->page][$OE]['mb'] = $this->lMargin; + $this->saveHTMLFooter[$this->page][$OE]['mh'] = $this->margin_header; + $this->saveHTMLFooter[$this->page][$OE]['mf'] = $this->margin_footer; + $this->saveHTMLFooter[$this->page][$OE]['pw'] = $this->h; + $this->saveHTMLFooter[$this->page][$OE]['ph'] = $this->w; + } else { + $this->saveHTMLFooter[$this->page][$OE]['ml'] = $this->lMargin; + $this->saveHTMLFooter[$this->page][$OE]['mr'] = $this->rMargin; + $this->saveHTMLFooter[$this->page][$OE]['mt'] = $this->tMargin; + $this->saveHTMLFooter[$this->page][$OE]['mb'] = $this->bMargin; + $this->saveHTMLFooter[$this->page][$OE]['mh'] = $this->margin_header; + $this->saveHTMLFooter[$this->page][$OE]['mf'] = $this->margin_footer; + $this->saveHTMLFooter[$this->page][$OE]['pw'] = $this->w; + $this->saveHTMLFooter[$this->page][$OE]['ph'] = $this->h; + } + } + + // mPDF 6 + function _shareHeaderFooterWidth($cl, $cc, $cr) + { + // mPDF 6 + $l = mb_strlen($cl, 'UTF-8'); + $c = mb_strlen($cc, 'UTF-8'); + $r = mb_strlen($cr, 'UTF-8'); + $s = max($l, $r); + $tw = $c + 2 * $s; + if ($tw > 0) { + return [intval($s * 100 / $tw), intval($c * 100 / $tw), intval($s * 100 / $tw)]; + } else { + return [33, 33, 33]; + } + } + + // mPDF 6 + // Create an HTML header/footer from array (non-HTML header/footer) + function _createHTMLheaderFooter($arr, $hf) + { + $lContent = (isset($arr['L']['content']) ? $arr['L']['content'] : ''); + $cContent = (isset($arr['C']['content']) ? $arr['C']['content'] : ''); + $rContent = (isset($arr['R']['content']) ? $arr['R']['content'] : ''); + list($lw, $cw, $rw) = $this->_shareHeaderFooterWidth($lContent, $cContent, $rContent); + if ($hf == 'H') { + $valign = 'bottom'; + $vpadding = '0 0 ' . $this->header_line_spacing . 'em 0'; + } else { + $valign = 'top'; + $vpadding = '' . $this->footer_line_spacing . 'em 0 0 0'; + } + if ($this->directionality == 'rtl') { // table columns get reversed so need different text-alignment + $talignL = 'right'; + $talignR = 'left'; + } else { + $talignL = 'left'; + $talignR = 'right'; + } + $html = '
'; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= '
' . $lContent . '' . $cContent . '' . $rContent . '
'; + return $html; + } + + function DefHeaderByName($name, $arr) + { + if (!$name) { + $name = '_nonhtmldefault'; + } + $html = $this->_createHTMLheaderFooter($arr, 'H'); + + $this->pageHTMLheaders[$name]['html'] = $html; + $this->pageHTMLheaders[$name]['h'] = $this->_getHtmlHeight($html); + } + + function DefFooterByName($name, $arr) + { + if (!$name) { + $name = '_nonhtmldefault'; + } + $html = $this->_createHTMLheaderFooter($arr, 'F'); + + $this->pageHTMLfooters[$name]['html'] = $html; + $this->pageHTMLfooters[$name]['h'] = $this->_getHtmlHeight($html); + } + + function SetHeaderByName($name, $side = 'O', $write = false) + { + if (!$name) { + $name = '_nonhtmldefault'; + } + $this->SetHTMLHeader($this->pageHTMLheaders[$name], $side, $write); + } + + function SetFooterByName($name, $side = 'O') + { + if (!$name) { + $name = '_nonhtmldefault'; + } + $this->SetHTMLFooter($this->pageHTMLfooters[$name], $side); + } + + function DefHTMLHeaderByName($name, $html) + { + if (!$name) { + $name = '_default'; + } + + $this->pageHTMLheaders[$name]['html'] = $html; + $this->pageHTMLheaders[$name]['h'] = $this->_getHtmlHeight($html); + } + + function DefHTMLFooterByName($name, $html) + { + if (!$name) { + $name = '_default'; + } + + $this->pageHTMLfooters[$name]['html'] = $html; + $this->pageHTMLfooters[$name]['h'] = $this->_getHtmlHeight($html); + } + + function SetHTMLHeaderByName($name, $side = 'O', $write = false) + { + if (!$name) { + $name = '_default'; + } + $this->SetHTMLHeader($this->pageHTMLheaders[$name], $side, $write); + } + + function SetHTMLFooterByName($name, $side = 'O') + { + if (!$name) { + $name = '_default'; + } + $this->SetHTMLFooter($this->pageHTMLfooters[$name], $side); + } + + function SetHeader($Harray = [], $side = '', $write = false) + { + $oddhtml = ''; + $evenhtml = ''; + + if (is_string($Harray)) { + + if (strlen($Harray) === 0) { + + $oddhtml = ''; + $evenhtml = ''; + + } elseif (strpos($Harray, '|') !== false) { + + $hdet = explode('|', $Harray); + + list($lw, $cw, $rw) = $this->_shareHeaderFooterWidth($hdet[0], $hdet[1], $hdet[2]); + $oddhtml = ''; + $oddhtml .= ''; + $oddhtml .= ''; + $oddhtml .= ''; + $oddhtml .= ''; + $oddhtml .= '
' . $hdet[0] . '' . $hdet[1] . '' . $hdet[2] . '
'; + + $evenhtml = ''; + $evenhtml .= ''; + $evenhtml .= ''; + $evenhtml .= ''; + $evenhtml .= ''; + $evenhtml .= '
' . $hdet[2] . '' . $hdet[1] . '' . $hdet[0] . '
'; + + } else { + + $oddhtml = '
' . $Harray . '
'; + $evenhtml = '
' . $Harray . '
'; + } + + } elseif (is_array($Harray) && !empty($Harray)) { + + $odd = null; + $even = null; + + if ($side === 'O') { + $odd = $Harray; + } elseif ($side === 'E') { + $even = $Harray; + } else { + $odd = Arrays::get($Harray, 'odd', null); + $even = Arrays::get($Harray, 'even', null); + } + + $oddhtml = $this->_createHTMLheaderFooter($odd, 'H'); + $evenhtml = $this->_createHTMLheaderFooter($even, 'H'); + } + + if ($side === 'E') { + $this->SetHTMLHeader($evenhtml, 'E', $write); + } elseif ($side === 'O') { + $this->SetHTMLHeader($oddhtml, 'O', $write); + } else { + $this->SetHTMLHeader($oddhtml, 'O', $write); + $this->SetHTMLHeader($evenhtml, 'E', $write); + } + } + + function SetFooter($Farray = [], $side = '') + { + $oddhtml = ''; + $evenhtml = ''; + + if (is_string($Farray)) { + + if (strlen($Farray) == 0) { + + $oddhtml = ''; + $evenhtml = ''; + + } elseif (strpos($Farray, '|') !== false) { + + $hdet = explode('|', $Farray); + $oddhtml = ''; + $oddhtml .= ''; + $oddhtml .= ''; + $oddhtml .= ''; + $oddhtml .= ''; + $oddhtml .= '
' . $hdet[0] . '' . $hdet[1] . '' . $hdet[2] . '
'; + + $evenhtml = ''; + $evenhtml .= ''; + $evenhtml .= ''; + $evenhtml .= ''; + $evenhtml .= ''; + $evenhtml .= '
' . $hdet[2] . '' . $hdet[1] . '' . $hdet[0] . '
'; + + } else { + + $oddhtml = '
' . $Farray . '
'; + + $evenhtml = '
' . $Farray . '
'; + } + + } elseif (is_array($Farray)) { + + $odd = null; + $even = null; + + if ($side === 'O') { + $odd = $Farray; + } elseif ($side == 'E') { + $even = $Farray; + } else { + $odd = Arrays::get($Farray, 'odd', null); + $even = Arrays::get($Farray, 'even', null); + } + + $oddhtml = $this->_createHTMLheaderFooter($odd, 'F'); + $evenhtml = $this->_createHTMLheaderFooter($even, 'F'); + } + + if ($side === 'E') { + $this->SetHTMLFooter($evenhtml, 'E'); + } elseif ($side === 'O') { + $this->SetHTMLFooter($oddhtml, 'O'); + } else { + $this->SetHTMLFooter($oddhtml, 'O'); + $this->SetHTMLFooter($evenhtml, 'E'); + } + } + + /* -- WATERMARK -- */ + + function SetWatermarkText($txt = '', $alpha = -1) + { + if ($alpha >= 0) { + $this->watermarkTextAlpha = $alpha; + } + $this->watermarkText = $txt; + } + + function SetWatermarkImage($src, $alpha = -1, $size = 'D', $pos = 'F') + { + if ($alpha >= 0) { + $this->watermarkImageAlpha = $alpha; + } + $this->watermarkImage = $src; + $this->watermark_size = $size; + $this->watermark_pos = $pos; + } + + /* -- END WATERMARK -- */ + + // Page footer + function Footer() + { + /* -- CSS-PAGE -- */ + // PAGED MEDIA - CROP / CROSS MARKS from @PAGE + if ($this->show_marks == 'CROP' || $this->show_marks == 'CROPCROSS') { + // Show TICK MARKS + $this->SetLineWidth(0.1); // = 0.1 mm + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $l = $this->cropMarkLength; + $m = $this->cropMarkMargin; // Distance of crop mark from margin + $b = $this->nonPrintMargin; // Non-printable border at edge of paper sheet + $ax1 = $b; + $bx = $this->page_box['outer_width_LR'] - $m; + $ax = max($ax1, $bx - $l); + $cx1 = $this->w - $b; + $dx = $this->w - $this->page_box['outer_width_LR'] + $m; + $cx = min($cx1, $dx + $l); + $ay1 = $b; + $by = $this->page_box['outer_width_TB'] - $m; + $ay = max($ay1, $by - $l); + $cy1 = $this->h - $b; + $dy = $this->h - $this->page_box['outer_width_TB'] + $m; + $cy = min($cy1, $dy + $l); + + $this->Line($ax, $this->page_box['outer_width_TB'], $bx, $this->page_box['outer_width_TB']); + $this->Line($cx, $this->page_box['outer_width_TB'], $dx, $this->page_box['outer_width_TB']); + $this->Line($ax, $this->h - $this->page_box['outer_width_TB'], $bx, $this->h - $this->page_box['outer_width_TB']); + $this->Line($cx, $this->h - $this->page_box['outer_width_TB'], $dx, $this->h - $this->page_box['outer_width_TB']); + $this->Line($this->page_box['outer_width_LR'], $ay, $this->page_box['outer_width_LR'], $by); + $this->Line($this->page_box['outer_width_LR'], $cy, $this->page_box['outer_width_LR'], $dy); + $this->Line($this->w - $this->page_box['outer_width_LR'], $ay, $this->w - $this->page_box['outer_width_LR'], $by); + $this->Line($this->w - $this->page_box['outer_width_LR'], $cy, $this->w - $this->page_box['outer_width_LR'], $dy); + + if ($this->printers_info) { + $hd = date('Y-m-d H:i') . ' Page ' . $this->page . ' of {nb}'; + $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetFont('arial', '', 7.5, true, true); + $this->x = $this->page_box['outer_width_LR'] + 1.5; + $this->y = 1; + $this->Cell($headerpgwidth, $this->FontSize, $hd, 0, 0, 'L', 0, '', 0, 0, 0, 'M'); + $this->SetFont($this->default_font, '', $this->original_default_font_size); + } + } + if ($this->show_marks == 'CROSS' || $this->show_marks == 'CROPCROSS') { + $this->SetLineWidth(0.1); // = 0.1 mm + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $l = 14 / 2; // longer length of the cross line (half) + $w = 6 / 2; // shorter width of the cross line (half) + $r = 1.2; // radius of circle + $m = $this->crossMarkMargin; // Distance of cross mark from margin + $x1 = $this->page_box['outer_width_LR'] - $m; + $x2 = $this->w - $this->page_box['outer_width_LR'] + $m; + $y1 = $this->page_box['outer_width_TB'] - $m; + $y2 = $this->h - $this->page_box['outer_width_TB'] + $m; + // Left + $this->Circle($x1, $this->h / 2, $r, 'S'); + $this->Line($x1 - $w, $this->h / 2, $x1 + $w, $this->h / 2); + $this->Line($x1, $this->h / 2 - $l, $x1, $this->h / 2 + $l); + // Right + $this->Circle($x2, $this->h / 2, $r, 'S'); + $this->Line($x2 - $w, $this->h / 2, $x2 + $w, $this->h / 2); + $this->Line($x2, $this->h / 2 - $l, $x2, $this->h / 2 + $l); + // Top + $this->Circle($this->w / 2, $y1, $r, 'S'); + $this->Line($this->w / 2, $y1 - $w, $this->w / 2, $y1 + $w); + $this->Line($this->w / 2 - $l, $y1, $this->w / 2 + $l, $y1); + // Bottom + $this->Circle($this->w / 2, $y2, $r, 'S'); + $this->Line($this->w / 2, $y2 - $w, $this->w / 2, $y2 + $w); + $this->Line($this->w / 2 - $l, $y2, $this->w / 2 + $l, $y2); + } + + /* -- END CSS-PAGE -- */ + + // mPDF 6 + // If @page set non-HTML headers/footers named, they were not read until later in the HTML code - so now set them + if ($this->page == 1) { + if ($this->firstPageBoxHeader) { + if (isset($this->pageHTMLheaders[$this->firstPageBoxHeader])) { + $this->HTMLHeader = $this->pageHTMLheaders[$this->firstPageBoxHeader]; + } + $this->Header(); + } + if ($this->firstPageBoxFooter) { + if (isset($this->pageHTMLfooters[$this->firstPageBoxFooter])) { + $this->HTMLFooter = $this->pageHTMLfooters[$this->firstPageBoxFooter]; + } + } + $this->firstPageBoxHeader = ''; + $this->firstPageBoxFooter = ''; + } + + + if (($this->mirrorMargins && ($this->page % 2 == 0) && $this->HTMLFooterE) || ($this->mirrorMargins && ($this->page % 2 == 1) && $this->HTMLFooter) || (!$this->mirrorMargins && $this->HTMLFooter)) { + $this->writeHTMLFooters(); + } + + /* -- WATERMARK -- */ + if (($this->watermarkText) && ($this->showWatermarkText)) { + $this->watermark($this->watermarkText, $this->watermarkAngle, 120, $this->watermarkTextAlpha); // Watermark text + } + if (($this->watermarkImage) && ($this->showWatermarkImage)) { + $this->watermarkImg($this->watermarkImage, $this->watermarkImageAlpha); // Watermark image + } + /* -- END WATERMARK -- */ + } + + /* -- HTML-CSS -- */ + + /** + * Write HTML code to the document + * + * Also used internally to parse HTML into buffers + * + * @param string $html + * @param int $mode Use HTMLParserMode constants. Controls what parts of the $html code is parsed. + * @param bool $init Clears and sets buffers to Top level block etc. + * @param bool $close If false leaves buffers etc. in current state, so that it can continue a block etc. + */ + function WriteHTML($html, $mode = HTMLParserMode::DEFAULT_MODE, $init = true, $close = true) + { + /* Check $html is an integer, float, string, boolean or class with __toString(), otherwise throw exception */ + if (is_scalar($html) === false) { + if (!is_object($html) || ! method_exists($html, '__toString')) { + throw new \Mpdf\MpdfException('WriteHTML() requires $html be an integer, float, string, boolean or an object with the __toString() magic method.'); + } + } + + // Check the mode is valid + if (in_array($mode, HTMLParserMode::getAllModes(), true) === false) { + throw new \Mpdf\MpdfException('WriteHTML() requires $mode to be one of the modes defined in HTMLParserMode'); + } + + /* Cast $html as a string */ + $html = (string) $html; + + // @log Parsing CSS & Headers + + if ($init) { + $this->headerbuffer = ''; + $this->textbuffer = []; + $this->fixedPosBlockSave = []; + } + if ($mode === HTMLParserMode::HEADER_CSS) { + $html = ''; + } // stylesheet only + + if ($this->allow_charset_conversion) { + if ($mode === HTMLParserMode::DEFAULT_MODE) { + $this->ReadCharset($html); + } + if ($this->charset_in && $mode !== HTMLParserMode::HTML_HEADER_BUFFER) { + $success = iconv($this->charset_in, 'UTF-8//TRANSLIT', $html); + if ($success) { + $html = $success; + } + } + } + + $html = $this->purify_utf8($html, false); + if ($init) { + $this->blklvl = 0; + $this->lastblocklevelchange = 0; + $this->blk = []; + $this->initialiseBlock($this->blk[0]); + $this->blk[0]['width'] = & $this->pgwidth; + $this->blk[0]['inner_width'] = & $this->pgwidth; + $this->blk[0]['blockContext'] = $this->blockContext; + } + + $zproperties = []; + if ($mode === HTMLParserMode::DEFAULT_MODE || $mode === HTMLParserMode::HEADER_CSS) { + $this->ReadMetaTags($html); + + if (preg_match('/]*href=["\']([^"\'>]*)["\']/i', $html, $m)) { + $this->SetBasePath($m[1]); + } + $html = $this->cssManager->ReadCSS($html); + + if ($this->autoLangToFont && !$this->usingCoreFont && preg_match('/]*lang=[\'\"](.*?)[\'\"]/ism', $html, $m)) { + $html_lang = $m[1]; + } + + if (preg_match('/]*dir=[\'\"]\s*rtl\s*[\'\"]/ism', $html)) { + $zproperties['DIRECTION'] = 'rtl'; + } + + // allow in-line CSS for body tag to be parsed // Get tag inline CSS + if (preg_match('/]*)>(.*?)<\/body>/ism', $html, $m) || preg_match('/]*)>(.*)$/ism', $html, $m)) { + $html = $m[2]; + // Changed to allow style="background: url('bg.jpg')" + if (preg_match('/style=[\"](.*?)[\"]/ism', $m[1], $mm) || preg_match('/style=[\'](.*?)[\']/ism', $m[1], $mm)) { + $zproperties = $this->cssManager->readInlineCSS($mm[1]); + } + if (preg_match('/dir=[\'\"]\s*rtl\s*[\'\"]/ism', $m[1])) { + $zproperties['DIRECTION'] = 'rtl'; + } + if (isset($html_lang) && $html_lang) { + $zproperties['LANG'] = $html_lang; + } + if ($this->autoLangToFont && !$this->onlyCoreFonts && preg_match('/lang=[\'\"](.*?)[\'\"]/ism', $m[1], $mm)) { + $zproperties['LANG'] = $mm[1]; + } + } + } + $properties = $this->cssManager->MergeCSS('BLOCK', 'BODY', ''); + if ($zproperties) { + $properties = $this->cssManager->array_merge_recursive_unique($properties, $zproperties); + } + + if (isset($properties['DIRECTION']) && $properties['DIRECTION']) { + $this->cssManager->CSS['BODY']['DIRECTION'] = $properties['DIRECTION']; + } + if (!isset($this->cssManager->CSS['BODY']['DIRECTION'])) { + $this->cssManager->CSS['BODY']['DIRECTION'] = $this->directionality; + } else { + $this->SetDirectionality($this->cssManager->CSS['BODY']['DIRECTION']); + } + + $this->setCSS($properties, '', 'BODY'); + + $this->blk[0]['InlineProperties'] = $this->saveInlineProperties(); + + if ($mode === HTMLParserMode::HEADER_CSS) { + return ''; + } + if (!isset($this->cssManager->CSS['BODY'])) { + $this->cssManager->CSS['BODY'] = []; + } + + /* -- BACKGROUNDS -- */ + if (isset($properties['BACKGROUND-GRADIENT'])) { + $this->bodyBackgroundGradient = $properties['BACKGROUND-GRADIENT']; + } + + if (isset($properties['BACKGROUND-IMAGE']) && $properties['BACKGROUND-IMAGE']) { + $ret = $this->SetBackground($properties, $this->pgwidth); + if ($ret) { + $this->bodyBackgroundImage = $ret; + } + } + /* -- END BACKGROUNDS -- */ + + /* -- CSS-PAGE -- */ + // If page-box is set + if ($this->state == 0 && ((isset($this->cssManager->CSS['@PAGE']) && $this->cssManager->CSS['@PAGE']) || (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST']) && $this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST']))) { // mPDF 5.7.3 + $this->page_box['current'] = ''; + $this->page_box['using'] = true; + list($pborientation, $pbmgl, $pbmgr, $pbmgt, $pbmgb, $pbmgh, $pbmgf, $hname, $fname, $bg, $resetpagenum, $pagenumstyle, $suppress, $marks, $newformat) = $this->SetPagedMediaCSS('', false, 'O'); + $this->DefOrientation = $this->CurOrientation = $pborientation; + $this->orig_lMargin = $this->DeflMargin = $pbmgl; + $this->orig_rMargin = $this->DefrMargin = $pbmgr; + $this->orig_tMargin = $this->tMargin = $pbmgt; + $this->orig_bMargin = $this->bMargin = $pbmgb; + $this->orig_hMargin = $this->margin_header = $pbmgh; + $this->orig_fMargin = $this->margin_footer = $pbmgf; + list($pborientation, $pbmgl, $pbmgr, $pbmgt, $pbmgb, $pbmgh, $pbmgf, $hname, $fname, $bg, $resetpagenum, $pagenumstyle, $suppress, $marks, $newformat) = $this->SetPagedMediaCSS('', true, 'O'); // first page + $this->show_marks = $marks; + if ($hname) { + $this->firstPageBoxHeader = $hname; + } + if ($fname) { + $this->firstPageBoxFooter = $fname; + } + } + /* -- END CSS-PAGE -- */ + + $parseonly = false; + $this->bufferoutput = false; + if ($mode == HTMLParserMode::HTML_PARSE_NO_WRITE) { + $parseonly = true; + // Close any open block tags + $arr = []; + $ai = 0; + for ($b = $this->blklvl; $b > 0; $b--) { + $this->tag->CloseTag($this->blk[$b]['tag'], $arr, $ai); + } + // Output any text left in buffer + if (count($this->textbuffer)) { + $this->printbuffer($this->textbuffer); + } + $this->textbuffer = []; + } elseif ($mode === HTMLParserMode::HTML_HEADER_BUFFER) { + // Close any open block tags + $arr = []; + $ai = 0; + for ($b = $this->blklvl; $b > 0; $b--) { + $this->tag->CloseTag($this->blk[$b]['tag'], $arr, $ai); + } + // Output any text left in buffer + if (count($this->textbuffer)) { + $this->printbuffer($this->textbuffer); + } + $this->bufferoutput = true; + $this->textbuffer = []; + $this->headerbuffer = ''; + $properties = $this->cssManager->MergeCSS('BLOCK', 'BODY', ''); + $this->setCSS($properties, '', 'BODY'); + } + + mb_internal_encoding('UTF-8'); + + $html = $this->AdjustHTML($html, $this->tabSpaces); // Try to make HTML look more like XHTML + + if ($this->autoScriptToLang) { + $html = $this->markScriptToLang($html); + } + + preg_match_all('/]*)>(.*?)<\/htmlpageheader>/si', $html, $h); + for ($i = 0; $i < count($h[1]); $i++) { + if (preg_match('/name=[\'|\"](.*?)[\'|\"]/', $h[1][$i], $n)) { + $this->pageHTMLheaders[$n[1]]['html'] = $h[2][$i]; + $this->pageHTMLheaders[$n[1]]['h'] = $this->_getHtmlHeight($h[2][$i]); + } + } + preg_match_all('/]*)>(.*?)<\/htmlpagefooter>/si', $html, $f); + for ($i = 0; $i < count($f[1]); $i++) { + if (preg_match('/name=[\'|\"](.*?)[\'|\"]/', $f[1][$i], $n)) { + $this->pageHTMLfooters[$n[1]]['html'] = $f[2][$i]; + $this->pageHTMLfooters[$n[1]]['h'] = $this->_getHtmlHeight($f[2][$i]); + } + } + + $html = preg_replace('//si', '', $html); + $html = preg_replace('//si', '', $html); + + if ($this->state == 0 && ($mode === HTMLParserMode::DEFAULT_MODE || $mode === HTMLParserMode::HTML_BODY)) { + $this->AddPage($this->CurOrientation); + } + + + if (isset($hname) && preg_match('/^html_(.*)$/i', $hname, $n)) { + $this->SetHTMLHeader($this->pageHTMLheaders[$n[1]], 'O', true); + } + if (isset($fname) && preg_match('/^html_(.*)$/i', $fname, $n)) { + $this->SetHTMLFooter($this->pageHTMLfooters[$n[1]], 'O'); + } + + + + $html = str_replace('checkSIP = false; + $this->checkSMP = false; + $this->checkCJK = false; + if ($this->onlyCoreFonts) { + $html = $this->SubstituteChars($html); + } else { + if (preg_match("/([" . $this->pregRTLchars . "])/u", $html)) { + $this->biDirectional = true; + } // *OTL* + if (preg_match("/([\x{20000}-\x{2FFFF}])/u", $html)) { + $this->checkSIP = true; + } + if (preg_match("/([\x{10000}-\x{1FFFF}])/u", $html)) { + $this->checkSMP = true; + } + /* -- CJK-FONTS -- */ + if (preg_match("/([" . $this->pregCJKchars . "])/u", $html)) { + $this->checkCJK = true; + } + /* -- END CJK-FONTS -- */ + } + + // Don't allow non-breaking spaces that are converted to substituted chars or will break anyway and mess up table width calc. + $html = str_replace('160', chr(32), $html); + $html = str_replace('', '|', $html); + $html = str_replace('', '|', $html); + $html = str_replace('', '|', $html); + + // Add new supported tags in the DisableTags function + $html = strip_tags($html, $this->enabledtags); // remove all unsupported tags, but the ones inside the 'enabledtags' string + // Explode the string in order to parse the HTML code + $a = preg_split('/<(.*?)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE); + // ? more accurate regexp that allows e.g.
+ // if changing - also change in fn.SubstituteChars() + // $a = preg_split ('/<((?:[^<>]+(?:"[^"]*"|\'[^\']*\')?)+)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE); + + if ($this->mb_enc) { + mb_internal_encoding($this->mb_enc); + } + $pbc = 0; + $this->subPos = -1; + $cnt = count($a); + for ($i = 0; $i < $cnt; $i++) { + $e = $a[$i]; + if ($i % 2 == 0) { + // TEXT + if ($this->blk[$this->blklvl]['hide']) { + continue; + } + if ($this->inlineDisplayOff) { + continue; + } + if ($this->inMeter) { + continue; + } + + if ($this->inFixedPosBlock) { + $this->fixedPosBlock .= $e; + continue; + } // *CSS-POSITION* + if (strlen($e) == 0) { + continue; + } + + if ($this->ignorefollowingspaces && !$this->ispre) { + if (strlen(ltrim($e)) == 0) { + continue; + } + if ($this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats' && substr($e, 0, 1) == ' ') { + $this->ignorefollowingspaces = false; + $e = ltrim($e); + } + } + + $this->OTLdata = null; // mPDF 5.7.1 + + $e = UtfString::strcode2utf($e); + $e = $this->lesser_entity_decode($e); + + if ($this->usingCoreFont) { + // If core font is selected in document which is not onlyCoreFonts - substitute with non-core font + if ($this->useSubstitutions && !$this->onlyCoreFonts && $this->subPos < $i && !$this->specialcontent) { + $cnt += $this->SubstituteCharsNonCore($a, $i, $e); + } + // CONVERT ENCODING + $e = mb_convert_encoding($e, $this->mb_enc, 'UTF-8'); + if ($this->textvar & TextVars::FT_UPPERCASE) { + $e = mb_strtoupper($e, $this->mb_enc); + } // mPDF 5.7.1 + elseif ($this->textvar & TextVars::FT_LOWERCASE) { + $e = mb_strtolower($e, $this->mb_enc); + } // mPDF 5.7.1 + elseif ($this->textvar & TextVars::FT_CAPITALIZE) { + $e = mb_convert_case($e, MB_CASE_TITLE, "UTF-8"); + } // mPDF 5.7.1 + } else { + if ($this->checkSIP && $this->CurrentFont['sipext'] && $this->subPos < $i && (!$this->specialcontent || !$this->useActiveForms)) { + $cnt += $this->SubstituteCharsSIP($a, $i, $e); + } + + if ($this->useSubstitutions && !$this->onlyCoreFonts && $this->CurrentFont['type'] != 'Type0' && $this->subPos < $i && (!$this->specialcontent || !$this->useActiveForms)) { + $cnt += $this->SubstituteCharsMB($a, $i, $e); + } + + if ($this->textvar & TextVars::FT_UPPERCASE) { + $e = mb_strtoupper($e, $this->mb_enc); + } elseif ($this->textvar & TextVars::FT_LOWERCASE) { + $e = mb_strtolower($e, $this->mb_enc); + } elseif ($this->textvar & TextVars::FT_CAPITALIZE) { + $e = mb_convert_case($e, MB_CASE_TITLE, "UTF-8"); + } + + /* -- OTL -- */ + // Use OTL OpenType Table Layout - GSUB & GPOS + if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL'] && (!$this->specialcontent || !$this->useActiveForms)) { + if (!$this->otl) { + $this->otl = new Otl($this, $this->fontCache); + } + $e = $this->otl->applyOTL($e, $this->CurrentFont['useOTL']); + $this->OTLdata = $this->otl->OTLdata; + $this->otl->removeChar($e, $this->OTLdata, "\xef\xbb\xbf"); // Remove ZWNBSP (also Byte order mark FEFF) + } /* -- END OTL -- */ + else { + // removes U+200E/U+200F LTR and RTL mark and U+200C/U+200D Zero-width Joiner and Non-joiner + $e = preg_replace("/[\xe2\x80\x8c\xe2\x80\x8d\xe2\x80\x8e\xe2\x80\x8f]/u", '', $e); + $e = preg_replace("/[\xef\xbb\xbf]/u", '', $e); // Remove ZWNBSP (also Byte order mark FEFF) + } + } + + if (($this->tts) || ($this->ttz) || ($this->tta)) { + $es = explode('|', $e); + $e = ''; + foreach ($es as $val) { + $e .= chr($val); + } + } + + // FORM ELEMENTS + if ($this->specialcontent) { + /* -- FORMS -- */ + // SELECT tag (form element) + if ($this->specialcontent == "type=select") { + $e = ltrim($e); + if (!empty($this->OTLdata)) { + $this->otl->trimOTLdata($this->OTLdata, true, false); + } // *OTL* + $stringwidth = $this->GetStringWidth($e); + if (!isset($this->selectoption['MAXWIDTH']) || $stringwidth > $this->selectoption['MAXWIDTH']) { + $this->selectoption['MAXWIDTH'] = $stringwidth; + } + if (!isset($this->selectoption['SELECTED']) || $this->selectoption['SELECTED'] == '') { + $this->selectoption['SELECTED'] = $e; + if (!empty($this->OTLdata)) { + $this->selectoption['SELECTED-OTLDATA'] = $this->OTLdata; + } // *OTL* + } + // Active Forms + if (isset($this->selectoption['ACTIVE']) && $this->selectoption['ACTIVE']) { + $this->selectoption['ITEMS'][] = ['exportValue' => $this->selectoption['currentVAL'], 'content' => $e, 'selected' => $this->selectoption['currentSEL']]; + } + $this->OTLdata = []; + } // TEXTAREA + else { + $objattr = unserialize($this->specialcontent); + $objattr['text'] = $e; + $objattr['OTLdata'] = $this->OTLdata; + $this->OTLdata = []; + $te = "\xbb\xa4\xactype=textarea,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + if ($this->tdbegin) { + $this->_saveCellTextBuffer($te, $this->HREF); + } else { + $this->_saveTextBuffer($te, $this->HREF); + } + } + /* -- END FORMS -- */ + } // TABLE + elseif ($this->tableLevel) { + /* -- TABLES -- */ + if ($this->tdbegin) { + if (($this->ignorefollowingspaces) && !$this->ispre) { + $e = ltrim($e); + if (!empty($this->OTLdata)) { + $this->otl->trimOTLdata($this->OTLdata, true, false); + } // *OTL* + } + if ($e || $e === '0') { + if ($this->blockjustfinished && $this->cell[$this->row][$this->col]['s'] > 0) { + $this->_saveCellTextBuffer("\n"); + if (!isset($this->cell[$this->row][$this->col]['maxs'])) { + $this->cell[$this->row][$this->col]['maxs'] = $this->cell[$this->row][$this->col]['s']; + } elseif ($this->cell[$this->row][$this->col]['maxs'] < $this->cell[$this->row][$this->col]['s']) { + $this->cell[$this->row][$this->col]['maxs'] = $this->cell[$this->row][$this->col]['s']; + } + $this->cell[$this->row][$this->col]['s'] = 0; // reset + } + $this->blockjustfinished = false; + + if (!isset($this->cell[$this->row][$this->col]['R']) || !$this->cell[$this->row][$this->col]['R']) { + if (isset($this->cell[$this->row][$this->col]['s'])) { + $this->cell[$this->row][$this->col]['s'] += $this->GetStringWidth($e, false, $this->OTLdata, $this->textvar); + } else { + $this->cell[$this->row][$this->col]['s'] = $this->GetStringWidth($e, false, $this->OTLdata, $this->textvar); + } + if (!empty($this->spanborddet)) { + $this->cell[$this->row][$this->col]['s'] += (isset($this->spanborddet['L']['w']) ? $this->spanborddet['L']['w'] : 0) + (isset($this->spanborddet['R']['w']) ? $this->spanborddet['R']['w'] : 0); + } + } + $this->_saveCellTextBuffer($e, $this->HREF); + if (substr($this->cell[$this->row][$this->col]['a'], 0, 1) == 'D') { + $dp = $this->decimal_align[substr($this->cell[$this->row][$this->col]['a'], 0, 2)]; + $s = preg_split('/' . preg_quote($dp, '/') . '/', $e, 2); // ? needs to be /u if not core + $s0 = $this->GetStringWidth($s[0], false); + if (isset($s[1]) && $s[1]) { + $s1 = $this->GetStringWidth(($s[1] . $dp), false); + } else { + $s1 = 0; + } + if (!isset($this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0'])) { + $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0'] = $s0; + } else { + $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0'] = max($s0, $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs0']); + } + if (!isset($this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1'])) { + $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1'] = $s1; + } else { + $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1'] = max($s1, $this->table[$this->tableLevel][$this->tbctr[$this->tableLevel]]['decimal_align'][$this->col]['maxs1']); + } + } + + $this->nestedtablejustfinished = false; + $this->linebreakjustfinished = false; + } + } + /* -- END TABLES -- */ + } // ALL ELSE + else { + if ($this->ignorefollowingspaces && !$this->ispre) { + $e = ltrim($e); + if (!empty($this->OTLdata)) { + $this->otl->trimOTLdata($this->OTLdata, true, false); + } // *OTL* + } + if ($e || $e === '0') { + $this->_saveTextBuffer($e, $this->HREF); + } + } + if ($e || $e === '0') { + $this->ignorefollowingspaces = false; // mPDF 6 + } + if (substr($e, -1, 1) == ' ' && !$this->ispre && $this->FontFamily != 'csymbol' && $this->FontFamily != 'czapfdingbats') { + $this->ignorefollowingspaces = true; + } + } else { // TAG ** + if (isset($e[0]) && $e[0] == '/') { + $endtag = trim(strtoupper(substr($e, 1))); + + /* -- CSS-POSITION -- */ + // mPDF 6 + if ($this->inFixedPosBlock) { + if (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags)) { + $this->fixedPosBlockDepth--; + } + if ($this->fixedPosBlockDepth == 0) { + $this->fixedPosBlockSave[] = [$this->fixedPosBlock, $this->fixedPosBlockBBox, $this->page]; + $this->fixedPosBlock = ''; + $this->inFixedPosBlock = false; + continue; + } + $this->fixedPosBlock .= '<' . $e . '>'; + continue; + } + /* -- END CSS-POSITION -- */ + + // mPDF 6 + // Correct for tags where HTML5 specifies optional end tags (see also OpenTag() ) + if ($this->allow_html_optional_endtags && !$parseonly) { + if (isset($this->blk[$this->blklvl]['tag'])) { + $closed = false; + // li end tag may be omitted if there is no more content in the parent element + if (!$closed && $this->blk[$this->blklvl]['tag'] == 'LI' && $endtag != 'LI' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) { + $this->tag->CloseTag('LI', $a, $i); + $closed = true; + } + // dd end tag may be omitted if there is no more content in the parent element + if (!$closed && $this->blk[$this->blklvl]['tag'] == 'DD' && $endtag != 'DD' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) { + $this->tag->CloseTag('DD', $a, $i); + $closed = true; + } + // p end tag may be omitted if there is no more content in the parent element and the parent element is not an A element [??????] + if (!$closed && $this->blk[$this->blklvl]['tag'] == 'P' && $endtag != 'P' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) { + $this->tag->CloseTag('P', $a, $i); + $closed = true; + } + // option end tag may be omitted if there is no more content in the parent element + if (!$closed && $this->blk[$this->blklvl]['tag'] == 'OPTION' && $endtag != 'OPTION' && (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags))) { + $this->tag->CloseTag('OPTION', $a, $i); + $closed = true; + } + } + /* -- TABLES -- */ + // Check for Table tags where HTML specifies optional end tags, + if ($endtag == 'TABLE') { + if ($this->lastoptionaltag == 'THEAD' || $this->lastoptionaltag == 'TBODY' || $this->lastoptionaltag == 'TFOOT') { + $this->tag->CloseTag($this->lastoptionaltag, $a, $i); + } + if ($this->lastoptionaltag == 'TR') { + $this->tag->CloseTag('TR', $a, $i); + } + if ($this->lastoptionaltag == 'TD' || $this->lastoptionaltag == 'TH') { + $this->tag->CloseTag($this->lastoptionaltag, $a, $i); + $this->tag->CloseTag('TR', $a, $i); + } + } + if ($endtag == 'THEAD' || $endtag == 'TBODY' || $endtag == 'TFOOT') { + if ($this->lastoptionaltag == 'TR') { + $this->tag->CloseTag('TR', $a, $i); + } + if ($this->lastoptionaltag == 'TD' || $this->lastoptionaltag == 'TH') { + $this->tag->CloseTag($this->lastoptionaltag, $a, $i); + $this->tag->CloseTag('TR', $a, $i); + } + } + if ($endtag == 'TR') { + if ($this->lastoptionaltag == 'TD' || $this->lastoptionaltag == 'TH') { + $this->tag->CloseTag($this->lastoptionaltag, $a, $i); + } + } + /* -- END TABLES -- */ + } + + + // mPDF 6 + if ($this->blk[$this->blklvl]['hide']) { + if (in_array($endtag, $this->outerblocktags) || in_array($endtag, $this->innerblocktags)) { + unset($this->blk[$this->blklvl]); + $this->blklvl--; + } + continue; + } + + // mPDF 6 + $this->tag->CloseTag($endtag, $a, $i); // mPDF 6 + } else { // OPENING TAG + if ($this->blk[$this->blklvl]['hide']) { + if (strpos($e, ' ')) { + $te = strtoupper(substr($e, 0, strpos($e, ' '))); + } else { + $te = strtoupper($e); + } + // mPDF 6 + if ($te == 'THEAD' || $te == 'TBODY' || $te == 'TFOOT' || $te == 'TR' || $te == 'TD' || $te == 'TH') { + $this->lastoptionaltag = $te; + } + if (in_array($te, $this->outerblocktags) || in_array($te, $this->innerblocktags)) { + $this->blklvl++; + $this->blk[$this->blklvl]['hide'] = true; + $this->blk[$this->blklvl]['tag'] = $te; // mPDF 6 + } + continue; + } + + /* -- CSS-POSITION -- */ + if ($this->inFixedPosBlock) { + if (strpos($e, ' ')) { + $te = strtoupper(substr($e, 0, strpos($e, ' '))); + } else { + $te = strtoupper($e); + } + $this->fixedPosBlock .= '<' . $e . '>'; + if (in_array($te, $this->outerblocktags) || in_array($te, $this->innerblocktags)) { + $this->fixedPosBlockDepth++; + } + continue; + } + /* -- END CSS-POSITION -- */ + $regexp = '|=\'(.*?)\'|s'; // eliminate single quotes, if any + $e = preg_replace($regexp, "=\"\$1\"", $e); + // changes anykey=anyvalue to anykey="anyvalue" (only do this inside [some] tags) + if (substr($e, 0, 10) != 'pageheader' && substr($e, 0, 10) != 'pagefooter' && substr($e, 0, 12) != 'tocpagebreak' && substr($e, 0, 10) != 'indexentry' && substr($e, 0, 8) != 'tocentry') { // mPDF 6 (ZZZ99H) + $regexp = '| (\\w+?)=([^\\s>"]+)|si'; + $e = preg_replace($regexp, " \$1=\"\$2\"", $e); + } + + $e = preg_replace('/ (\\S+?)\s*=\s*"/i', " \\1=\"", $e); + + // Fix path values, if needed + $orig_srcpath = ''; + if ((stristr($e, "href=") !== false) or ( stristr($e, "src=") !== false)) { + $regexp = '/ (href|src)\s*=\s*"(.*?)"/i'; + preg_match($regexp, $e, $auxiliararray); + if (isset($auxiliararray[2])) { + $path = $auxiliararray[2]; + } else { + $path = ''; + } + if (trim($path) != '' && !(stristr($e, "src=") !== false && substr($path, 0, 4) == 'var:') && substr($path, 0, 1) != '@') { + $path = htmlspecialchars_decode($path); // mPDF 5.7.4 URLs + $orig_srcpath = $path; + $this->GetFullPath($path); + $regexp = '/ (href|src)="(.*?)"/i'; + $e = preg_replace($regexp, ' \\1="' . $path . '"', $e); + } + }//END of Fix path values + // Extract attributes + $contents = []; + $contents1 = []; + $contents2 = []; + // Changed to allow style="background: url('bg.jpg')" + // Changed to improve performance; maximum length of \S (attribute) = 16 + // Increase allowed attribute name to 32 - cutting off "toc-even-header-name" etc. + preg_match_all('/\\S{1,32}=["][^"]*["]/', $e, $contents1); + preg_match_all('/\\S{1,32}=[\'][^\']*[\']/i', $e, $contents2); + + $contents = array_merge($contents1, $contents2); + preg_match('/\\S+/', $e, $a2); + $tag = (isset($a2[0]) ? strtoupper($a2[0]) : ''); + $attr = []; + if ($orig_srcpath) { + $attr['ORIG_SRC'] = $orig_srcpath; + } + if (!empty($contents)) { + foreach ($contents[0] as $v) { + // Changed to allow style="background: url('bg.jpg')" + if (preg_match('/^([^=]*)=["]?([^"]*)["]?$/', $v, $a3) || preg_match('/^([^=]*)=[\']?([^\']*)[\']?$/', $v, $a3)) { + if (strtoupper($a3[1]) == 'ID' || strtoupper($a3[1]) == 'CLASS') { // 4.2.013 Omits STYLE + $attr[strtoupper($a3[1])] = trim(strtoupper($a3[2])); + } // includes header-style-right etc. used for + elseif (preg_match('/^(HEADER|FOOTER)-STYLE/i', $a3[1])) { + $attr[strtoupper($a3[1])] = trim(strtoupper($a3[2])); + } else { + $attr[strtoupper($a3[1])] = trim($a3[2]); + } + } + } + } + $this->tag->OpenTag($tag, $attr, $a, $i); // mPDF 6 + /* -- CSS-POSITION -- */ + if ($this->inFixedPosBlock) { + $this->fixedPosBlockBBox = [$tag, $attr, $this->x, $this->y]; + $this->fixedPosBlock = ''; + $this->fixedPosBlockDepth = 1; + } + /* -- END CSS-POSITION -- */ + if (preg_match('/\/$/', $e)) { + $this->tag->CloseTag($tag, $a, $i); + } + } + } // end TAG + } // end of foreach($a as $i=>$e) + + if ($close) { + // Close any open block tags + for ($b = $this->blklvl; $b > 0; $b--) { + $this->tag->CloseTag($this->blk[$b]['tag'], $a, $i); + } + + // Output any text left in buffer + if (count($this->textbuffer) && !$parseonly) { + $this->printbuffer($this->textbuffer); + } + if (!$parseonly) { + $this->textbuffer = []; + } + + /* -- CSS-FLOAT -- */ + // If ended with a float, need to move to end page + $currpos = $this->page * 1000 + $this->y; + if (isset($this->blk[$this->blklvl]['float_endpos']) && $this->blk[$this->blklvl]['float_endpos'] > $currpos) { + $old_page = $this->page; + $new_page = intval($this->blk[$this->blklvl]['float_endpos'] / 1000); + if ($old_page != $new_page) { + $s = $this->PrintPageBackgrounds(); + // Writes after the marker so not overwritten later by page background etc. + $this->pages[$this->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]); + $this->pageBackgrounds = []; + $this->page = $new_page; + $this->ResetMargins(); + $this->Reset(); + $this->pageoutput[$this->page] = []; + } + $this->y = (($this->blk[$this->blklvl]['float_endpos'] * 1000) % 1000000) / 1000; // mod changes operands to integers before processing + } + /* -- END CSS-FLOAT -- */ + + /* -- CSS-IMAGE-FLOAT -- */ + $this->printfloatbuffer(); + /* -- END CSS-IMAGE-FLOAT -- */ + + // Create Internal Links, if needed + if (!empty($this->internallink)) { + + foreach ($this->internallink as $k => $v) { + + if (strpos($k, "#") !== false) { + continue; + } + + if (!is_array($v)) { + continue; + } + + $ypos = $v['Y']; + $pagenum = $v['PAGE']; + $sharp = "#"; + + while (array_key_exists($sharp . $k, $this->internallink)) { + $internallink = $this->internallink[$sharp . $k]; + $this->SetLink($internallink, $ypos, $pagenum); + $sharp .= "#"; + } + } + } + + $this->bufferoutput = false; + + /* -- CSS-POSITION -- */ + if (count($this->fixedPosBlockSave)) { + foreach ($this->fixedPosBlockSave as $fpbs) { + $old_page = $this->page; + $this->page = $fpbs[2]; + $this->WriteFixedPosHTML($fpbs[0], 0, 0, 100, 100, 'auto', $fpbs[1]); // 0,0,10,10 are overwritten by bbox + $this->page = $old_page; + } + $this->fixedPosBlockSave = []; + } + /* -- END CSS-POSITION -- */ + } + } + + /* -- CSS-POSITION -- */ + + function WriteFixedPosHTML($html, $x, $y, $w, $h, $overflow = 'visible', $bounding = []) + { + // $overflow can be 'hidden', 'visible' or 'auto' - 'auto' causes autofit to size + // Annotations disabled - enabled in mPDF 5.0 + // Links do work + // Will always go on current page (or start Page 1 if required) + // Probably INCOMPATIBLE WITH keep with table, columns etc. + // Called externally or interally via
+ // When used internally, $x $y $w $h and $overflow are all overridden by $bounding + + $overflow = strtolower($overflow); + if ($this->state == 0) { + $this->AddPage($this->CurOrientation); + } + $save_y = $this->y; + $save_x = $this->x; + $this->fullImageHeight = $this->h; + $save_cols = false; + /* -- COLUMNS -- */ + if ($this->ColActive) { + $save_cols = true; + $save_nbcol = $this->NbCol; // other values of gap and vAlign will not change by setting Columns off + $this->SetColumns(0); + } + /* -- END COLUMNS -- */ + $save_annots = $this->title2annots; // *ANNOTATIONS* + $this->writingHTMLheader = true; // a FIX to stop pagebreaks etc. + $this->writingHTMLfooter = true; + $this->InFooter = true; // suppresses autopagebreaks + $save_bgs = $this->pageBackgrounds; + $checkinnerhtml = preg_replace('/\s/', '', $html); + $rotate = 0; + + if ($w > $this->w) { + $x = 0; + $w = $this->w; + } + if ($h > $this->h) { + $y = 0; + $h = $this->h; + } + if ($x > $this->w) { + $x = $this->w - $w; + } + if ($y > $this->h) { + $y = $this->h - $h; + } + + if (!empty($bounding)) { + // $cont_ containing block = full physical page (position: absolute) or page inside margins (position: fixed) + // $bbox_ Bounding box is the
which is positioned absolutely/fixed + // top/left/right/bottom/width/height/background*/border*/padding*/margin* are taken from bounding + // font*[family/size/style/weight]/line-height/text*[align/decoration/transform/indent]/color are transferred to $inner + // as an enclosing
(after having checked ID/CLASS) + // $x, $y, $w, $h are inside of $bbox_ = containing box for $inner_ + // $inner_ InnerHTML is the contents of that block to be output + $tag = $bounding[0]; + $attr = $bounding[1]; + $orig_x0 = $bounding[2]; + $orig_y0 = $bounding[3]; + + // As in WriteHTML() initialising + $this->blklvl = 0; + $this->lastblocklevelchange = 0; + $this->blk = []; + $this->initialiseBlock($this->blk[0]); + + $this->blk[0]['width'] = & $this->pgwidth; + $this->blk[0]['inner_width'] = & $this->pgwidth; + + $this->blk[0]['blockContext'] = $this->blockContext; + + $properties = $this->cssManager->MergeCSS('BLOCK', 'BODY', ''); + $this->setCSS($properties, '', 'BODY'); + $this->blklvl = 1; + $this->initialiseBlock($this->blk[1]); + $this->blk[1]['tag'] = $tag; + $this->blk[1]['attr'] = $attr; + $this->Reset(); + $p = $this->cssManager->MergeCSS('BLOCK', $tag, $attr); + if (isset($p['ROTATE']) && ($p['ROTATE'] == 90 || $p['ROTATE'] == -90 || $p['ROTATE'] == 180)) { + $rotate = $p['ROTATE']; + } // mPDF 6 + if (isset($p['OVERFLOW'])) { + $overflow = strtolower($p['OVERFLOW']); + } + if (strtolower($p['POSITION']) == 'fixed') { + $cont_w = $this->pgwidth; // $this->blk[0]['inner_width']; + $cont_h = $this->h - $this->tMargin - $this->bMargin; + $cont_x = $this->lMargin; + $cont_y = $this->tMargin; + } else { + $cont_w = $this->w; // ABSOLUTE; + $cont_h = $this->h; + $cont_x = 0; + $cont_y = 0; + } + + // Pass on in-line properties to the innerhtml + $css = ''; + if (isset($p['TEXT-ALIGN'])) { + $css .= 'text-align: ' . strtolower($p['TEXT-ALIGN']) . '; '; + } + if (isset($p['TEXT-TRANSFORM'])) { + $css .= 'text-transform: ' . strtolower($p['TEXT-TRANSFORM']) . '; '; + } + if (isset($p['TEXT-INDENT'])) { + $css .= 'text-indent: ' . strtolower($p['TEXT-INDENT']) . '; '; + } + if (isset($p['TEXT-DECORATION'])) { + $css .= 'text-decoration: ' . strtolower($p['TEXT-DECORATION']) . '; '; + } + if (isset($p['FONT-FAMILY'])) { + $css .= 'font-family: ' . strtolower($p['FONT-FAMILY']) . '; '; + } + if (isset($p['FONT-STYLE'])) { + $css .= 'font-style: ' . strtolower($p['FONT-STYLE']) . '; '; + } + if (isset($p['FONT-WEIGHT'])) { + $css .= 'font-weight: ' . strtolower($p['FONT-WEIGHT']) . '; '; + } + if (isset($p['FONT-SIZE'])) { + $css .= 'font-size: ' . strtolower($p['FONT-SIZE']) . '; '; + } + if (isset($p['LINE-HEIGHT'])) { + $css .= 'line-height: ' . strtolower($p['LINE-HEIGHT']) . '; '; + } + if (isset($p['TEXT-SHADOW'])) { + $css .= 'text-shadow: ' . strtolower($p['TEXT-SHADOW']) . '; '; + } + if (isset($p['LETTER-SPACING'])) { + $css .= 'letter-spacing: ' . strtolower($p['LETTER-SPACING']) . '; '; + } + // mPDF 6 + if (isset($p['FONT-VARIANT-POSITION'])) { + $css .= 'font-variant-position: ' . strtolower($p['FONT-VARIANT-POSITION']) . '; '; + } + if (isset($p['FONT-VARIANT-CAPS'])) { + $css .= 'font-variant-caps: ' . strtolower($p['FONT-VARIANT-CAPS']) . '; '; + } + if (isset($p['FONT-VARIANT-LIGATURES'])) { + $css .= 'font-variant-ligatures: ' . strtolower($p['FONT-VARIANT-LIGATURES']) . '; '; + } + if (isset($p['FONT-VARIANT-NUMERIC'])) { + $css .= 'font-variant-numeric: ' . strtolower($p['FONT-VARIANT-NUMERIC']) . '; '; + } + if (isset($p['FONT-VARIANT-ALTERNATES'])) { + $css .= 'font-variant-alternates: ' . strtolower($p['FONT-VARIANT-ALTERNATES']) . '; '; + } + if (isset($p['FONT-FEATURE-SETTINGS'])) { + $css .= 'font-feature-settings: ' . strtolower($p['FONT-FEATURE-SETTINGS']) . '; '; + } + if (isset($p['FONT-LANGUAGE-OVERRIDE'])) { + $css .= 'font-language-override: ' . strtolower($p['FONT-LANGUAGE-OVERRIDE']) . '; '; + } + if (isset($p['FONT-KERNING'])) { + $css .= 'font-kerning: ' . strtolower($p['FONT-KERNING']) . '; '; + } + + if (isset($p['COLOR'])) { + $css .= 'color: ' . strtolower($p['COLOR']) . '; '; + } + if (isset($p['Z-INDEX'])) { + $css .= 'z-index: ' . $p['Z-INDEX'] . '; '; + } + if ($css) { + $html = '
' . $html . '
'; + } + // Copy over (only) the properties to set for border and background + $pb = []; + $pb['MARGIN-TOP'] = (isset($p['MARGIN-TOP']) ? $p['MARGIN-TOP'] : ''); + $pb['MARGIN-RIGHT'] = (isset($p['MARGIN-RIGHT']) ? $p['MARGIN-RIGHT'] : ''); + $pb['MARGIN-BOTTOM'] = (isset($p['MARGIN-BOTTOM']) ? $p['MARGIN-BOTTOM'] : ''); + $pb['MARGIN-LEFT'] = (isset($p['MARGIN-LEFT']) ? $p['MARGIN-LEFT'] : ''); + $pb['PADDING-TOP'] = (isset($p['PADDING-TOP']) ? $p['PADDING-TOP'] : ''); + $pb['PADDING-RIGHT'] = (isset($p['PADDING-RIGHT']) ? $p['PADDING-RIGHT'] : ''); + $pb['PADDING-BOTTOM'] = (isset($p['PADDING-BOTTOM']) ? $p['PADDING-BOTTOM'] : ''); + $pb['PADDING-LEFT'] = (isset($p['PADDING-LEFT']) ? $p['PADDING-LEFT'] : ''); + $pb['BORDER-TOP'] = (isset($p['BORDER-TOP']) ? $p['BORDER-TOP'] : ''); + $pb['BORDER-RIGHT'] = (isset($p['BORDER-RIGHT']) ? $p['BORDER-RIGHT'] : ''); + $pb['BORDER-BOTTOM'] = (isset($p['BORDER-BOTTOM']) ? $p['BORDER-BOTTOM'] : ''); + $pb['BORDER-LEFT'] = (isset($p['BORDER-LEFT']) ? $p['BORDER-LEFT'] : ''); + if (isset($p['BORDER-TOP-LEFT-RADIUS-H'])) { + $pb['BORDER-TOP-LEFT-RADIUS-H'] = $p['BORDER-TOP-LEFT-RADIUS-H']; + } + if (isset($p['BORDER-TOP-LEFT-RADIUS-V'])) { + $pb['BORDER-TOP-LEFT-RADIUS-V'] = $p['BORDER-TOP-LEFT-RADIUS-V']; + } + if (isset($p['BORDER-TOP-RIGHT-RADIUS-H'])) { + $pb['BORDER-TOP-RIGHT-RADIUS-H'] = $p['BORDER-TOP-RIGHT-RADIUS-H']; + } + if (isset($p['BORDER-TOP-RIGHT-RADIUS-V'])) { + $pb['BORDER-TOP-RIGHT-RADIUS-V'] = $p['BORDER-TOP-RIGHT-RADIUS-V']; + } + if (isset($p['BORDER-BOTTOM-LEFT-RADIUS-H'])) { + $pb['BORDER-BOTTOM-LEFT-RADIUS-H'] = $p['BORDER-BOTTOM-LEFT-RADIUS-H']; + } + if (isset($p['BORDER-BOTTOM-LEFT-RADIUS-V'])) { + $pb['BORDER-BOTTOM-LEFT-RADIUS-V'] = $p['BORDER-BOTTOM-LEFT-RADIUS-V']; + } + if (isset($p['BORDER-BOTTOM-RIGHT-RADIUS-H'])) { + $pb['BORDER-BOTTOM-RIGHT-RADIUS-H'] = $p['BORDER-BOTTOM-RIGHT-RADIUS-H']; + } + if (isset($p['BORDER-BOTTOM-RIGHT-RADIUS-V'])) { + $pb['BORDER-BOTTOM-RIGHT-RADIUS-V'] = $p['BORDER-BOTTOM-RIGHT-RADIUS-V']; + } + if (isset($p['BACKGROUND-COLOR'])) { + $pb['BACKGROUND-COLOR'] = $p['BACKGROUND-COLOR']; + } + if (isset($p['BOX-SHADOW'])) { + $pb['BOX-SHADOW'] = $p['BOX-SHADOW']; + } + /* -- BACKGROUNDS -- */ + if (isset($p['BACKGROUND-IMAGE'])) { + $pb['BACKGROUND-IMAGE'] = $p['BACKGROUND-IMAGE']; + } + if (isset($p['BACKGROUND-IMAGE-RESIZE'])) { + $pb['BACKGROUND-IMAGE-RESIZE'] = $p['BACKGROUND-IMAGE-RESIZE']; + } + if (isset($p['BACKGROUND-IMAGE-OPACITY'])) { + $pb['BACKGROUND-IMAGE-OPACITY'] = $p['BACKGROUND-IMAGE-OPACITY']; + } + if (isset($p['BACKGROUND-REPEAT'])) { + $pb['BACKGROUND-REPEAT'] = $p['BACKGROUND-REPEAT']; + } + if (isset($p['BACKGROUND-POSITION'])) { + $pb['BACKGROUND-POSITION'] = $p['BACKGROUND-POSITION']; + } + if (isset($p['BACKGROUND-GRADIENT'])) { + $pb['BACKGROUND-GRADIENT'] = $p['BACKGROUND-GRADIENT']; + } + if (isset($p['BACKGROUND-SIZE'])) { + $pb['BACKGROUND-SIZE'] = $p['BACKGROUND-SIZE']; + } + if (isset($p['BACKGROUND-ORIGIN'])) { + $pb['BACKGROUND-ORIGIN'] = $p['BACKGROUND-ORIGIN']; + } + if (isset($p['BACKGROUND-CLIP'])) { + $pb['BACKGROUND-CLIP'] = $p['BACKGROUND-CLIP']; + } + + /* -- END BACKGROUNDS -- */ + + $this->setCSS($pb, 'BLOCK', $tag); + + // ================================================================ + $bbox_br = $this->blk[1]['border_right']['w']; + $bbox_bl = $this->blk[1]['border_left']['w']; + $bbox_bt = $this->blk[1]['border_top']['w']; + $bbox_bb = $this->blk[1]['border_bottom']['w']; + $bbox_pr = $this->blk[1]['padding_right']; + $bbox_pl = $this->blk[1]['padding_left']; + $bbox_pt = $this->blk[1]['padding_top']; + $bbox_pb = $this->blk[1]['padding_bottom']; + $bbox_mr = $this->blk[1]['margin_right']; + if (isset($p['MARGIN-RIGHT']) && strtolower($p['MARGIN-RIGHT']) == 'auto') { + $bbox_mr = 'auto'; + } + $bbox_ml = $this->blk[1]['margin_left']; + if (isset($p['MARGIN-LEFT']) && strtolower($p['MARGIN-LEFT']) == 'auto') { + $bbox_ml = 'auto'; + } + $bbox_mt = $this->blk[1]['margin_top']; + if (isset($p['MARGIN-TOP']) && strtolower($p['MARGIN-TOP']) == 'auto') { + $bbox_mt = 'auto'; + } + $bbox_mb = $this->blk[1]['margin_bottom']; + if (isset($p['MARGIN-BOTTOM']) && strtolower($p['MARGIN-BOTTOM']) == 'auto') { + $bbox_mb = 'auto'; + } + if (isset($p['LEFT']) && strtolower($p['LEFT']) != 'auto') { + $bbox_left = $this->sizeConverter->convert($p['LEFT'], $cont_w, $this->FontSize, false); + } else { + $bbox_left = 'auto'; + } + if (isset($p['TOP']) && strtolower($p['TOP']) != 'auto') { + $bbox_top = $this->sizeConverter->convert($p['TOP'], $cont_h, $this->FontSize, false); + } else { + $bbox_top = 'auto'; + } + if (isset($p['RIGHT']) && strtolower($p['RIGHT']) != 'auto') { + $bbox_right = $this->sizeConverter->convert($p['RIGHT'], $cont_w, $this->FontSize, false); + } else { + $bbox_right = 'auto'; + } + if (isset($p['BOTTOM']) && strtolower($p['BOTTOM']) != 'auto') { + $bbox_bottom = $this->sizeConverter->convert($p['BOTTOM'], $cont_h, $this->FontSize, false); + } else { + $bbox_bottom = 'auto'; + } + if (isset($p['WIDTH']) && strtolower($p['WIDTH']) != 'auto') { + $inner_w = $this->sizeConverter->convert($p['WIDTH'], $cont_w, $this->FontSize, false); + } else { + $inner_w = 'auto'; + } + if (isset($p['HEIGHT']) && strtolower($p['HEIGHT']) != 'auto') { + $inner_h = $this->sizeConverter->convert($p['HEIGHT'], $cont_h, $this->FontSize, false); + } else { + $inner_h = 'auto'; + } + + // If bottom or right pos are set and not left / top - save this to adjust rotated block later + if ($rotate == 90 || $rotate == -90) { // mPDF 6 + if ($bbox_left === 'auto' && $bbox_right !== 'auto') { + $rot_rpos = $bbox_right; + } else { + $rot_rpos = false; + } + if ($bbox_top === 'auto' && $bbox_bottom !== 'auto') { + $rot_bpos = $bbox_bottom; + } else { + $rot_bpos = false; + } + } + + // ================================================================ + if ($checkinnerhtml == '' && $inner_h === 'auto') { + $inner_h = 0.0001; + } + if ($checkinnerhtml == '' && $inner_w === 'auto') { + $inner_w = 2 * $this->GetCharWidth('W', false); + } + // ================================================================ + // Algorithm from CSS2.1 See http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height + // mPD 5.3.14 + // Special case (not CSS) if all not specified, centre vertically on page + $bbox_top_orig = ''; + if ($bbox_top === 'auto' && $inner_h === 'auto' && $bbox_bottom === 'auto' && $bbox_mt === 'auto' && $bbox_mb === 'auto') { + $bbox_top_orig = $bbox_top; + if ($bbox_mt === 'auto') { + $bbox_mt = 0; + } + if ($bbox_mb === 'auto') { + $bbox_mb = 0; + } + $bbox_top = $orig_y0 - $bbox_mt - $cont_y; + // solve for $bbox_bottom when content_h known - $inner_h=='auto' && $bbox_bottom=='auto' + } // mPD 5.3.14 + elseif ($bbox_top === 'auto' && $inner_h === 'auto' && $bbox_bottom === 'auto') { + $bbox_top_orig = $bbox_top = $orig_y0 - $cont_y; + if ($bbox_mt === 'auto') { + $bbox_mt = 0; + } + if ($bbox_mb === 'auto') { + $bbox_mb = 0; + } + // solve for $bbox_bottom when content_h known - $inner_h=='auto' && $bbox_bottom=='auto' + } elseif ($bbox_top !== 'auto' && $inner_h !== 'auto' && $bbox_bottom !== 'auto') { + if ($bbox_mt === 'auto' && $bbox_mb === 'auto') { + $x = $cont_h - $bbox_top - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_bottom; + $bbox_mt = $bbox_mb = ($x / 2); + } elseif ($bbox_mt === 'auto') { + $bbox_mt = $cont_h - $bbox_top - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom; + } elseif ($bbox_mb === 'auto') { + $bbox_mb = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_bottom; + } else { + $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt; + } + } else { + if ($bbox_mt === 'auto') { + $bbox_mt = 0; + } + if ($bbox_mb === 'auto') { + $bbox_mb = 0; + } + if ($bbox_top === 'auto' && $inner_h === 'auto' && $bbox_bottom !== 'auto') { + // solve for $bbox_top when content_h known - $inner_h=='auto' && $bbox_top =='auto' + } elseif ($bbox_top === 'auto' && $bbox_bottom === 'auto' && $inner_h !== 'auto') { + $bbox_top = $orig_y0 - $bbox_mt - $cont_y; + $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt; + } elseif ($inner_h === 'auto' && $bbox_bottom === 'auto' && $bbox_top !== 'auto') { + // solve for $bbox_bottom when content_h known - $inner_h=='auto' && $bbox_bottom=='auto' + } elseif ($bbox_top === 'auto' && $inner_h !== 'auto' && $bbox_bottom !== 'auto') { + $bbox_top = $cont_h - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt - $bbox_bottom; + } elseif ($inner_h === 'auto' && $bbox_top !== 'auto' && $bbox_bottom !== 'auto') { + $inner_h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mt - $bbox_bottom; + } elseif ($bbox_bottom === 'auto' && $bbox_top !== 'auto' && $inner_h !== 'auto') { + $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mt; + } + } + + // THEN DO SAME FOR WIDTH + // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width + if ($bbox_left === 'auto' && $inner_w === 'auto' && $bbox_right === 'auto') { + if ($bbox_ml === 'auto') { + $bbox_ml = 0; + } + if ($bbox_mr === 'auto') { + $bbox_mr = 0; + } + // IF containing element RTL, should set $bbox_right + $bbox_left = $orig_x0 - $bbox_ml - $cont_x; + // solve for $bbox_right when content_w known - $inner_w=='auto' && $bbox_right=='auto' + } elseif ($bbox_left !== 'auto' && $inner_w !== 'auto' && $bbox_right !== 'auto') { + if ($bbox_ml === 'auto' && $bbox_mr === 'auto') { + $x = $cont_w - $bbox_left - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_right; + $bbox_ml = $bbox_mr = ($x / 2); + } elseif ($bbox_ml === 'auto') { + $bbox_ml = $cont_w - $bbox_left - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_mr - $bbox_right; + } elseif ($bbox_mr === 'auto') { + $bbox_mr = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_right; + } else { + $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml; + } + } else { + if ($bbox_ml === 'auto') { + $bbox_ml = 0; + } + if ($bbox_mr === 'auto') { + $bbox_mr = 0; + } + if ($bbox_left === 'auto' && $inner_w === 'auto' && $bbox_right !== 'auto') { + // solve for $bbox_left when content_w known - $inner_w=='auto' && $bbox_left =='auto' + } elseif ($bbox_left === 'auto' && $bbox_right === 'auto' && $inner_w !== 'auto') { + // IF containing element RTL, should set $bbox_right + $bbox_left = $orig_x0 - $bbox_ml - $cont_x; + $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml; + } elseif ($inner_w === 'auto' && $bbox_right === 'auto' && $bbox_left !== 'auto') { + // solve for $bbox_right when content_w known - $inner_w=='auto' && $bbox_right=='auto' + } elseif ($bbox_left === 'auto' && $inner_w !== 'auto' && $bbox_right !== 'auto') { + $bbox_left = $cont_w - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml - $bbox_right; + } elseif ($inner_w === 'auto' && $bbox_left !== 'auto' && $bbox_right !== 'auto') { + $inner_w = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $bbox_pr - $bbox_br - $bbox_ml - $bbox_right; + } elseif ($bbox_right === 'auto' && $bbox_left !== 'auto' && $inner_w !== 'auto') { + $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml; + } + } + + // ================================================================ + // ================================================================ + /* -- BACKGROUNDS -- */ + if (isset($pb['BACKGROUND-IMAGE']) && $pb['BACKGROUND-IMAGE']) { + $ret = $this->SetBackground($pb, $this->blk[1]['inner_width']); + if ($ret) { + $this->blk[1]['background-image'] = $ret; + } + } + /* -- END BACKGROUNDS -- */ + + $bbox_top_auto = $bbox_top === 'auto'; + $bbox_left_auto = $bbox_left === 'auto'; + $bbox_right_auto = $bbox_right === 'auto'; + $bbox_bottom_auto = $bbox_bottom === 'auto'; + + $bbox_top = is_numeric($bbox_top) ? $bbox_top : 0; + $bbox_left = is_numeric($bbox_left) ? $bbox_left : 0; + $bbox_right = is_numeric($bbox_right) ? $bbox_right : 0; + $bbox_bottom = is_numeric($bbox_bottom) ? $bbox_bottom : 0; + + $y = $cont_y + $bbox_top + $bbox_mt + $bbox_bt + $bbox_pt; + $h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom; + + $x = $cont_x + $bbox_left + $bbox_ml + $bbox_bl + $bbox_pl; + $w = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $bbox_pr - $bbox_br - $bbox_mr - $bbox_right; + + // Set (temporary) values for x y w h to do first paint, if values are auto + if ($inner_h === 'auto' && $bbox_top_auto) { + $y = $cont_y + $bbox_mt + $bbox_bt + $bbox_pt; + $h = $cont_h - ($bbox_bottom + $bbox_mt + $bbox_mb + $bbox_bt + $bbox_bb + $bbox_pt + $bbox_pb); + } elseif ($inner_h === 'auto' && $bbox_bottom_auto) { + $y = $cont_y + $bbox_top + $bbox_mt + $bbox_bt + $bbox_pt; + $h = $cont_h - ($bbox_top + $bbox_mt + $bbox_mb + $bbox_bt + $bbox_bb + $bbox_pt + $bbox_pb); + } + if ($inner_w === 'auto' && $bbox_left_auto) { + $x = $cont_x + $bbox_ml + $bbox_bl + $bbox_pl; + $w = $cont_w - ($bbox_right + $bbox_ml + $bbox_mr + $bbox_bl + $bbox_br + $bbox_pl + $bbox_pr); + } elseif ($inner_w === 'auto' && $bbox_right_auto) { + $x = $cont_x + $bbox_left + $bbox_ml + $bbox_bl + $bbox_pl; + $w = $cont_w - ($bbox_left + $bbox_ml + $bbox_mr + $bbox_bl + $bbox_br + $bbox_pl + $bbox_pr); + } + + $bbox_y = $cont_y + $bbox_top + $bbox_mt; + $bbox_x = $cont_x + $bbox_left + $bbox_ml; + + $saved_block1 = $this->blk[1]; + + unset($p); + unset($pb); + + // ================================================================ + if ($inner_w === 'auto') { // do a first write + $this->lMargin = $x; + $this->rMargin = $this->w - $w - $x; + + // SET POSITION & FONT VALUES + $this->pgwidth = $this->w - $this->lMargin - $this->rMargin; + $this->pageoutput[$this->page] = []; + $this->x = $x; + $this->y = $y; + $this->HTMLheaderPageLinks = []; + $this->HTMLheaderPageAnnots = []; + $this->HTMLheaderPageForms = []; + $this->pageBackgrounds = []; + $this->maxPosR = 0; + $this->maxPosL = $this->w; // For RTL + $this->WriteHTML($html, HTMLParserMode::HTML_HEADER_BUFFER); + $inner_w = $this->maxPosR - $this->lMargin; + if ($bbox_right_auto) { + $bbox_right = $cont_w - $bbox_left - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml; + } elseif ($bbox_left_auto) { + $bbox_left = $cont_w - $bbox_ml - $bbox_bl - $bbox_pl - $inner_w - $bbox_pr - $bbox_br - $bbox_ml - $bbox_right; + $bbox_x = $cont_x + $bbox_left + $bbox_ml; + $inner_x = $bbox_x + $bbox_bl + $bbox_pl; + $x = $inner_x; + } + + $w = $inner_w; + $bbox_y = $cont_y + $bbox_top + $bbox_mt; + $bbox_x = $cont_x + $bbox_left + $bbox_ml; + } + + if ($inner_h === 'auto') { // do a first write + + $this->lMargin = $x; + $this->rMargin = $this->w - $w - $x; + + // SET POSITION & FONT VALUES + $this->pgwidth = $this->w - $this->lMargin - $this->rMargin; + $this->pageoutput[$this->page] = []; + $this->x = $x; + $this->y = $y; + $this->HTMLheaderPageLinks = []; + $this->HTMLheaderPageAnnots = []; + $this->HTMLheaderPageForms = []; + $this->pageBackgrounds = []; + $this->WriteHTML($html, HTMLParserMode::HTML_HEADER_BUFFER); + $inner_h = $this->y - $y; + + if ($overflow != 'hidden' && $overflow != 'visible') { // constrained + if (($this->y + $bbox_pb + $bbox_bb) > ($cont_y + $cont_h)) { + $adj = ($this->y + $bbox_pb + $bbox_bb) - ($cont_y + $cont_h); + $inner_h -= $adj; + } + } + if ($bbox_bottom_auto && $bbox_top_orig === 'auto') { + $bbox_bottom = $bbox_top = ($cont_h - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb) / 2; + if ($overflow != 'hidden' && $overflow != 'visible') { // constrained + if ($bbox_top < 0) { + $bbox_top = 0; + $inner_h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom; + } + } + $bbox_y = $cont_y + $bbox_top + $bbox_mt; + $inner_y = $bbox_y + $bbox_bt + $bbox_pt; + $y = $inner_y; + } elseif ($bbox_bottom_auto) { + $bbox_bottom = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb; + } elseif ($bbox_top_auto) { + $bbox_top = $cont_h - $bbox_mt - $bbox_bt - $bbox_pt - $inner_h - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom; + if ($overflow != 'hidden' && $overflow != 'visible') { // constrained + if ($bbox_top < 0) { + $bbox_top = 0; + $inner_h = $cont_h - $bbox_top - $bbox_mt - $bbox_bt - $bbox_pt - $bbox_pb - $bbox_bb - $bbox_mb - $bbox_bottom; + } + } + $bbox_y = $cont_y + $bbox_top + $bbox_mt; + $inner_y = $bbox_y + $bbox_bt + $bbox_pt; + $y = $inner_y; + } + $h = $inner_h; + $bbox_y = $cont_y + $bbox_top + $bbox_mt; + $bbox_x = $cont_x + $bbox_left + $bbox_ml; + } + + $inner_w = $w; + $inner_h = $h; + } + + $this->lMargin = $x; + $this->rMargin = $this->w - $w - $x; + + // SET POSITION & FONT VALUES + $this->pgwidth = $this->w - $this->lMargin - $this->rMargin; + $this->pageoutput[$this->page] = []; + + $this->x = $x; + $this->y = $y; + + $this->HTMLheaderPageLinks = []; + $this->HTMLheaderPageAnnots = []; + $this->HTMLheaderPageForms = []; + + $this->pageBackgrounds = []; + + $this->WriteHTML($html, HTMLParserMode::HTML_HEADER_BUFFER); + + $actual_h = $this->y - $y; + $use_w = $w; + $use_h = $h; + $ratio = $actual_h / $use_w; + + if ($overflow != 'hidden' && $overflow != 'visible') { + $target = $h / $w; + if ($target > 0) { + if (($ratio / $target) > 1) { + $nl = ceil($actual_h / $this->lineheight); + $l = $use_w * $nl; + $est_w = sqrt(($l * $this->lineheight) / $target) * 0.8; + $use_w += ($est_w - $use_w) - ($w / 100); + } + $bpcstart = ($ratio / $target); + $bpcctr = 1; + + while (($ratio / $target) > 1) { + // @log 'Auto-sizing fixed-position block $bpcctr++ + + $this->x = $x; + $this->y = $y; + + if (($ratio / $target) > 1.5 || ($ratio / $target) < 0.6) { + $use_w += ($w / $this->incrementFPR1); + } elseif (($ratio / $target) > 1.2 || ($ratio / $target) < 0.85) { + $use_w += ($w / $this->incrementFPR2); + } elseif (($ratio / $target) > 1.1 || ($ratio / $target) < 0.91) { + $use_w += ($w / $this->incrementFPR3); + } else { + $use_w += ($w / $this->incrementFPR4); + } + + $use_h = $use_w * $target; + $this->rMargin = $this->w - $use_w - $x; + $this->pgwidth = $this->w - $this->lMargin - $this->rMargin; + $this->HTMLheaderPageLinks = []; + $this->HTMLheaderPageAnnots = []; + $this->HTMLheaderPageForms = []; + $this->pageBackgrounds = []; + $this->WriteHTML($html, HTMLParserMode::HTML_HEADER_BUFFER); + $actual_h = $this->y - $y; + $ratio = $actual_h / $use_w; + } + } + } + + $shrink_f = $w / $use_w; + + // ================================================================ + + $this->pages[$this->page] .= '___BEFORE_BORDERS___'; + $block_s = $this->PrintPageBackgrounds(); // Save to print later inside clipping path + $this->pageBackgrounds = []; + + // ================================================================ + + if ($rotate == 90 || $rotate == -90) { // mPDF 6 + $prerotw = $bbox_bl + $bbox_pl + $inner_w + $bbox_pr + $bbox_br; + $preroth = $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb; + $rot_start = " q\n"; + if ($rotate == 90) { + if ($rot_rpos !== false) { + $adjw = $prerotw; + } // width before rotation + else { + $adjw = $preroth; + } // height before rotation + if ($rot_bpos !== false) { + $adjh = -$prerotw + $preroth; + } else { + $adjh = 0; + } + } else { + if ($rot_rpos !== false) { + $adjw = $prerotw - $preroth; + } else { + $adjw = 0; + } + if ($rot_bpos !== false) { + $adjh = $preroth; + } // height before rotation + else { + $adjh = $prerotw; + } // width before rotation + } + $rot_start .= $this->transformTranslate($adjw, $adjh, true) . "\n"; + $rot_start .= $this->transformRotate($rotate, $bbox_x, $bbox_y, true) . "\n"; + $rot_end = " Q\n"; + } elseif ($rotate == 180) { // mPDF 6 + $rot_start = " q\n"; + $rot_start .= $this->transformTranslate($bbox_bl + $bbox_pl + $inner_w + $bbox_pr + $bbox_br, $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb, true) . "\n"; + $rot_start .= $this->transformRotate(180, $bbox_x, $bbox_y, true) . "\n"; + $rot_end = " Q\n"; + } else { + $rot_start = ''; + $rot_end = ''; + } + + // ================================================================ + if (!empty($bounding)) { + // WHEN HEIGHT // BOTTOM EDGE IS KNOWN and $this->y is set to the bottom + // Re-instate saved $this->blk[1] + $this->blk[1] = $saved_block1; + + // These are only needed when painting border/background + $this->blk[1]['width'] = $bbox_w = $cont_w - $bbox_left - $bbox_ml - $bbox_mr - $bbox_right; + $this->blk[1]['x0'] = $bbox_x; + $this->blk[1]['y0'] = $bbox_y; + $this->blk[1]['startpage'] = $this->page; + $this->blk[1]['y1'] = $bbox_y + $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb; + $this->writer->write($rot_start); + $this->PaintDivBB('', 0, 1); // Prints borders and sets backgrounds in $this->pageBackgrounds + $this->writer->write($rot_end); + } + + $s = $this->PrintPageBackgrounds(); + $s = $rot_start . $s . $rot_end; + $this->pages[$this->page] = preg_replace('/___BEFORE_BORDERS___/', "\n" . $s . "\n", $this->pages[$this->page]); + $this->pageBackgrounds = []; + + $this->writer->write($rot_start); + + // Clipping Output + if ($overflow == 'hidden') { + // Bounding rectangle to clip + $clip_y1 = $this->y; + if (!empty($bounding) && ($this->y + $bbox_pb + $bbox_bb) > ($bbox_y + $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb )) { + $clip_y1 = ($bbox_y + $bbox_bt + $bbox_pt + $inner_h + $bbox_pb + $bbox_bb ) - ($bbox_pb + $bbox_bb); + } + // $op = 'W* n'; // Clipping + $op = 'W n'; // Clipping alternative mode + $this->writer->write("q"); + $ch = $clip_y1 - $y; + $this->writer->write(sprintf('%.3F %.3F %.3F %.3F re %s', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, $w * Mpdf::SCALE, -$ch * Mpdf::SCALE, $op)); + if (!empty($block_s)) { + $tmp = "q\n" . sprintf('%.3F %.3F %.3F %.3F re %s', $x * Mpdf::SCALE, ($this->h - $y) * Mpdf::SCALE, $w * Mpdf::SCALE, -$ch * Mpdf::SCALE, $op); + $tmp .= "\n" . $block_s . "\nQ"; + $block_s = $tmp; + } + } + + + if (!empty($block_s)) { + if ($shrink_f != 1) { // i.e. autofit has resized the box + $tmp = "q\n" . $this->transformScale(($shrink_f * 100), ($shrink_f * 100), $x, $y, true); + $tmp .= "\n" . $block_s . "\nQ"; + $block_s = $tmp; + } + $this->writer->write($block_s); + } + + + + if ($shrink_f != 1) { // i.e. autofit has resized the box + $this->StartTransform(); + $this->transformScale(($shrink_f * 100), ($shrink_f * 100), $x, $y); + } + + $this->writer->write($this->headerbuffer); + + if ($shrink_f != 1) { // i.e. autofit has resized the box + $this->StopTransform(); + } + + if ($overflow == 'hidden') { + // End clipping + $this->writer->write("Q"); + } + + $this->writer->write($rot_end); + + + // Page Links + foreach ($this->HTMLheaderPageLinks as $lk) { + if ($rotate) { + $tmp = $lk[2]; // Switch h - w + $lk[2] = $lk[3]; + $lk[3] = $tmp; + + $lx1 = (($lk[0] / Mpdf::SCALE)); + $ly1 = (($this->h - ($lk[1] / Mpdf::SCALE))); + if ($rotate == 90) { + $adjx = -($lx1 - $bbox_x) + ($preroth - ($ly1 - $bbox_y)); + $adjy = -($ly1 - $bbox_y) + ($lx1 - $bbox_x); + $lk[2] = -$lk[2]; + } elseif ($rotate == -90) { + $adjx = -($lx1 - $bbox_x) + ($ly1 - $bbox_y); + $adjy = -($ly1 - $bbox_y) - ($lx1 - $bbox_x) + $prerotw; + $lk[3] = -$lk[3]; + } + if ($rot_rpos !== false) { + $adjx += $prerotw - $preroth; + } + if ($rot_bpos !== false) { + $adjy += $preroth - $prerotw; + } + $lx1 += $adjx; + $ly1 += $adjy; + + $lk[0] = $lx1 * Mpdf::SCALE; + $lk[1] = ($this->h - $ly1) * Mpdf::SCALE; + } + if ($shrink_f != 1) { // i.e. autofit has resized the box + $lx1 = (($lk[0] / Mpdf::SCALE) - $x); + $lx2 = $x + ($lx1 * $shrink_f); + $lk[0] = $lx2 * Mpdf::SCALE; + $ly1 = (($this->h - ($lk[1] / Mpdf::SCALE)) - $y); + $ly2 = $y + ($ly1 * $shrink_f); + $lk[1] = ($this->h - $ly2) * Mpdf::SCALE; + $lk[2] *= $shrink_f; // width + $lk[3] *= $shrink_f; // height + } + $this->PageLinks[$this->page][] = $lk; + } + + foreach ($this->HTMLheaderPageForms as $n => $f) { + if ($shrink_f != 1) { // i.e. autofit has resized the box + $f['x'] = $x + (($f['x'] - $x) * $shrink_f); + $f['y'] = $y + (($f['y'] - $y) * $shrink_f); + $f['w'] *= $shrink_f; + $f['h'] *= $shrink_f; + $f['style']['fontsize'] *= $shrink_f; + } + $this->form->forms[$f['n']] = $f; + } + // Page Annotations + foreach ($this->HTMLheaderPageAnnots as $lk) { + if ($rotate) { + if ($rotate == 90) { + $adjx = -($lk['x'] - $bbox_x) + ($preroth - ($lk['y'] - $bbox_y)); + $adjy = -($lk['y'] - $bbox_y) + ($lk['x'] - $bbox_x); + } elseif ($rotate == -90) { + $adjx = -($lk['x'] - $bbox_x) + ($lk['y'] - $bbox_y); + $adjy = -($lk['y'] - $bbox_y) - ($lk['x'] - $bbox_x) + $prerotw; + } + if ($rot_rpos !== false) { + $adjx += $prerotw - $preroth; + } + if ($rot_bpos !== false) { + $adjy += $preroth - $prerotw; + } + $lk['x'] += $adjx; + $lk['y'] += $adjy; + } + if ($shrink_f != 1) { // i.e. autofit has resized the box + $lk['x'] = $x + (($lk['x'] - $x) * $shrink_f); + $lk['y'] = $y + (($lk['y'] - $y) * $shrink_f); + } + $this->PageAnnots[$this->page][] = $lk; + } + + // Restore + $this->headerbuffer = ''; + $this->HTMLheaderPageLinks = []; + $this->HTMLheaderPageAnnots = []; + $this->HTMLheaderPageForms = []; + $this->pageBackgrounds = $save_bgs; + $this->writingHTMLheader = false; + + $this->writingHTMLfooter = false; + $this->fullImageHeight = false; + $this->ResetMargins(); + $this->pgwidth = $this->w - $this->lMargin - $this->rMargin; + $this->SetXY($save_x, $save_y); + $this->title2annots = $save_annots; // *ANNOTATIONS* + $this->InFooter = false; // turns back on autopagebreaks + $this->pageoutput[$this->page] = []; + $this->pageoutput[$this->page]['Font'] = ''; + /* -- COLUMNS -- */ + if ($save_cols) { + $this->SetColumns($save_nbcol, $this->colvAlign, $this->ColGap); + } + /* -- END COLUMNS -- */ + } + + /* -- END CSS-POSITION -- */ + + function initialiseBlock(&$blk) + { + $blk['margin_top'] = 0; + $blk['margin_left'] = 0; + $blk['margin_bottom'] = 0; + $blk['margin_right'] = 0; + $blk['padding_top'] = 0; + $blk['padding_left'] = 0; + $blk['padding_bottom'] = 0; + $blk['padding_right'] = 0; + $blk['border_top']['w'] = 0; + $blk['border_left']['w'] = 0; + $blk['border_bottom']['w'] = 0; + $blk['border_right']['w'] = 0; + $blk['direction'] = 'ltr'; + $blk['hide'] = false; + $blk['outer_left_margin'] = 0; + $blk['outer_right_margin'] = 0; + $blk['cascadeCSS'] = []; + $blk['block-align'] = false; + $blk['bgcolor'] = false; + $blk['page_break_after_avoid'] = false; + $blk['keep_block_together'] = false; + $blk['float'] = false; + $blk['line_height'] = ''; + $blk['margin_collapse'] = false; + } + + function border_details($bd) + { + $prop = preg_split('/\s+/', trim($bd)); + + if (isset($this->blk[$this->blklvl]['inner_width'])) { + $refw = $this->blk[$this->blklvl]['inner_width']; + } elseif (isset($this->blk[$this->blklvl - 1]['inner_width'])) { + $refw = $this->blk[$this->blklvl - 1]['inner_width']; + } else { + $refw = $this->w; + } + if (count($prop) == 1) { + $bsize = $this->sizeConverter->convert($prop[0], $refw, $this->FontSize, false); + if ($bsize > 0) { + return ['s' => 1, 'w' => $bsize, 'c' => $this->colorConverter->convert(0, $this->PDFAXwarnings), 'style' => 'solid']; + } else { + return ['w' => 0, 's' => 0]; + } + } elseif (count($prop) == 2) { + // 1px solid + if (in_array($prop[1], $this->borderstyles) || $prop[1] == 'none' || $prop[1] == 'hidden') { + $prop[2] = ''; + } // solid #000000 + elseif (in_array($prop[0], $this->borderstyles) || $prop[0] == 'none' || $prop[0] == 'hidden') { + $prop[0] = ''; + $prop[1] = $prop[0]; + $prop[2] = $prop[1]; + } // 1px #000000 + else { + $prop[1] = ''; + $prop[2] = $prop[1]; + } + } elseif (count($prop) == 3) { + // Change #000000 1px solid to 1px solid #000000 (proper) + if (substr($prop[0], 0, 1) == '#') { + $tmp = $prop[0]; + $prop[0] = $prop[1]; + $prop[1] = $prop[2]; + $prop[2] = $tmp; + } // Change solid #000000 1px to 1px solid #000000 (proper) + elseif (substr($prop[0], 1, 1) == '#') { + $tmp = $prop[1]; + $prop[0] = $prop[2]; + $prop[1] = $prop[0]; + $prop[2] = $tmp; + } // Change solid 1px #000000 to 1px solid #000000 (proper) + elseif (in_array($prop[0], $this->borderstyles) || $prop[0] == 'none' || $prop[0] == 'hidden') { + $tmp = $prop[0]; + $prop[0] = $prop[1]; + $prop[1] = $tmp; + } + } else { + return []; + } + // Size + $bsize = $this->sizeConverter->convert($prop[0], $refw, $this->FontSize, false); + // color + $coul = $this->colorConverter->convert($prop[2], $this->PDFAXwarnings); // returns array + // Style + $prop[1] = strtolower($prop[1]); + if (in_array($prop[1], $this->borderstyles) && $bsize > 0) { + $on = 1; + } elseif ($prop[1] == 'hidden') { + $on = 1; + $bsize = 0; + $coul = ''; + } elseif ($prop[1] == 'none') { + $on = 0; + $bsize = 0; + $coul = ''; + } else { + $on = 0; + $bsize = 0; + $coul = ''; + $prop[1] = ''; + } + return ['s' => $on, 'w' => $bsize, 'c' => $coul, 'style' => $prop[1], 'dom' => 0]; + } + + /* -- END HTML-CSS -- */ + + + /* -- BORDER-RADIUS -- */ + + function _borderPadding($a, $b, &$px, &$py) + { + // $px and py are padding long axis (x) and short axis (y) + $added = 0; // extra padding + + $x = $a - $px; + $y = $b - $py; + // Check if Falls within ellipse of border radius + if (( (($x + $added) * ($x + $added)) / ($a * $a) + (($y + $added) * ($y + $added)) / ($b * $b) ) <= 1) { + return false; + } + + $t = atan2($y, $x); + + $newx = $b / sqrt((($b * $b) / ($a * $a)) + ( tan($t) * tan($t) )); + $newy = $a / sqrt((($a * $a) / ($b * $b)) + ( (1 / tan($t)) * (1 / tan($t)) )); + $px = max($px, $a - $newx + $added); + $py = max($py, $b - $newy + $added); + } + + /* -- END BORDER-RADIUS -- */ + /* -- HTML-CSS -- */ + /* -- CSS-PAGE -- */ + + function SetPagedMediaCSS($name, $first, $oddEven) + { + if ($oddEven == 'E') { + if ($this->directionality == 'rtl') { + $side = 'R'; + } else { + $side = 'L'; + } + } else { + if ($this->directionality == 'rtl') { + $side = 'L'; + } else { + $side = 'R'; + } + } + $name = strtoupper($name); + $p = []; + $p['SIZE'] = 'AUTO'; + + // Uses mPDF original margins as default + $p['MARGIN-RIGHT'] = strval($this->orig_rMargin) . 'mm'; + $p['MARGIN-LEFT'] = strval($this->orig_lMargin) . 'mm'; + $p['MARGIN-TOP'] = strval($this->orig_tMargin) . 'mm'; + $p['MARGIN-BOTTOM'] = strval($this->orig_bMargin) . 'mm'; + $p['MARGIN-HEADER'] = strval($this->orig_hMargin) . 'mm'; + $p['MARGIN-FOOTER'] = strval($this->orig_fMargin) . 'mm'; + + // Basic page + selector + if (isset($this->cssManager->CSS['@PAGE'])) { + $zp = $this->cssManager->CSS['@PAGE']; + } else { + $zp = []; + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + + if (isset($p['EVEN-HEADER-NAME']) && $oddEven == 'E') { + $p['HEADER'] = $p['EVEN-HEADER-NAME']; + unset($p['EVEN-HEADER-NAME']); + } + if (isset($p['ODD-HEADER-NAME']) && $oddEven != 'E') { + $p['HEADER'] = $p['ODD-HEADER-NAME']; + unset($p['ODD-HEADER-NAME']); + } + if (isset($p['EVEN-FOOTER-NAME']) && $oddEven == 'E') { + $p['FOOTER'] = $p['EVEN-FOOTER-NAME']; + unset($p['EVEN-FOOTER-NAME']); + } + if (isset($p['ODD-FOOTER-NAME']) && $oddEven != 'E') { + $p['FOOTER'] = $p['ODD-FOOTER-NAME']; + unset($p['ODD-FOOTER-NAME']); + } + + // If right/Odd page + if (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>RIGHT']) && $side == 'R') { + $zp = $this->cssManager->CSS['@PAGE>>PSEUDO>>RIGHT']; + } else { + $zp = []; + } + if (isset($zp['SIZE'])) { + unset($zp['SIZE']); + } + if (isset($zp['SHEET-SIZE'])) { + unset($zp['SHEET-SIZE']); + } + // Disallow margin-left or -right on :LEFT or :RIGHT + if (isset($zp['MARGIN-LEFT'])) { + unset($zp['MARGIN-LEFT']); + } + if (isset($zp['MARGIN-RIGHT'])) { + unset($zp['MARGIN-RIGHT']); + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + + // If left/Even page + if (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>LEFT']) && $side == 'L') { + $zp = $this->cssManager->CSS['@PAGE>>PSEUDO>>LEFT']; + } else { + $zp = []; + } + if (isset($zp['SIZE'])) { + unset($zp['SIZE']); + } + if (isset($zp['SHEET-SIZE'])) { + unset($zp['SHEET-SIZE']); + } + // Disallow margin-left or -right on :LEFT or :RIGHT + if (isset($zp['MARGIN-LEFT'])) { + unset($zp['MARGIN-LEFT']); + } + if (isset($zp['MARGIN-RIGHT'])) { + unset($zp['MARGIN-RIGHT']); + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + + // If first page + if (isset($this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST']) && $first) { + $zp = $this->cssManager->CSS['@PAGE>>PSEUDO>>FIRST']; + } else { + $zp = []; + } + if (isset($zp['SIZE'])) { + unset($zp['SIZE']); + } + if (isset($zp['SHEET-SIZE'])) { + unset($zp['SHEET-SIZE']); + } + // Disallow margin-left or -right on :FIRST // mPDF 5.7.3 + if (isset($zp['MARGIN-LEFT'])) { + unset($zp['MARGIN-LEFT']); + } + if (isset($zp['MARGIN-RIGHT'])) { + unset($zp['MARGIN-RIGHT']); + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + + // If named page + if ($name) { + if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name])) { + $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name]; + } else { + $zp = []; + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + + if (isset($p['EVEN-HEADER-NAME']) && $oddEven == 'E') { + $p['HEADER'] = $p['EVEN-HEADER-NAME']; + unset($p['EVEN-HEADER-NAME']); + } + if (isset($p['ODD-HEADER-NAME']) && $oddEven != 'E') { + $p['HEADER'] = $p['ODD-HEADER-NAME']; + unset($p['ODD-HEADER-NAME']); + } + if (isset($p['EVEN-FOOTER-NAME']) && $oddEven == 'E') { + $p['FOOTER'] = $p['EVEN-FOOTER-NAME']; + unset($p['EVEN-FOOTER-NAME']); + } + if (isset($p['ODD-FOOTER-NAME']) && $oddEven != 'E') { + $p['FOOTER'] = $p['ODD-FOOTER-NAME']; + unset($p['ODD-FOOTER-NAME']); + } + + // If named right/Odd page + if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>RIGHT']) && $side == 'R') { + $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>RIGHT']; + } else { + $zp = []; + } + if (isset($zp['SIZE'])) { + unset($zp['SIZE']); + } + if (isset($zp['SHEET-SIZE'])) { + unset($zp['SHEET-SIZE']); + } + // Disallow margin-left or -right on :LEFT or :RIGHT + if (isset($zp['MARGIN-LEFT'])) { + unset($zp['MARGIN-LEFT']); + } + if (isset($zp['MARGIN-RIGHT'])) { + unset($zp['MARGIN-RIGHT']); + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + + // If named left/Even page + if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>LEFT']) && $side == 'L') { + $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>LEFT']; + } else { + $zp = []; + } + if (isset($zp['SIZE'])) { + unset($zp['SIZE']); + } + if (isset($zp['SHEET-SIZE'])) { + unset($zp['SHEET-SIZE']); + } + // Disallow margin-left or -right on :LEFT or :RIGHT + if (isset($zp['MARGIN-LEFT'])) { + unset($zp['MARGIN-LEFT']); + } + if (isset($zp['MARGIN-RIGHT'])) { + unset($zp['MARGIN-RIGHT']); + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + + // If named first page + if (isset($this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>FIRST']) && $first) { + $zp = $this->cssManager->CSS['@PAGE>>NAMED>>' . $name . '>>PSEUDO>>FIRST']; + } else { + $zp = []; + } + if (isset($zp['SIZE'])) { + unset($zp['SIZE']); + } + if (isset($zp['SHEET-SIZE'])) { + unset($zp['SHEET-SIZE']); + } + // Disallow margin-left or -right on :FIRST // mPDF 5.7.3 + if (isset($zp['MARGIN-LEFT'])) { + unset($zp['MARGIN-LEFT']); + } + if (isset($zp['MARGIN-RIGHT'])) { + unset($zp['MARGIN-RIGHT']); + } + if (is_array($zp) && !empty($zp)) { + $p = array_merge($p, $zp); + } + } + + $orientation = $mgl = $mgr = $mgt = $mgb = $mgh = $mgf = ''; + $header = $footer = ''; + $resetpagenum = $pagenumstyle = $suppress = ''; + $marks = ''; + $bg = []; + + $newformat = ''; + + + if (isset($p['SHEET-SIZE']) && is_array($p['SHEET-SIZE'])) { + $newformat = $p['SHEET-SIZE']; + if ($newformat[0] > $newformat[1]) { // landscape + $newformat = array_reverse($newformat); + $p['ORIENTATION'] = 'L'; + } else { + $p['ORIENTATION'] = 'P'; + } + $this->_setPageSize($newformat, $p['ORIENTATION']); + } + + if (isset($p['SIZE']) && is_array($p['SIZE']) && !$newformat) { + if ($p['SIZE']['W'] > $p['SIZE']['H']) { + $p['ORIENTATION'] = 'L'; + } else { + $p['ORIENTATION'] = 'P'; + } + } + if (is_array($p['SIZE'])) { + if ($p['SIZE']['W'] > $this->fw) { + $p['SIZE']['W'] = $this->fw; + } // mPD 4.2 use fw not fPt + if ($p['SIZE']['H'] > $this->fh) { + $p['SIZE']['H'] = $this->fh; + } + if (($p['ORIENTATION'] == $this->DefOrientation && !$newformat) || ($newformat && $p['ORIENTATION'] == 'P')) { + $outer_width_LR = ($this->fw - $p['SIZE']['W']) / 2; + $outer_width_TB = ($this->fh - $p['SIZE']['H']) / 2; + } else { + $outer_width_LR = ($this->fh - $p['SIZE']['W']) / 2; + $outer_width_TB = ($this->fw - $p['SIZE']['H']) / 2; + } + $pgw = $p['SIZE']['W']; + $pgh = $p['SIZE']['H']; + } else { // AUTO LANDSCAPE PORTRAIT + $outer_width_LR = 0; + $outer_width_TB = 0; + if (!$newformat) { + if (strtoupper($p['SIZE']) == 'AUTO') { + $p['ORIENTATION'] = $this->DefOrientation; + } elseif (strtoupper($p['SIZE']) == 'LANDSCAPE') { + $p['ORIENTATION'] = 'L'; + } else { + $p['ORIENTATION'] = 'P'; + } + } + if (($p['ORIENTATION'] == $this->DefOrientation && !$newformat) || ($newformat && $p['ORIENTATION'] == 'P')) { + $pgw = $this->fw; + $pgh = $this->fh; + } else { + $pgw = $this->fh; + $pgh = $this->fw; + } + } + + if (isset($p['HEADER']) && $p['HEADER']) { + $header = $p['HEADER']; + } + if (isset($p['FOOTER']) && $p['FOOTER']) { + $footer = $p['FOOTER']; + } + if (isset($p['RESETPAGENUM']) && $p['RESETPAGENUM']) { + $resetpagenum = $p['RESETPAGENUM']; + } + if (isset($p['PAGENUMSTYLE']) && $p['PAGENUMSTYLE']) { + $pagenumstyle = $p['PAGENUMSTYLE']; + } + if (isset($p['SUPPRESS']) && $p['SUPPRESS']) { + $suppress = $p['SUPPRESS']; + } + + if (isset($p['MARKS'])) { + if (preg_match('/cross/i', $p['MARKS']) && preg_match('/crop/i', $p['MARKS'])) { + $marks = 'CROPCROSS'; + } elseif (strtoupper($p['MARKS']) == 'CROP') { + $marks = 'CROP'; + } elseif (strtoupper($p['MARKS']) == 'CROSS') { + $marks = 'CROSS'; + } + } + + if (isset($p['BACKGROUND-COLOR']) && $p['BACKGROUND-COLOR']) { + $bg['BACKGROUND-COLOR'] = $p['BACKGROUND-COLOR']; + } + /* -- BACKGROUNDS -- */ + if (isset($p['BACKGROUND-GRADIENT']) && $p['BACKGROUND-GRADIENT']) { + $bg['BACKGROUND-GRADIENT'] = $p['BACKGROUND-GRADIENT']; + } + if (isset($p['BACKGROUND-IMAGE']) && $p['BACKGROUND-IMAGE']) { + $bg['BACKGROUND-IMAGE'] = $p['BACKGROUND-IMAGE']; + } + if (isset($p['BACKGROUND-REPEAT']) && $p['BACKGROUND-REPEAT']) { + $bg['BACKGROUND-REPEAT'] = $p['BACKGROUND-REPEAT']; + } + if (isset($p['BACKGROUND-POSITION']) && $p['BACKGROUND-POSITION']) { + $bg['BACKGROUND-POSITION'] = $p['BACKGROUND-POSITION']; + } + if (isset($p['BACKGROUND-IMAGE-RESIZE']) && $p['BACKGROUND-IMAGE-RESIZE']) { + $bg['BACKGROUND-IMAGE-RESIZE'] = $p['BACKGROUND-IMAGE-RESIZE']; + } + if (isset($p['BACKGROUND-IMAGE-OPACITY'])) { + $bg['BACKGROUND-IMAGE-OPACITY'] = $p['BACKGROUND-IMAGE-OPACITY']; + } + /* -- END BACKGROUNDS -- */ + + if (isset($p['MARGIN-LEFT'])) { + $mgl = $this->sizeConverter->convert($p['MARGIN-LEFT'], $pgw) + $outer_width_LR; + } + if (isset($p['MARGIN-RIGHT'])) { + $mgr = $this->sizeConverter->convert($p['MARGIN-RIGHT'], $pgw) + $outer_width_LR; + } + if (isset($p['MARGIN-BOTTOM'])) { + $mgb = $this->sizeConverter->convert($p['MARGIN-BOTTOM'], $pgh) + $outer_width_TB; + } + if (isset($p['MARGIN-TOP'])) { + $mgt = $this->sizeConverter->convert($p['MARGIN-TOP'], $pgh) + $outer_width_TB; + } + if (isset($p['MARGIN-HEADER'])) { + $mgh = $this->sizeConverter->convert($p['MARGIN-HEADER'], $pgh) + $outer_width_TB; + } + if (isset($p['MARGIN-FOOTER'])) { + $mgf = $this->sizeConverter->convert($p['MARGIN-FOOTER'], $pgh) + $outer_width_TB; + } + + if (isset($p['ORIENTATION']) && $p['ORIENTATION']) { + $orientation = $p['ORIENTATION']; + } + $this->page_box['outer_width_LR'] = $outer_width_LR; // Used in MARKS:crop etc. + $this->page_box['outer_width_TB'] = $outer_width_TB; + + return [$orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $header, $footer, $bg, $resetpagenum, $pagenumstyle, $suppress, $marks, $newformat]; + } + + /* -- END CSS-PAGE -- */ + + + + /* -- CSS-FLOAT -- */ + + // Added mPDF 3.0 Float DIV - CLEAR + function ClearFloats($clear, $blklvl = 0) + { + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($blklvl, true); + $end = $currpos = ($this->page * 1000 + $this->y); + if ($clear == 'BOTH' && ($l_exists || $r_exists)) { + $this->pageoutput[$this->page] = []; + $end = max($l_max, $r_max, $currpos); + } elseif ($clear == 'RIGHT' && $r_exists) { + $this->pageoutput[$this->page] = []; + $end = max($r_max, $currpos); + } elseif ($clear == 'LEFT' && $l_exists) { + $this->pageoutput[$this->page] = []; + $end = max($l_max, $currpos); + } else { + return; + } + $old_page = $this->page; + $new_page = intval($end / 1000); + if ($old_page != $new_page) { + $s = $this->PrintPageBackgrounds(); + // Writes after the marker so not overwritten later by page background etc. + $this->pages[$this->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]); + $this->pageBackgrounds = []; + $this->page = $new_page; + } + $this->ResetMargins(); + $this->pageoutput[$this->page] = []; + $this->y = (($end * 1000) % 1000000) / 1000; // mod changes operands to integers before processing + } + + // Added mPDF 3.0 Float DIV + function GetFloatDivInfo($blklvl = 0, $clear = false) + { + // If blklvl specified, only returns floats at that level - for ClearFloats + $l_exists = false; + $r_exists = false; + $l_max = 0; + $r_max = 0; + $l_width = 0; + $r_width = 0; + if (count($this->floatDivs)) { + $currpos = ($this->page * 1000 + $this->y); + foreach ($this->floatDivs as $f) { + if (($clear && $f['blockContext'] == $this->blk[$blklvl]['blockContext']) || (!$clear && $currpos >= $f['startpos'] && $currpos < ($f['endpos'] - 0.001) && $f['blklvl'] > $blklvl && $f['blockContext'] == $this->blk[$blklvl]['blockContext'])) { + if ($f['side'] == 'L') { + $l_exists = true; + $l_max = max($l_max, $f['endpos']); + $l_width = max($l_width, $f['w']); + } + if ($f['side'] == 'R') { + $r_exists = true; + $r_max = max($r_max, $f['endpos']); + $r_width = max($r_width, $f['w']); + } + } + } + } + return [$l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width]; + } + + /* -- END CSS-FLOAT -- */ + + // LIST MARKERS // mPDF 6 Lists + function _setListMarker($listitemtype, $listitemimage, $listitemposition) + { + // if position:inside (and NOT table) - output now as a textbuffer; (so if next is block, will move to new line) + // elseif position:outside (and NOT table) - output in front of first textbuffer output by setting listitem (cf. _saveTextBuffer) + $e = ''; + $this->listitem = ''; + $spacer = ' '; + // IMAGE + if ($listitemimage && $listitemimage != 'none') { + $listitemimage = trim(preg_replace('/url\(["\']*(.*?)["\']*\)/', '\\1', $listitemimage)); + + // ? Restrict maximum height/width of list marker?? + $maxWidth = 100; + $maxHeight = 100; + + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['padding_top'] = 0; + $objattr['padding_bottom'] = 0; + $objattr['padding_left'] = 0; + $objattr['padding_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $objattr['visibility'] = 'visible'; + $srcpath = $listitemimage; + $orig_srcpath = $listitemimage; + + $objattr['vertical-align'] = 'BS'; // vertical alignment of marker (baseline) + $w = 0; + $h = 0; + + // Image file + $info = $this->imageProcessor->getImage($srcpath, true, true, $orig_srcpath); + if (!$info) { + return; + } + + if ($info['w'] == 0 && $info['h'] == 0) { + $info['h'] = $this->sizeConverter->convert('1em', $this->blk[$this->blklvl]['inner_width'], $this->FontSize, false); + } + + $objattr['file'] = $srcpath; + + // Default width and height calculation if needed + if ($w == 0 and $h == 0) { + /* -- IMAGES-WMF -- */ + if ($info['type'] == 'wmf') { + // WMF units are twips (1/20pt) + // divide by 20 to get points + // divide by k to get user units + $w = abs($info['w']) / (20 * Mpdf::SCALE); + $h = abs($info['h']) / (20 * Mpdf::SCALE); + } else { /* -- END IMAGES-WMF -- */ + if ($info['type'] == 'svg') { + // SVG units are pixels + $w = abs($info['w']) / Mpdf::SCALE; + $h = abs($info['h']) / Mpdf::SCALE; + } else { + // Put image at default image dpi + $w = ($info['w'] / Mpdf::SCALE) * (72 / $this->img_dpi); + $h = ($info['h'] / Mpdf::SCALE) * (72 / $this->img_dpi); + } + } + } + // IF WIDTH OR HEIGHT SPECIFIED + if ($w == 0) { + $w = abs($h * $info['w'] / $info['h']); + } + if ($h == 0) { + $h = abs($w * $info['h'] / $info['w']); + } + + if ($w > $maxWidth) { + $w = $maxWidth; + $h = abs($w * $info['h'] / $info['w']); + } + + if ($h > $maxHeight) { + $h = $maxHeight; + $w = abs($h * $info['w'] / $info['h']); + } + + $objattr['type'] = 'image'; + $objattr['itype'] = $info['type']; + + $objattr['orig_h'] = $info['h']; + $objattr['orig_w'] = $info['w']; + + /* -- IMAGES-WMF -- */ + if ($info['type'] == 'wmf') { + $objattr['wmf_x'] = $info['x']; + $objattr['wmf_y'] = $info['y']; + } else { /* -- END IMAGES-WMF -- */ + if ($info['type'] == 'svg') { + $objattr['wmf_x'] = $info['x']; + $objattr['wmf_y'] = $info['y']; + } + } + + $objattr['height'] = $h; + $objattr['width'] = $w; + $objattr['image_height'] = $h; + $objattr['image_width'] = $w; + + $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr'); + $objattr['listmarker'] = true; + + $objattr['listmarkerposition'] = $listitemposition; + + $e = "\xbb\xa4\xactype=image,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + $this->_saveTextBuffer($e); + + if ($listitemposition == 'inside') { + $e = $spacer; + $this->_saveTextBuffer($e); + } + } elseif ($listitemtype == 'disc' || $listitemtype == 'circle' || $listitemtype == 'square') { // SYMBOL (needs new font) + $objattr = []; + $objattr['type'] = 'listmarker'; + $objattr['listmarkerposition'] = $listitemposition; + $objattr['width'] = 0; + $size = $this->sizeConverter->convert($this->list_symbol_size, $this->FontSize); + $objattr['size'] = $size; + $objattr['offset'] = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize); + + if ($listitemposition == 'inside') { + $objattr['width'] = $size + $objattr['offset']; + } + + $objattr['height'] = $this->FontSize; + $objattr['vertical-align'] = 'T'; + $objattr['text'] = ''; + $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr'); + $objattr['bullet'] = $listitemtype; + $objattr['colorarray'] = $this->colorarray; + $objattr['fontfamily'] = $this->FontFamily; + $objattr['fontsize'] = $this->FontSize; + $objattr['fontsizept'] = $this->FontSizePt; + $objattr['fontstyle'] = $this->FontStyle; + + $e = "\xbb\xa4\xactype=listmarker,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + $this->listitem = $this->_saveTextBuffer($e, '', '', true); // true returns array + + } elseif (preg_match('/U\+([a-fA-F0-9]+)/i', $listitemtype, $m)) { // SYMBOL 2 (needs new font) + + if ($this->_charDefined($this->CurrentFont['cw'], hexdec($m[1]))) { + $list_item_marker = UtfString::codeHex2utf($m[1]); + } else { + $list_item_marker = '-'; + } + if (preg_match('/rgb\(.*?\)/', $listitemtype, $m)) { + $list_item_color = $this->colorConverter->convert($m[0], $this->PDFAXwarnings); + } else { + $list_item_color = ''; + } + + // SAVE then SET COLR + $save_colorarray = $this->colorarray; + if ($list_item_color) { + $this->colorarray = $list_item_color; + } + + if ($listitemposition == 'inside') { + $e = $list_item_marker . $spacer; + $this->_saveTextBuffer($e); + } else { + $objattr = []; + $objattr['type'] = 'listmarker'; + $objattr['width'] = 0; + $objattr['height'] = $this->FontSize; + $objattr['vertical-align'] = 'T'; + $objattr['text'] = $list_item_marker; + $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr'); + $objattr['colorarray'] = $this->colorarray; + $objattr['fontfamily'] = $this->FontFamily; + $objattr['fontsize'] = $this->FontSize; + $objattr['fontsizept'] = $this->FontSizePt; + $objattr['fontstyle'] = $this->FontStyle; + $e = "\xbb\xa4\xactype=listmarker,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + $this->listitem = $this->_saveTextBuffer($e, '', '', true); // true returns array + } + + // RESET COLOR + $this->colorarray = $save_colorarray; + + } else { // TEXT + $counter = $this->listcounter[$this->listlvl]; + + if ($listitemtype == 'none') { + return; + } + + $num = $this->_getStyledNumber($counter, $listitemtype, true); + + if ($listitemposition == 'inside') { + $e = $num . $this->list_number_suffix . $spacer; + $this->_saveTextBuffer($e); + } else { + if (isset($this->blk[$this->blklvl]['direction']) && $this->blk[$this->blklvl]['direction'] == 'rtl') { + // REPLACE MIRRORED RTL $this->list_number_suffix e.g. ) -> ( (NB could use Ucdn::$mirror_pairs) + $m = strtr($this->list_number_suffix, ")]}", "([{") . $num; + } else { + $m = $num . $this->list_number_suffix; + } + + $objattr = []; + $objattr['type'] = 'listmarker'; + $objattr['width'] = 0; + $objattr['height'] = $this->FontSize; + $objattr['vertical-align'] = 'T'; + $objattr['text'] = $m; + $objattr['dir'] = (isset($this->blk[$this->blklvl]['direction']) ? $this->blk[$this->blklvl]['direction'] : 'ltr'); + $objattr['colorarray'] = $this->colorarray; + $objattr['fontfamily'] = $this->FontFamily; + $objattr['fontsize'] = $this->FontSize; + $objattr['fontsizept'] = $this->FontSizePt; + $objattr['fontstyle'] = $this->FontStyle; + $e = "\xbb\xa4\xactype=listmarker,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + + $this->listitem = $this->_saveTextBuffer($e, '', '', true); // true returns array + } + } + } + + // mPDF Lists + function _getListMarkerWidth(&$currblk, &$a, &$i) + { + $blt_width = 0; + + $markeroffset = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize); + + // Get Maximum number in the list + $maxnum = $this->listcounter[$this->listlvl]; + if ($currblk['list_style_type'] != 'disc' && $currblk['list_style_type'] != 'circle' && $currblk['list_style_type'] != 'square') { + $lvl = 1; + for ($j = $i + 2; $j < count($a); $j+=2) { + $e = $a[$j]; + if (!$e) { + continue; + } + if ($e[0] == '/') { // end tag + $e = strtoupper(substr($e, 1)); + if ($e == 'OL' || $e == 'UL') { + if ($lvl == 1) { + break; + } + $lvl--; + } + } else { // opening tag + if (strpos($e, ' ')) { + $e = substr($e, 0, strpos($e, ' ')); + } + $e = strtoupper($e); + if ($e == 'LI') { + if ($lvl == 1) { + $maxnum++; + } + } elseif ($e == 'OL' || $e == 'UL') { + $lvl++; + } + } + } + } + + $decToAlpha = new Conversion\DecToAlpha(); + $decToRoman = new Conversion\DecToRoman(); + $decToOther = new Conversion\DecToOther($this); + + switch ($currblk['list_style_type']) { + case 'decimal': + case '1': + $blt_width = $this->GetStringWidth(str_repeat('5', strlen($maxnum)) . $this->list_number_suffix); + break; + case 'none': + $blt_width = 0; + break; + case 'upper-alpha': + case 'upper-latin': + case 'A': + $maxnumA = $decToAlpha->convert($maxnum, true); + if ($maxnum < 13) { + $blt_width = $this->GetStringWidth('D' . $this->list_number_suffix); + } else { + $blt_width = $this->GetStringWidth(str_repeat('W', strlen($maxnumA)) . $this->list_number_suffix); + } + break; + case 'lower-alpha': + case 'lower-latin': + case 'a': + $maxnuma = $decToAlpha->convert($maxnum, false); + if ($maxnum < 13) { + $blt_width = $this->GetStringWidth('b' . $this->list_number_suffix); + } else { + $blt_width = $this->GetStringWidth(str_repeat('m', strlen($maxnuma)) . $this->list_number_suffix); + } + break; + case 'upper-roman': + case 'I': + if ($maxnum > 87) { + $bbit = 87; + } elseif ($maxnum > 86) { + $bbit = 86; + } elseif ($maxnum > 37) { + $bbit = 38; + } elseif ($maxnum > 36) { + $bbit = 37; + } elseif ($maxnum > 27) { + $bbit = 28; + } elseif ($maxnum > 26) { + $bbit = 27; + } elseif ($maxnum > 17) { + $bbit = 18; + } elseif ($maxnum > 16) { + $bbit = 17; + } elseif ($maxnum > 7) { + $bbit = 8; + } elseif ($maxnum > 6) { + $bbit = 7; + } elseif ($maxnum > 3) { + $bbit = 4; + } else { + $bbit = $maxnum; + } + + $maxlnum = $decToRoman->convert($bbit, true); + $blt_width = $this->GetStringWidth($maxlnum . $this->list_number_suffix); + + break; + case 'lower-roman': + case 'i': + if ($maxnum > 87) { + $bbit = 87; + } elseif ($maxnum > 86) { + $bbit = 86; + } elseif ($maxnum > 37) { + $bbit = 38; + } elseif ($maxnum > 36) { + $bbit = 37; + } elseif ($maxnum > 27) { + $bbit = 28; + } elseif ($maxnum > 26) { + $bbit = 27; + } elseif ($maxnum > 17) { + $bbit = 18; + } elseif ($maxnum > 16) { + $bbit = 17; + } elseif ($maxnum > 7) { + $bbit = 8; + } elseif ($maxnum > 6) { + $bbit = 7; + } elseif ($maxnum > 3) { + $bbit = 4; + } else { + $bbit = $maxnum; + } + $maxlnum = $decToRoman->convert($bbit, false); + $blt_width = $this->GetStringWidth($maxlnum . $this->list_number_suffix); + break; + + case 'disc': + case 'circle': + case 'square': + $size = $this->sizeConverter->convert($this->list_symbol_size, $this->FontSize); + $offset = $this->sizeConverter->convert($this->list_marker_offset, $this->FontSize); + $blt_width = $size + $offset; + break; + + case 'arabic-indic': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0660), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'persian': + case 'urdu': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x06F0), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'bengali': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x09E6), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'devanagari': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0966), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'gujarati': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0AE6), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'gurmukhi': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0A66), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'kannada': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0CE6), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'malayalam': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(6, 0x0D66), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'oriya': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0B66), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'telugu': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(3, 0x0C66), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'tamil': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(9, 0x0BE6), strlen($maxnum)) . $this->list_number_suffix); + break; + case 'thai': + $blt_width = $this->GetStringWidth(str_repeat($decToOther->convert(5, 0x0E50), strlen($maxnum)) . $this->list_number_suffix); + break; + default: + $blt_width = $this->GetStringWidth(str_repeat('5', strlen($maxnum)) . $this->list_number_suffix); + break; + } + + return ($blt_width + $markeroffset); + } + + function _saveTextBuffer($t, $link = '', $intlink = '', $return = false) + { + // mPDF 6 Lists + $arr = []; + $arr[0] = $t; + if (isset($link) && $link) { + $arr[1] = $link; + } + $arr[2] = $this->currentfontstyle; + if (isset($this->colorarray) && $this->colorarray) { + $arr[3] = $this->colorarray; + } + $arr[4] = $this->currentfontfamily; + $arr[5] = $this->currentLang; // mPDF 6 + if (isset($intlink) && $intlink) { + $arr[7] = $intlink; + } + // mPDF 6 + // If Kerning set for OTL, and useOTL has positive value, but has not set for this particular script, + // set for kerning via kern table + // e.g. Latin script when useOTL set as 0x80 + if (isset($this->OTLtags['Plus']) && strpos($this->OTLtags['Plus'], 'kern') !== false && empty($this->OTLdata['GPOSinfo'])) { + $this->textvar = ($this->textvar | TextVars::FC_KERNING); + } + $arr[8] = $this->textvar; // mPDF 5.7.1 + if (isset($this->textparam) && $this->textparam) { + $arr[9] = $this->textparam; + } + if (isset($this->spanbgcolorarray) && $this->spanbgcolorarray) { + $arr[10] = $this->spanbgcolorarray; + } + $arr[11] = $this->currentfontsize; + if (isset($this->ReqFontStyle) && $this->ReqFontStyle) { + $arr[12] = $this->ReqFontStyle; + } + if (isset($this->lSpacingCSS) && $this->lSpacingCSS) { + $arr[14] = $this->lSpacingCSS; + } + if (isset($this->wSpacingCSS) && $this->wSpacingCSS) { + $arr[15] = $this->wSpacingCSS; + } + if (isset($this->spanborddet) && $this->spanborddet) { + $arr[16] = $this->spanborddet; + } + if (isset($this->textshadow) && $this->textshadow) { + $arr[17] = $this->textshadow; + } + if (isset($this->OTLdata) && $this->OTLdata) { + $arr[18] = $this->OTLdata; + $this->OTLdata = []; + } // mPDF 5.7.1 + else { + $arr[18] = null; + } + // mPDF 6 Lists + if ($return) { + return ($arr); + } + if ($this->listitem) { + $this->textbuffer[] = $this->listitem; + $this->listitem = []; + } + $this->textbuffer[] = $arr; + } + + function _saveCellTextBuffer($t, $link = '', $intlink = '') + { + $arr = []; + $arr[0] = $t; + if (isset($link) && $link) { + $arr[1] = $link; + } + $arr[2] = $this->currentfontstyle; + if (isset($this->colorarray) && $this->colorarray) { + $arr[3] = $this->colorarray; + } + $arr[4] = $this->currentfontfamily; + if (isset($intlink) && $intlink) { + $arr[7] = $intlink; + } + // mPDF 6 + // If Kerning set for OTL, and useOTL has positive value, but has not set for this particular script, + // set for kerning via kern table + // e.g. Latin script when useOTL set as 0x80 + if (isset($this->OTLtags['Plus']) && strpos($this->OTLtags['Plus'], 'kern') !== false && empty($this->OTLdata['GPOSinfo'])) { + $this->textvar = ($this->textvar | TextVars::FC_KERNING); + } + $arr[8] = $this->textvar; // mPDF 5.7.1 + if (isset($this->textparam) && $this->textparam) { + $arr[9] = $this->textparam; + } + if (isset($this->spanbgcolorarray) && $this->spanbgcolorarray) { + $arr[10] = $this->spanbgcolorarray; + } + $arr[11] = $this->currentfontsize; + if (isset($this->ReqFontStyle) && $this->ReqFontStyle) { + $arr[12] = $this->ReqFontStyle; + } + if (isset($this->lSpacingCSS) && $this->lSpacingCSS) { + $arr[14] = $this->lSpacingCSS; + } + if (isset($this->wSpacingCSS) && $this->wSpacingCSS) { + $arr[15] = $this->wSpacingCSS; + } + if (isset($this->spanborddet) && $this->spanborddet) { + $arr[16] = $this->spanborddet; + } + if (isset($this->textshadow) && $this->textshadow) { + $arr[17] = $this->textshadow; + } + if (isset($this->OTLdata) && $this->OTLdata) { + $arr[18] = $this->OTLdata; + $this->OTLdata = []; + } // mPDF 5.7.1 + else { + $arr[18] = null; + } + $this->cell[$this->row][$this->col]['textbuffer'][] = $arr; + } + + function printbuffer($arrayaux, $blockstate = 0, $is_table = false, $table_draft = false, $cell_dir = '') + { + // $blockstate = 0; // NO margins/padding + // $blockstate = 1; // Top margins/padding only + // $blockstate = 2; // Bottom margins/padding only + // $blockstate = 3; // Top & bottom margins/padding + $this->spanbgcolorarray = ''; + $this->spanbgcolor = false; + $this->spanborder = false; + $this->spanborddet = []; + $paint_ht_corr = 0; + /* -- CSS-FLOAT -- */ + if (count($this->floatDivs)) { + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->GetFloatDivInfo($this->blklvl); + if (($this->blk[$this->blklvl]['inner_width'] - $l_width - $r_width) < (2 * $this->GetCharWidth('W', false))) { + // Too narrow to fit - try to move down past L or R float + if ($l_max < $r_max && ($this->blk[$this->blklvl]['inner_width'] - $r_width) > (2 * $this->GetCharWidth('W', false))) { + $this->ClearFloats('LEFT', $this->blklvl); + } elseif ($r_max < $l_max && ($this->blk[$this->blklvl]['inner_width'] - $l_width) > (2 * $this->GetCharWidth('W', false))) { + $this->ClearFloats('RIGHT', $this->blklvl); + } else { + $this->ClearFloats('BOTH', $this->blklvl); + } + } + } + /* -- END CSS-FLOAT -- */ + $bak_y = $this->y; + $bak_x = $this->x; + $align = ''; + if (!$is_table) { + if (isset($this->blk[$this->blklvl]['align']) && $this->blk[$this->blklvl]['align']) { + $align = $this->blk[$this->blklvl]['align']; + } + // Block-align is set by e.g. <.. align="center"> Takes priority for this block but not inherited + if (isset($this->blk[$this->blklvl]['block-align']) && $this->blk[$this->blklvl]['block-align']) { + $align = $this->blk[$this->blklvl]['block-align']; + } + if (isset($this->blk[$this->blklvl]['direction'])) { + $blockdir = $this->blk[$this->blklvl]['direction']; + } else { + $blockdir = ""; + } + $this->divwidth = $this->blk[$this->blklvl]['width']; + } else { + $align = $this->cellTextAlign; + $blockdir = $cell_dir; + } + $oldpage = $this->page; + + // ADDED for Out of Block now done as Flowing Block + if ($this->divwidth == 0) { + $this->divwidth = $this->pgwidth; + } + + if (!$is_table) { + $this->SetLineHeight($this->FontSizePt, $this->blk[$this->blklvl]['line_height']); + } + $this->divheight = $this->lineheight; + $old_height = $this->divheight; + + // As a failsafe - if font has been set but not output to page + if (!$table_draft) { + $this->SetFont($this->default_font, '', $this->default_font_size, true, true); // force output to page + } + + $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, true, $blockdir, $table_draft); + + $array_size = count($arrayaux); + + // Added - Otherwise

did not output top margins/padding for 1st/2nd div + if ($array_size == 0) { + $this->finishFlowingBlock(true); + } // true = END of flowing block + // mPDF 6 + // ALL the chunks of textbuffer need to have at least basic OTLdata set + // First make sure each element/chunk has the OTLdata for Bidi set. + for ($i = 0; $i < $array_size; $i++) { + if (empty($arrayaux[$i][18])) { + if (substr($arrayaux[$i][0], 0, 3) == "\xbb\xa4\xac") { // object identifier has been identified! + $unicode = [0xFFFC]; // Object replacement character + } else { + $unicode = $this->UTF8StringToArray($arrayaux[$i][0], false); + } + $is_strong = false; + $this->getBasicOTLdata($arrayaux[$i][18], $unicode, $is_strong); + } + // Gets messed up if try and use core fonts inside a paragraph of text which needs to be BiDi re-ordered or OTLdata set + if (($blockdir == 'rtl' || $this->biDirectional) && isset($arrayaux[$i][4]) && in_array($arrayaux[$i][4], ['ccourier', 'ctimes', 'chelvetica', 'csymbol', 'czapfdingbats'])) { + throw new \Mpdf\MpdfException("You cannot use core fonts in a document which contains RTL text."); + } + } + // mPDF 6 + // Process bidirectional text ready for bidi-re-ordering (which is done after line-breaks are established in WriteFlowingBlock etc.) + if (($blockdir == 'rtl' || $this->biDirectional) && !$table_draft) { + if (empty($this->otl)) { + $this->otl = new Otl($this, $this->fontCache); + } + $this->otl->bidiPrepare($arrayaux, $blockdir); + $array_size = count($arrayaux); + } + + + // Remove empty items // mPDF 6 + for ($i = $array_size - 1; $i > 0; $i--) { + if (empty($arrayaux[$i][0]) && (isset($arrayaux[$i][16]) && $arrayaux[$i][16] !== '0') && empty($arrayaux[$i][7])) { + unset($arrayaux[$i]); + } + } + + // Correct adjoining borders for inline elements + if (isset($arrayaux[0][16])) { + $lastspanborder = $arrayaux[0][16]; + } else { + $lastspanborder = false; + } + for ($i = 1; $i < $array_size; $i++) { + if (isset($arrayaux[$i][16]) && $arrayaux[$i][16] == $lastspanborder && + ((!isset($arrayaux[$i][9]['bord-decoration']) && !isset($arrayaux[$i - 1][9]['bord-decoration'])) || + (isset($arrayaux[$i][9]['bord-decoration']) && isset($arrayaux[$i - 1][9]['bord-decoration']) && $arrayaux[$i][9]['bord-decoration'] == $arrayaux[$i - 1][9]['bord-decoration']) + ) + ) { + if (isset($arrayaux[$i][16]['R'])) { + $lastspanborder = $arrayaux[$i][16]; + } else { + $lastspanborder = false; + } + $arrayaux[$i][16]['L']['s'] = 0; + $arrayaux[$i][16]['L']['w'] = 0; + $arrayaux[$i - 1][16]['R']['s'] = 0; + $arrayaux[$i - 1][16]['R']['w'] = 0; + } else { + if (isset($arrayaux[$i][16]['R'])) { + $lastspanborder = $arrayaux[$i][16]; + } else { + $lastspanborder = false; + } + } + } + + for ($i = 0; $i < $array_size; $i++) { + // COLS + $oldcolumn = $this->CurrCol; + $vetor = isset($arrayaux[$i]) ? $arrayaux[$i] : null; + if ($i == 0 && $vetor[0] != "\n" && ! $this->ispre) { + $vetor[0] = ltrim($vetor[0]); + if (!empty($vetor[18])) { + $this->otl->trimOTLdata($vetor[18], true, false); + } // *OTL* + } + + // FIXED TO ALLOW IT TO SHOW '0' + if (empty($vetor[0]) && !($vetor[0] === '0') && empty($vetor[7])) { // Ignore empty text and not carrying an internal link + // Check if it is the last element. If so then finish printing the block + if ($i == ($array_size - 1)) { + $this->finishFlowingBlock(true); + } // true = END of flowing block + continue; + } + + + // Activating buffer properties + if (isset($vetor[11]) && $vetor[11] != '') { // Font Size + if ($is_table && $this->shrin_k) { + $this->SetFontSize($vetor[11] / $this->shrin_k, false); + } else { + $this->SetFontSize($vetor[11], false); + } + } + + if (isset($vetor[17]) && !empty($vetor[17])) { // TextShadow + $this->textshadow = $vetor[17]; + } + if (isset($vetor[16]) && !empty($vetor[16])) { // Border + $this->spanborddet = $vetor[16]; + $this->spanborder = true; + } + + if (isset($vetor[15])) { // Word spacing + $this->wSpacingCSS = $vetor[15]; + if ($this->wSpacingCSS && strtoupper($this->wSpacingCSS) != 'NORMAL') { + $this->minwSpacing = $this->sizeConverter->convert($this->wSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3 + } + } + if (isset($vetor[14])) { // Letter spacing + $this->lSpacingCSS = $vetor[14]; + if (($this->lSpacingCSS || $this->lSpacingCSS === '0') && strtoupper($this->lSpacingCSS) != 'NORMAL') { + $this->fixedlSpacing = $this->sizeConverter->convert($this->lSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3 + } + } + + + if (isset($vetor[10]) and ! empty($vetor[10])) { // Background color + $this->spanbgcolorarray = $vetor[10]; + $this->spanbgcolor = true; + } + if (isset($vetor[9]) and ! empty($vetor[9])) { // Text parameters - Outline + hyphens + $this->textparam = $vetor[9]; + $this->SetTextOutline($this->textparam); + // mPDF 5.7.3 inline text-decoration parameters + if ($is_table && $this->shrin_k) { + if (isset($this->textparam['text-baseline'])) { + $this->textparam['text-baseline'] /= $this->shrin_k; + } + if (isset($this->textparam['decoration-baseline'])) { + $this->textparam['decoration-baseline'] /= $this->shrin_k; + } + if (isset($this->textparam['decoration-fontsize'])) { + $this->textparam['decoration-fontsize'] /= $this->shrin_k; + } + } + } + if (isset($vetor[8])) { // mPDF 5.7.1 + $this->textvar = $vetor[8]; + } + if (isset($vetor[7]) and $vetor[7] != '') { // internal target: + $ily = $this->y; + if ($this->table_rotate) { + $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page, "tbrot" => true]; + } elseif ($this->kwt) { + $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page, "kwt" => true]; + } elseif ($this->ColActive) { + $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page, "col" => $this->CurrCol]; + } elseif (!$this->keep_block_together) { + $this->internallink[$vetor[7]] = ["Y" => $ily, "PAGE" => $this->page]; + } + if (empty($vetor[0])) { // Ignore empty text + // Check if it is the last element. If so then finish printing the block + if ($i == ($array_size - 1)) { + $this->finishFlowingBlock(true); + } // true = END of flowing block + continue; + } + } + if (isset($vetor[5]) and $vetor[5] != '') { // Language // mPDF 6 + $this->currentLang = $vetor[5]; + } + if (isset($vetor[4]) and $vetor[4] != '') { // Font Family + $font = $this->SetFont($vetor[4], $this->FontStyle, 0, false); + } + if (!empty($vetor[3])) { // Font Color + $cor = $vetor[3]; + $this->SetTColor($cor); + } + if (isset($vetor[2]) and $vetor[2] != '') { // Bold,Italic styles + $this->SetStyles($vetor[2]); + } + + if (isset($vetor[12]) and $vetor[12] != '') { // Requested Bold,Italic + $this->ReqFontStyle = $vetor[12]; + } + if (isset($vetor[1]) and $vetor[1] != '') { // LINK + if (strpos($vetor[1], ".") === false && strpos($vetor[1], "@") !== 0) { // assuming every external link has a dot indicating extension (e.g: .html .txt .zip www.somewhere.com etc.) + // Repeated reference to same anchor? + while (array_key_exists($vetor[1], $this->internallink)) { + $vetor[1] = "#" . $vetor[1]; + } + $this->internallink[$vetor[1]] = $this->AddLink(); + $vetor[1] = $this->internallink[$vetor[1]]; + } + $this->HREF = $vetor[1]; // HREF link style set here ****** + } + + // SPECIAL CONTENT - IMAGES & FORM OBJECTS + // Print-out special content + + if (substr($vetor[0], 0, 3) == "\xbb\xa4\xac") { // identifier has been identified! + $objattr = $this->_getObjAttr($vetor[0]); + + /* -- TABLES -- */ + if ($objattr['type'] == 'nestedtable') { + if ($objattr['nestedcontent']) { + $level = $objattr['level']; + $table = &$this->table[$level][$objattr['table']]; + + if ($table_draft) { + $this->y += $this->table[($level + 1)][$objattr['nestedcontent']]['h']; // nested table height + $this->finishFlowingBlock(false, 'nestedtable'); + } else { + $cell = &$table['cells'][$objattr['row']][$objattr['col']]; + $this->finishFlowingBlock(false, 'nestedtable'); + $save_dw = $this->divwidth; + $save_buffer = $this->cellBorderBuffer; + $this->cellBorderBuffer = []; + $ncx = $this->x; + list($dummyx, $w) = $this->_tableGetWidth($table, $objattr['row'], $objattr['col']); + $ntw = $this->table[($level + 1)][$objattr['nestedcontent']]['w']; // nested table width + if (!$this->simpleTables) { + if ($this->packTableData) { + list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cell['borderbin']); + } else { + $br = $cell['border_details']['R']['w']; + $bl = $cell['border_details']['L']['w']; + } + if ($table['borders_separate']) { + $innerw = $w - $bl - $br - $cell['padding']['L'] - $cell['padding']['R'] - $table['border_spacing_H']; + } else { + $innerw = $w - $bl / 2 - $br / 2 - $cell['padding']['L'] - $cell['padding']['R']; + } + } elseif ($this->simpleTables) { + if ($table['borders_separate']) { + $innerw = $w - $table['simple']['border_details']['L']['w'] - $table['simple']['border_details']['R']['w'] - $cell['padding']['L'] - $cell['padding']['R'] - $table['border_spacing_H']; + } else { + $innerw = $w - $table['simple']['border_details']['L']['w'] / 2 - $table['simple']['border_details']['R']['w'] / 2 - $cell['padding']['L'] - $cell['padding']['R']; + } + } + if ($cell['a'] == 'C' || $this->table[($level + 1)][$objattr['nestedcontent']]['a'] == 'C') { + $ncx += ($innerw - $ntw) / 2; + } elseif ($cell['a'] == 'R' || $this->table[($level + 1)][$objattr['nestedcontent']]['a'] == 'R') { + $ncx += $innerw - $ntw; + } + $this->x = $ncx; + + $this->_tableWrite($this->table[($level + 1)][$objattr['nestedcontent']]); + $this->cellBorderBuffer = $save_buffer; + $this->x = $bak_x; + $this->divwidth = $save_dw; + } + + $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, false, $blockdir, $table_draft); + } + } else { + /* -- END TABLES -- */ + if ($is_table) { // *TABLES* + $maxWidth = $this->divwidth; // *TABLES* + } // *TABLES* + else { // *TABLES* + $maxWidth = $this->divwidth - ($this->blk[$this->blklvl]['padding_left'] + $this->blk[$this->blklvl]['border_left']['w'] + $this->blk[$this->blklvl]['padding_right'] + $this->blk[$this->blklvl]['border_right']['w']); + } // *TABLES* + + /* -- CSS-IMAGE-FLOAT -- */ + // If float (already) exists at this level + if (isset($this->floatmargins['R']) && $this->y <= $this->floatmargins['R']['y1'] && $this->y >= $this->floatmargins['R']['y0']) { + $maxWidth -= $this->floatmargins['R']['w']; + } + if (isset($this->floatmargins['L']) && $this->y <= $this->floatmargins['L']['y1'] && $this->y >= $this->floatmargins['L']['y0']) { + $maxWidth -= $this->floatmargins['L']['w']; + } + /* -- END CSS-IMAGE-FLOAT -- */ + + list($skipln) = $this->inlineObject($objattr['type'], '', $this->y, $objattr, $this->lMargin, ($this->flowingBlockAttr['contentWidth'] / Mpdf::SCALE), $maxWidth, $this->flowingBlockAttr['height'], false, $is_table); + // 1 -> New line needed because of width + // -1 -> Will fit width on line but NEW PAGE REQUIRED because of height + // -2 -> Will not fit on line therefore needs new line but thus NEW PAGE REQUIRED + $iby = $this->y; + $oldpage = $this->page; + $oldcol = $this->CurrCol; + if (($skipln == 1 || $skipln == -2) && !isset($objattr['float'])) { + $this->finishFlowingBlock(false, $objattr['type']); + $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, false, $blockdir, $table_draft); + } + + if (!$table_draft) { + $thispage = $this->page; + if ($this->CurrCol != $oldcol) { + $changedcol = true; + } else { + $changedcol = false; + } + + // the previous lines can already have triggered page break or column change + if (!$changedcol && $skipln < 0 && $this->AcceptPageBreak() && $thispage == $oldpage) { + $this->AddPage($this->CurOrientation); + + // Added to correct Images already set on line before page advanced + // i.e. if second inline image on line is higher than first and forces new page + if (count($this->objectbuffer)) { + $yadj = $iby - $this->y; + foreach ($this->objectbuffer as $ib => $val) { + if ($this->objectbuffer[$ib]['OUTER-Y']) { + $this->objectbuffer[$ib]['OUTER-Y'] -= $yadj; + } + if ($this->objectbuffer[$ib]['BORDER-Y']) { + $this->objectbuffer[$ib]['BORDER-Y'] -= $yadj; + } + if ($this->objectbuffer[$ib]['INNER-Y']) { + $this->objectbuffer[$ib]['INNER-Y'] -= $yadj; + } + } + } + } + + // Added to correct for OddEven Margins + if ($this->page != $oldpage) { + if (($this->page - $oldpage) % 2 == 1) { + $bak_x += $this->MarginCorrection; + } + $oldpage = $this->page; + $y = $this->tMargin - $paint_ht_corr; + $this->oldy = $this->tMargin - $paint_ht_corr; + $old_height = 0; + } + $this->x = $bak_x; + /* -- COLUMNS -- */ + // COLS + // OR COLUMN CHANGE + if ($this->CurrCol != $oldcolumn) { + if ($this->directionality == 'rtl') { // *OTL* + $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL* + } // *OTL* + else { // *OTL* + $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); + } // *OTL* + $this->x = $bak_x; + $oldcolumn = $this->CurrCol; + $y = $this->y0 - $paint_ht_corr; + $this->oldy = $this->y0 - $paint_ht_corr; + $old_height = 0; + } + /* -- END COLUMNS -- */ + } + + /* -- CSS-IMAGE-FLOAT -- */ + if ($objattr['type'] == 'image' && isset($objattr['float'])) { + $fy = $this->y; + + // DIV TOP MARGIN/BORDER/PADDING + if ($this->flowingBlockAttr['newblock'] && ($this->flowingBlockAttr['blockstate'] == 1 || $this->flowingBlockAttr['blockstate'] == 3) && $this->flowingBlockAttr['lineCount'] == 0) { + $fy += $this->blk[$this->blklvl]['margin_top'] + $this->blk[$this->blklvl]['padding_top'] + $this->blk[$this->blklvl]['border_top']['w']; + } + + if ($objattr['float'] == 'R') { + $fx = $this->w - $this->rMargin - $objattr['width'] - ($this->blk[$this->blklvl]['outer_right_margin'] + $this->blk[$this->blklvl]['border_right']['w'] + $this->blk[$this->blklvl]['padding_right']); + } elseif ($objattr['float'] == 'L') { + $fx = $this->lMargin + ($this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['border_left']['w'] + $this->blk[$this->blklvl]['padding_left']); + } + $w = $objattr['width']; + $h = abs($objattr['height']); + + $widthLeft = $maxWidth - ($this->flowingBlockAttr['contentWidth'] / Mpdf::SCALE); + $maxHeight = $this->h - ($this->tMargin + $this->margin_header + $this->bMargin + 10); + // For Images + $extraWidth = ($objattr['border_left']['w'] + $objattr['border_right']['w'] + $objattr['margin_left'] + $objattr['margin_right']); + $extraHeight = ($objattr['border_top']['w'] + $objattr['border_bottom']['w'] + $objattr['margin_top'] + $objattr['margin_bottom']); + + if ($objattr['itype'] == 'wmf' || $objattr['itype'] == 'svg') { + $file = $objattr['file']; + $info = $this->formobjects[$file]; + } else { + $file = $objattr['file']; + $info = $this->images[$file]; + } + $img_w = $w - $extraWidth; + $img_h = $h - $extraHeight; + if ($objattr['border_left']['w']) { + $objattr['BORDER-WIDTH'] = $img_w + (($objattr['border_left']['w'] + $objattr['border_right']['w']) / 2); + $objattr['BORDER-HEIGHT'] = $img_h + (($objattr['border_top']['w'] + $objattr['border_bottom']['w']) / 2); + $objattr['BORDER-X'] = $fx + $objattr['margin_left'] + (($objattr['border_left']['w']) / 2); + $objattr['BORDER-Y'] = $fy + $objattr['margin_top'] + (($objattr['border_top']['w']) / 2); + } + $objattr['INNER-WIDTH'] = $img_w; + $objattr['INNER-HEIGHT'] = $img_h; + $objattr['INNER-X'] = $fx + $objattr['margin_left'] + ($objattr['border_left']['w']); + $objattr['INNER-Y'] = $fy + $objattr['margin_top'] + ($objattr['border_top']['w']); + $objattr['ID'] = $info['i']; + $objattr['OUTER-WIDTH'] = $w; + $objattr['OUTER-HEIGHT'] = $h; + $objattr['OUTER-X'] = $fx; + $objattr['OUTER-Y'] = $fy; + if ($objattr['float'] == 'R') { + // If R float already exists at this level + $this->floatmargins['R']['skipline'] = false; + if (isset($this->floatmargins['R']['y1']) && $this->floatmargins['R']['y1'] > 0 && $fy < $this->floatmargins['R']['y1']) { + $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1 + } // If L float already exists at this level + elseif (isset($this->floatmargins['L']['y1']) && $this->floatmargins['L']['y1'] > 0 && $fy < $this->floatmargins['L']['y1']) { + // Final check distance between floats is not now too narrow to fit text + $mw = 2 * $this->GetCharWidth('W', false); + if (($this->blk[$this->blklvl]['inner_width'] - $w - $this->floatmargins['L']['w']) < $mw) { + $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1 + } else { + $this->floatmargins['R']['x'] = $fx; + $this->floatmargins['R']['w'] = $w; + $this->floatmargins['R']['y0'] = $fy; + $this->floatmargins['R']['y1'] = $fy + $h; + if ($skipln == 1) { + $this->floatmargins['R']['skipline'] = true; + $this->floatmargins['R']['id'] = count($this->floatbuffer) + 0; + $objattr['skipline'] = true; + } + $this->floatbuffer[] = $objattr; + } + } else { + $this->floatmargins['R']['x'] = $fx; + $this->floatmargins['R']['w'] = $w; + $this->floatmargins['R']['y0'] = $fy; + $this->floatmargins['R']['y1'] = $fy + $h; + if ($skipln == 1) { + $this->floatmargins['R']['skipline'] = true; + $this->floatmargins['R']['id'] = count($this->floatbuffer) + 0; + $objattr['skipline'] = true; + } + $this->floatbuffer[] = $objattr; + } + } elseif ($objattr['float'] == 'L') { + // If L float already exists at this level + $this->floatmargins['L']['skipline'] = false; + if (isset($this->floatmargins['L']['y1']) && $this->floatmargins['L']['y1'] > 0 && $fy < $this->floatmargins['L']['y1']) { + $this->floatmargins['L']['skipline'] = false; + $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1 + } // If R float already exists at this level + elseif (isset($this->floatmargins['R']['y1']) && $this->floatmargins['R']['y1'] > 0 && $fy < $this->floatmargins['R']['y1']) { + // Final check distance between floats is not now too narrow to fit text + $mw = 2 * $this->GetCharWidth('W', false); + if (($this->blk[$this->blklvl]['inner_width'] - $w - $this->floatmargins['R']['w']) < $mw) { + $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1 + } else { + $this->floatmargins['L']['x'] = $fx + $w; + $this->floatmargins['L']['w'] = $w; + $this->floatmargins['L']['y0'] = $fy; + $this->floatmargins['L']['y1'] = $fy + $h; + if ($skipln == 1) { + $this->floatmargins['L']['skipline'] = true; + $this->floatmargins['L']['id'] = count($this->floatbuffer) + 0; + $objattr['skipline'] = true; + } + $this->floatbuffer[] = $objattr; + } + } else { + $this->floatmargins['L']['x'] = $fx + $w; + $this->floatmargins['L']['w'] = $w; + $this->floatmargins['L']['y0'] = $fy; + $this->floatmargins['L']['y1'] = $fy + $h; + if ($skipln == 1) { + $this->floatmargins['L']['skipline'] = true; + $this->floatmargins['L']['id'] = count($this->floatbuffer) + 0; + $objattr['skipline'] = true; + } + $this->floatbuffer[] = $objattr; + } + } + } else { + /* -- END CSS-IMAGE-FLOAT -- */ + $this->WriteFlowingBlock($vetor[0], (isset($vetor[18]) ? $vetor[18] : null)); // mPDF 5.7.1 + /* -- CSS-IMAGE-FLOAT -- */ + } + /* -- END CSS-IMAGE-FLOAT -- */ + } // *TABLES* + } // END If special content + else { // THE text + if ($this->tableLevel) { + $paint_ht_corr = 0; + } // To move the y up when new column/page started if div border needed + else { + $paint_ht_corr = $this->blk[$this->blklvl]['border_top']['w']; + } + + if ($vetor[0] == "\n") { // We are reading a
now turned into newline ("\n") + if ($this->flowingBlockAttr['content']) { + $this->finishFlowingBlock(false, 'br'); + } elseif ($is_table) { + $this->y+= $this->_computeLineheight($this->cellLineHeight); + } elseif (!$is_table) { + $this->DivLn($this->lineheight); + if ($this->ColActive) { + $this->breakpoints[$this->CurrCol][] = $this->y; + } // *COLUMNS* + } + // Added to correct for OddEven Margins + if ($this->page != $oldpage) { + if (($this->page - $oldpage) % 2 == 1) { + $bak_x += $this->MarginCorrection; + } + $oldpage = $this->page; + $y = $this->tMargin - $paint_ht_corr; + $this->oldy = $this->tMargin - $paint_ht_corr; + $old_height = 0; + } + $this->x = $bak_x; + /* -- COLUMNS -- */ + // COLS + // OR COLUMN CHANGE + if ($this->CurrCol != $oldcolumn) { + if ($this->directionality == 'rtl') { // *OTL* + $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL* + } // *OTL* + else { // *OTL* + $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); + } // *OTL* + $this->x = $bak_x; + $oldcolumn = $this->CurrCol; + $y = $this->y0 - $paint_ht_corr; + $this->oldy = $this->y0 - $paint_ht_corr; + $old_height = 0; + } + /* -- END COLUMNS -- */ + $this->newFlowingBlock($this->divwidth, $this->divheight, $align, $is_table, $blockstate, false, $blockdir, $table_draft); + } else { + $this->WriteFlowingBlock($vetor[0], $vetor[18]); // mPDF 5.7.1 + // Added to correct for OddEven Margins + if ($this->page != $oldpage) { + if (($this->page - $oldpage) % 2 == 1) { + $bak_x += $this->MarginCorrection; + $this->x = $bak_x; + } + $oldpage = $this->page; + $y = $this->tMargin - $paint_ht_corr; + $this->oldy = $this->tMargin - $paint_ht_corr; + $old_height = 0; + } + /* -- COLUMNS -- */ + // COLS + // OR COLUMN CHANGE + if ($this->CurrCol != $oldcolumn) { + if ($this->directionality == 'rtl') { // *OTL* + $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL* + } // *OTL* + else { // *OTL* + $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); + } // *OTL* + $this->x = $bak_x; + $oldcolumn = $this->CurrCol; + $y = $this->y0 - $paint_ht_corr; + $this->oldy = $this->y0 - $paint_ht_corr; + $old_height = 0; + } + /* -- END COLUMNS -- */ + } + } + + // Check if it is the last element. If so then finish printing the block + if ($i == ($array_size - 1)) { + $this->finishFlowingBlock(true); // true = END of flowing block + // Added to correct for OddEven Margins + if ($this->page != $oldpage) { + if (($this->page - $oldpage) % 2 == 1) { + $bak_x += $this->MarginCorrection; + $this->x = $bak_x; + } + $oldpage = $this->page; + $y = $this->tMargin - $paint_ht_corr; + $this->oldy = $this->tMargin - $paint_ht_corr; + $old_height = 0; + } + + /* -- COLUMNS -- */ + // COLS + // OR COLUMN CHANGE + if ($this->CurrCol != $oldcolumn) { + if ($this->directionality == 'rtl') { // *OTL* + $bak_x -= ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); // *OTL* + } // *OTL* + else { // *OTL* + $bak_x += ($this->CurrCol - $oldcolumn) * ($this->ColWidth + $this->ColGap); + } // *OTL* + $this->x = $bak_x; + $oldcolumn = $this->CurrCol; + $y = $this->y0 - $paint_ht_corr; + $this->oldy = $this->y0 - $paint_ht_corr; + $old_height = 0; + } + /* -- END COLUMNS -- */ + } + + // RESETTING VALUES + $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings)); + $this->colorarray = ''; + $this->spanbgcolorarray = ''; + $this->spanbgcolor = false; + $this->spanborder = false; + $this->spanborddet = []; + $this->HREF = ''; + $this->textparam = []; + $this->SetTextOutline(); + + $this->textvar = 0x00; // mPDF 5.7.1 + $this->OTLtags = []; + $this->textshadow = ''; + + $this->currentfontfamily = ''; + $this->currentfontsize = ''; + $this->currentfontstyle = ''; + $this->currentLang = $this->default_lang; // mPDF 6 + $this->RestrictUnicodeFonts($this->default_available_fonts); // mPDF 6 + /* -- TABLES -- */ + if ($this->tableLevel) { + $this->SetLineHeight('', $this->table[1][1]['cellLineHeight']); // *TABLES* + } else { /* -- END TABLES -- */ + if (isset($this->blk[$this->blklvl]['line_height']) && $this->blk[$this->blklvl]['line_height']) { + $this->SetLineHeight('', $this->blk[$this->blklvl]['line_height']); // sets default line height + } + } + $this->ResetStyles(); + $this->lSpacingCSS = ''; + $this->wSpacingCSS = ''; + $this->fixedlSpacing = false; + $this->minwSpacing = 0; + $this->SetDash(); + $this->dash_on = false; + $this->dotted_on = false; + }//end of for(i=0;iReset(); // mPDF 6 + // PAINT DIV BORDER // DISABLED IN COLUMNS AS DOESN'T WORK WHEN BROKEN ACROSS COLS?? + if ((isset($this->blk[$this->blklvl]['border']) || isset($this->blk[$this->blklvl]['bgcolor']) || isset($this->blk[$this->blklvl]['box_shadow'])) && $blockstate && ($this->y != $this->oldy)) { + $bottom_y = $this->y; // Does not include Bottom Margin + if (isset($this->blk[$this->blklvl]['startpage']) && $this->blk[$this->blklvl]['startpage'] != $this->page && $blockstate != 1) { + $this->PaintDivBB('pagetop', $blockstate); + } elseif ($blockstate != 1) { + $this->PaintDivBB('', $blockstate); + } + $this->y = $bottom_y; + $this->x = $bak_x; + } + + // Reset Font + $this->SetFontSize($this->default_font_size, false); + if ($table_draft) { + $ch = $this->y - $bak_y; + $this->y = $bak_y; + $this->x = $bak_x; + return $ch; + } + } + + function _setDashBorder($style, $div, $cp, $side) + { + if ($style == 'dashed' && (($side == 'L' || $side == 'R') || ($side == 'T' && $div != 'pagetop' && !$cp) || ($side == 'B' && $div != 'pagebottom') )) { + $dashsize = 2; // final dash will be this + 1*linewidth + $dashsizek = 1.5; // ratio of Dash/Blank + $this->SetDash($dashsize, ($dashsize / $dashsizek) + ($this->LineWidth * 2)); + } elseif ($style == 'dotted' || ($side == 'T' && ($div == 'pagetop' || $cp)) || ($side == 'B' && $div == 'pagebottom')) { + // Round join and cap + $this->SetLineJoin(1); + $this->SetLineCap(1); + $this->SetDash(0.001, ($this->LineWidth * 3)); + } + } + + function _setBorderLine($b, $k = 1) + { + $this->SetLineWidth($b['w'] / $k); + $this->SetDColor($b['c']); + if ($b['c'][0] == 5) { // RGBa + $this->SetAlpha(ord($b['c'][4]) / 100, 'Normal', false, 'S'); // mPDF 5.7.2 + } elseif ($b['c'][0] == 6) { // CMYKa + $this->SetAlpha(ord($b['c'][5]) / 100, 'Normal', false, 'S'); // mPDF 5.7.2 + } + } + + function PaintDivBB($divider = '', $blockstate = 0, $blvl = 0) + { + // Borders & backgrounds are done elsewhere for columns - messes up the repositioning in printcolumnbuffer + if ($this->ColActive) { + return; + } // *COLUMNS* + if ($this->keep_block_together) { + return; + } // mPDF 6 + $save_y = $this->y; + if (!$blvl) { + $blvl = $this->blklvl; + } + $x0 = $x1 = $y0 = $y1 = 0; + + // Added mPDF 3.0 Float DIV + if (isset($this->blk[$blvl]['bb_painted'][$this->page]) && $this->blk[$blvl]['bb_painted'][$this->page]) { + return; + } // *CSS-FLOAT* + + if (isset($this->blk[$blvl]['x0'])) { + $x0 = $this->blk[$blvl]['x0']; + } // left + if (isset($this->blk[$blvl]['y1'])) { + $y1 = $this->blk[$blvl]['y1']; + } // bottom + // Added mPDF 3.0 Float DIV - ensures backgrounds/borders are drawn to bottom of page + if ($y1 == 0) { + if ($divider == 'pagebottom') { + $y1 = $this->h - $this->bMargin; + } else { + $y1 = $this->y; + } + } + + $continuingpage = (isset($this->blk[$blvl]['startpage']) && $this->blk[$blvl]['startpage'] != $this->page); + + if (isset($this->blk[$blvl]['y0'])) { + $y0 = $this->blk[$blvl]['y0']; + } + $h = $y1 - $y0; + $w = $this->blk[$blvl]['width']; + $x1 = $x0 + $w; + + // Set border-widths as used here + $border_top = $this->blk[$blvl]['border_top']['w']; + $border_bottom = $this->blk[$blvl]['border_bottom']['w']; + $border_left = $this->blk[$blvl]['border_left']['w']; + $border_right = $this->blk[$blvl]['border_right']['w']; + if (!$this->blk[$blvl]['border_top'] || $divider == 'pagetop' || $continuingpage) { + $border_top = 0; + } + if (!$this->blk[$blvl]['border_bottom'] || $blockstate == 1 || $divider == 'pagebottom') { + $border_bottom = 0; + } + + $brTL_H = 0; + $brTL_V = 0; + $brTR_H = 0; + $brTR_V = 0; + $brBL_H = 0; + $brBL_V = 0; + $brBR_H = 0; + $brBR_V = 0; + + $brset = false; + /* -- BORDER-RADIUS -- */ + if (isset($this->blk[$blvl]['border_radius_TL_H'])) { + $brTL_H = $this->blk[$blvl]['border_radius_TL_H']; + $brset = true; + } + if (isset($this->blk[$blvl]['border_radius_TL_V'])) { + $brTL_V = $this->blk[$blvl]['border_radius_TL_V']; + $brset = true; + } + if (isset($this->blk[$blvl]['border_radius_TR_H'])) { + $brTR_H = $this->blk[$blvl]['border_radius_TR_H']; + $brset = true; + } + if (isset($this->blk[$blvl]['border_radius_TR_V'])) { + $brTR_V = $this->blk[$blvl]['border_radius_TR_V']; + $brset = true; + } + if (isset($this->blk[$blvl]['border_radius_BR_H'])) { + $brBR_H = $this->blk[$blvl]['border_radius_BR_H']; + $brset = true; + } + if (isset($this->blk[$blvl]['border_radius_BR_V'])) { + $brBR_V = $this->blk[$blvl]['border_radius_BR_V']; + $brset = true; + } + if (isset($this->blk[$blvl]['border_radius_BL_H'])) { + $brBL_H = $this->blk[$blvl]['border_radius_BL_H']; + $brset = true; + } + if (isset($this->blk[$blvl]['border_radius_BL_V'])) { + $brBL_V = $this->blk[$blvl]['border_radius_BL_V']; + $brset = true; + } + + if (!$this->blk[$blvl]['border_top'] || $divider == 'pagetop' || $continuingpage) { + $brTL_H = 0; + $brTL_V = 0; + $brTR_H = 0; + $brTR_V = 0; + } + if (!$this->blk[$blvl]['border_bottom'] || $blockstate == 1 || $divider == 'pagebottom') { + $brBL_H = 0; + $brBL_V = 0; + $brBR_H = 0; + $brBR_V = 0; + } + + // Disallow border-radius if it is smaller than the border width. + if ($brTL_H < min($border_left, $border_top)) { + $brTL_H = $brTL_V = 0; + } + if ($brTL_V < min($border_left, $border_top)) { + $brTL_V = $brTL_H = 0; + } + if ($brTR_H < min($border_right, $border_top)) { + $brTR_H = $brTR_V = 0; + } + if ($brTR_V < min($border_right, $border_top)) { + $brTR_V = $brTR_H = 0; + } + if ($brBL_H < min($border_left, $border_bottom)) { + $brBL_H = $brBL_V = 0; + } + if ($brBL_V < min($border_left, $border_bottom)) { + $brBL_V = $brBL_H = 0; + } + if ($brBR_H < min($border_right, $border_bottom)) { + $brBR_H = $brBR_V = 0; + } + if ($brBR_V < min($border_right, $border_bottom)) { + $brBR_V = $brBR_H = 0; + } + + // CHECK FOR radii that sum to > width or height of div ******** + $f = min($h / ($brTL_V + $brBL_V + 0.001), $h / ($brTR_V + $brBR_V + 0.001), $w / ($brTL_H + $brTR_H + 0.001), $w / ($brBL_H + $brBR_H + 0.001)); + if ($f < 1) { + $brTL_H *= $f; + $brTL_V *= $f; + $brTR_H *= $f; + $brTR_V *= $f; + $brBL_H *= $f; + $brBL_V *= $f; + $brBR_H *= $f; + $brBR_V *= $f; + } + /* -- END BORDER-RADIUS -- */ + + $tbcol = $this->colorConverter->convert(255, $this->PDFAXwarnings); + for ($l = 0; $l <= $blvl; $l++) { + if ($this->blk[$l]['bgcolor']) { + $tbcol = $this->blk[$l]['bgcolorarray']; + } + } + + // BORDERS + if (isset($this->blk[$blvl]['y0']) && $this->blk[$blvl]['y0']) { + $y0 = $this->blk[$blvl]['y0']; + } + $h = $y1 - $y0; + $w = $this->blk[$blvl]['width']; + + if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) { + $tbd = $this->blk[$blvl]['border_top']; + + $legend = ''; + $legbreakL = 0; + $legbreakR = 0; + // BORDER LEGEND + if (isset($this->blk[$blvl]['border_legend']) && $this->blk[$blvl]['border_legend']) { + $legend = $this->blk[$blvl]['border_legend']; // Same structure array as textbuffer + $txt = $legend[0] = ltrim($legend[0]); + if (!empty($legend[18])) { + $this->otl->trimOTLdata($legend[18], true, false); + } // *OTL* + // Set font, size, style, color + $this->SetFont($legend[4], $legend[2], $legend[11]); + if (isset($legend[3]) && $legend[3]) { + $cor = $legend[3]; + $this->SetTColor($cor); + } + $stringWidth = $this->GetStringWidth($txt, true, $legend[18], $legend[8]); + $save_x = $this->x; + $save_y = $this->y; + $save_currentfontfamily = $this->FontFamily; + $save_currentfontsize = $this->FontSizePt; + $save_currentfontstyle = $this->FontStyle; + $this->y = $y0 - $this->FontSize / 2 + $this->blk[$blvl]['border_top']['w'] / 2; + $this->x = $x0 + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_left']['w']; + + // Set the distance from the border line to the text ? make configurable variable + $gap = 0.2 * $this->FontSize; + $legbreakL = $this->x - $gap; + $legbreakR = $this->x + $stringWidth + $gap; + $this->magic_reverse_dir($txt, $this->blk[$blvl]['direction'], $legend[18]); + $fill = ''; + $this->Cell($stringWidth, $this->FontSize, $txt, '', 0, 'C', $fill, '', 0, 0, 0, 'M', $fill, false, $legend[18], $legend[8]); + // Reset + $this->x = $save_x; + $this->y = $save_y; + $this->SetFont($save_currentfontfamily, $save_currentfontstyle, $save_currentfontsize); + $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + + if (isset($tbd['s']) && $tbd['s']) { + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('q'); + $this->SetLineWidth(0); + $this->writer->write(sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE)); + $this->writer->write(' h W n '); // Ends path no-op & Sets the clipping path + } + + $this->_setBorderLine($tbd); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $legbreakL -= $border_top / 2; // because line cap different + $legbreakR += $border_top / 2; + $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'T'); + } /* -- BORDER-RADIUS -- */ elseif (($brTL_V && $brTL_H) || ($brTR_V && $brTR_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $this->SetLineJoin(0); + $this->SetLineCap(0); + } + $s = ''; + if ($brTR_H && $brTR_V) { + $s .= ($this->_EllipseArc($x0 + $w - $brTR_H, $y0 + $brTR_V, $brTR_H - $border_top / 2, $brTR_V - $border_top / 2, 1, 2, true)) . "\n"; + } else { /* -- END BORDER-RADIUS -- */ + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F m ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F m ', ($x0 + $w - ($border_top / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + } + /* -- BORDER-RADIUS -- */ + if ($brTL_H && $brTL_V) { + if ($legend) { + if ($legbreakR < ($x0 + $w - $brTR_H)) { + $s .= (sprintf('%.3F %.3F l ', $legbreakR * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + if ($legbreakL > ($x0 + $brTL_H )) { + $s .= (sprintf('%.3F %.3F m ', $legbreakL * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + $s .= (sprintf('%.3F %.3F l ', ($x0 + $brTL_H ) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE) . "\n"); + } else { + $s .= (sprintf('%.3F %.3F m ', ($x0 + $brTL_H ) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + } else { + $s .= (sprintf('%.3F %.3F l ', ($x0 + $brTL_H ) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + $s .= ($this->_EllipseArc($x0 + $brTL_H, $y0 + $brTL_V, $brTL_H - $border_top / 2, $brTL_V - $border_top / 2, 2, 1)) . "\n"; + } else { + /* -- END BORDER-RADIUS -- */ + if ($legend) { + if ($legbreakR < ($x0 + $w)) { + $s .= (sprintf('%.3F %.3F l ', $legbreakR * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + if ($legbreakL > ($x0)) { + $s .= (sprintf('%.3F %.3F m ', $legbreakL * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F l ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_top / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + } elseif ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F m ', ($x0 + $border_top / 2) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + } elseif ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F l ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_top / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_top / 2))) * Mpdf::SCALE)) . "\n"; + } + /* -- BORDER-RADIUS -- */ + } + /* -- END BORDER-RADIUS -- */ + $s .= 'S' . "\n"; + $this->writer->write($s); + + if ($tbd['style'] == 'double') { + $this->SetLineWidth($tbd['w'] / 3); + $this->SetDColor($tbcol); + $this->writer->write($s); + } + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('Q'); + } + + // Reset Corners and Dash off + $this->SetLineWidth(0.1); + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + // Reinstate line above for dotted line divider when block border crosses a page + // elseif ($divider == 'pagetop' || $continuingpage) { + + if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') { + $tbd = $this->blk[$blvl]['border_bottom']; + if (isset($tbd['s']) && $tbd['s']) { + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('q'); + $this->SetLineWidth(0); + $this->writer->write(sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE)); + $this->writer->write(' h W n '); // Ends path no-op & Sets the clipping path + } + + $this->_setBorderLine($tbd); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'B'); + } /* -- BORDER-RADIUS -- */ elseif (($brBL_V && $brBL_H) || ($brBR_V && $brBR_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $this->SetLineJoin(0); + $this->SetLineCap(0); + } + $s = ''; + if ($brBL_H && $brBL_V) { + $s .= ($this->_EllipseArc($x0 + $brBL_H, $y0 + $h - $brBL_V, $brBL_H - $border_bottom / 2, $brBL_V - $border_bottom / 2, 3, 2, true)) . "\n"; + } else { /* -- END BORDER-RADIUS -- */ + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F m ', ($x0 + ($border_bottom / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n"; + } + } + /* -- BORDER-RADIUS -- */ + if ($brBR_H && $brBR_V) { + $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_bottom / 2) - $brBR_H ) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n"; + $s .= ($this->_EllipseArc($x0 + $w - $brBR_H, $y0 + $h - $brBR_V, $brBR_H - $border_bottom / 2, $brBR_V - $border_bottom / 2, 4, 1)) . "\n"; + } else { /* -- END BORDER-RADIUS -- */ + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_bottom / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_bottom / 2))) * Mpdf::SCALE)) . "\n"; + } + } + $s .= 'S' . "\n"; + $this->writer->write($s); + + if ($tbd['style'] == 'double') { + $this->SetLineWidth($tbd['w'] / 3); + $this->SetDColor($tbcol); + $this->writer->write($s); + } + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('Q'); + } + + // Reset Corners and Dash off + $this->SetLineWidth(0.1); + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + // Reinstate line below for dotted line divider when block border crosses a page + // elseif ($blockstate == 1 || $divider == 'pagebottom') { + + if ($this->blk[$blvl]['border_left']) { + $tbd = $this->blk[$blvl]['border_left']; + if (isset($tbd['s']) && $tbd['s']) { + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('q'); + $this->SetLineWidth(0); + $this->writer->write(sprintf('%.3F %.3F m ', ($x0) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $border_left) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE)); + $this->writer->write(' h W n '); // Ends path no-op & Sets the clipping path + } + + $this->_setBorderLine($tbd); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'L'); + } /* -- BORDER-RADIUS -- */ elseif (($brTL_V && $brTL_H) || ($brBL_V && $brBL_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $this->SetLineJoin(0); + $this->SetLineCap(0); + } + $s = ''; + if ($brTL_V && $brTL_H) { + $s .= ($this->_EllipseArc($x0 + $brTL_H, $y0 + $brTL_V, $brTL_H - $border_left / 2, $brTL_V - $border_left / 2, 2, 2, true)) . "\n"; + } else { /* -- END BORDER-RADIUS -- */ + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F m ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F m ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_left / 2))) * Mpdf::SCALE)) . "\n"; + } + } + /* -- BORDER-RADIUS -- */ + if ($brBL_V && $brBL_H) { + $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_left / 2) - $brBL_V) ) * Mpdf::SCALE)) . "\n"; + $s .= ($this->_EllipseArc($x0 + $brBL_H, $y0 + $h - $brBL_V, $brBL_H - $border_left / 2, $brBL_V - $border_left / 2, 3, 1)) . "\n"; + } else { /* -- END BORDER-RADIUS -- */ + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h) ) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F l ', ($x0 + ($border_left / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_left / 2)) ) * Mpdf::SCALE)) . "\n"; + } + } + $s .= 'S' . "\n"; + $this->writer->write($s); + + if ($tbd['style'] == 'double') { + $this->SetLineWidth($tbd['w'] / 3); + $this->SetDColor($tbcol); + $this->writer->write($s); + } + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('Q'); + } + + // Reset Corners and Dash off + $this->SetLineWidth(0.1); + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + if ($this->blk[$blvl]['border_right']) { + $tbd = $this->blk[$blvl]['border_right']; + if (isset($tbd['s']) && $tbd['s']) { + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('q'); + $this->SetLineWidth(0); + $this->writer->write(sprintf('%.3F %.3F m ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $border_top)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $w - $border_right) * Mpdf::SCALE, ($this->h - ($y0 + $h - $border_bottom)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F l ', ($x0 + $w) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE)); + $this->writer->write(' h W n '); // Ends path no-op & Sets the clipping path + } + + $this->_setBorderLine($tbd); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], $divider, $continuingpage, 'R'); + } /* -- BORDER-RADIUS -- */ elseif (($brTR_V && $brTR_H) || ($brBR_V && $brBR_H) || $tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $this->SetLineJoin(0); + $this->SetLineCap(0); + } + $s = ''; + if ($brBR_V && $brBR_H) { + $s .= ($this->_EllipseArc($x0 + $w - $brBR_H, $y0 + $h - $brBR_V, $brBR_H - $border_right / 2, $brBR_V - $border_right / 2, 4, 2, true)) . "\n"; + } else { /* -- END BORDER-RADIUS -- */ + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F m ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h)) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F m ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + $h - ($border_right / 2))) * Mpdf::SCALE)) . "\n"; + } + } + /* -- BORDER-RADIUS -- */ + if ($brTR_V && $brTR_H) { + $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_right / 2) + $brTR_V) ) * Mpdf::SCALE)) . "\n"; + $s .= ($this->_EllipseArc($x0 + $w - $brTR_H, $y0 + $brTR_V, $brTR_H - $border_right / 2, $brTR_V - $border_right / 2, 1, 1)) . "\n"; + } else { /* -- END BORDER-RADIUS -- */ + if ($tbd['style'] == 'solid' || $tbd['style'] == 'double') { + $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0) ) * Mpdf::SCALE)) . "\n"; + } else { + $s .= (sprintf('%.3F %.3F l ', ($x0 + $w - ($border_right / 2)) * Mpdf::SCALE, ($this->h - ($y0 + ($border_right / 2)) ) * Mpdf::SCALE)) . "\n"; + } + } + $s .= 'S' . "\n"; + $this->writer->write($s); + + if ($tbd['style'] == 'double') { + $this->SetLineWidth($tbd['w'] / 3); + $this->SetDColor($tbcol); + $this->writer->write($s); + } + if (!$brset && $tbd['style'] != 'dotted' && $tbd['style'] != 'dashed') { + $this->writer->write('Q'); + } + + // Reset Corners and Dash off + $this->SetLineWidth(0.1); + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + + + $this->SetDash(); + $this->y = $save_y; + + + // BACKGROUNDS are disabled in columns/kbt/headers - messes up the repositioning in printcolumnbuffer + if ($this->ColActive || $this->kwt || $this->keep_block_together) { + return; + } + + + $bgx0 = $x0; + $bgx1 = $x1; + $bgy0 = $y0; + $bgy1 = $y1; + + // Defined br values represent the radius of the outer curve - need to take border-width/2 from each radius for drawing the borders + if (isset($this->blk[$blvl]['background_clip']) && $this->blk[$blvl]['background_clip'] == 'padding-box') { + $brbgTL_H = max(0, $brTL_H - $this->blk[$blvl]['border_left']['w']); + $brbgTL_V = max(0, $brTL_V - $this->blk[$blvl]['border_top']['w']); + $brbgTR_H = max(0, $brTR_H - $this->blk[$blvl]['border_right']['w']); + $brbgTR_V = max(0, $brTR_V - $this->blk[$blvl]['border_top']['w']); + $brbgBL_H = max(0, $brBL_H - $this->blk[$blvl]['border_left']['w']); + $brbgBL_V = max(0, $brBL_V - $this->blk[$blvl]['border_bottom']['w']); + $brbgBR_H = max(0, $brBR_H - $this->blk[$blvl]['border_right']['w']); + $brbgBR_V = max(0, $brBR_V - $this->blk[$blvl]['border_bottom']['w']); + $bgx0 += $this->blk[$blvl]['border_left']['w']; + $bgx1 -= $this->blk[$blvl]['border_right']['w']; + if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) { + $bgy0 += $this->blk[$blvl]['border_top']['w']; + } + if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') { + $bgy1 -= $this->blk[$blvl]['border_bottom']['w']; + } + } elseif (isset($this->blk[$blvl]['background_clip']) && $this->blk[$blvl]['background_clip'] == 'content-box') { + $brbgTL_H = max(0, $brTL_H - $this->blk[$blvl]['border_left']['w'] - $this->blk[$blvl]['padding_left']); + $brbgTL_V = max(0, $brTL_V - $this->blk[$blvl]['border_top']['w'] - $this->blk[$blvl]['padding_top']); + $brbgTR_H = max(0, $brTR_H - $this->blk[$blvl]['border_right']['w'] - $this->blk[$blvl]['padding_right']); + $brbgTR_V = max(0, $brTR_V - $this->blk[$blvl]['border_top']['w'] - $this->blk[$blvl]['padding_top']); + $brbgBL_H = max(0, $brBL_H - $this->blk[$blvl]['border_left']['w'] - $this->blk[$blvl]['padding_left']); + $brbgBL_V = max(0, $brBL_V - $this->blk[$blvl]['border_bottom']['w'] - $this->blk[$blvl]['padding_bottom']); + $brbgBR_H = max(0, $brBR_H - $this->blk[$blvl]['border_right']['w'] - $this->blk[$blvl]['padding_right']); + $brbgBR_V = max(0, $brBR_V - $this->blk[$blvl]['border_bottom']['w'] - $this->blk[$blvl]['padding_bottom']); + $bgx0 += $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left']; + $bgx1 -= $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']; + if (($this->blk[$blvl]['border_top']['w'] || $this->blk[$blvl]['padding_top']) && $divider != 'pagetop' && !$continuingpage) { + $bgy0 += $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top']; + } + if (($this->blk[$blvl]['border_bottom']['w'] || $this->blk[$blvl]['padding_bottom']) && $blockstate != 1 && $divider != 'pagebottom') { + $bgy1 -= $this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']; + } + } else { + $brbgTL_H = $brTL_H; + $brbgTL_V = $brTL_V; + $brbgTR_H = $brTR_H; + $brbgTR_V = $brTR_V; + $brbgBL_H = $brBL_H; + $brbgBL_V = $brBL_V; + $brbgBR_H = $brBR_H; + $brbgBR_V = $brBR_V; + } + + // Set clipping path + $s = ' q 0 w '; // Line width=0 + $s .= sprintf('%.3F %.3F m ', ($bgx0 + $brbgTL_H ) * Mpdf::SCALE, ($this->h - $bgy0) * Mpdf::SCALE); // start point TL before the arc + /* -- BORDER-RADIUS -- */ + if ($brbgTL_H || $brbgTL_V) { + $s .= $this->_EllipseArc($bgx0 + $brbgTL_H, $bgy0 + $brbgTL_V, $brbgTL_H, $brbgTL_V, 2); // segment 2 TL + } + /* -- END BORDER-RADIUS -- */ + $s .= sprintf('%.3F %.3F l ', ($bgx0) * Mpdf::SCALE, ($this->h - ($bgy1 - $brbgBL_V )) * Mpdf::SCALE); // line to BL + /* -- BORDER-RADIUS -- */ + if ($brbgBL_H || $brbgBL_V) { + $s .= $this->_EllipseArc($bgx0 + $brbgBL_H, $bgy1 - $brbgBL_V, $brbgBL_H, $brbgBL_V, 3); // segment 3 BL + } + /* -- END BORDER-RADIUS -- */ + $s .= sprintf('%.3F %.3F l ', ($bgx1 - $brbgBR_H ) * Mpdf::SCALE, ($this->h - ($bgy1)) * Mpdf::SCALE); // line to BR + /* -- BORDER-RADIUS -- */ + if ($brbgBR_H || $brbgBR_V) { + $s .= $this->_EllipseArc($bgx1 - $brbgBR_H, $bgy1 - $brbgBR_V, $brbgBR_H, $brbgBR_V, 4); // segment 4 BR + } + /* -- END BORDER-RADIUS -- */ + $s .= sprintf('%.3F %.3F l ', ($bgx1) * Mpdf::SCALE, ($this->h - ($bgy0 + $brbgTR_V)) * Mpdf::SCALE); // line to TR + /* -- BORDER-RADIUS -- */ + if ($brbgTR_H || $brbgTR_V) { + $s .= $this->_EllipseArc($bgx1 - $brbgTR_H, $bgy0 + $brbgTR_V, $brbgTR_H, $brbgTR_V, 1); // segment 1 TR + } + /* -- END BORDER-RADIUS -- */ + $s .= sprintf('%.3F %.3F l ', ($bgx0 + $brbgTL_H ) * Mpdf::SCALE, ($this->h - $bgy0) * Mpdf::SCALE); // line to TL + // Box Shadow + $shadow = ''; + if (isset($this->blk[$blvl]['box_shadow']) && $this->blk[$blvl]['box_shadow'] && $h > 0) { + foreach ($this->blk[$blvl]['box_shadow'] as $sh) { + // Colors + if ($sh['col'][0] == 1) { + $colspace = 'Gray'; + if ($sh['col'][2] == 1) { + $col1 = '1' . $sh['col'][1] . '1' . $sh['col'][3]; + } else { + $col1 = '1' . $sh['col'][1] . '1' . chr(100); + } + $col2 = '1' . $sh['col'][1] . '1' . chr(0); + } elseif ($sh['col'][0] == 4) { // CMYK + $colspace = 'CMYK'; + $col1 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . chr(100); + $col2 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . chr(0); + } elseif ($sh['col'][0] == 5) { // RGBa + $colspace = 'RGB'; + $col1 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4]; + $col2 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . chr(0); + } elseif ($sh['col'][0] == 6) { // CMYKa + $colspace = 'CMYK'; + $col1 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . $sh['col'][5]; + $col2 = '6' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . $sh['col'][4] . chr(0); + } else { + $colspace = 'RGB'; + $col1 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . chr(100); + $col2 = '5' . $sh['col'][1] . $sh['col'][2] . $sh['col'][3] . chr(0); + } + + // Use clipping path as set above (and rectangle around page) to clip area outside box + $shadow .= $s; // Use the clipping path with W* + $shadow .= sprintf('0 %.3F m %.3F %.3F l ', $this->h * Mpdf::SCALE, $this->w * Mpdf::SCALE, $this->h * Mpdf::SCALE); + $shadow .= sprintf('%.3F 0 l 0 0 l 0 %.3F l ', $this->w * Mpdf::SCALE, $this->h * Mpdf::SCALE); + $shadow .= 'W n' . "\n"; + + $sh['blur'] = abs($sh['blur']); // cannot have negative blur value + // Ensure spread/blur do not make effective shadow width/height < 0 + // Could do more complex things but this just adjusts spread value + if (-$sh['spread'] + $sh['blur'] / 2 > min($w / 2, $h / 2)) { + $sh['spread'] = $sh['blur'] / 2 - min($w / 2, $h / 2) + 0.01; + } + // Shadow Offset + if ($sh['x'] || $sh['y']) { + $shadow .= sprintf(' q 1 0 0 1 %.4F %.4F cm', $sh['x'] * Mpdf::SCALE, -$sh['y'] * Mpdf::SCALE) . "\n"; + } + + // Set path for INNER shadow + $shadow .= ' q 0 w '; + $shadow .= $this->SetFColor($col1, true) . "\n"; + if ($col1[0] == 5 && ord($col1[4]) < 100) { // RGBa + $shadow .= $this->SetAlpha(ord($col1[4]) / 100, 'Normal', true, 'F') . "\n"; + } elseif ($col1[0] == 6 && ord($col1[5]) < 100) { // CMYKa + $shadow .= $this->SetAlpha(ord($col1[5]) / 100, 'Normal', true, 'F') . "\n"; + } elseif ($col1[0] == 1 && $col1[2] == 1 && ord($col1[3]) < 100) { // Gray + $shadow .= $this->SetAlpha(ord($col1[3]) / 100, 'Normal', true, 'F') . "\n"; + } + + // Blur edges + $mag = 0.551784; // Bezier Control magic number for 4-part spline for circle/ellipse + $mag2 = 0.551784; // Bezier Control magic number to fill in edge of blurred rectangle + $d1 = $sh['spread'] + $sh['blur'] / 2; + $d2 = $sh['spread'] - $sh['blur'] / 2; + $bl = $sh['blur']; + $x00 = $x0 - $d1; + $y00 = $y0 - $d1; + $w00 = $w + $d1 * 2; + $h00 = $h + $d1 * 2; + + // If any border-radius is greater width-negative spread(inner edge), ignore radii for shadow or screws up + $flatten = false; + if (max($brbgTR_H, $brbgTL_H, $brbgBR_H, $brbgBL_H) >= $w + $d2) { + $flatten = true; + } + if (max($brbgTR_V, $brbgTL_V, $brbgBR_V, $brbgBL_V) >= $h + $d2) { + $flatten = true; + } + + + // TOP RIGHT corner + $p1x = $x00 + $w00 - $d1 - $brbgTR_H; + $p1c2x = $p1x + ($d2 + $brbgTR_H) * $mag; + $p1y = $y00 + $bl; + $p2x = $x00 + $w00 - $d1 - $brbgTR_H; + $p2c2x = $p2x + ($d1 + $brbgTR_H) * $mag; + $p2y = $y00; + $p2c1y = $p2y + $bl / 2; + $p3x = $x00 + $w00; + $p3c2x = $p3x - $bl / 2; + $p3y = $y00 + $d1 + $brbgTR_V; + $p3c1y = $p3y - ($d1 + $brbgTR_V) * $mag; + $p4x = $x00 + $w00 - $bl; + $p4y = $y00 + $d1 + $brbgTR_V; + $p4c2y = $p4y - ($d2 + $brbgTR_V) * $mag; + if (-$d2 > min($brbgTR_H, $brbgTR_V) || $flatten) { + $p1x = $x00 + $w00 - $bl; + $p1c2x = $p1x; + $p2x = $x00 + $w00 - $bl; + $p2c2x = $p2x + $bl * $mag2; + $p3y = $y00 + $bl; + $p3c1y = $p3y - $bl * $mag2; + $p4y = $y00 + $bl; + $p4c2y = $p4y; + } + + $shadow .= sprintf('%.3F %.3F m ', ($p1x ) * Mpdf::SCALE, ($this->h - ($p1y )) * Mpdf::SCALE); + $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1c2x) * Mpdf::SCALE, ($this->h - ($p1y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4c2y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE); + $patch_array[0]['f'] = 0; + $patch_array[0]['points'] = [$p1x, $p1y, $p1x, $p1y, + $p2x, $p2c1y, $p2x, $p2y, $p2c2x, $p2y, + $p3x, $p3c1y, $p3x, $p3y, $p3c2x, $p3y, + $p4x, $p4y, $p4x, $p4y, $p4x, $p4c2y, + $p1c2x, $p1y]; + $patch_array[0]['colors'] = [$col1, $col2, $col2, $col1]; + + + // RIGHT + $p1x = $x00 + $w00; // control point only matches p3 preceding + $p1y = $y00 + $d1 + $brbgTR_V; + $p2x = $x00 + $w00 - $bl; // control point only matches p4 preceding + $p2y = $y00 + $d1 + $brbgTR_V; + $p3x = $x00 + $w00 - $bl; + $p3y = $y00 + $h00 - $d1 - $brbgBR_V; + $p4x = $x00 + $w00; + $p4c1x = $p4x - $bl / 2; + $p4y = $y00 + $h00 - $d1 - $brbgBR_V; + if (-$d2 > min($brbgTR_H, $brbgTR_V) || $flatten) { + $p1y = $y00 + $bl; + $p2y = $y00 + $bl; + } + if (-$d2 > min($brbgBR_H, $brbgBR_V) || $flatten) { + $p3y = $y00 + $h00 - $bl; + $p4y = $y00 + $h00 - $bl; + } + + $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE); + $patch_array[1]['f'] = 2; + $patch_array[1]['points'] = [$p2x, $p2y, + $p3x, $p3y, $p3x, $p3y, $p3x, $p3y, + $p4c1x, $p4y, $p4x, $p4y, $p4x, $p4y, + $p1x, $p1y]; + $patch_array[1]['colors'] = [$col1, $col2]; + + + // BOTTOM RIGHT corner + $p1x = $x00 + $w00 - $bl; // control points only matches p3 preceding + $p1y = $y00 + $h00 - $d1 - $brbgBR_V; + $p1c2y = $p1y + ($d2 + $brbgBR_V) * $mag; + $p2x = $x00 + $w00; // control point only matches p4 preceding + $p2y = $y00 + $h00 - $d1 - $brbgBR_V; + $p2c2y = $p2y + ($d1 + $brbgBR_V) * $mag; + $p3x = $x00 + $w00 - $d1 - $brbgBR_H; + $p3c1x = $p3x + ($d1 + $brbgBR_H) * $mag; + $p3y = $y00 + $h00; + $p3c2y = $p3y - $bl / 2; + $p4x = $x00 + $w00 - $d1 - $brbgBR_H; + $p4c2x = $p4x + ($d2 + $brbgBR_H) * $mag; + $p4y = $y00 + $h00 - $bl; + + if (-$d2 > min($brbgBR_H, $brbgBR_V) || $flatten) { + $p1y = $y00 + $h00 - $bl; + $p1c2y = $p1y; + $p2y = $y00 + $h00 - $bl; + $p2c2y = $p2y + $bl * $mag2; + $p3x = $x00 + $w00 - $bl; + $p3c1x = $p3x + $bl * $mag2; + $p4x = $x00 + $w00 - $bl; + $p4c2x = $p4x; + } + + $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1x) * Mpdf::SCALE, ($this->h - ($p1c2y)) * Mpdf::SCALE, ($p4c2x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE); + $patch_array[2]['f'] = 2; + $patch_array[2]['points'] = [$p2x, $p2c2y, + $p3c1x, $p3y, $p3x, $p3y, $p3x, $p3c2y, + $p4x, $p4y, $p4x, $p4y, $p4c2x, $p4y, + $p1x, $p1c2y]; + $patch_array[2]['colors'] = [$col2, $col1]; + + + + // BOTTOM + $p1x = $x00 + $w00 - $d1 - $brbgBR_H; // control point only matches p3 preceding + $p1y = $y00 + $h00; + $p2x = $x00 + $w00 - $d1 - $brbgBR_H; // control point only matches p4 preceding + $p2y = $y00 + $h00 - $bl; + $p3x = $x00 + $d1 + $brbgBL_H; + $p3y = $y00 + $h00 - $bl; + $p4x = $x00 + $d1 + $brbgBL_H; + $p4y = $y00 + $h00; + $p4c1y = $p4y - $bl / 2; + + if (-$d2 > min($brbgBR_H, $brbgBR_V) || $flatten) { + $p1x = $x00 + $w00 - $bl; + $p2x = $x00 + $w00 - $bl; + } + if (-$d2 > min($brbgBL_H, $brbgBL_V) || $flatten) { + $p3x = $x00 + $bl; + $p4x = $x00 + $bl; + } + + $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE); + $patch_array[3]['f'] = 2; + $patch_array[3]['points'] = [$p2x, $p2y, + $p3x, $p3y, $p3x, $p3y, $p3x, $p3y, + $p4x, $p4c1y, $p4x, $p4y, $p4x, $p4y, + $p1x, $p1y]; + $patch_array[3]['colors'] = [$col1, $col2]; + + // BOTTOM LEFT corner + $p1x = $x00 + $d1 + $brbgBL_H; + $p1c2x = $p1x - ($d2 + $brbgBL_H) * $mag; // control points only matches p3 preceding + $p1y = $y00 + $h00 - $bl; + $p2x = $x00 + $d1 + $brbgBL_H; + $p2c2x = $p2x - ($d1 + $brbgBL_H) * $mag; // control point only matches p4 preceding + $p2y = $y00 + $h00; + $p3x = $x00; + $p3c2x = $p3x + $bl / 2; + $p3y = $y00 + $h00 - $d1 - $brbgBL_V; + $p3c1y = $p3y + ($d1 + $brbgBL_V) * $mag; + $p4x = $x00 + $bl; + $p4y = $y00 + $h00 - $d1 - $brbgBL_V; + $p4c2y = $p4y + ($d2 + $brbgBL_V) * $mag; + if (-$d2 > min($brbgBL_H, $brbgBL_V) || $flatten) { + $p1x = $x00 + $bl; + $p1c2x = $p1x; + $p2x = $x00 + $bl; + $p2c2x = $p2x - $bl * $mag2; + $p3y = $y00 + $h00 - $bl; + $p3c1y = $p3y + $bl * $mag2; + $p4y = $y00 + $h00 - $bl; + $p4c2y = $p4y; + } + + $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1c2x) * Mpdf::SCALE, ($this->h - ($p1y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4c2y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE); + $patch_array[4]['f'] = 2; + $patch_array[4]['points'] = [$p2c2x, $p2y, + $p3x, $p3c1y, $p3x, $p3y, $p3c2x, $p3y, + $p4x, $p4y, $p4x, $p4y, $p4x, $p4c2y, + $p1c2x, $p1y]; + $patch_array[4]['colors'] = [$col2, $col1]; + + + // LEFT - joins on the right (C3-C4 of previous): f = 2 + $p1x = $x00; // control point only matches p3 preceding + $p1y = $y00 + $h00 - $d1 - $brbgBL_V; + $p2x = $x00 + $bl; // control point only matches p4 preceding + $p2y = $y00 + $h00 - $d1 - $brbgBL_V; + $p3x = $x00 + $bl; + $p3y = $y00 + $d1 + $brbgTL_V; + $p4x = $x00; + $p4c1x = $p4x + $bl / 2; + $p4y = $y00 + $d1 + $brbgTL_V; + if (-$d2 > min($brbgBL_H, $brbgBL_V) || $flatten) { + $p1y = $y00 + $h00 - $bl; + $p2y = $y00 + $h00 - $bl; + } + if (-$d2 > min($brbgTL_H, $brbgTL_V) || $flatten) { + $p3y = $y00 + $bl; + $p4y = $y00 + $bl; + } + + $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE); + $patch_array[5]['f'] = 2; + $patch_array[5]['points'] = [$p2x, $p2y, + $p3x, $p3y, $p3x, $p3y, $p3x, $p3y, + $p4c1x, $p4y, $p4x, $p4y, $p4x, $p4y, + $p1x, $p1y]; + $patch_array[5]['colors'] = [$col1, $col2]; + + // TOP LEFT corner + $p1x = $x00 + $bl; // control points only matches p3 preceding + $p1y = $y00 + $d1 + $brbgTL_V; + $p1c2y = $p1y - ($d2 + $brbgTL_V) * $mag; + $p2x = $x00; // control point only matches p4 preceding + $p2y = $y00 + $d1 + $brbgTL_V; + $p2c2y = $p2y - ($d1 + $brbgTL_V) * $mag; + $p3x = $x00 + $d1 + $brbgTL_H; + $p3c1x = $p3x - ($d1 + $brbgTL_H) * $mag; + $p3y = $y00; + $p3c2y = $p3y + $bl / 2; + $p4x = $x00 + $d1 + $brbgTL_H; + $p4c2x = $p4x - ($d2 + $brbgTL_H) * $mag; + $p4y = $y00 + $bl; + + if (-$d2 > min($brbgTL_H, $brbgTL_V) || $flatten) { + $p1y = $y00 + $bl; + $p1c2y = $p1y; + $p2y = $y00 + $bl; + $p2c2y = $p2y - $bl * $mag2; + $p3x = $x00 + $bl; + $p3c1x = $p3x - $bl * $mag2; + $p4x = $x00 + $bl; + $p4c2x = $p4x; + } + + $shadow .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($p1x) * Mpdf::SCALE, ($this->h - ($p1c2y)) * Mpdf::SCALE, ($p4c2x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE, ($p4x) * Mpdf::SCALE, ($this->h - ($p4y)) * Mpdf::SCALE); + $patch_array[6]['f'] = 2; + $patch_array[6]['points'] = [$p2x, $p2c2y, + $p3c1x, $p3y, $p3x, $p3y, $p3x, $p3c2y, + $p4x, $p4y, $p4x, $p4y, $p4c2x, $p4y, + $p1x, $p1c2y]; + $patch_array[6]['colors'] = [$col2, $col1]; + + + // TOP - joins on the right (C3-C4 of previous): f = 2 + $p1x = $x00 + $d1 + $brbgTL_H; // control point only matches p3 preceding + $p1y = $y00; + $p2x = $x00 + $d1 + $brbgTL_H; // control point only matches p4 preceding + $p2y = $y00 + $bl; + $p3x = $x00 + $w00 - $d1 - $brbgTR_H; + $p3y = $y00 + $bl; + $p4x = $x00 + $w00 - $d1 - $brbgTR_H; + $p4y = $y00; + $p4c1y = $p4y + $bl / 2; + if (-$d2 > min($brbgTL_H, $brbgTL_V) || $flatten) { + $p1x = $x00 + $bl; + $p2x = $x00 + $bl; + } + if (-$d2 > min($brbgTR_H, $brbgTR_V) || $flatten) { + $p3x = $x00 + $w00 - $bl; + $p4x = $x00 + $w00 - $bl; + } + + $shadow .= sprintf('%.3F %.3F l ', ($p3x ) * Mpdf::SCALE, ($this->h - ($p3y )) * Mpdf::SCALE); + $patch_array[7]['f'] = 2; + $patch_array[7]['points'] = [$p2x, $p2y, + $p3x, $p3y, $p3x, $p3y, $p3x, $p3y, + $p4x, $p4c1y, $p4x, $p4y, $p4x, $p4y, + $p1x, $p1y]; + $patch_array[7]['colors'] = [$col1, $col2]; + + $shadow .= ' h f Q ' . "\n"; // Close path and Fill the inner solid shadow + + if ($bl) { + $shadow .= $this->gradient->CoonsPatchMesh($x00, $y00, $w00, $h00, $patch_array, $x00, $x00 + $w00, $y00, $y00 + $h00, $colspace, true); + } + + if ($sh['x'] || $sh['y']) { + $shadow .= ' Q' . "\n"; // Shadow Offset + } + $shadow .= ' Q' . "\n"; // Ends path no-op & Sets the clipping path + } + } + + $s .= ' W n '; // Ends path no-op & Sets the clipping path + + if ($this->blk[$blvl]['bgcolor']) { + $this->pageBackgrounds[$blvl][] = [ + 'x' => $x0, + 'y' => $y0, + 'w' => $w, + 'h' => $h, + 'col' => $this->blk[$blvl]['bgcolorarray'], + 'clippath' => $s, + 'visibility' => $this->visibility, + 'shadow' => $shadow, + 'z-index' => $this->current_layer, + ]; + } elseif ($shadow) { + $this->pageBackgrounds[$blvl][] = [ + 'x' => 0, + 'y' => 0, + 'w' => 0, + 'h' => 0, + 'shadowonly' => true, + 'col' => '', + 'clippath' => '', + 'visibility' => $this->visibility, + 'shadow' => $shadow, + 'z-index' => $this->current_layer, + ]; + } + + /* -- BACKGROUNDS -- */ + if (isset($this->blk[$blvl]['gradient'])) { + $g = $this->gradient->parseBackgroundGradient($this->blk[$blvl]['gradient']); + if ($g) { + $gx = $x0; + $gy = $y0; + $this->pageBackgrounds[$blvl][] = [ + 'gradient' => true, + 'x' => $gx, + 'y' => $gy, + 'w' => $w, + 'h' => $h, + 'gradtype' => $g['type'], + 'stops' => $g['stops'], + 'colorspace' => $g['colorspace'], + 'coords' => $g['coords'], + 'extend' => $g['extend'], + 'clippath' => $s, + 'visibility' => $this->visibility, + 'z-index' => $this->current_layer + ]; + } + } + + if (isset($this->blk[$blvl]['background-image'])) { + if (isset($this->blk[$blvl]['background-image']['gradient']) && $this->blk[$blvl]['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $this->blk[$blvl]['background-image']['gradient'])) { + $g = $this->gradient->parseMozGradient($this->blk[$blvl]['background-image']['gradient']); + if ($g) { + $gx = $x0; + $gy = $y0; + // origin specifies the background-positioning-area (bpa) + if ($this->blk[$blvl]['background-image']['origin'] == 'padding-box') { + $gx += $this->blk[$blvl]['border_left']['w']; + $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']); + if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) { + $gy += $this->blk[$blvl]['border_top']['w']; + } + if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') { + $gy1 = $y1 - $this->blk[$blvl]['border_bottom']['w']; + } else { + $gy1 = $y1; + } + $h = $gy1 - $gy; + } elseif ($this->blk[$blvl]['background-image']['origin'] == 'content-box') { + $gx += $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left']; + $w -= ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']); + if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) { + $gy += $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top']; + } + if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') { + $gy1 = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']); + } else { + $gy1 = $y1 - $this->blk[$blvl]['padding_bottom']; + } + $h = $gy1 - $gy; + } + + if (isset($this->blk[$blvl]['background-image']['size']['w']) && $this->blk[$blvl]['background-image']['size']['w']) { + $size = $this->blk[$blvl]['background-image']['size']; + if ($size['w'] != 'contain' && $size['w'] != 'cover') { + if (stristr($size['w'], '%')) { + $size['w'] = (float) $size['w']; + $size['w'] /= 100; + $w *= $size['w']; + } elseif ($size['w'] != 'auto') { + $w = $size['w']; + } + if (stristr($size['h'], '%')) { + $size['h'] = (float) $size['h']; + $size['h'] /= 100; + $h *= $size['h']; + } elseif ($size['h'] != 'auto') { + $h = $size['h']; + } + } + } + $this->pageBackgrounds[$blvl][] = [ + 'gradient' => true, + 'x' => $gx, + 'y' => $gy, + 'w' => $w, + 'h' => $h, + 'gradtype' => $g['type'], + 'stops' => $g['stops'], + 'colorspace' => $g['colorspace'], + 'coords' => $g['coords'], + 'extend' => $g['extend'], + 'clippath' => $s, + 'visibility' => $this->visibility, + 'z-index' => $this->current_layer + ]; + } + + } else { + + $image_id = $this->blk[$blvl]['background-image']['image_id']; + $orig_w = $this->blk[$blvl]['background-image']['orig_w']; + $orig_h = $this->blk[$blvl]['background-image']['orig_h']; + $x_pos = $this->blk[$blvl]['background-image']['x_pos']; + $y_pos = $this->blk[$blvl]['background-image']['y_pos']; + $x_repeat = $this->blk[$blvl]['background-image']['x_repeat']; + $y_repeat = $this->blk[$blvl]['background-image']['y_repeat']; + $resize = $this->blk[$blvl]['background-image']['resize']; + $opacity = $this->blk[$blvl]['background-image']['opacity']; + $itype = $this->blk[$blvl]['background-image']['itype']; + $size = $this->blk[$blvl]['background-image']['size']; + // origin specifies the background-positioning-area (bpa) + + $bpa = ['x' => $x0, 'y' => $y0, 'w' => $w, 'h' => $h]; + + if ($this->blk[$blvl]['background-image']['origin'] == 'padding-box') { + + $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w']; + $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['border_right']['w']); + if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) { + $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w']; + } else { + $bpa['y'] = $y0; + } + if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') { + $bpay = $y1 - $this->blk[$blvl]['border_bottom']['w']; + } else { + $bpay = $y1; + } + $bpa['h'] = $bpay - $bpa['y']; + + } elseif ($this->blk[$blvl]['background-image']['origin'] == 'content-box') { + + $bpa['x'] = $x0 + $this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left']; + $bpa['w'] = $w - ($this->blk[$blvl]['border_left']['w'] + $this->blk[$blvl]['padding_left'] + $this->blk[$blvl]['border_right']['w'] + $this->blk[$blvl]['padding_right']); + if ($this->blk[$blvl]['border_top'] && $divider != 'pagetop' && !$continuingpage) { + $bpa['y'] = $y0 + $this->blk[$blvl]['border_top']['w'] + $this->blk[$blvl]['padding_top']; + } else { + $bpa['y'] = $y0 + $this->blk[$blvl]['padding_top']; + } + if ($this->blk[$blvl]['border_bottom'] && $blockstate != 1 && $divider != 'pagebottom') { + $bpay = $y1 - ($this->blk[$blvl]['border_bottom']['w'] + $this->blk[$blvl]['padding_bottom']); + } else { + $bpay = $y1 - $this->blk[$blvl]['padding_bottom']; + } + $bpa['h'] = $bpay - $bpa['y']; + + } + + $this->pageBackgrounds[$blvl][] = [ + 'x' => $x0, + 'y' => $y0, + 'w' => $w, + 'h' => $h, + 'image_id' => $image_id, + 'orig_w' => $orig_w, + 'orig_h' => $orig_h, + 'x_pos' => $x_pos, + 'y_pos' => $y_pos, + 'x_repeat' => $x_repeat, + 'y_repeat' => $y_repeat, + 'clippath' => $s, + 'resize' => $resize, + 'opacity' => $opacity, + 'itype' => $itype, + 'visibility' => $this->visibility, + 'z-index' => $this->current_layer, + 'size' => $size, + 'bpa' => $bpa + ]; + } + } + /* -- END BACKGROUNDS -- */ + + // Float DIV + $this->blk[$blvl]['bb_painted'][$this->page] = true; + } + /* -- BORDER-RADIUS -- */ + + function _EllipseArc($x0, $y0, $rx, $ry, $seg = 1, $part = false, $start = false) + { + // Anticlockwise segment 1-4 TR-TL-BL-BR (part=1 or 2) + $s = ''; + + if ($rx < 0) { + $rx = 0; + } + + if ($ry < 0) { + $ry = 0; + } + + $rx *= Mpdf::SCALE; + $ry *= Mpdf::SCALE; + + $astart = 0; + + if ($seg == 1) { // Top Right + $afinish = 90; + $nSeg = 4; + } elseif ($seg == 2) { // Top Left + $afinish = 180; + $nSeg = 8; + } elseif ($seg == 3) { // Bottom Left + $afinish = 270; + $nSeg = 12; + } else { // Bottom Right + $afinish = 360; + $nSeg = 16; + } + + $astart = deg2rad((float) $astart); + $afinish = deg2rad((float) $afinish); + + $totalAngle = $afinish - $astart; + $dt = $totalAngle / $nSeg; // segment angle + $dtm = $dt / 3; + $x0 *= Mpdf::SCALE; + $y0 = ($this->h - $y0) * Mpdf::SCALE; + $t1 = $astart; + $a0 = $x0 + ($rx * cos($t1)); + $b0 = $y0 + ($ry * sin($t1)); + $c0 = -$rx * sin($t1); + $d0 = $ry * cos($t1); + $op = false; + + for ($i = 1; $i <= $nSeg; $i++) { + // Draw this bit of the total curve + $t1 = ($i * $dt) + $astart; + $a1 = $x0 + ($rx * cos($t1)); + $b1 = $y0 + ($ry * sin($t1)); + $c1 = -$rx * sin($t1); + $d1 = $ry * cos($t1); + if ($i > ($nSeg - 4) && (!$part || ($part == 1 && $i <= $nSeg - 2) || ($part == 2 && $i > $nSeg - 2))) { + if ($start && !$op) { + $s .= sprintf('%.3F %.3F m ', $a0, $b0); + } + $s .= sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c ', ($a0 + ($c0 * $dtm)), ($b0 + ($d0 * $dtm)), ($a1 - ($c1 * $dtm)), ($b1 - ($d1 * $dtm)), $a1, $b1); + $op = true; + } + $a0 = $a1; + $b0 = $b1; + $c0 = $c1; + $d0 = $d1; + } + + return $s; + } + + /* -- END BORDER-RADIUS -- */ + + function PaintDivLnBorder($state = 0, $blvl = 0, $h = 0) + { + // $state = 0 normal; 1 top; 2 bottom; 3 top and bottom + $this->ColDetails[$this->CurrCol]['bottom_margin'] = $this->y + $h; + + $save_y = $this->y; + + $w = $this->blk[$blvl]['width']; + $x0 = $this->x; // left + $y0 = $this->y; // top + $x1 = $this->x + $w; // bottom + $y1 = $this->y + $h; // bottom + $continuingpage = (isset($this->blk[$blvl]['startpage']) && $this->blk[$blvl]['startpage'] != $this->page); + + if ($this->blk[$blvl]['border_top'] && ($state == 1 || $state == 3)) { + $tbd = $this->blk[$blvl]['border_top']; + if (isset($tbd['s']) && $tbd['s']) { + $this->_setBorderLine($tbd); + $this->y = $y0 + ($tbd['w'] / 2); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], '', $continuingpage, 'T'); + $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $this->y); + } else { + $this->SetLineJoin(0); + $this->SetLineCap(0); + $this->Line($x0, $this->y, $x0 + $w, $this->y); + } + $this->y += $tbd['w']; + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + if ($this->blk[$blvl]['border_left']) { + $tbd = $this->blk[$blvl]['border_left']; + if (isset($tbd['s']) && $tbd['s']) { + $this->_setBorderLine($tbd); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->y = $y0 + ($tbd['w'] / 2); + $this->_setDashBorder($tbd['style'], '', $continuingpage, 'L'); + $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + ($tbd['w'] / 2), $y0 + $h - ($tbd['w'] / 2)); + } else { + $this->y = $y0; + $this->SetLineJoin(0); + $this->SetLineCap(0); + $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + ($tbd['w'] / 2), $y0 + $h); + } + $this->y += $tbd['w']; + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + if ($this->blk[$blvl]['border_right']) { + $tbd = $this->blk[$blvl]['border_right']; + if (isset($tbd['s']) && $tbd['s']) { + $this->_setBorderLine($tbd); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->y = $y0 + ($tbd['w'] / 2); + $this->_setDashBorder($tbd['style'], '', $continuingpage, 'R'); + $this->Line($x0 + $w - ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $y0 + $h - ($tbd['w'] / 2)); + } else { + $this->y = $y0; + $this->SetLineJoin(0); + $this->SetLineCap(0); + $this->Line($x0 + $w - ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $y0 + $h); + } + $this->y += $tbd['w']; + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + if ($this->blk[$blvl]['border_bottom'] && $state > 1) { + $tbd = $this->blk[$blvl]['border_bottom']; + if (isset($tbd['s']) && $tbd['s']) { + $this->_setBorderLine($tbd); + $this->y = $y0 + $h - ($tbd['w'] / 2); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], '', $continuingpage, 'B'); + $this->Line($x0 + ($tbd['w'] / 2), $this->y, $x0 + $w - ($tbd['w'] / 2), $this->y); + } else { + $this->SetLineJoin(0); + $this->SetLineCap(0); + $this->Line($x0, $this->y, $x0 + $w, $this->y); + } + $this->y += $tbd['w']; + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + $this->SetDash(); + $this->y = $save_y; + } + + function PaintImgBorder($objattr, $is_table) + { + // Borders are disabled in columns - messes up the repositioning in printcolumnbuffer + if ($this->ColActive) { + return; + } // *COLUMNS* + if ($is_table) { + $k = $this->shrin_k; + } else { + $k = 1; + } + $h = (isset($objattr['BORDER-HEIGHT']) ? $objattr['BORDER-HEIGHT'] : 0); + $w = (isset($objattr['BORDER-WIDTH']) ? $objattr['BORDER-WIDTH'] : 0); + $x0 = (isset($objattr['BORDER-X']) ? $objattr['BORDER-X'] : 0); + $y0 = (isset($objattr['BORDER-Y']) ? $objattr['BORDER-Y'] : 0); + + // BORDERS + if ($objattr['border_top']) { + $tbd = $objattr['border_top']; + if (!empty($tbd['s'])) { + $this->_setBorderLine($tbd, $k); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], '', '', 'T'); + } + $this->Line($x0, $y0, $x0 + $w, $y0); + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + if ($objattr['border_left']) { + $tbd = $objattr['border_left']; + if (!empty($tbd['s'])) { + $this->_setBorderLine($tbd, $k); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], '', '', 'L'); + } + $this->Line($x0, $y0, $x0, $y0 + $h); + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + if ($objattr['border_right']) { + $tbd = $objattr['border_right']; + if (!empty($tbd['s'])) { + $this->_setBorderLine($tbd, $k); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], '', '', 'R'); + } + $this->Line($x0 + $w, $y0, $x0 + $w, $y0 + $h); + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + if ($objattr['border_bottom']) { + $tbd = $objattr['border_bottom']; + if (!empty($tbd['s'])) { + $this->_setBorderLine($tbd, $k); + if ($tbd['style'] == 'dotted' || $tbd['style'] == 'dashed') { + $this->_setDashBorder($tbd['style'], '', '', 'B'); + } + $this->Line($x0, $y0 + $h, $x0 + $w, $y0 + $h); + // Reset Corners and Dash off + $this->SetLineJoin(2); + $this->SetLineCap(2); + $this->SetDash(); + } + } + $this->SetDash(); + $this->SetAlpha(1); + } + + /* -- END HTML-CSS -- */ + + function Reset() + { + $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings)); + $this->SetAlpha(1); + $this->colorarray = ''; + + $this->spanbgcolorarray = ''; + $this->spanbgcolor = false; + $this->spanborder = false; + $this->spanborddet = []; + + $this->ResetStyles(); + + $this->HREF = ''; + $this->textparam = []; + $this->SetTextOutline(); + + $this->textvar = 0x00; // mPDF 5.7.1 + $this->OTLtags = []; + $this->textshadow = ''; + + $this->currentLang = $this->default_lang; // mPDF 6 + $this->RestrictUnicodeFonts($this->default_available_fonts); // mPDF 6 + $this->SetFont($this->default_font, '', 0, false); + $this->SetFontSize($this->default_font_size, false); + + $this->currentfontfamily = ''; + $this->currentfontsize = ''; + $this->currentfontstyle = ''; + + /* -- TABLES -- */ + if ($this->tableLevel && isset($this->table[1][1]['cellLineHeight'])) { + $this->SetLineHeight('', $this->table[1][1]['cellLineHeight']); // *TABLES* + } else { /* -- END TABLES -- */ + if (isset($this->blk[$this->blklvl]['line_height']) && $this->blk[$this->blklvl]['line_height']) { + $this->SetLineHeight('', $this->blk[$this->blklvl]['line_height']); // sets default line height + } + } + + $this->lSpacingCSS = ''; + $this->wSpacingCSS = ''; + $this->fixedlSpacing = false; + $this->minwSpacing = 0; + $this->SetDash(); // restore to no dash + $this->dash_on = false; + $this->dotted_on = false; + $this->divwidth = 0; + $this->divheight = 0; + $this->cellTextAlign = ''; + $this->cellLineHeight = ''; + $this->cellLineStackingStrategy = ''; + $this->cellLineStackingShift = ''; + $this->oldy = -1; + + $bodystyle = []; + if (isset($this->cssManager->CSS['BODY']['FONT-STYLE'])) { + $bodystyle['FONT-STYLE'] = $this->cssManager->CSS['BODY']['FONT-STYLE']; + } + if (isset($this->cssManager->CSS['BODY']['FONT-WEIGHT'])) { + $bodystyle['FONT-WEIGHT'] = $this->cssManager->CSS['BODY']['FONT-WEIGHT']; + } + if (isset($this->cssManager->CSS['BODY']['COLOR'])) { + $bodystyle['COLOR'] = $this->cssManager->CSS['BODY']['COLOR']; + } + if (isset($bodystyle)) { + $this->setCSS($bodystyle, 'BLOCK', 'BODY'); + } + } + + /* -- HTML-CSS -- */ + + function ReadMetaTags($html) + { + // changes anykey=anyvalue to anykey="anyvalue" (only do this when this happens inside tags) + $regexp = '/ (\\w+?)=([^\\s>"]+)/si'; + $html = preg_replace($regexp, " \$1=\"\$2\"", $html); + if (preg_match('/(.*?)<\/title>/si', $html, $m)) { + $this->SetTitle($m[1]); + } + preg_match_all('/<meta [^>]*?(name|content)="([^>]*?)" [^>]*?(name|content)="([^>]*?)".*?>/si', $html, $aux); + $firstattr = $aux[1]; + $secondattr = $aux[3]; + for ($i = 0; $i < count($aux[0]); $i++) { + $name = ( strtoupper($firstattr[$i]) == "NAME" ) ? strtoupper($aux[2][$i]) : strtoupper($aux[4][$i]); + $content = ( strtoupper($firstattr[$i]) == "CONTENT" ) ? $aux[2][$i] : $aux[4][$i]; + switch ($name) { + case "KEYWORDS": + $this->SetKeywords($content); + break; + case "AUTHOR": + $this->SetAuthor($content); + break; + case "DESCRIPTION": + $this->SetSubject($content); + break; + } + } + } + + function ReadCharset($html) + { + // Charset conversion + if ($this->allow_charset_conversion) { + if (preg_match('/<head.*charset=([^\'\"\s]*).*<\/head>/si', $html, $m)) { + if (strtoupper($m[1]) != 'UTF-8') { + $this->charset_in = strtoupper($m[1]); + } + } + } + } + + function setCSS($arrayaux, $type = '', $tag = '') + { + // type= INLINE | BLOCK | TABLECELL // tag= BODY + if (!is_array($arrayaux)) { + return; // Removes PHP Warning + } + + // mPDF 5.7.3 inline text-decoration parameters + $preceeding_fontkey = $this->FontFamily . $this->FontStyle; + $preceeding_fontsize = $this->FontSize; + $spanbordset = false; + $spanbgset = false; + // mPDF 6 + $prevlevel = (($this->blklvl == 0) ? 0 : $this->blklvl - 1); + + // Set font size first so that e.g. MARGIN 0.83em works on font size for this element + if (isset($arrayaux['FONT-SIZE'])) { + $v = $arrayaux['FONT-SIZE']; + if (is_numeric($v[0]) || ($v[0] === '.')) { + if ($type == 'BLOCK' && $this->blklvl > 0 && isset($this->blk[$this->blklvl - 1]['InlineProperties']) && isset($this->blk[$this->blklvl - 1]['InlineProperties']['size'])) { + $mmsize = $this->sizeConverter->convert($v, $this->blk[$this->blklvl - 1]['InlineProperties']['size']); + } elseif ($type == 'TABLECELL') { + $mmsize = $this->sizeConverter->convert($v, $this->default_font_size / Mpdf::SCALE); + } else { + $mmsize = $this->sizeConverter->convert($v, $this->FontSize); + } + $this->SetFontSize($mmsize * (Mpdf::SCALE), false); // Get size in points (pt) + } else { + $v = strtoupper($v); + if (isset($this->fontsizes[$v])) { + $this->SetFontSize($this->fontsizes[$v] * $this->default_font_size, false); + } + } + if ($tag == 'BODY') { + $this->SetDefaultFontSize($this->FontSizePt); + } + } + + // mPDF 6 + if (isset($arrayaux['LANG']) && $arrayaux['LANG']) { + if ($this->autoLangToFont && !$this->usingCoreFont) { + if ($arrayaux['LANG'] != $this->default_lang && $arrayaux['LANG'] != 'UTF-8') { + list ($coreSuitable, $mpdf_pdf_unifont) = $this->languageToFont->getLanguageOptions($arrayaux['LANG'], $this->useAdobeCJK); + if ($mpdf_pdf_unifont) { + $arrayaux['FONT-FAMILY'] = $mpdf_pdf_unifont; + } + if ($tag == 'BODY') { + $this->default_lang = $arrayaux['LANG']; + } + } + } + $this->currentLang = $arrayaux['LANG']; + } + + // FOR INLINE and BLOCK OR 'BODY' + if (isset($arrayaux['FONT-FAMILY'])) { + $v = $arrayaux['FONT-FAMILY']; + // If it is a font list, get all font types + $aux_fontlist = explode(",", $v); + $found = 0; + foreach ($aux_fontlist as $f) { + $fonttype = trim($f); + $fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype); + $fonttype = preg_replace('/ /', '', $fonttype); + $v = strtolower(trim($fonttype)); + if (isset($this->fonttrans[$v]) && $this->fonttrans[$v]) { + $v = $this->fonttrans[$v]; + } + if ((!$this->onlyCoreFonts && in_array($v, $this->available_unifonts)) || + in_array($v, ['ccourier', 'ctimes', 'chelvetica']) || + ($this->onlyCoreFonts && in_array($v, ['courier', 'times', 'helvetica', 'arial'])) || + in_array($v, ['sjis', 'uhc', 'big5', 'gb'])) { + $fonttype = $v; + $found = 1; + break; + } + } + if (!$found) { + foreach ($aux_fontlist as $f) { + $fonttype = trim($f); + $fonttype = preg_replace('/["\']*(.*?)["\']*/', '\\1', $fonttype); + $fonttype = preg_replace('/ /', '', $fonttype); + $v = strtolower(trim($fonttype)); + if (isset($this->fonttrans[$v]) && $this->fonttrans[$v]) { + $v = $this->fonttrans[$v]; + } + if (in_array($v, $this->sans_fonts) || in_array($v, $this->serif_fonts) || in_array($v, $this->mono_fonts)) { + $fonttype = $v; + break; + } + } + } + + if ($tag == 'BODY') { + $this->SetDefaultFont($fonttype); + } + $this->SetFont($fonttype, $this->currentfontstyle, 0, false); + } else { + $this->SetFont($this->currentfontfamily, $this->currentfontstyle, 0, false); + } + + foreach ($arrayaux as $k => $v) { + if ($type != 'INLINE' && $tag != 'BODY' && $type != 'TABLECELL') { + switch ($k) { + // BORDERS + case 'BORDER-TOP': + $this->blk[$this->blklvl]['border_top'] = $this->border_details($v); + if ($this->blk[$this->blklvl]['border_top']['s']) { + $this->blk[$this->blklvl]['border'] = 1; + } + break; + case 'BORDER-BOTTOM': + $this->blk[$this->blklvl]['border_bottom'] = $this->border_details($v); + if ($this->blk[$this->blklvl]['border_bottom']['s']) { + $this->blk[$this->blklvl]['border'] = 1; + } + break; + case 'BORDER-LEFT': + $this->blk[$this->blklvl]['border_left'] = $this->border_details($v); + if ($this->blk[$this->blklvl]['border_left']['s']) { + $this->blk[$this->blklvl]['border'] = 1; + } + break; + case 'BORDER-RIGHT': + $this->blk[$this->blklvl]['border_right'] = $this->border_details($v); + if ($this->blk[$this->blklvl]['border_right']['s']) { + $this->blk[$this->blklvl]['border'] = 1; + } + break; + + // PADDING + case 'PADDING-TOP': + $this->blk[$this->blklvl]['padding_top'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'PADDING-BOTTOM': + $this->blk[$this->blklvl]['padding_bottom'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'PADDING-LEFT': + if (($tag == 'UL' || $tag == 'OL') && $v == 'auto') { + $this->blk[$this->blklvl]['padding_left'] = 'auto'; + break; + } + $this->blk[$this->blklvl]['padding_left'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'PADDING-RIGHT': + if (($tag == 'UL' || $tag == 'OL') && $v == 'auto') { + $this->blk[$this->blklvl]['padding_right'] = 'auto'; + break; + } + $this->blk[$this->blklvl]['padding_right'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + + // MARGINS + case 'MARGIN-TOP': + $tmp = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + if (isset($this->blk[$this->blklvl]['lastbottommargin'])) { + if ($tmp > $this->blk[$this->blklvl]['lastbottommargin']) { + $tmp -= $this->blk[$this->blklvl]['lastbottommargin']; + } else { + $tmp = 0; + } + } + $this->blk[$this->blklvl]['margin_top'] = $tmp; + break; + case 'MARGIN-BOTTOM': + $this->blk[$this->blklvl]['margin_bottom'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'MARGIN-LEFT': + $this->blk[$this->blklvl]['margin_left'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'MARGIN-RIGHT': + $this->blk[$this->blklvl]['margin_right'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + + /* -- BORDER-RADIUS -- */ + case 'BORDER-TOP-LEFT-RADIUS-H': + $this->blk[$this->blklvl]['border_radius_TL_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'BORDER-TOP-LEFT-RADIUS-V': + $this->blk[$this->blklvl]['border_radius_TL_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'BORDER-TOP-RIGHT-RADIUS-H': + $this->blk[$this->blklvl]['border_radius_TR_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'BORDER-TOP-RIGHT-RADIUS-V': + $this->blk[$this->blklvl]['border_radius_TR_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'BORDER-BOTTOM-LEFT-RADIUS-H': + $this->blk[$this->blklvl]['border_radius_BL_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'BORDER-BOTTOM-LEFT-RADIUS-V': + $this->blk[$this->blklvl]['border_radius_BL_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'BORDER-BOTTOM-RIGHT-RADIUS-H': + $this->blk[$this->blklvl]['border_radius_BR_H'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + case 'BORDER-BOTTOM-RIGHT-RADIUS-V': + $this->blk[$this->blklvl]['border_radius_BR_V'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + break; + /* -- END BORDER-RADIUS -- */ + + case 'BOX-SHADOW': + $bs = $this->cssManager->setCSSboxshadow($v); + if ($bs) { + $this->blk[$this->blklvl]['box_shadow'] = $bs; + } + break; + + case 'BACKGROUND-CLIP': + if (strtoupper($v) == 'PADDING-BOX') { + $this->blk[$this->blklvl]['background_clip'] = 'padding-box'; + } elseif (strtoupper($v) == 'CONTENT-BOX') { + $this->blk[$this->blklvl]['background_clip'] = 'content-box'; + } + break; + + case 'PAGE-BREAK-AFTER': + if (strtoupper($v) == 'AVOID') { + $this->blk[$this->blklvl]['page_break_after_avoid'] = true; + } elseif (strtoupper($v) == 'ALWAYS' || strtoupper($v) == 'LEFT' || strtoupper($v) == 'RIGHT') { + $this->blk[$this->blklvl]['page_break_after'] = strtoupper($v); + } + break; + + // mPDF 6 pagebreaktype + case 'BOX-DECORATION-BREAK': + if (strtoupper($v) == 'CLONE') { + $this->blk[$this->blklvl]['box_decoration_break'] = 'clone'; + } elseif (strtoupper($v) == 'SLICE') { + $this->blk[$this->blklvl]['box_decoration_break'] = 'slice'; + } + break; + + case 'WIDTH': + if (strtoupper($v) != 'AUTO') { + $this->blk[$this->blklvl]['css_set_width'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false); + } + break; + + // mPDF 6 Lists + // LISTS + case 'LIST-STYLE-TYPE': + $this->blk[$this->blklvl]['list_style_type'] = strtolower($v); + break; + case 'LIST-STYLE-IMAGE': + $this->blk[$this->blklvl]['list_style_image'] = strtolower($v); + break; + case 'LIST-STYLE-POSITION': + $this->blk[$this->blklvl]['list_style_position'] = strtolower($v); + break; + }//end of switch($k) + } + + + if ($type != 'INLINE' && $type != 'TABLECELL') { // All block-level, including BODY tag + switch ($k) { + case 'TEXT-INDENT': + // Computed value - to inherit + $this->blk[$this->blklvl]['text_indent'] = $this->sizeConverter->convert($v, $this->blk[$prevlevel]['inner_width'], $this->FontSize, false) . 'mm'; + break; + + case 'MARGIN-COLLAPSE': // Custom tag to collapse margins at top and bottom of page + if (strtoupper($v) == 'COLLAPSE') { + $this->blk[$this->blklvl]['margin_collapse'] = true; + } + break; + + case 'LINE-HEIGHT': + $this->blk[$this->blklvl]['line_height'] = $this->fixLineheight($v); + if (!$this->blk[$this->blklvl]['line_height']) { + $this->blk[$this->blklvl]['line_height'] = 'N'; + } // mPDF 6 + break; + + // mPDF 6 + case 'LINE-STACKING-STRATEGY': + $this->blk[$this->blklvl]['line_stacking_strategy'] = strtolower($v); + break; + + case 'LINE-STACKING-SHIFT': + $this->blk[$this->blklvl]['line_stacking_shift'] = strtolower($v); + break; + + case 'TEXT-ALIGN': // left right center justify + switch (strtoupper($v)) { + case 'LEFT': + $this->blk[$this->blklvl]['align'] = "L"; + break; + case 'CENTER': + $this->blk[$this->blklvl]['align'] = "C"; + break; + case 'RIGHT': + $this->blk[$this->blklvl]['align'] = "R"; + break; + case 'JUSTIFY': + $this->blk[$this->blklvl]['align'] = "J"; + break; + } + break; + + /* -- BACKGROUNDS -- */ + case 'BACKGROUND-GRADIENT': + if ($type == 'BLOCK') { + $this->blk[$this->blklvl]['gradient'] = $v; + } + break; + /* -- END BACKGROUNDS -- */ + + case 'DIRECTION': + if ($v) { + $this->blk[$this->blklvl]['direction'] = strtolower($v); + } + break; + } + } + + // FOR INLINE ONLY + if ($type == 'INLINE') { + switch ($k) { + case 'DISPLAY': + if (strtoupper($v) == 'NONE') { + $this->inlineDisplayOff = true; + } + break; + case 'DIRECTION': + break; + } + } + // FOR INLINE ONLY + if ($type == 'INLINE') { + switch ($k) { + // BORDERS + case 'BORDER-TOP': + $this->spanborddet['T'] = $this->border_details($v); + $this->spanborder = true; + $spanbordset = true; + break; + case 'BORDER-BOTTOM': + $this->spanborddet['B'] = $this->border_details($v); + $this->spanborder = true; + $spanbordset = true; + break; + case 'BORDER-LEFT': + $this->spanborddet['L'] = $this->border_details($v); + $this->spanborder = true; + $spanbordset = true; + break; + case 'BORDER-RIGHT': + $this->spanborddet['R'] = $this->border_details($v); + $this->spanborder = true; + $spanbordset = true; + break; + case 'VISIBILITY': // block is set in OpenTag + $v = strtolower($v); + if ($v == 'visible' || $v == 'hidden' || $v == 'printonly' || $v == 'screenonly') { + $this->textparam['visibility'] = $v; + } + break; + }//end of switch($k) + } + + if ($type != 'TABLECELL') { + // FOR INLINE and BLOCK + switch ($k) { + case 'TEXT-ALIGN': // left right center justify + if (strtoupper($v) == 'NOJUSTIFY' && $this->blk[$this->blklvl]['align'] == "J") { + $this->blk[$this->blklvl]['align'] = ""; + } + break; + // bgcolor only - to stay consistent with original html2fpdf + case 'BACKGROUND': + case 'BACKGROUND-COLOR': + $cor = $this->colorConverter->convert($v, $this->PDFAXwarnings); + if ($cor) { + if ($tag == 'BODY') { + $this->bodyBackgroundColor = $cor; + } elseif ($type == 'INLINE') { + $this->spanbgcolorarray = $cor; + $this->spanbgcolor = true; + $spanbgset = true; + } else { + $this->blk[$this->blklvl]['bgcolorarray'] = $cor; + $this->blk[$this->blklvl]['bgcolor'] = true; + } + } elseif ($type != 'INLINE') { + if ($this->ColActive) { + $this->blk[$this->blklvl]['bgcolorarray'] = $this->blk[$prevlevel]['bgcolorarray']; + $this->blk[$this->blklvl]['bgcolor'] = $this->blk[$prevlevel]['bgcolor']; + } + } + break; + + case 'VERTICAL-ALIGN': // super and sub only dealt with here e.g. <SUB> and <SUP> + switch (strtoupper($v)) { + case 'SUPER': + $this->textvar = ($this->textvar | TextVars::FA_SUPERSCRIPT); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FA_SUBSCRIPT); + // mPDF 5.7.3 inline text-decoration parameters + if (isset($this->textparam['text-baseline'])) { + $this->textparam['text-baseline'] += ($this->baselineSup) * $preceeding_fontsize; + } else { + $this->textparam['text-baseline'] = ($this->baselineSup) * $preceeding_fontsize; + } + break; + case 'SUB': + $this->textvar = ($this->textvar | TextVars::FA_SUBSCRIPT); + $this->textvar = ($this->textvar & ~TextVars::FA_SUPERSCRIPT); + // mPDF 5.7.3 inline text-decoration parameters + if (isset($this->textparam['text-baseline'])) { + $this->textparam['text-baseline'] += ($this->baselineSub) * $preceeding_fontsize; + } else { + $this->textparam['text-baseline'] = ($this->baselineSub) * $preceeding_fontsize; + } + break; + case 'BASELINE': + $this->textvar = ($this->textvar & ~TextVars::FA_SUBSCRIPT); + $this->textvar = ($this->textvar & ~TextVars::FA_SUPERSCRIPT); + // mPDF 5.7.3 inline text-decoration parameters + if (isset($this->textparam['text-baseline'])) { + unset($this->textparam['text-baseline']); + } + break; + // mPDF 5.7.3 inline text-decoration parameters + default: + $lh = $this->_computeLineheight($this->blk[$this->blklvl]['line_height']); + $sz = $this->sizeConverter->convert($v, $lh, $this->FontSize, false); + $this->textvar = ($this->textvar & ~TextVars::FA_SUBSCRIPT); + $this->textvar = ($this->textvar & ~TextVars::FA_SUPERSCRIPT); + if ($sz) { + if ($sz > 0) { + $this->textvar = ($this->textvar | TextVars::FA_SUPERSCRIPT); + } else { + $this->textvar = ($this->textvar | TextVars::FA_SUBSCRIPT); + } + if (isset($this->textparam['text-baseline'])) { + $this->textparam['text-baseline'] += $sz; + } else { + $this->textparam['text-baseline'] = $sz; + } + } + } + break; + }//end of switch($k) + } + + + // FOR ALL + switch ($k) { + case 'LETTER-SPACING': + $this->lSpacingCSS = $v; + if (($this->lSpacingCSS || $this->lSpacingCSS === '0') && strtoupper($this->lSpacingCSS) != 'NORMAL') { + $this->fixedlSpacing = $this->sizeConverter->convert($this->lSpacingCSS, $this->FontSize); + } + break; + + case 'WORD-SPACING': + $this->wSpacingCSS = $v; + if ($this->wSpacingCSS && strtoupper($this->wSpacingCSS) != 'NORMAL') { + $this->minwSpacing = $this->sizeConverter->convert($this->wSpacingCSS, $this->FontSize); + } + break; + + case 'FONT-STYLE': // italic normal oblique + switch (strtoupper($v)) { + case 'ITALIC': + case 'OBLIQUE': + $this->SetStyle('I', true); + break; + case 'NORMAL': + $this->SetStyle('I', false); + break; + } + break; + + case 'FONT-WEIGHT': // normal bold // Does not support: bolder, lighter, 100..900(step value=100) + switch (strtoupper($v)) { + case 'BOLD': + $this->SetStyle('B', true); + break; + case 'NORMAL': + $this->SetStyle('B', false); + break; + } + break; + + case 'FONT-KERNING': + if (strtoupper($v) == 'NORMAL' || (strtoupper($v) == 'AUTO' && $this->useKerning)) { + /* -- OTL -- */ + if ($this->CurrentFont['haskernGPOS']) { + if (isset($this->OTLtags['Plus'])) { + $this->OTLtags['Plus'] .= ' kern'; + } else { + $this->OTLtags['Plus'] = ' kern'; + } + } /* -- END OTL -- */ else { // *OTL* + $this->textvar = ($this->textvar | TextVars::FC_KERNING); + } // *OTL* + } elseif (strtoupper($v) == 'NONE' || (strtoupper($v) == 'AUTO' && !$this->useKerning)) { + if (isset($this->OTLtags['Plus'])) { + $this->OTLtags['Plus'] = str_replace('kern', '', $this->OTLtags['Plus']); // *OTL* + } + if (isset($this->OTLtags['FFPlus'])) { + $this->OTLtags['FFPlus'] = preg_replace('/kern[\d]*/', '', $this->OTLtags['FFPlus']); + } + $this->textvar = ($this->textvar & ~TextVars::FC_KERNING); + } + break; + + /* -- OTL -- */ + case 'FONT-LANGUAGE-OVERRIDE': + $v = strtoupper($v); + if (strpos($v, 'NORMAL') !== false) { + $this->fontLanguageOverride = ''; + } else { + $this->fontLanguageOverride = trim($v); + } + break; + + + case 'FONT-VARIANT-POSITION': + if (isset($this->OTLtags['Plus'])) { + $this->OTLtags['Plus'] = str_replace(['sups', 'subs'], '', $this->OTLtags['Plus']); + } + switch (strtoupper($v)) { + case 'SUPER': + $this->OTLtags['Plus'] .= ' sups'; + break; + case 'SUB': + $this->OTLtags['Plus'] .= ' subs'; + break; + case 'NORMAL': + break; + } + break; + + case 'FONT-VARIANT-CAPS': + $v = strtoupper($v); + if (!isset($this->OTLtags['Plus'])) { + $this->OTLtags['Plus'] = ''; + } + $this->OTLtags['Plus'] = str_replace(['c2sc', 'smcp', 'c2pc', 'pcap', 'unic', 'titl'], '', $this->OTLtags['Plus']); + $this->textvar = ($this->textvar & ~TextVars::FC_SMALLCAPS); // ?????????????? <small-caps> + if (strpos($v, 'ALL-SMALL-CAPS') !== false) { + $this->OTLtags['Plus'] .= ' c2sc smcp'; + } elseif (strpos($v, 'SMALL-CAPS') !== false) { + if (isset($this->CurrentFont['hassmallcapsGSUB']) && $this->CurrentFont['hassmallcapsGSUB']) { + $this->OTLtags['Plus'] .= ' smcp'; + } else { + $this->textvar = ($this->textvar | TextVars::FC_SMALLCAPS); + } + } elseif (strpos($v, 'ALL-PETITE-CAPS') !== false) { + $this->OTLtags['Plus'] .= ' c2pc pcap'; + } elseif (strpos($v, 'PETITE-CAPS') !== false) { + $this->OTLtags['Plus'] .= ' pcap'; + } elseif (strpos($v, 'UNICASE') !== false) { + $this->OTLtags['Plus'] .= ' unic'; + } elseif (strpos($v, 'TITLING-CAPS') !== false) { + $this->OTLtags['Plus'] .= ' titl'; + } + break; + + case 'FONT-VARIANT-LIGATURES': + $v = strtoupper($v); + if (!isset($this->OTLtags['Plus'])) { + $this->OTLtags['Plus'] = ''; + } + if (!isset($this->OTLtags['Minus'])) { + $this->OTLtags['Minus'] = ''; + } + if (strpos($v, 'NORMAL') !== false) { + $this->OTLtags['Minus'] = str_replace(['liga', 'clig', 'calt'], '', $this->OTLtags['Minus']); + $this->OTLtags['Plus'] = str_replace(['dlig', 'hlig'], '', $this->OTLtags['Plus']); + } elseif (strpos($v, 'NONE') !== false) { + $this->OTLtags['Minus'] .= ' liga clig calt'; + $this->OTLtags['Plus'] = str_replace(['dlig', 'hlig'], '', $this->OTLtags['Plus']); + } + if (strpos($v, 'NO-COMMON-LIGATURES') !== false) { + $this->OTLtags['Minus'] .= ' liga clig'; + } elseif (strpos($v, 'COMMON-LIGATURES') !== false) { + $this->OTLtags['Minus'] = str_replace(['liga', 'clig'], '', $this->OTLtags['Minus']); + } + if (strpos($v, 'NO-CONTEXTUAL') !== false) { + $this->OTLtags['Minus'] .= ' calt'; + } elseif (strpos($v, 'CONTEXTUAL') !== false) { + $this->OTLtags['Minus'] = str_replace('calt', '', $this->OTLtags['Minus']); + } + if (strpos($v, 'NO-DISCRETIONARY-LIGATURES') !== false) { + $this->OTLtags['Plus'] = str_replace('dlig', '', $this->OTLtags['Plus']); + } elseif (strpos($v, 'DISCRETIONARY-LIGATURES') !== false) { + $this->OTLtags['Plus'] .= ' dlig'; + } + if (strpos($v, 'NO-HISTORICAL-LIGATURES') !== false) { + $this->OTLtags['Plus'] = str_replace('hlig', '', $this->OTLtags['Plus']); + } elseif (strpos($v, 'HISTORICAL-LIGATURES') !== false) { + $this->OTLtags['Plus'] .= ' hlig'; + } + + break; + + case 'FONT-VARIANT-NUMERIC': + $v = strtoupper($v); + if (!isset($this->OTLtags['Plus'])) { + $this->OTLtags['Plus'] = ''; + } + if (strpos($v, 'NORMAL') !== false) { + $this->OTLtags['Plus'] = str_replace(['ordn', 'zero', 'lnum', 'onum', 'pnum', 'tnum', 'frac', 'afrc'], '', $this->OTLtags['Plus']); + } + if (strpos($v, 'ORDINAL') !== false) { + $this->OTLtags['Plus'] .= ' ordn'; + } + if (strpos($v, 'SLASHED-ZERO') !== false) { + $this->OTLtags['Plus'] .= ' zero'; + } + if (strpos($v, 'LINING-NUMS') !== false) { + $this->OTLtags['Plus'] .= ' lnum'; + $this->OTLtags['Plus'] = str_replace('onum', '', $this->OTLtags['Plus']); + } elseif (strpos($v, 'OLDSTYLE-NUMS') !== false) { + $this->OTLtags['Plus'] .= ' onum'; + $this->OTLtags['Plus'] = str_replace('lnum', '', $this->OTLtags['Plus']); + } + if (strpos($v, 'PROPORTIONAL-NUMS') !== false) { + $this->OTLtags['Plus'] .= ' pnum'; + $this->OTLtags['Plus'] = str_replace('tnum', '', $this->OTLtags['Plus']); + } elseif (strpos($v, 'TABULAR-NUMS') !== false) { + $this->OTLtags['Plus'] .= ' tnum'; + $this->OTLtags['Plus'] = str_replace('pnum', '', $this->OTLtags['Plus']); + } + if (strpos($v, 'DIAGONAL-FRACTIONS') !== false) { + $this->OTLtags['Plus'] .= ' frac'; + $this->OTLtags['Plus'] = str_replace('afrc', '', $this->OTLtags['Plus']); + } elseif (strpos($v, 'STACKED-FRACTIONS') !== false) { + $this->OTLtags['Plus'] .= ' afrc'; + $this->OTLtags['Plus'] = str_replace('frac', '', $this->OTLtags['Plus']); + } + break; + + case 'FONT-VARIANT-ALTERNATES': // Only supports historical-forms + $v = strtoupper($v); + if (!isset($this->OTLtags['Plus'])) { + $this->OTLtags['Plus'] = ''; + } + if (strpos($v, 'NORMAL') !== false) { + $this->OTLtags['Plus'] = str_replace('hist', '', $this->OTLtags['Plus']); + } + if (strpos($v, 'HISTORICAL-FORMS') !== false) { + $this->OTLtags['Plus'] .= ' hist'; + } + break; + + + case 'FONT-FEATURE-SETTINGS': + $v = strtolower($v); + if (strpos($v, 'normal') !== false) { + $this->OTLtags['FFMinus'] = ''; + $this->OTLtags['FFPlus'] = ''; + } else { + if (!isset($this->OTLtags['FFPlus'])) { + $this->OTLtags['FFPlus'] = ''; + } + if (!isset($this->OTLtags['FFMinus'])) { + $this->OTLtags['FFMinus'] = ''; + } + $tags = preg_split('/[,]/', $v); + foreach ($tags as $t) { + if (preg_match('/[\"\']([a-zA-Z0-9]{4})[\"\']\s*(on|off|\d*){0,1}/', $t, $m)) { + if ($m[2] == 'off' || $m[2] === '0') { + if (strpos($this->OTLtags['FFMinus'], $m[1]) === false) { + $this->OTLtags['FFMinus'] .= ' ' . $m[1]; + } + $this->OTLtags['FFPlus'] = preg_replace('/' . $m[1] . '[\d]*/', '', $this->OTLtags['FFPlus']); + } else { + if ($m[2] == 'on') { + $m[2] = '1'; + } + if (strpos($this->OTLtags['FFPlus'], $m[1]) === false) { + $this->OTLtags['FFPlus'] .= ' ' . $m[1] . $m[2]; + } + $this->OTLtags['FFMinus'] = str_replace($m[1], '', $this->OTLtags['FFMinus']); + } + } + } + } + break; + /* -- END OTL -- */ + + + case 'TEXT-TRANSFORM': // none uppercase lowercase // Does support: capitalize + switch (strtoupper($v)) { // Not working 100% + case 'CAPITALIZE': + $this->textvar = ($this->textvar | TextVars::FT_CAPITALIZE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_UPPERCASE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_LOWERCASE); // mPDF 5.7.1 + break; + case 'UPPERCASE': + $this->textvar = ($this->textvar | TextVars::FT_UPPERCASE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_LOWERCASE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_CAPITALIZE); // mPDF 5.7.1 + break; + case 'LOWERCASE': + $this->textvar = ($this->textvar | TextVars::FT_LOWERCASE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_UPPERCASE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_CAPITALIZE); // mPDF 5.7.1 + break; + case 'NONE': + break; + $this->textvar = ($this->textvar & ~TextVars::FT_UPPERCASE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_LOWERCASE); // mPDF 5.7.1 + $this->textvar = ($this->textvar & ~TextVars::FT_CAPITALIZE); // mPDF 5.7.1 + } + break; + + case 'TEXT-SHADOW': + $ts = $this->cssManager->setCSStextshadow($v); + if ($ts) { + $this->textshadow = $ts; + } + break; + + case 'HYPHENS': + if (strtoupper($v) == 'NONE') { + $this->textparam['hyphens'] = 2; + } elseif (strtoupper($v) == 'AUTO') { + $this->textparam['hyphens'] = 1; + } elseif (strtoupper($v) == 'MANUAL') { + $this->textparam['hyphens'] = 0; + } + break; + + case 'TEXT-OUTLINE': + if (strtoupper($v) == 'NONE') { + $this->textparam['outline-s'] = false; + } + break; + + case 'TEXT-OUTLINE-WIDTH': + case 'OUTLINE-WIDTH': + switch (strtoupper($v)) { + case 'THIN': + $v = '0.03em'; + break; + case 'MEDIUM': + $v = '0.05em'; + break; + case 'THICK': + $v = '0.07em'; + break; + } + $w = $this->sizeConverter->convert($v, $this->FontSize, $this->FontSize); + if ($w) { + $this->textparam['outline-WIDTH'] = $w; + $this->textparam['outline-s'] = true; + } else { + $this->textparam['outline-s'] = false; + } + break; + + case 'TEXT-OUTLINE-COLOR': + case 'OUTLINE-COLOR': + if (strtoupper($v) == 'INVERT') { + if ($this->colorarray) { + $cor = $this->colorarray; + $this->textparam['outline-COLOR'] = $this->colorConverter->invert($cor); + } else { + $this->textparam['outline-COLOR'] = $this->colorConverter->convert(255, $this->PDFAXwarnings); + } + } else { + $cor = $this->colorConverter->convert($v, $this->PDFAXwarnings); + if ($cor) { + $this->textparam['outline-COLOR'] = $cor; + } + } + break; + + case 'COLOR': // font color + $cor = $this->colorConverter->convert($v, $this->PDFAXwarnings); + if ($cor) { + $this->colorarray = $cor; + $this->SetTColor($cor); + } + break; + }//end of switch($k) + }//end of foreach + // mPDF 5.7.3 inline text-decoration parameters + // Needs to be set at the end - after vertical-align = super/sub, so that textparam['text-baseline'] is set + if (isset($arrayaux['TEXT-DECORATION'])) { + $v = $arrayaux['TEXT-DECORATION']; // none underline line-through (strikeout) // Does not support: blink + if (stristr($v, 'LINE-THROUGH')) { + $this->textvar = ($this->textvar | TextVars::FD_LINETHROUGH); + // mPDF 5.7.3 inline text-decoration parameters + if (isset($this->textparam['text-baseline'])) { + $this->textparam['s-decoration']['baseline'] = $this->textparam['text-baseline']; + } else { + $this->textparam['s-decoration']['baseline'] = 0; + } + $this->textparam['s-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle; + $this->textparam['s-decoration']['fontsize'] = $this->FontSize; + $this->textparam['s-decoration']['color'] = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG + } + if (stristr($v, 'UNDERLINE')) { + $this->textvar = ($this->textvar | TextVars::FD_UNDERLINE); + // mPDF 5.7.3 inline text-decoration parameters + if (isset($this->textparam['text-baseline'])) { + $this->textparam['u-decoration']['baseline'] = $this->textparam['text-baseline']; + } else { + $this->textparam['u-decoration']['baseline'] = 0; + } + $this->textparam['u-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle; + $this->textparam['u-decoration']['fontsize'] = $this->FontSize; + $this->textparam['u-decoration']['color'] = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG + } + if (stristr($v, 'OVERLINE')) { + $this->textvar = ($this->textvar | TextVars::FD_OVERLINE); + // mPDF 5.7.3 inline text-decoration parameters + if (isset($this->textparam['text-baseline'])) { + $this->textparam['o-decoration']['baseline'] = $this->textparam['text-baseline']; + } else { + $this->textparam['o-decoration']['baseline'] = 0; + } + $this->textparam['o-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle; + $this->textparam['o-decoration']['fontsize'] = $this->FontSize; + $this->textparam['o-decoration']['color'] = strtoupper($this->TextColor); // change 0 0 0 rg to 0 0 0 RG + } + if (stristr($v, 'NONE')) { + $this->textvar = ($this->textvar & ~TextVars::FD_UNDERLINE); + $this->textvar = ($this->textvar & ~TextVars::FD_LINETHROUGH); + $this->textvar = ($this->textvar & ~TextVars::FD_OVERLINE); + // mPDF 5.7.3 inline text-decoration parameters + if (isset($this->textparam['u-decoration'])) { + unset($this->textparam['u-decoration']); + } + if (isset($this->textparam['s-decoration'])) { + unset($this->textparam['s-decoration']); + } + if (isset($this->textparam['o-decoration'])) { + unset($this->textparam['o-decoration']); + } + } + } + // mPDF 6 + if ($spanbordset) { // BORDER has been set on this INLINE element + if (isset($this->textparam['text-baseline'])) { + $this->textparam['bord-decoration']['baseline'] = $this->textparam['text-baseline']; + } else { + $this->textparam['bord-decoration']['baseline'] = 0; + } + $this->textparam['bord-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle; + $this->textparam['bord-decoration']['fontsize'] = $this->FontSize; + } + if ($spanbgset) { // BACKGROUND[-COLOR] has been set on this INLINE element + if (isset($this->textparam['text-baseline'])) { + $this->textparam['bg-decoration']['baseline'] = $this->textparam['text-baseline']; + } else { + $this->textparam['bg-decoration']['baseline'] = 0; + } + $this->textparam['bg-decoration']['fontkey'] = $this->FontFamily . $this->FontStyle; + $this->textparam['bg-decoration']['fontsize'] = $this->FontSize; + } + } + + /* -- END HTML-CSS -- */ + + function SetStyle($tag, $enable) + { + $this->$tag = $enable; + $style = ''; + foreach (['B', 'I'] as $s) { + if ($this->$s) { + $style .= $s; + } + } + $this->currentfontstyle = $style; + $this->SetFont('', $style, 0, false); + } + + // Set multiple styles at one time + function SetStylesArray($arr) + { + $style = ''; + foreach (['B', 'I'] as $s) { + if (isset($arr[$s])) { + if ($arr[$s]) { + $this->$s = true; + $style .= $s; + } else { + $this->$s = false; + } + } elseif ($this->$s) { + $style .= $s; + } + } + $this->currentfontstyle = $style; + $this->SetFont('', $style, 0, false); + } + + // Set multiple styles at one $str e.g. "BI" + function SetStyles($str) + { + $style = ''; + foreach (['B', 'I'] as $s) { + if (strpos($str, $s) !== false) { + $this->$s = true; + $style .= $s; + } else { + $this->$s = false; + } + } + $this->currentfontstyle = $style; + $this->SetFont('', $style, 0, false); + } + + function ResetStyles() + { + foreach (['B', 'I'] as $s) { + $this->$s = false; + } + $this->currentfontstyle = ''; + $this->SetFont('', '', 0, false); + } + + function DisableTags($str = '') + { + if ($str == '') { // enable all tags + // Insert new supported tags in the long string below. + $this->enabledtags = "<a><acronym><address><article><aside><b><bdi><bdo><big><blockquote><br><caption><center><cite><code><del><details><dd><div><dl><dt><em><fieldset><figcaption><figure><font><form><h1><h2><h3><h4><h5><h6><hgroup><hr><i><img><input><ins><kbd><legend><li><main><mark><meter><nav><ol><option><p><pre><progress><q><s><samp><section><select><small><span><strike><strong><sub><summary><sup><table><tbody><td><template><textarea><tfoot><th><thead><time><tr><tt><u><ul><var><footer><header><annotation><bookmark><textcircle><barcode><dottab><indexentry><indexinsert><watermarktext><watermarkimage><tts><ttz><tta><column_break><columnbreak><newcolumn><newpage><page_break><pagebreak><formfeed><columns><toc><tocentry><tocpagebreak><pageheader><pagefooter><setpageheader><setpagefooter><sethtmlpageheader><sethtmlpagefooter>"; + } else { + $str = explode(",", $str); + foreach ($str as $v) { + $this->enabledtags = str_replace(trim($v), '', $this->enabledtags); + } + } + } + + /* -- TABLES -- */ + + function TableCheckMinWidth($maxwidth, $forcewrap = 0, $textbuffer = [], $checkletter = false) + { + // mPDF 6 + $acclength = 0; // mPDF 6 (accumulated length across > 1 chunk) + $acclongest = 0; // mPDF 6 (accumulated length max across > 1 chunk) + $biggestword = 0; + $toonarrow = false; + if ((count($textbuffer) == 0) or ( (count($textbuffer) == 1) && ($textbuffer[0][0] == ''))) { + return 0; + } + + foreach ($textbuffer as $chunk) { + $line = $chunk[0]; + $OTLdata = (isset($chunk[18]) ? $chunk[18] : null); + + // mPDF ITERATION + if ($this->iterationCounter) { + $line = preg_replace('/{iteration ([a-zA-Z0-9_]+)}/', '\\1', $line); + } + + // IMAGES & FORM ELEMENTS + if (substr($line, 0, 3) == "\xbb\xa4\xac") { // inline object - FORM element or IMAGE! + $objattr = $this->_getObjAttr($line); + if ($objattr['type'] != 'hr' && isset($objattr['width']) && ($objattr['width'] / $this->shrin_k) > ($maxwidth + 0.0001)) { + if (($objattr['width'] / $this->shrin_k) > $biggestword) { + $biggestword = ($objattr['width'] / $this->shrin_k); + } + $toonarrow = true; + } + continue; + } + + if ($line == "\n") { + $acclength = 0; // mPDF 6 (accumulated length across > 1 chunk) + continue; + } + $line = trim($line); + if (!empty($OTLdata)) { + $this->otl->trimOTLdata($OTLdata, true, true); + } // *OTL* + // SET FONT SIZE/STYLE from $chunk[n] + // FONTSIZE + if (isset($chunk[11]) and $chunk[11] != '') { + if ($this->shrin_k) { + $this->SetFontSize($chunk[11] / $this->shrin_k, false); + } else { + $this->SetFontSize($chunk[11], false); + } + } + // FONTFAMILY + if (isset($chunk[4]) and $chunk[4] != '') { + $font = $this->SetFont($chunk[4], $this->FontStyle, 0, false); + } + // B I + if (isset($chunk[2]) and $chunk[2] != '') { + $this->SetStyles($chunk[2]); + } + + $lbw = $rbw = 0; // Border widths + if (isset($chunk[16]) && !empty($chunk[16])) { // Border + $this->spanborddet = $chunk[16]; + $lbw = (isset($this->spanborddet['L']['w']) ? $this->spanborddet['L']['w'] : 0); + $rbw = (isset($this->spanborddet['R']['w']) ? $this->spanborddet['R']['w'] : 0); + } + if (isset($chunk[15])) { // Word spacing + $this->wSpacingCSS = $chunk[15]; + if ($this->wSpacingCSS && strtoupper($this->wSpacingCSS) != 'NORMAL') { + $this->minwSpacing = $this->sizeConverter->convert($this->wSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3 + } + } + if (isset($chunk[14])) { // Letter spacing + $this->lSpacingCSS = $chunk[14]; + if (($this->lSpacingCSS || $this->lSpacingCSS === '0') && strtoupper($this->lSpacingCSS) != 'NORMAL') { + $this->fixedlSpacing = $this->sizeConverter->convert($this->lSpacingCSS, $this->FontSize) / $this->shrin_k; // mPDF 5.7.3 + } + } + if (isset($chunk[8])) { // mPDF 5.7.1 + $this->textvar = $chunk[8]; + } + + // mPDF 6 + // If overflow==wrap ($checkletter) OR (No word breaks and contains CJK) + if ($checkletter || (!preg_match('/(\xe2\x80\x8b| )/', trim($line)) && preg_match("/([" . $this->pregCJKchars . "])/u", $line) )) { + if (preg_match("/([" . $this->pregCJKchars . "])/u", $line)) { + $checkCJK = true; + } else { + $checkCJK = false; + } + + $letters = preg_split('//u', $line); + foreach ($letters as $k => $letter) { + // mPDF 6 + if ($checkCJK) { + if (preg_match("/[" . $this->CJKleading . "]/u", $letter) && $k > 0) { + $letter = $letters[$k - 1] . $letter; + } + if (preg_match("/[" . $this->CJKfollowing . "]/u", $letter) && $k < (count($letters) - 1)) { + $letter = $letter . $letters[$k + 1]; + } + } + + $letterwidth = $this->GetStringWidth($letter, false, false, $chunk[8]); // Pass $textvar ($chunk[8]), but do OTLdata here + // so don't have to split OTLdata for each word + if ($k == 0) { + $letterwidth += $lbw; + } + if ($k == (count($letters) - 1)) { + $letterwidth += $rbw; + } + + // Warn user that maxwidth is insufficient + if ($letterwidth > $maxwidth + 0.0001) { + if ($letterwidth > $biggestword) { + $biggestword = $letterwidth; + } + $toonarrow = true; + } + } + } else { + // mPDF 6 + // Need to account for any XAdvance in GPOSinfo (OTLdata = $chunk[18]) + $wordXAdvance = []; + if (isset($chunk[18]) && $chunk[18]) { + preg_match_all('/(\xe2\x80\x8b| )/', $line, $spaces, PREG_OFFSET_CAPTURE); // U+200B Zero Width word boundary, or space + $lastoffset = 0; + $k = -1; // Added so that if no spaces found, "last word" later is calculated for the one and only word + foreach ($spaces[0] as $k => $m) { + $offset = $m[1]; + // ...TableCheckMinWidth... + // At this point, BIDI not applied, Writing direction is not set, and XAdvanceL balances XAdvanceR + for ($n = $lastoffset; $n < $offset; $n++) { + if (isset($chunk[18]['GPOSinfo'][$n]['XAdvanceL'])) { + if (isset($wordXAdvance[$k])) { + $wordXAdvance[$k] += $chunk[18]['GPOSinfo'][$n]['XAdvanceL']; + } else { + $wordXAdvance[$k] = $chunk[18]['GPOSinfo'][$n]['XAdvanceL']; + } + } + } + $lastoffset = $offset + 1; + } + + $k++; // last word + foreach ($chunk[18]['GPOSinfo'] as $n => $gpos) { + if ($n >= $lastoffset && isset($chunk[18]['GPOSinfo'][$n]['XAdvanceL'])) { + if (isset($wordXAdvance[$k])) { + $wordXAdvance[$k] += $chunk[18]['GPOSinfo'][$n]['XAdvanceL']; + } else { + $wordXAdvance[$k] = $chunk[18]['GPOSinfo'][$n]['XAdvanceL']; + } + } + } + } + + $words = preg_split('/(\xe2\x80\x8b| )/', $line); // U+200B Zero Width word boundary, or space + foreach ($words as $k => $word) { + $word = trim($word); + $wordwidth = $this->GetStringWidth($word, false, false, $chunk[8]); // Pass $textvar ($chunk[8]), but do OTLdata here + // so don't have to split OTLdata for each word + if (isset($wordXAdvance[$k])) { + $wordwidth += ($wordXAdvance[$k] * 1000 / $this->CurrentFont['unitsPerEm']) * ($this->FontSize / 1000); + } + if ($k == 0) { + $wordwidth += $lbw; + } + if ($k == (count($words) - 1)) { + $wordwidth += $rbw; + } + + // mPDF 6 + if (count($words) == 1 && substr($chunk[0], 0, 1) != ' ') { + $acclength += $wordwidth; + } elseif (count($words) > 1 && $k == 0 && substr($chunk[0], 0, 1) != ' ') { + $acclength += $wordwidth; + } else { + $acclength = $wordwidth; + } + $acclongest = max($acclongest, $acclength); + if (count($words) == 1 && substr($chunk[0], -1, 1) == ' ') { + $acclength = 0; + } elseif (count($words) > 1 && ($k != (count($words) - 1) || substr($chunk[0], -1, 1) == ' ')) { + $acclength = 0; + } + + // Warn user that maxwidth is insufficient + if ($wordwidth > $maxwidth + 0.0001) { + if ($wordwidth > $biggestword) { + $biggestword = $wordwidth; + } + $toonarrow = true; + } + } + } + + // mPDF 6 Accumulated length of biggest word - across multiple chunks + if ($acclongest > $maxwidth + 0.0001) { + if ($acclongest > $biggestword) { + $biggestword = $acclongest; + } + $toonarrow = true; + } + + // RESET FONT SIZE/STYLE + // RESETTING VALUES + // Now we must deactivate what we have used + if (isset($chunk[2]) and $chunk[2] != '') { + $this->ResetStyles(); + } + if (isset($chunk[4]) and $chunk[4] != '') { + $this->SetFont($this->default_font, $this->FontStyle, 0, false); + } + if (isset($chunk[11]) and $chunk[11] != '') { + $this->SetFontSize($this->default_font_size, false); + } + $this->spanborddet = []; + $this->textvar = 0x00; // mPDF 5.7.1 + $this->OTLtags = []; + $this->lSpacingCSS = ''; + $this->wSpacingCSS = ''; + $this->fixedlSpacing = false; + $this->minwSpacing = 0; + } + + // Return -(wordsize) if word is bigger than maxwidth + // ADDED + if (($toonarrow) && ($this->table_error_report)) { + throw new \Mpdf\MpdfException("Word is too long to fit in table - " . $this->table_error_report_param); + } + if ($toonarrow) { + return -$biggestword; + } else { + return 1; + } + } + + function shrinkTable(&$table, $k) + { + $table['border_spacing_H'] /= $k; + $table['border_spacing_V'] /= $k; + + $table['padding']['T'] /= $k; + $table['padding']['R'] /= $k; + $table['padding']['B'] /= $k; + $table['padding']['L'] /= $k; + + $table['margin']['T'] /= $k; + $table['margin']['R'] /= $k; + $table['margin']['B'] /= $k; + $table['margin']['L'] /= $k; + + $table['border_details']['T']['w'] /= $k; + $table['border_details']['R']['w'] /= $k; + $table['border_details']['B']['w'] /= $k; + $table['border_details']['L']['w'] /= $k; + + if (isset($table['max_cell_border_width']['T'])) { + $table['max_cell_border_width']['T'] /= $k; + } + if (isset($table['max_cell_border_width']['R'])) { + $table['max_cell_border_width']['R'] /= $k; + } + if (isset($table['max_cell_border_width']['B'])) { + $table['max_cell_border_width']['B'] /= $k; + } + if (isset($table['max_cell_border_width']['L'])) { + $table['max_cell_border_width']['L'] /= $k; + } + + if ($this->simpleTables) { + $table['simple']['border_details']['T']['w'] /= $k; + $table['simple']['border_details']['R']['w'] /= $k; + $table['simple']['border_details']['B']['w'] /= $k; + $table['simple']['border_details']['L']['w'] /= $k; + } + + $table['miw'] /= $k; + $table['maw'] /= $k; + + for ($j = 0; $j < $table['nc']; $j++) { // columns + + $table['wc'][$j]['miw'] = isset($table['wc'][$j]['miw']) ? $table['wc'][$j]['miw'] : 0; + $table['wc'][$j]['maw'] = isset($table['wc'][$j]['maw']) ? $table['wc'][$j]['maw'] : 0; + + $table['wc'][$j]['miw'] /= $k; + $table['wc'][$j]['maw'] /= $k; + + if (isset($table['decimal_align'][$j]['maxs0']) && $table['decimal_align'][$j]['maxs0']) { + $table['decimal_align'][$j]['maxs0'] /= $k; + } + + if (isset($table['decimal_align'][$j]['maxs1']) && $table['decimal_align'][$j]['maxs1']) { + $table['decimal_align'][$j]['maxs1'] /= $k; + } + + if (isset($table['wc'][$j]['absmiw']) && $table['wc'][$j]['absmiw']) { + $table['wc'][$j]['absmiw'] /= $k; + } + + for ($i = 0; $i < $table['nr']; $i++) { // rows + + $c = &$table['cells'][$i][$j]; + + if (isset($c) && $c) { + + if (!$this->simpleTables) { + + if ($this->packTableData) { + + $cell = $this->_unpackCellBorder($c['borderbin']); + + $cell['border_details']['T']['w'] /= $k; + $cell['border_details']['R']['w'] /= $k; + $cell['border_details']['B']['w'] /= $k; + $cell['border_details']['L']['w'] /= $k; + $cell['border_details']['mbw']['TL'] /= $k; + $cell['border_details']['mbw']['TR'] /= $k; + $cell['border_details']['mbw']['BL'] /= $k; + $cell['border_details']['mbw']['BR'] /= $k; + $cell['border_details']['mbw']['LT'] /= $k; + $cell['border_details']['mbw']['LB'] /= $k; + $cell['border_details']['mbw']['RT'] /= $k; + $cell['border_details']['mbw']['RB'] /= $k; + + $c['borderbin'] = $this->_packCellBorder($cell); + + } else { + + $c['border_details']['T']['w'] /= $k; + $c['border_details']['R']['w'] /= $k; + $c['border_details']['B']['w'] /= $k; + $c['border_details']['L']['w'] /= $k; + $c['border_details']['mbw']['TL'] /= $k; + $c['border_details']['mbw']['TR'] /= $k; + $c['border_details']['mbw']['BL'] /= $k; + $c['border_details']['mbw']['BR'] /= $k; + $c['border_details']['mbw']['LT'] /= $k; + $c['border_details']['mbw']['LB'] /= $k; + $c['border_details']['mbw']['RT'] /= $k; + $c['border_details']['mbw']['RB'] /= $k; + } + } + + $c['padding']['T'] /= $k; + $c['padding']['R'] /= $k; + $c['padding']['B'] /= $k; + $c['padding']['L'] /= $k; + + $c['maxs'] = isset($c['maxs']) ? $c['maxs'] /= $k : null; + $c['w'] = isset($c['w']) ? $c['w'] /= $k : null; + + $c['s'] = isset($c['s']) ? $c['s'] /= $k : 0; + $c['h'] = isset($c['h']) ? $c['h'] /= $k : null; + + $c['miw'] = isset($c['miw']) ? $c['miw'] /= $k : 0; + $c['maw'] = isset($c['maw']) ? $c['maw'] /= $k : 0; + + $c['absmiw'] = isset($c['absmiw']) ? $c['absmiw'] /= $k : null; + + $c['nestedmaw'] = isset($c['nestedmaw']) ? $c['nestedmaw'] /= $k : null; + $c['nestedmiw'] = isset($c['nestedmiw']) ? $c['nestedmiw'] /= $k : null; + + if (isset($c['textbuffer'])) { + foreach ($c['textbuffer'] as $n => $tb) { + if (!empty($tb[16])) { + !isset($c['textbuffer'][$n][16]['T']) || $c['textbuffer'][$n][16]['T']['w'] /= $k; + !isset($c['textbuffer'][$n][16]['B']) || $c['textbuffer'][$n][16]['B']['w'] /= $k; + !isset($c['textbuffer'][$n][16]['L']) || $c['textbuffer'][$n][16]['L']['w'] /= $k; + !isset($c['textbuffer'][$n][16]['R']) || $c['textbuffer'][$n][16]['R']['w'] /= $k; + } + } + } + + unset($c); + } + + } // rows + } // columns + } + + function read_short(&$fh) + { + $s = fread($fh, 2); + $a = (ord($s[0]) << 8) + ord($s[1]); + if ($a & (1 << 15)) { + $a = ($a - (1 << 16)); + } + return $a; + } + + function _packCellBorder($cell) + { + if (!is_array($cell) || !isset($cell)) { + return ''; + } + + if (!$this->packTableData) { + return $cell; + } + // = 186 bytes + $bindata = pack("nnda6A10nnda6A10nnda6A10nnda6A10nd9", $cell['border'], $cell['border_details']['R']['s'], $cell['border_details']['R']['w'], $cell['border_details']['R']['c'], $cell['border_details']['R']['style'], $cell['border_details']['R']['dom'], $cell['border_details']['L']['s'], $cell['border_details']['L']['w'], $cell['border_details']['L']['c'], $cell['border_details']['L']['style'], $cell['border_details']['L']['dom'], $cell['border_details']['T']['s'], $cell['border_details']['T']['w'], $cell['border_details']['T']['c'], $cell['border_details']['T']['style'], $cell['border_details']['T']['dom'], $cell['border_details']['B']['s'], $cell['border_details']['B']['w'], $cell['border_details']['B']['c'], $cell['border_details']['B']['style'], $cell['border_details']['B']['dom'], $cell['border_details']['mbw']['BL'], $cell['border_details']['mbw']['BR'], $cell['border_details']['mbw']['RT'], $cell['border_details']['mbw']['RB'], $cell['border_details']['mbw']['TL'], $cell['border_details']['mbw']['TR'], $cell['border_details']['mbw']['LT'], $cell['border_details']['mbw']['LB'], (isset($cell['border_details']['cellposdom']) ? $cell['border_details']['cellposdom'] : 0)); + return $bindata; + } + + function _getBorderWidths($bindata) + { + if (!$bindata) { + return [0, 0, 0, 0]; + } + if (!$this->packTableData) { + return [$bindata['border_details']['T']['w'], $bindata['border_details']['R']['w'], $bindata['border_details']['B']['w'], $bindata['border_details']['L']['w']]; + } + + $bd = unpack("nbord/nrs/drw/a6rca/A10rst/nrd/nls/dlw/a6lca/A10lst/nld/nts/dtw/a6tca/A10tst/ntd/nbs/dbw/a6bca/A10bst/nbd/dmbl/dmbr/dmrt/dmrb/dmtl/dmtr/dmlt/dmlb/dcpd", $bindata); + $cell['border_details']['R']['w'] = $bd['rw']; + $cell['border_details']['L']['w'] = $bd['lw']; + $cell['border_details']['T']['w'] = $bd['tw']; + $cell['border_details']['B']['w'] = $bd['bw']; + return [$bd['tw'], $bd['rw'], $bd['bw'], $bd['lw']]; + } + + function _unpackCellBorder($bindata) + { + if (!$bindata) { + return []; + } + if (!$this->packTableData) { + return $bindata; + } + + $bd = unpack("nbord/nrs/drw/a6rca/A10rst/nrd/nls/dlw/a6lca/A10lst/nld/nts/dtw/a6tca/A10tst/ntd/nbs/dbw/a6bca/A10bst/nbd/dmbl/dmbr/dmrt/dmrb/dmtl/dmtr/dmlt/dmlb/dcpd", $bindata); + + $cell['border'] = $bd['bord']; + $cell['border_details']['R']['s'] = $bd['rs']; + $cell['border_details']['R']['w'] = $bd['rw']; + $cell['border_details']['R']['c'] = str_pad($bd['rca'], 6, "\x00"); + $cell['border_details']['R']['style'] = trim($bd['rst']); + $cell['border_details']['R']['dom'] = $bd['rd']; + + $cell['border_details']['L']['s'] = $bd['ls']; + $cell['border_details']['L']['w'] = $bd['lw']; + $cell['border_details']['L']['c'] = str_pad($bd['lca'], 6, "\x00"); + $cell['border_details']['L']['style'] = trim($bd['lst']); + $cell['border_details']['L']['dom'] = $bd['ld']; + + $cell['border_details']['T']['s'] = $bd['ts']; + $cell['border_details']['T']['w'] = $bd['tw']; + $cell['border_details']['T']['c'] = str_pad($bd['tca'], 6, "\x00"); + $cell['border_details']['T']['style'] = trim($bd['tst']); + $cell['border_details']['T']['dom'] = $bd['td']; + + $cell['border_details']['B']['s'] = $bd['bs']; + $cell['border_details']['B']['w'] = $bd['bw']; + $cell['border_details']['B']['c'] = str_pad($bd['bca'], 6, "\x00"); + $cell['border_details']['B']['style'] = trim($bd['bst']); + $cell['border_details']['B']['dom'] = $bd['bd']; + + $cell['border_details']['mbw']['BL'] = $bd['mbl']; + $cell['border_details']['mbw']['BR'] = $bd['mbr']; + $cell['border_details']['mbw']['RT'] = $bd['mrt']; + $cell['border_details']['mbw']['RB'] = $bd['mrb']; + $cell['border_details']['mbw']['TL'] = $bd['mtl']; + $cell['border_details']['mbw']['TR'] = $bd['mtr']; + $cell['border_details']['mbw']['LT'] = $bd['mlt']; + $cell['border_details']['mbw']['LB'] = $bd['mlb']; + $cell['border_details']['cellposdom'] = $bd['cpd']; + + + return($cell); + } + + ////////////////////////TABLE CODE (from PDFTable)///////////////////////////////////// + ////////////////////////TABLE CODE (from PDFTable)///////////////////////////////////// + ////////////////////////TABLE CODE (from PDFTable)///////////////////////////////////// + // table Array of (w, h, bc, nr, wc, hr, cells) + // w Width of table + // h Height of table + // nc Number column + // nr Number row + // hr List of height of each row + // wc List of width of each column + // cells List of cells of each rows, cells[i][j] is a cell in the table + function _tableColumnWidth(&$table, $firstpass = false) + { + $cs = &$table['cells']; + + $nc = $table['nc']; + $nr = $table['nr']; + $listspan = []; + + if ($table['borders_separate']) { + $tblbw = $table['border_details']['L']['w'] + $table['border_details']['R']['w'] + $table['margin']['L'] + $table['margin']['R'] + $table['padding']['L'] + $table['padding']['R'] + $table['border_spacing_H']; + } else { + $tblbw = $table['max_cell_border_width']['L'] / 2 + $table['max_cell_border_width']['R'] / 2 + $table['margin']['L'] + $table['margin']['R']; + } + + // ADDED table['l'][colno] + // = total length of text approx (using $c['s']) in that column - used to approximately distribute col widths in _tableWidth + // + for ($j = 0; $j < $nc; $j++) { // columns + $wc = &$table['wc'][$j]; + for ($i = 0; $i < $nr; $i++) { // rows + if (isset($cs[$i][$j]) && $cs[$i][$j]) { + $c = &$cs[$i][$j]; + + if ($this->simpleTables) { + if ($table['borders_separate']) { // NB twice border width + $extrcw = $table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w'] + $c['padding']['L'] + $c['padding']['R'] + $table['border_spacing_H']; + } else { + $extrcw = $table['simple']['border_details']['L']['w'] / 2 + $table['simple']['border_details']['R']['w'] / 2 + $c['padding']['L'] + $c['padding']['R']; + } + } else { + if ($this->packTableData) { + list($bt, $br, $bb, $bl) = $this->_getBorderWidths($c['borderbin']); + } else { + $br = $c['border_details']['R']['w']; + $bl = $c['border_details']['L']['w']; + } + if ($table['borders_separate']) { // NB twice border width + $extrcw = $bl + $br + $c['padding']['L'] + $c['padding']['R'] + $table['border_spacing_H']; + } else { + $extrcw = $bl / 2 + $br / 2 + $c['padding']['L'] + $c['padding']['R']; + } + } + + // $mw = $this->GetStringWidth('W') + $extrcw ; + $mw = $extrcw; // mPDF 6 + if (substr($c['a'], 0, 1) == 'D') { + $mw = $table['decimal_align'][$j]['maxs0'] + $table['decimal_align'][$j]['maxs1'] + $extrcw; + } + + $c['absmiw'] = $mw; + + if (isset($c['R']) && $c['R']) { + $c['maw'] = $c['miw'] = $this->FontSize + $extrcw; + if (isset($c['w'])) { // If cell width is specified + if ($c['miw'] < $c['w']) { + $c['miw'] = $c['w']; + } + } + if (!isset($c['colspan'])) { + if ($wc['miw'] < $c['miw']) { + $wc['miw'] = $c['miw']; + } + if ($wc['maw'] < $c['maw']) { + $wc['maw'] = $c['maw']; + } + + if ($firstpass) { + if (isset($table['l'][$j])) { + $table['l'][$j] += $c['miw']; + } else { + $table['l'][$j] = $c['miw']; + } + } + } + if ($c['miw'] > $wc['miw']) { + $wc['miw'] = $c['miw']; + } + if ($wc['miw'] > $wc['maw']) { + $wc['maw'] = $wc['miw']; + } + continue; + } + + if ($firstpass) { + if (isset($c['s'])) { + $c['s'] += $extrcw; + } + if (isset($c['maxs'])) { + $c['maxs'] += $extrcw; + } + if (isset($c['nestedmiw'])) { + $c['nestedmiw'] += $extrcw; + } + if (isset($c['nestedmaw'])) { + $c['nestedmaw'] += $extrcw; + } + } + + + // If minimum width has already been set by a nested table or inline object (image/form), use it + if (isset($c['nestedmiw']) && (!isset($this->table[1][1]['overflow']) || $this->table[1][1]['overflow'] != 'visible')) { + $miw = $c['nestedmiw']; + } else { + $miw = $mw; + } + + if (isset($c['maxs']) && $c['maxs'] != '') { + $c['s'] = $c['maxs']; + } + + // If maximum width has already been set by a nested table, use it + if (isset($c['nestedmaw'])) { + $c['maw'] = $c['nestedmaw']; + } else { + $c['maw'] = $c['s']; + } + + if (isset($table['overflow']) && $table['overflow'] == 'visible' && $table['level'] == 1) { + if (($c['maw'] + $tblbw) > $this->blk[$this->blklvl]['inner_width']) { + $c['maw'] = $this->blk[$this->blklvl]['inner_width'] - $tblbw; + } + } + + if (isset($c['nowrap']) && $c['nowrap']) { + $miw = $c['maw']; + } + + if (isset($c['wpercent']) && $firstpass) { + if (isset($c['colspan'])) { // Not perfect - but % set on colspan is shared equally on cols. + for ($k = 0; $k < $c['colspan']; $k++) { + $table['wc'][($j + $k)]['wpercent'] = $c['wpercent'] / $c['colspan']; + } + } else { + if (isset($table['w']) && $table['w']) { + $c['w'] = $c['wpercent'] / 100 * ($table['w'] - $tblbw ); + } + $wc['wpercent'] = $c['wpercent']; + } + } + + if (isset($table['overflow']) && $table['overflow'] == 'visible' && $table['level'] == 1) { + if (isset($c['w']) && ($c['w'] + $tblbw) > $this->blk[$this->blklvl]['inner_width']) { + $c['w'] = $this->blk[$this->blklvl]['inner_width'] - $tblbw; + } + } + + + if (isset($c['w'])) { // If cell width is specified + if ($miw < $c['w']) { + $c['miw'] = $c['w']; + } // Cell min width = that specified + if ($miw > $c['w']) { + $c['miw'] = $c['w'] = $miw; + } // If width specified is less than minimum allowed (W) increase it + // mPDF 5.7.4 Do not set column width in colspan + // cf. http://www.mpdf1.com/forum/discussion/2221/colspan-bug + if (!isset($c['colspan'])) { + if (!isset($wc['w'])) { + $wc['w'] = 1; + } // If the Col width is not specified = set it to 1 + } + // mPDF 5.7.3 cf. http://www.mpdf1.com/forum/discussion/1648/nested-table-bug- + $c['maw'] = $c['w']; + } else { + $c['miw'] = $miw; + } // If cell width not specified -> set Cell min width it to minimum allowed (W) + + if (isset($c['miw']) && $c['maw'] < $c['miw']) { + $c['maw'] = $c['miw']; + } // If Cell max width < Minwidth - increase it to = + if (!isset($c['colspan'])) { + if (isset($c['miw']) && $wc['miw'] < $c['miw']) { + $wc['miw'] = $c['miw']; + } // Update Col Minimum and maximum widths + if ($wc['maw'] < $c['maw']) { + $wc['maw'] = $c['maw']; + } + if ((isset($wc['absmiw']) && $wc['absmiw'] < $c['absmiw']) || !isset($wc['absmiw'])) { + $wc['absmiw'] = $c['absmiw']; + } // Update Col Minimum and maximum widths + + if (isset($table['l'][$j])) { + $table['l'][$j] += $c['s']; + } else { + $table['l'][$j] = $c['s']; + } + } else { + $listspan[] = [$i, $j]; + } + + // Check if minimum width of the whole column is big enough for largest word to fit + // mPDF 6 + if (isset($c['textbuffer'])) { + if (isset($table['overflow']) && $table['overflow'] == 'wrap') { + $letter = true; + } // check for maximum width of letters + else { + $letter = false; + } + $minwidth = $this->TableCheckMinWidth($wc['miw'] - $extrcw, 0, $c['textbuffer'], $letter); + } else { + $minwidth = 0; + } + if ($minwidth < 0) { + // increase minimum width + if (!isset($c['colspan'])) { + $wc['miw'] = max((isset($wc['miw']) ? $wc['miw'] : 0), ((-$minwidth) + $extrcw)); + } else { + $c['miw'] = max((isset($c['miw']) ? $c['miw'] : 0), ((-$minwidth) + $extrcw)); + } + } + if (!isset($c['colspan'])) { + if ($wc['miw'] > $wc['maw']) { + $wc['maw'] = $wc['miw']; + } // update maximum width, if needed + } + } + unset($c); + }//rows + }//columns + // COLUMN SPANS + $wc = &$table['wc']; + foreach ($listspan as $span) { + list($i, $j) = $span; + $c = &$cs[$i][$j]; + $lc = $j + $c['colspan']; + if ($lc > $nc) { + $lc = $nc; + } + $wis = $wisa = 0; + $was = $wasa = 0; + $list = []; + for ($k = $j; $k < $lc; $k++) { + if (isset($table['l'][$k])) { + if ($c['R']) { + $table['l'][$k] += $c['miw'] / $c['colspan']; + } else { + $table['l'][$k] += $c['s'] / $c['colspan']; + } + } else { + if ($c['R']) { + $table['l'][$k] = $c['miw'] / $c['colspan']; + } else { + $table['l'][$k] = $c['s'] / $c['colspan']; + } + } + $wis += $wc[$k]['miw']; // $wis is the sum of the column miw in the colspan + $was += $wc[$k]['maw']; // $was is the sum of the column maw in the colspan + if (!isset($c['w'])) { + $list[] = $k; + $wisa += $wc[$k]['miw']; // $wisa is the sum of the column miw in cells with no width specified in the colspan + $wasa += $wc[$k]['maw']; // $wasa is the sum of the column maw in cells with no width specified in the colspan + } + } + if ($c['miw'] > $wis) { + if (!$wis) { + for ($k = $j; $k < $lc; $k++) { + $wc[$k]['miw'] = $c['miw'] / $c['colspan']; + } + } elseif (!count($list) && $wis != 0) { + $wi = $c['miw'] - $wis; + for ($k = $j; $k < $lc; $k++) { + $wc[$k]['miw'] += ($wc[$k]['miw'] / $wis) * $wi; + } + } else { + $wi = $c['miw'] - $wis; + // mPDF 5.7.2 Extra min width distributed proportionately to all cells in colspan without a specified width + // cf. http://www.mpdf1.com/forum/discussion/1607#Item_4 + foreach ($list as $k) { + if (!isset($wc[$k]['w']) || !$wc[$k]['w']) { + $wc[$k]['miw'] += ($wc[$k]['miw'] / $wisa) * $wi; + } + } // mPDF 5.7.2 + } + } + if ($c['maw'] > $was) { + if (!$wis) { + for ($k = $j; $k < $lc; $k++) { + $wc[$k]['maw'] = $c['maw'] / $c['colspan']; + } + } elseif (!count($list) && $was != 0) { + $wi = $c['maw'] - $was; + for ($k = $j; $k < $lc; $k++) { + $wc[$k]['maw'] += ($wc[$k]['maw'] / $was) * $wi; + } + } else { + $wi = $c['maw'] - $was; + // mPDF 5.7.4 Extra max width distributed evenly to all cells in colspan without a specified width + // cf. http://www.mpdf1.com/forum/discussion/2221/colspan-bug + foreach ($list as $k) { + $wc[$k]['maw'] += $wi / count($list); + } + } + } + unset($c); + } + + $checkminwidth = 0; + $checkmaxwidth = 0; + $totallength = 0; + + for ($i = 0; $i < $nc; $i++) { + $checkminwidth += $table['wc'][$i]['miw']; + $checkmaxwidth += $table['wc'][$i]['maw']; + $totallength += isset($table['l']) ? $table['l'][$i] : 0; + } + + if (!isset($table['w']) && $firstpass) { + $sumpc = 0; + $notset = 0; + for ($i = 0; $i < $nc; $i++) { + if (isset($table['wc'][$i]['wpercent']) && $table['wc'][$i]['wpercent']) { + $sumpc += $table['wc'][$i]['wpercent']; + } else { + $notset++; + } + } + + // If sum of widths as % >= 100% and not all columns are set + // Set a nominal width of 1% for unset columns + if ($sumpc >= 100 && $notset) { + for ($i = 0; $i < $nc; $i++) { + if ((!isset($table['wc'][$i]['wpercent']) || !$table['wc'][$i]['wpercent']) && + (!isset($table['wc'][$i]['w']) || !$table['wc'][$i]['w'])) { + $table['wc'][$i]['wpercent'] = 1; + } + } + } + + + if ($sumpc) { // if any percents are set + $sumnonpc = (100 - $sumpc); + $sumpc = max($sumpc, 100); + $miwleft = 0; + $miwleftcount = 0; + $miwsurplusnonpc = 0; + $maxcalcmiw = 0; + $mawleft = 0; + $mawleftcount = 0; + $mawsurplusnonpc = 0; + $maxcalcmaw = 0; + $mawnon = 0; + $miwnon = 0; + for ($i = 0; $i < $nc; $i++) { + if (isset($table['wc'][$i]['wpercent'])) { + $maxcalcmiw = max($maxcalcmiw, ($table['wc'][$i]['miw'] * $sumpc / $table['wc'][$i]['wpercent'])); + $maxcalcmaw = max($maxcalcmaw, ($table['wc'][$i]['maw'] * $sumpc / $table['wc'][$i]['wpercent'])); + } else { + $miwleft += $table['wc'][$i]['miw']; + $mawleft += $table['wc'][$i]['maw']; + if (!isset($table['wc'][$i]['w'])) { + $miwleftcount++; + $mawleftcount++; + } + } + } + if ($miwleft && $sumnonpc > 0) { + $miwnon = $miwleft * 100 / $sumnonpc; + } + if ($mawleft && $sumnonpc > 0) { + $mawnon = $mawleft * 100 / $sumnonpc; + } + if (($miwnon > $checkminwidth || $maxcalcmiw > $checkminwidth) && $this->keep_table_proportions) { + if ($miwnon > $maxcalcmiw) { + $miwsurplusnonpc = round((($miwnon * $sumnonpc / 100) - $miwleft), 3); + $checkminwidth = $miwnon; + } else { + $checkminwidth = $maxcalcmiw; + } + for ($i = 0; $i < $nc; $i++) { + if (isset($table['wc'][$i]['wpercent'])) { + $newmiw = $checkminwidth * $table['wc'][$i]['wpercent'] / 100; + if ($table['wc'][$i]['miw'] < $newmiw) { + $table['wc'][$i]['miw'] = $newmiw; + } + $table['wc'][$i]['w'] = 1; + } elseif ($miwsurplusnonpc && !$table['wc'][$i]['w']) { + $table['wc'][$i]['miw'] += $miwsurplusnonpc / $miwleftcount; + } + } + } + if (($mawnon > $checkmaxwidth || $maxcalcmaw > $checkmaxwidth)) { + if ($mawnon > $maxcalcmaw) { + $mawsurplusnonpc = round((($mawnon * $sumnonpc / 100) - $mawleft), 3); + $checkmaxwidth = $mawnon; + } else { + $checkmaxwidth = $maxcalcmaw; + } + for ($i = 0; $i < $nc; $i++) { + if (isset($table['wc'][$i]['wpercent'])) { + $newmaw = $checkmaxwidth * $table['wc'][$i]['wpercent'] / 100; + if ($table['wc'][$i]['maw'] < $newmaw) { + $table['wc'][$i]['maw'] = $newmaw; + } + $table['wc'][$i]['w'] = 1; + } elseif ($mawsurplusnonpc && !$table['wc'][$i]['w']) { + $table['wc'][$i]['maw'] += $mawsurplusnonpc / $mawleftcount; + } + if ($table['wc'][$i]['maw'] < $table['wc'][$i]['miw']) { + $table['wc'][$i]['maw'] = $table['wc'][$i]['miw']; + } + } + } + if ($checkminwidth > $checkmaxwidth) { + $checkmaxwidth = $checkminwidth; + } + } + } + + if (isset($table['wpercent']) && $table['wpercent']) { + $checkminwidth *= (100 / $table['wpercent']); + $checkmaxwidth *= (100 / $table['wpercent']); + } + + + $checkminwidth += $tblbw; + $checkmaxwidth += $tblbw; + + // Table['miw'] set by percent in first pass may be larger than sum of column miw + if ((isset($table['miw']) && $checkminwidth > $table['miw']) || !isset($table['miw'])) { + $table['miw'] = $checkminwidth; + } + if ((isset($table['maw']) && $checkmaxwidth > $table['maw']) || !isset($table['maw'])) { + $table['maw'] = $checkmaxwidth; + } + $table['tl'] = $totallength; + + // mPDF 6 + if ($this->table_rotate) { + $mxw = $this->tbrot_maxw; + } else { + $mxw = $this->blk[$this->blklvl]['inner_width']; + } + + if (!isset($table['overflow'])) { + $table['overflow'] = null; + } + + if ($table['overflow'] == 'visible') { + return [0, 0]; + } elseif ($table['overflow'] == 'hidden' && !$this->table_rotate && !$this->ColActive && $checkminwidth > $mxw) { + $table['w'] = $table['miw']; + return [0, 0]; + } + // elseif ($table['overflow']=='wrap') { return array(0,0); } // mPDF 6 + + if (isset($table['w']) && $table['w']) { + + if ($table['w'] >= $checkminwidth && $table['w'] <= $mxw) { + $table['maw'] = $mxw = $table['w']; + } elseif ($table['w'] >= $checkminwidth && $table['w'] > $mxw && $this->keep_table_proportions) { + $checkminwidth = $table['w']; + } elseif ($table['w'] < $checkminwidth && $checkminwidth < $mxw && $this->keep_table_proportions) { + $table['maw'] = $table['w'] = $checkminwidth; + } else { + unset($table['w']); + } + } + + $ratio = $checkminwidth / $mxw; + + if ($checkminwidth > $mxw) { + return [($ratio + 0.001), $checkminwidth]; // 0.001 to allow for rounded numbers when resizing + } + + unset($cs); + + return [0, 0]; + } + + function _tableWidth(&$table) + { + $widthcols = &$table['wc']; + $numcols = $table['nc']; + $tablewidth = 0; + + if ($table['borders_separate']) { + $tblbw = $table['border_details']['L']['w'] + $table['border_details']['R']['w'] + $table['margin']['L'] + $table['margin']['R'] + $table['padding']['L'] + $table['padding']['R'] + $table['border_spacing_H']; + } else { + $tblbw = $table['max_cell_border_width']['L'] / 2 + $table['max_cell_border_width']['R'] / 2 + $table['margin']['L'] + $table['margin']['R']; + } + + if ($table['level'] > 1 && isset($table['w'])) { + + if (isset($table['wpercent']) && $table['wpercent']) { + $table['w'] = $temppgwidth = (($table['w'] - $tblbw) * $table['wpercent'] / 100) + $tblbw; + } else { + $temppgwidth = $table['w']; + } + + } elseif ($this->table_rotate) { + + $temppgwidth = $this->tbrot_maxw; + + // If it is less than 1/20th of the remaining page height to finish the DIV (i.e. DIV padding + table bottom margin) then allow for this + $enddiv = $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w']; + + if ($enddiv / $temppgwidth < 0.05) { + $temppgwidth -= $enddiv; + } + + } else { + + if (isset($table['w']) && $table['w'] < $this->blk[$this->blklvl]['inner_width']) { + $notfullwidth = 1; + $temppgwidth = $table['w']; + } elseif ($table['overflow'] == 'visible' && $table['level'] == 1) { + $temppgwidth = null; + } elseif ($table['overflow'] == 'hidden' && !$this->ColActive && isset($table['w']) && $table['w'] > $this->blk[$this->blklvl]['inner_width'] && $table['w'] == $table) { + // $temppgwidth = $this->blk[$this->blklvl]['inner_width']; + $temppgwidth = $table['w']; + } else { + $temppgwidth = $this->blk[$this->blklvl]['inner_width']; + } + + } + + $totaltextlength = 0; // Added - to sum $table['l'][colno] + $totalatextlength = 0; // Added - to sum $table['l'][colno] for those columns where width not set + $percentages_set = 0; + + for ($i = 0; $i < $numcols; $i++) { + if (isset($widthcols[$i]['wpercent'])) { + $tablewidth += $widthcols[$i]['maw']; + $percentages_set = 1; + } elseif (isset($widthcols[$i]['w'])) { + $tablewidth += $widthcols[$i]['miw']; + } else { + $tablewidth += $widthcols[$i]['maw']; + } + $totaltextlength += isset($table['l']) ? $table['l'][$i] : 0; + } + + if (!$totaltextlength) { + $totaltextlength = 1; + } + + $tablewidth += $tblbw; // Outer half of table borders + + if ($tablewidth > $temppgwidth) { + $table['w'] = $temppgwidth; + } elseif ($tablewidth < $temppgwidth && !isset($table['w']) && $percentages_set) { // if any widths set as percentages and max width fits < page width + $table['w'] = $table['maw']; + } + + // if table width is set and is > allowed width + if (isset($table['w']) && $table['w'] > $temppgwidth) { + $table['w'] = $temppgwidth; + } + + // IF the table width is now set - Need to distribute columns widths + // mPDF 5.7.3 + // If the table width is already set to the maximum width (e.g. nested table), then use maximum column widths exactly + if (isset($table['w']) && ($table['w'] == $tablewidth) && !$percentages_set) { + + // This sets the columns all to maximum width + for ($i = 0; $i < $numcols; $i++) { + $widthcols[$i] = $widthcols[$i]['maw']; + } + + } elseif (isset($table['w'])) { // elseif the table width is set distribute width using algorithm + + $wis = $wisa = 0; + $list = []; + $notsetlist = []; + + for ($i = 0; $i < $numcols; $i++) { + $wis += $widthcols[$i]['miw']; + if (!isset($widthcols[$i]['w']) || ($widthcols[$i]['w'] && $table['w'] > $temppgwidth && !$this->keep_table_proportions && !$notfullwidth )) { + $list[] = $i; + $wisa += $widthcols[$i]['miw']; + $totalatextlength += $table['l'][$i]; + } + } + + if (!$totalatextlength) { + $totalatextlength = 1; + } + + // Allocate spare (more than col's minimum width) across the cols according to their approx total text length + // Do it by setting minimum width here + if ($table['w'] > $wis + $tblbw) { + + // First set any cell widths set as percentages + if ($table['w'] < $temppgwidth || $this->keep_table_proportions) { + for ($k = 0; $k < $numcols; $k++) { + if (isset($widthcols[$k]['wpercent'])) { + $curr = $widthcols[$k]['miw']; + $widthcols[$k]['miw'] = ($table['w'] - $tblbw) * $widthcols[$k]['wpercent'] / 100; + $wis += $widthcols[$k]['miw'] - $curr; + $wisa += $widthcols[$k]['miw'] - $curr; + } + } + } + + // Now allocate surplus up to maximum width of each column + $surplus = 0; + $ttl = 0; // number of surplus columns + + if (!count($list)) { + + $wi = ($table['w'] - ($wis + $tblbw)); // i.e. extra space to distribute + + for ($k = 0; $k < $numcols; $k++) { + + $spareratio = ($table['l'][$k] / $totaltextlength); // gives ratio to divide up free space + + // Don't allocate more than Maximum required width - save rest in surplus + if ($widthcols[$k]['miw'] + ($wi * $spareratio) >= $widthcols[$k]['maw']) { // mPDF 5.7.3 + $surplus += ($wi * $spareratio) - ($widthcols[$k]['maw'] - $widthcols[$k]['miw']); + $widthcols[$k]['miw'] = $widthcols[$k]['maw']; + } else { + $notsetlist[] = $k; + $ttl += $table['l'][$k]; + $widthcols[$k]['miw'] += ($wi * $spareratio); + } + } + + } else { + + $wi = ($table['w'] - ($wis + $tblbw)); // i.e. extra space to distribute + + foreach ($list as $k) { + + $spareratio = ($table['l'][$k] / $totalatextlength); // gives ratio to divide up free space + + // Don't allocate more than Maximum required width - save rest in surplus + if ($widthcols[$k]['miw'] + ($wi * $spareratio) >= $widthcols[$k]['maw']) { // mPDF 5.7.3 + $surplus += ($wi * $spareratio) - ($widthcols[$k]['maw'] - $widthcols[$k]['miw']); + $widthcols[$k]['miw'] = $widthcols[$k]['maw']; + } else { + $notsetlist[] = $k; + $ttl += $table['l'][$k]; + $widthcols[$k]['miw'] += ($wi * $spareratio); + } + } + } + + // If surplus still left over apportion it across columns + if ($surplus) { + + if (count($notsetlist) && count($notsetlist) < $numcols) { // if some are set only add to remaining - otherwise add to all of them + foreach ($notsetlist as $i) { + if ($ttl) { + $widthcols[$i]['miw'] += $surplus * $table['l'][$i] / $ttl; + } + } + } elseif (count($list) && count($list) < $numcols) { // If some widths are defined, and others have been added up to their maxmum + foreach ($list as $i) { + $widthcols[$i]['miw'] += $surplus / count($list); + } + } elseif ($numcols) { // If all columns + $ttl = array_sum($table['l']); + if ($ttl) { + for ($i = 0; $i < $numcols; $i++) { + $widthcols[$i]['miw'] += $surplus * $table['l'][$i] / $ttl; + } + } + } + } + } + + // This sets the columns all to minimum width (which has been increased above if appropriate) + for ($i = 0; $i < $numcols; $i++) { + $widthcols[$i] = $widthcols[$i]['miw']; + } + + // TABLE NOT WIDE ENOUGH EVEN FOR MINIMUM CONTENT WIDTH + // If sum of column widths set are too wide for table + $checktablewidth = 0; + for ($i = 0; $i < $numcols; $i++) { + $checktablewidth += $widthcols[$i]; + } + + if ($checktablewidth > ($temppgwidth + 0.001 - $tblbw)) { + + $usedup = 0; + $numleft = 0; + + for ($i = 0; $i < $numcols; $i++) { + if ((isset($widthcols[$i]) && $widthcols[$i] > (($temppgwidth - $tblbw) / $numcols)) && (!isset($widthcols[$i]['w']))) { + $numleft++; + unset($widthcols[$i]); + } else { + $usedup += $widthcols[$i]; + } + } + + for ($i = 0; $i < $numcols; $i++) { + if (!isset($widthcols[$i]) || !$widthcols[$i]) { + $widthcols[$i] = ((($temppgwidth - $tblbw) - $usedup) / ($numleft)); + } + } + } + + } else { // table has no width defined + + $table['w'] = $tablewidth; + + for ($i = 0; $i < $numcols; $i++) { + + if (isset($widthcols[$i]['wpercent']) && $this->keep_table_proportions) { + $colwidth = $widthcols[$i]['maw']; + } elseif (isset($widthcols[$i]['w'])) { + $colwidth = $widthcols[$i]['miw']; + } else { + $colwidth = $widthcols[$i]['maw']; + } + + unset($widthcols[$i]); + $widthcols[$i] = $colwidth; + + } + } + + if ($table['overflow'] === 'visible' && $table['level'] == 1) { + + if ($tablewidth > $this->blk[$this->blklvl]['inner_width']) { + + for ($j = 0; $j < $numcols; $j++) { // columns + + for ($i = 0; $i < $table['nr']; $i++) { // rows + + if (isset($table['cells'][$i][$j]) && $table['cells'][$i][$j]) { + + $colspan = (isset($table['cells'][$i][$j]['colspan']) ? $table['cells'][$i][$j]['colspan'] : 1); + + if ($colspan > 1) { + $w = 0; + + for ($c = $j; $c < ($j + $colspan); $c++) { + $w += $widthcols[$c]; + } + + if ($w > $this->blk[$this->blklvl]['inner_width']) { + $diff = $w - ($this->blk[$this->blklvl]['inner_width'] - $tblbw); + for ($c = $j; $c < ($j + $colspan); $c++) { + $widthcols[$c] -= $diff * ($widthcols[$c] / $w); + } + $table['w'] -= $diff; + $table['csp'][$j] = $w - $diff; + } + } + } + } + } + } + + $pgNo = 0; + $currWc = 0; + + for ($i = 0; $i < $numcols; $i++) { // columns + + if (isset($table['csp'][$i])) { + $w = $table['csp'][$i]; + unset($table['csp'][$i]); + } else { + $w = $widthcols[$i]; + } + + if (($currWc + $w + $tblbw) > $this->blk[$this->blklvl]['inner_width']) { + $pgNo++; + $currWc = $widthcols[$i]; + } else { + $currWc += $widthcols[$i]; + } + + $table['colPg'][$i] = $pgNo; + } + } + } + + function _tableHeight(&$table) + { + $level = $table['level']; + $levelid = $table['levelid']; + $cells = &$table['cells']; + $numcols = $table['nc']; + $numrows = $table['nr']; + $listspan = []; + $checkmaxheight = 0; + $headerrowheight = 0; + $checkmaxheightplus = 0; + $headerrowheightplus = 0; + $firstrowheight = 0; + + $footerrowheight = 0; + $footerrowheightplus = 0; + if ($this->table_rotate) { + $temppgheight = $this->tbrot_maxh; + $remainingpage = $this->tbrot_maxh; + } else { + $temppgheight = ($this->h - $this->bMargin - $this->tMargin) - $this->kwt_height; + $remainingpage = ($this->h - $this->bMargin - $this->y) - $this->kwt_height; + + // If it is less than 1/20th of the remaining page height to finish the DIV (i.e. DIV padding + table bottom margin) + // then allow for this + $enddiv = $this->blk[$this->blklvl]['padding_bottom'] + $this->blk[$this->blklvl]['border_bottom']['w'] + $table['margin']['B']; + if ($remainingpage > $enddiv && $enddiv / $remainingpage < 0.05) { + $remainingpage -= $enddiv; + } elseif ($remainingpage == 0) { + $remainingpage = 0.001; + } + if ($temppgheight > $enddiv && $enddiv / $temppgheight < 0.05) { + $temppgheight -= $enddiv; + } elseif ($temppgheight == 0) { + $temppgheight = 0.001; + } + } + if ($remainingpage < 0) { + $remainingpage = 0.001; + } + if ($temppgheight < 0) { + $temppgheight = 0.001; + } + + for ($i = 0; $i < $numrows; $i++) { // rows + $heightrow = &$table['hr'][$i]; + for ($j = 0; $j < $numcols; $j++) { // columns + if (isset($cells[$i][$j]) && $cells[$i][$j]) { + $c = &$cells[$i][$j]; + + if ($this->simpleTables) { + if ($table['borders_separate']) { // NB twice border width + $extraWLR = ($table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w']) + ($c['padding']['L'] + $c['padding']['R']) + $table['border_spacing_H']; + $extrh = ($table['simple']['border_details']['T']['w'] + $table['simple']['border_details']['B']['w']) + ($c['padding']['T'] + $c['padding']['B']) + $table['border_spacing_V']; + } else { + $extraWLR = ($table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w']) / 2 + ($c['padding']['L'] + $c['padding']['R']); + $extrh = ($table['simple']['border_details']['T']['w'] + $table['simple']['border_details']['B']['w']) / 2 + ($c['padding']['T'] + $c['padding']['B']); + } + } else { + if ($this->packTableData) { + list($bt, $br, $bb, $bl) = $this->_getBorderWidths($c['borderbin']); + } else { + $bt = $c['border_details']['T']['w']; + $bb = $c['border_details']['B']['w']; + $br = $c['border_details']['R']['w']; + $bl = $c['border_details']['L']['w']; + } + if ($table['borders_separate']) { // NB twice border width + $extraWLR = $bl + $br + $c['padding']['L'] + $c['padding']['R'] + $table['border_spacing_H']; + $extrh = $bt + $bb + $c['padding']['T'] + $c['padding']['B'] + $table['border_spacing_V']; + } else { + $extraWLR = $bl / 2 + $br / 2 + $c['padding']['L'] + $c['padding']['R']; + $extrh = $bt / 2 + $bb / 2 + $c['padding']['T'] + $c['padding']['B']; + } + } + + if ($table['overflow'] == 'visible' && $level == 1) { + list($x, $cw) = $this->_splitTableGetWidth($table, $i, $j); + } else { + list($x, $cw) = $this->_tableGetWidth($table, $i, $j); + } + + + // Get CELL HEIGHT + // ++ extra parameter forces wrap to break word + if ($c['R'] && isset($c['textbuffer'])) { + $str = ''; + foreach ($c['textbuffer'] as $t) { + $str .= $t[0] . ' '; + } + $str = rtrim($str); + $s_fs = $this->FontSizePt; + $s_f = $this->FontFamily; + $s_st = $this->FontStyle; + $this->SetFont($c['textbuffer'][0][4], $c['textbuffer'][0][2], $c['textbuffer'][0][11] / $this->shrin_k, true, true); + $tempch = $this->GetStringWidth($str, true, $c['textbuffer'][0][18], $c['textbuffer'][0][8]); + if ($c['R'] >= 45 && $c['R'] < 90) { + $tempch = ((sin(deg2rad($c['R']))) * $tempch ) + ((sin(deg2rad($c['R']))) * (($c['textbuffer'][0][11] / Mpdf::SCALE) / $this->shrin_k)); + } + $this->SetFont($s_f, $s_st, $s_fs, true, true); + $ch = ($tempch ) + $extrh; + } else { + if (isset($c['textbuffer']) && !empty($c['textbuffer'])) { + $this->cellLineHeight = $c['cellLineHeight']; + $this->cellLineStackingStrategy = $c['cellLineStackingStrategy']; + $this->cellLineStackingShift = $c['cellLineStackingShift']; + $this->divwidth = $cw - $extraWLR; + $tempch = $this->printbuffer($c['textbuffer'], '', true, true); + } else { + $tempch = 0; + } + + // Added cellpadding top and bottom. (Lineheight already adjusted) + $ch = $tempch + $extrh; + } + // If height is defined and it is bigger than calculated $ch then update values + if (isset($c['h']) && $c['h'] > $ch) { + $c['mih'] = $ch; // in order to keep valign working + $ch = $c['h']; + } else { + $c['mih'] = $ch; + } + if (isset($c['rowspan'])) { + $listspan[] = [$i, $j]; + } elseif ($heightrow < $ch) { + $heightrow = $ch; + } + + // this is the extra used in _tableWrite to determine whether to trigger a page change + if ($table['borders_separate']) { + if ($i == ($numrows - 1) || (isset($c['rowspan']) && ($i + $c['rowspan']) == ($numrows))) { + $extra = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2; + } else { + $extra = $table['border_spacing_V'] / 2; + } + } else { + if (!$this->simpleTables) { + $extra = $bb / 2; + } elseif ($this->simpleTables) { + $extra = $table['simple']['border_details']['B']['w'] / 2; + } + } + if (isset($table['is_thead'][$i]) && $table['is_thead'][$i]) { + if ($j == 0) { + $headerrowheight += $ch; + $headerrowheightplus += $ch + $extra; + } + } elseif (isset($table['is_tfoot'][$i]) && $table['is_tfoot'][$i]) { + if ($j == 0) { + $footerrowheight += $ch; + $footerrowheightplus += $ch + $extra; + } + } else { + $checkmaxheight = max($checkmaxheight, $ch); + $checkmaxheightplus = max($checkmaxheightplus, $ch + $extra); + } + if ($this->tableLevel == 1 && $i == (isset($table['headernrows']) ? $table['headernrows'] : 0)) { + $firstrowheight = max($ch, $firstrowheight); + } + unset($c); + } + }//end of columns + }//end of rows + + $heightrow = &$table['hr']; + foreach ($listspan as $span) { + list($i, $j) = $span; + $c = &$cells[$i][$j]; + $lr = $i + $c['rowspan']; + if ($lr > $numrows) { + $lr = $numrows; + } + $hs = $hsa = 0; + $list = []; + for ($k = $i; $k < $lr; $k++) { + $hs += $heightrow[$k]; + // mPDF 6 + $sh = false; // specified height + for ($m = 0; $m < $numcols; $m++) { // columns + $tc = &$cells[$k][$m]; + if (isset($tc['rowspan'])) { + continue; + } + if (isset($tc['h'])) { + $sh = true; + break; + } + } + if (!$sh) { + $list[] = $k; + } + } + + if ($table['borders_separate']) { + if ($i == ($numrows - 1) || ($i + $c['rowspan']) == ($numrows)) { + $extra = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2; + } else { + $extra = $table['border_spacing_V'] / 2; + } + } else { + if (!$this->simpleTables) { + if ($this->packTableData) { + list($bt, $br, $bb, $bl) = $this->_getBorderWidths($c['borderbin']); + } else { + $bb = $c['border_details']['B']['w']; + } + $extra = $bb / 2; + } elseif ($this->simpleTables) { + $extra = $table['simple']['border_details']['B']['w'] / 2; + } + } + if (!empty($table['is_thead'][$i])) { + $headerrowheight = max($headerrowheight, $hs); + $headerrowheightplus = max($headerrowheightplus, $hs + $extra); + } elseif (!empty($table['is_tfoot'][$i])) { + $footerrowheight = max($footerrowheight, $hs); + $footerrowheightplus = max($footerrowheightplus, $hs + $extra); + } else { + $checkmaxheight = max($checkmaxheight, $hs); + $checkmaxheightplus = max($checkmaxheightplus, $hs + $extra); + } + if ($this->tableLevel == 1 && $i == (isset($table['headernrows']) ? $table['headernrows'] : 0)) { + $firstrowheight = max($hs, $firstrowheight); + } + + if ($c['mih'] > $hs) { + if (!$hs) { + for ($k = $i; $k < $lr; $k++) { + $heightrow[$k] = $c['mih'] / $c['rowspan']; + } + } elseif (!count($list)) { // no rows in the rowspan have a height specified, so share amongst all rows equally + $hi = $c['mih'] - $hs; + for ($k = $i; $k < $lr; $k++) { + $heightrow[$k] += ($heightrow[$k] / $hs) * $hi; + } + } else { + $hi = $c['mih'] - $hs; // mPDF 6 + foreach ($list as $k) { + $heightrow[$k] += $hi / (count($list)); // mPDF 6 + } + } + } + unset($c); + + // If rowspans overlap so that one or more rows do not have a height set... + // i.e. for one or more rows, the only cells (explicit) in that row have rowspan>1 + // so heightrow is still == 0 + if ($heightrow[$i] == 0) { + // Get row extent to analyse above and below + $top = $i; + foreach ($listspan as $checkspan) { + list($cki, $ckj) = $checkspan; + $c = &$cells[$cki][$ckj]; + if (isset($c['rowspan']) && $c['rowspan'] > 1) { + if (($cki + $c['rowspan'] - 1) >= $i) { + $top = min($top, $cki); + } + } + } + $bottom = $i + $c['rowspan'] - 1; + // Check for overconstrained conditions + for ($k = $top; $k <= $bottom; $k++) { + // if ['hr'] for any of the others is also 0, then abort (too complicated) + if ($k != $i && $heightrow[$k] == 0) { + break(1); + } + // check again that top and bottom are not crossed by rowspans - or abort (too complicated) + if ($k == $top) { + // ???? take account of colspan as well??? + for ($m = 0; $m < $numcols; $m++) { // columns + if (!isset($cells[$k][$m]) || $cells[$k][$m] == 0) { + break(2); + } + } + } elseif ($k == $bottom) { + // ???? take account of colspan as well??? + for ($m = 0; $m < $numcols; $m++) { // columns + $c = &$cells[$k][$m]; + if (isset($c['rowspan']) && $c['rowspan'] > 1) { + break(2); + } + } + } + } + // By columns add up col height using ['h'] if set or ['mih'] if not + // Intentionally do not substract border-spacing + $colH = []; + $extH = 0; + $newhr = []; + for ($m = 0; $m < $numcols; $m++) { // columns + for ($k = $top; $k <= $bottom; $k++) { + if (isset($cells[$k][$m]) && $cells[$k][$m] != 0) { + $c = &$cells[$k][$m]; + if (isset($c['h']) && $c['h']) { + $useh = $c['h']; + } // ???? take account of colspan as well??? + else { + $useh = $c['mih']; + } + if (isset($colH[$m])) { + $colH[$m] += $useh; + } else { + $colH[$m] = $useh; + } + if (!isset($c['rowspan']) || $c['rowspan'] < 2) { + $newhr[$k] = max((isset($newhr[$k]) ? $newhr[$k] : 0), $useh); + } + } + } + $extH = max($extH, $colH[$m]); // mPDF 6 + } + $newhr[$i] = $extH - array_sum($newhr); + for ($k = $top; $k <= $bottom; $k++) { + $heightrow[$k] = $newhr[$k]; + } + } + + + unset($c); + } + + $table['h'] = array_sum($heightrow); + unset($heightrow); + + if ($table['borders_separate']) { + $table['h'] += $table['margin']['T'] + $table['margin']['B'] + $table['border_details']['T']['w'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] + $table['padding']['T'] + $table['padding']['B']; + } else { + $table['h'] += $table['margin']['T'] + $table['margin']['B'] + $table['max_cell_border_width']['T'] / 2 + $table['max_cell_border_width']['B'] / 2; + } + + $maxrowheight = $checkmaxheightplus + $headerrowheightplus + $footerrowheightplus; + $maxfirstrowheight = $firstrowheight + $headerrowheightplus + $footerrowheightplus; // includes thead, 1st row and tfoot + return [$table['h'], $maxrowheight, $temppgheight, $remainingpage, $maxfirstrowheight]; + } + + function _tableGetWidth(&$table, $i, $j) + { + $cell = &$table['cells'][$i][$j]; + if ($cell) { + if (isset($cell['x0'])) { + return [$cell['x0'], $cell['w0']]; + } + $x = 0; + $widthcols = &$table['wc']; + for ($k = 0; $k < $j; $k++) { + $x += $widthcols[$k]; + } + $w = $widthcols[$j]; + if (isset($cell['colspan'])) { + for ($k = $j + $cell['colspan'] - 1; $k > $j; $k--) { + $w += $widthcols[$k]; + } + } + $cell['x0'] = $x; + $cell['w0'] = $w; + return [$x, $w]; + } + return [0, 0]; + } + + function _splitTableGetWidth(&$table, $i, $j) + { + $cell = &$table['cells'][$i][$j]; + if ($cell) { + if (isset($cell['x0'])) { + return [$cell['x0'], $cell['w0']]; + } + $x = 0; + $widthcols = &$table['wc']; + $pg = $table['colPg'][$j]; + for ($k = 0; $k < $j; $k++) { + if ($table['colPg'][$k] == $pg) { + $x += $widthcols[$k]; + } + } + $w = $widthcols[$j]; + if (isset($cell['colspan'])) { + for ($k = $j + $cell['colspan'] - 1; $k > $j; $k--) { + if ($table['colPg'][$k] == $pg) { + $w += $widthcols[$k]; + } + } + } + $cell['x0'] = $x; + $cell['w0'] = $w; + return [$x, $w]; + } + return [0, 0]; + } + + function _tableGetHeight(&$table, $i, $j) + { + $cell = &$table['cells'][$i][$j]; + if ($cell) { + if (isset($cell['y0'])) { + return [$cell['y0'], $cell['h0']]; + } + $y = 0; + $heightrow = &$table['hr']; + for ($k = 0; $k < $i; $k++) { + $y += $heightrow[$k]; + } + $h = $heightrow[$i]; + if (isset($cell['rowspan'])) { + for ($k = $i + $cell['rowspan'] - 1; $k > $i; $k--) { + if (array_key_exists($k, $heightrow)) { + $h += $heightrow[$k]; + } else { + $this->logger->debug('Possible non-wellformed HTML markup in a table', ['context' => LogContext::HTML_MARKUP]); + } + } + } + $cell['y0'] = $y; + $cell['h0'] = $h; + return [$y, $h]; + } + return [0, 0]; + } + + function _tableGetMaxRowHeight($table, $row) + { + if ($row == $table['nc'] - 1) { + return $table['hr'][$row]; + } + $maxrowheight = $table['hr'][$row]; + for ($i = $row + 1; $i < $table['nr']; $i++) { + $cellsset = 0; + for ($j = 0; $j < $table['nc']; $j++) { + if ($table['cells'][$i][$j]) { + if (isset($table['cells'][$i][$j]['colspan'])) { + $cellsset += $table['cells'][$i][$j]['colspan']; + } else { + $cellsset += 1; + } + } + } + if ($cellsset == $table['nc']) { + return $maxrowheight; + } else { + $maxrowheight += $table['hr'][$i]; + } + } + return $maxrowheight; + } + + // CHANGED TO ALLOW TABLE BORDER TO BE SPECIFIED CORRECTLY - added border_details + function _tableRect($x, $y, $w, $h, $bord = -1, $details = [], $buffer = false, $bSeparate = false, $cort = 'cell', $tablecorner = '', $bsv = 0, $bsh = 0) + { + $cellBorderOverlay = []; + + if ($bord == -1) { + $this->Rect($x, $y, $w, $h); + } elseif ($this->simpleTables && ($cort == 'cell')) { + $this->SetLineWidth($details['L']['w']); + if ($details['L']['c']) { + $this->SetDColor($details['L']['c']); + } else { + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + $this->SetLineJoin(0); + $this->Rect($x, $y, $w, $h); + } elseif ($bord) { + if (!$bSeparate && $buffer) { + $priority = 'LRTB'; + for ($p = 0; $p < strlen($priority); $p++) { + $side = $priority[$p]; + $details['p'] = $side; + + $dom = 0; + if (isset($details[$side]['w'])) { + $dom += ($details[$side]['w'] * 100000); + } + if (isset($details[$side]['style'])) { + $dom += (array_search($details[$side]['style'], $this->borderstyles) * 100); + } + if (isset($details[$side]['dom'])) { + $dom += ($details[$side]['dom'] * 10); + } + + // Precedence to darker colours at joins + $coldom = 0; + if (isset($details[$side]['c']) && is_array($details[$side]['c'])) { + if ($details[$side]['c'][0] == 3) { // RGB + $coldom = 10 - (((ord($details[$side]['c'][1]) * 1.00) + (ord($details[$side]['c'][2]) * 1.00) + (ord($details[$side]['c'][3]) * 1.00)) / 76.5); + } + } // 10 black - 0 white + if ($coldom) { + $dom += $coldom; + } + // Lastly precedence to RIGHT and BOTTOM cells at joins + if (isset($details['cellposdom'])) { + $dom += $details['cellposdom']; + } + + $save = false; + if ($side == 'T' && $this->issetBorder($bord, Border::TOP)) { + $cbord = Border::TOP; + $save = true; + } elseif ($side == 'L' && $this->issetBorder($bord, Border::LEFT)) { + $cbord = Border::LEFT; + $save = true; + } elseif ($side == 'R' && $this->issetBorder($bord, Border::RIGHT)) { + $cbord = Border::RIGHT; + $save = true; + } elseif ($side == 'B' && $this->issetBorder($bord, Border::BOTTOM)) { + $cbord = Border::BOTTOM; + $save = true; + } + + if ($save) { + $this->cellBorderBuffer[] = pack("A16nCnda6A10d14", str_pad(sprintf("%08.7f", $dom), 16, "0", STR_PAD_LEFT), $cbord, ord($side), $details[$side]['s'], $details[$side]['w'], $details[$side]['c'], $details[$side]['style'], $x, $y, $w, $h, $details['mbw']['BL'], $details['mbw']['BR'], $details['mbw']['RT'], $details['mbw']['RB'], $details['mbw']['TL'], $details['mbw']['TR'], $details['mbw']['LT'], $details['mbw']['LB'], $details['cellposdom'], 0); + if ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'groove' || $details[$side]['style'] == 'inset' || $details[$side]['style'] == 'outset' || $details[$side]['style'] == 'double') { + $details[$side]['overlay'] = true; + $this->cellBorderBuffer[] = pack("A16nCnda6A10d14", str_pad(sprintf("%08.7f", ($dom + 4)), 16, "0", STR_PAD_LEFT), $cbord, ord($side), $details[$side]['s'], $details[$side]['w'], $details[$side]['c'], $details[$side]['style'], $x, $y, $w, $h, $details['mbw']['BL'], $details['mbw']['BR'], $details['mbw']['RT'], $details['mbw']['RB'], $details['mbw']['TL'], $details['mbw']['TR'], $details['mbw']['LT'], $details['mbw']['LB'], $details['cellposdom'], 1); + } + } + } + return; + } + + if (isset($details['p']) && strlen($details['p']) > 1) { + $priority = $details['p']; + } else { + $priority = 'LTRB'; + } + $Tw = 0; + $Rw = 0; + $Bw = 0; + $Lw = 0; + if (isset($details['T']['w'])) { + $Tw = $details['T']['w']; + } + if (isset($details['R']['w'])) { + $Rw = $details['R']['w']; + } + if (isset($details['B']['w'])) { + $Bw = $details['B']['w']; + } + if (isset($details['L']['w'])) { + $Lw = $details['L']['w']; + } + + $x2 = $x + $w; + $y2 = $y + $h; + $oldlinewidth = $this->LineWidth; + + for ($p = 0; $p < strlen($priority); $p++) { + $side = $priority[$p]; + $xadj = 0; + $xadj2 = 0; + $yadj = 0; + $yadj2 = 0; + $print = false; + if ($Tw && $side == 'T' && $this->issetBorder($bord, Border::TOP)) { // TOP + $ly1 = $y; + $ly2 = $y; + $lx1 = $x; + $lx2 = $x2; + $this->SetLineWidth($Tw); + if ($cort == 'cell' || strpos($tablecorner, 'L') !== false) { + if ($Tw > $Lw) { + $xadj = ($Tw - $Lw) / 2; + } + if ($Tw < $Lw) { + $xadj = ($Tw + $Lw) / 2; + } + } else { + $xadj = $Tw / 2 - $bsh / 2; + } + if ($cort == 'cell' || strpos($tablecorner, 'R') !== false) { + if ($Tw > $Rw) { + $xadj2 = ($Tw - $Rw) / 2; + } + if ($Tw < $Rw) { + $xadj2 = ($Tw + $Rw) / 2; + } + } else { + $xadj2 = $Tw / 2 - $bsh / 2; + } + if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['TL'])) { + $xadj = ($Tw - $details['mbw']['TL']) / 2; + } + if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['TR'])) { + $xadj2 = ($Tw - $details['mbw']['TR']) / 2; + } + $print = true; + } + if ($Lw && $side == 'L' && $this->issetBorder($bord, Border::LEFT)) { // LEFT + $ly1 = $y; + $ly2 = $y2; + $lx1 = $x; + $lx2 = $x; + $this->SetLineWidth($Lw); + if ($cort == 'cell' || strpos($tablecorner, 'T') !== false) { + if ($Lw > $Tw) { + $yadj = ($Lw - $Tw) / 2; + } + if ($Lw < $Tw) { + $yadj = ($Lw + $Tw) / 2; + } + } else { + $yadj = $Lw / 2 - $bsv / 2; + } + if ($cort == 'cell' || strpos($tablecorner, 'B') !== false) { + if ($Lw > $Bw) { + $yadj2 = ($Lw - $Bw) / 2; + } + if ($Lw < $Bw) { + $yadj2 = ($Lw + $Bw) / 2; + } + } else { + $yadj2 = $Lw / 2 - $bsv / 2; + } + if (!$bSeparate && $details['mbw']['LT']) { + $yadj = ($Lw - $details['mbw']['LT']) / 2; + } + if (!$bSeparate && $details['mbw']['LB']) { + $yadj2 = ($Lw - $details['mbw']['LB']) / 2; + } + $print = true; + } + if ($Rw && $side == 'R' && $this->issetBorder($bord, Border::RIGHT)) { // RIGHT + $ly1 = $y; + $ly2 = $y2; + $lx1 = $x2; + $lx2 = $x2; + $this->SetLineWidth($Rw); + if ($cort == 'cell' || strpos($tablecorner, 'T') !== false) { + if ($Rw < $Tw) { + $yadj = ($Rw + $Tw) / 2; + } + if ($Rw > $Tw) { + $yadj = ($Rw - $Tw) / 2; + } + } else { + $yadj = $Rw / 2 - $bsv / 2; + } + + if ($cort == 'cell' || strpos($tablecorner, 'B') !== false) { + if ($Rw > $Bw) { + $yadj2 = ($Rw - $Bw) / 2; + } + if ($Rw < $Bw) { + $yadj2 = ($Rw + $Bw) / 2; + } + } else { + $yadj2 = $Rw / 2 - $bsv / 2; + } + + if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['RT'])) { + $yadj = ($Rw - $details['mbw']['RT']) / 2; + } + if (!$bSeparate && !empty($details['mbw']) && !empty($details['mbw']['RB'])) { + $yadj2 = ($Rw - $details['mbw']['RB']) / 2; + } + $print = true; + } + if ($Bw && $side == 'B' && $this->issetBorder($bord, Border::BOTTOM)) { // BOTTOM + $ly1 = $y2; + $ly2 = $y2; + $lx1 = $x; + $lx2 = $x2; + $this->SetLineWidth($Bw); + if ($cort == 'cell' || strpos($tablecorner, 'L') !== false) { + if ($Bw > $Lw) { + $xadj = ($Bw - $Lw) / 2; + } + if ($Bw < $Lw) { + $xadj = ($Bw + $Lw) / 2; + } + } else { + $xadj = $Bw / 2 - $bsh / 2; + } + if ($cort == 'cell' || strpos($tablecorner, 'R') !== false) { + if ($Bw > $Rw) { + $xadj2 = ($Bw - $Rw) / 2; + } + if ($Bw < $Rw) { + $xadj2 = ($Bw + $Rw) / 2; + } + } else { + $xadj2 = $Bw / 2 - $bsh / 2; + } + if (!$bSeparate && isset($details['mbw']) && isset($details['mbw']['BL'])) { + $xadj = ($Bw - $details['mbw']['BL']) / 2; + } + if (!$bSeparate && isset($details['mbw']) && isset($details['mbw']['BR'])) { + $xadj2 = ($Bw - $details['mbw']['BR']) / 2; + } + $print = true; + } + + // Now draw line + if ($print) { + /* -- TABLES-ADVANCED-BORDERS -- */ + if ($details[$side]['style'] == 'double') { + if (!isset($details[$side]['overlay']) || !$details[$side]['overlay'] || $bSeparate) { + if ($details[$side]['c']) { + $this->SetDColor($details[$side]['c']); + } else { + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2); + } + if ((isset($details[$side]['overlay']) && $details[$side]['overlay']) || $bSeparate) { + if ($bSeparate && $cort == 'table') { + if ($side == 'T') { + $xadj -= $this->LineWidth / 2; + $xadj2 -= $this->LineWidth; + if ($this->issetBorder($bord, Border::LEFT)) { + $xadj += $this->LineWidth / 2; + } + if ($this->issetBorder($bord, Border::RIGHT)) { + $xadj2 += $this->LineWidth; + } + } + if ($side == 'L') { + $yadj -= $this->LineWidth / 2; + $yadj2 -= $this->LineWidth; + if ($this->issetBorder($bord, Border::TOP)) { + $yadj += $this->LineWidth / 2; + } + if ($this->issetBorder($bord, Border::BOTTOM)) { + $yadj2 += $this->LineWidth; + } + } + if ($side == 'B') { + $xadj -= $this->LineWidth / 2; + $xadj2 -= $this->LineWidth; + if ($this->issetBorder($bord, Border::LEFT)) { + $xadj += $this->LineWidth / 2; + } + if ($this->issetBorder($bord, Border::RIGHT)) { + $xadj2 += $this->LineWidth; + } + } + if ($side == 'R') { + $yadj -= $this->LineWidth / 2; + $yadj2 -= $this->LineWidth; + if ($this->issetBorder($bord, Border::TOP)) { + $yadj += $this->LineWidth / 2; + } + if ($this->issetBorder($bord, Border::BOTTOM)) { + $yadj2 += $this->LineWidth; + } + } + } + + $this->SetLineWidth($this->LineWidth / 3); + + $tbcol = $this->colorConverter->convert(255, $this->PDFAXwarnings); + for ($l = 0; $l <= $this->blklvl; $l++) { + if ($this->blk[$l]['bgcolor']) { + $tbcol = ($this->blk[$l]['bgcolorarray']); + } + } + + if ($bSeparate) { + $cellBorderOverlay[] = [ + 'x' => $lx1 + $xadj, + 'y' => $ly1 + $yadj, + 'x2' => $lx2 - $xadj2, + 'y2' => $ly2 - $yadj2, + 'col' => $tbcol, + 'lw' => $this->LineWidth, + ]; + } else { + $this->SetDColor($tbcol); + $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2); + } + } + } elseif (isset($details[$side]['style']) && ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'groove' || $details[$side]['style'] == 'inset' || $details[$side]['style'] == 'outset')) { + if (!isset($details[$side]['overlay']) || !$details[$side]['overlay'] || $bSeparate) { + if ($details[$side]['c']) { + $this->SetDColor($details[$side]['c']); + } else { + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + if ($details[$side]['style'] == 'outset' || $details[$side]['style'] == 'groove') { + $nc = $this->colorConverter->darken($details[$side]['c']); + $this->SetDColor($nc); + } elseif ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'inset') { + $nc = $this->colorConverter->lighten($details[$side]['c']); + $this->SetDColor($nc); + } + $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2); + } + if ((isset($details[$side]['overlay']) && $details[$side]['overlay']) || $bSeparate) { + if ($details[$side]['c']) { + $this->SetDColor($details[$side]['c']); + } else { + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + $doubleadj = ($this->LineWidth) / 3; + $this->SetLineWidth($this->LineWidth / 2); + $xadj3 = $yadj3 = $wadj3 = $hadj3 = 0; + + if ($details[$side]['style'] == 'ridge' || $details[$side]['style'] == 'inset') { + $nc = $this->colorConverter->darken($details[$side]['c']); + + if ($bSeparate && $cort == 'table') { + if ($side == 'T') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = -$this->LineWidth / 2; + $wadj3 = $this->LineWidth; + if ($this->issetBorder($bord, Border::LEFT)) { + $xadj3 += $this->LineWidth; + $wadj3 -= $this->LineWidth; + } + if ($this->issetBorder($bord, Border::RIGHT)) { + $wadj3 -= $this->LineWidth * 2; + } + } + if ($side == 'L') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = -$this->LineWidth / 2; + $hadj3 = $this->LineWidth; + if ($this->issetBorder($bord, Border::TOP)) { + $yadj3 += $this->LineWidth; + $hadj3 -= $this->LineWidth; + } + if ($this->issetBorder($bord, Border::BOTTOM)) { + $hadj3 -= $this->LineWidth * 2; + } + } + if ($side == 'B') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = -$this->LineWidth / 2; + $wadj3 = $this->LineWidth; + } + if ($side == 'R') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = -$this->LineWidth / 2; + $hadj3 = $this->LineWidth; + } + } elseif ($side == 'T') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = $this->LineWidth / 2; + $wadj3 = -$this->LineWidth * 2; + } elseif ($side == 'L') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = $this->LineWidth / 2; + $hadj3 = -$this->LineWidth * 2; + } elseif ($side == 'B' && $bSeparate) { + $yadj3 = $this->LineWidth / 2; + $wadj3 = $this->LineWidth / 2; + } elseif ($side == 'R' && $bSeparate) { + $xadj3 = $this->LineWidth / 2; + $hadj3 = $this->LineWidth / 2; + } elseif ($side == 'B') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = $this->LineWidth / 2; + } elseif ($side == 'R') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = $this->LineWidth / 2; + } + } else { + $nc = $this->colorConverter->lighten($details[$side]['c']); + + if ($bSeparate && $cort == 'table') { + if ($side == 'T') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = -$this->LineWidth / 2; + $wadj3 = $this->LineWidth; + if ($this->issetBorder($bord, Border::LEFT)) { + $xadj3 += $this->LineWidth; + $wadj3 -= $this->LineWidth; + } + } + if ($side == 'L') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = -$this->LineWidth / 2; + $hadj3 = $this->LineWidth; + if ($this->issetBorder($bord, Border::TOP)) { + $yadj3 += $this->LineWidth; + $hadj3 -= $this->LineWidth; + } + } + if ($side == 'B') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = -$this->LineWidth / 2; + $wadj3 = $this->LineWidth; + if ($this->issetBorder($bord, Border::LEFT)) { + $xadj3 += $this->LineWidth; + $wadj3 -= $this->LineWidth; + } + } + if ($side == 'R') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = -$this->LineWidth / 2; + $hadj3 = $this->LineWidth; + if ($this->issetBorder($bord, Border::TOP)) { + $yadj3 += $this->LineWidth; + $hadj3 -= $this->LineWidth; + } + } + } elseif ($side == 'T') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = $this->LineWidth / 2; + } elseif ($side == 'L') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = $this->LineWidth / 2; + } elseif ($side == 'B' && $bSeparate) { + $yadj3 = $this->LineWidth / 2; + $xadj3 = $this->LineWidth / 2; + } elseif ($side == 'R' && $bSeparate) { + $xadj3 = $this->LineWidth / 2; + $yadj3 = $this->LineWidth / 2; + } elseif ($side == 'B') { + $yadj3 = $this->LineWidth / 2; + $xadj3 = -$this->LineWidth / 2; + $wadj3 = $this->LineWidth; + } elseif ($side == 'R') { + $xadj3 = $this->LineWidth / 2; + $yadj3 = -$this->LineWidth / 2; + $hadj3 = $this->LineWidth; + } + } + + if ($bSeparate) { + $cellBorderOverlay[] = [ + 'x' => $lx1 + $xadj + $xadj3, + 'y' => $ly1 + $yadj + $yadj3, + 'x2' => $lx2 - $xadj2 + $xadj3 + $wadj3, + 'y2' => $ly2 - $yadj2 + $yadj3 + $hadj3, + 'col' => $nc, + 'lw' => $this->LineWidth, + ]; + } else { + $this->SetDColor($nc); + $this->Line($lx1 + $xadj + $xadj3, $ly1 + $yadj + $yadj3, $lx2 - $xadj2 + $xadj3 + $wadj3, $ly2 - $yadj2 + $yadj3 + $hadj3); + } + } + } else { + /* -- END TABLES-ADVANCED-BORDERS -- */ + if ($details[$side]['style'] == 'dashed') { + $dashsize = 2; // final dash will be this + 1*linewidth + $dashsizek = 1.5; // ratio of Dash/Blank + $this->SetDash($dashsize, ($dashsize / $dashsizek) + ($this->LineWidth * 2)); + } elseif ($details[$side]['style'] == 'dotted') { + $this->SetLineJoin(1); + $this->SetLineCap(1); + $this->SetDash(0.001, ($this->LineWidth * 2)); + } + if ($details[$side]['c']) { + $this->SetDColor($details[$side]['c']); + } else { + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + $this->Line($lx1 + $xadj, $ly1 + $yadj, $lx2 - $xadj2, $ly2 - $yadj2); + /* -- TABLES-ADVANCED-BORDERS -- */ + } + /* -- END TABLES-ADVANCED-BORDERS -- */ + + // Reset Corners + $this->SetDash(); + // BUTT style line cap + $this->SetLineCap(2); + } + } + + if ($bSeparate && count($cellBorderOverlay)) { + foreach ($cellBorderOverlay as $cbo) { + $this->SetLineWidth($cbo['lw']); + $this->SetDColor($cbo['col']); + $this->Line($cbo['x'], $cbo['y'], $cbo['x2'], $cbo['y2']); + } + } + + // $this->SetLineWidth($oldlinewidth); + // $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + } + + /* -- TABLES -- */ + /* -- TABLES-ADVANCED-BORDERS -- */ + + /* -- END TABLES-ADVANCED-BORDERS -- */ + + function setBorder(&$var, $flag, $set = true) + { + $flag = intval($flag); + if ($set) { + $set = true; + } + $var = intval($var); + $var = $set ? ($var | $flag) : ($var & ~$flag); + } + + function issetBorder($var, $flag) + { + $flag = intval($flag); + $var = intval($var); + return (($var & $flag) == $flag); + } + + function _table2cellBorder(&$tableb, &$cbdb, &$cellb, $bval) + { + if ($tableb && $tableb['w'] > $cbdb['w']) { + $cbdb = $tableb; + $this->setBorder($cellb, $bval); + } elseif ($tableb && $tableb['w'] == $cbdb['w'] && array_search($tableb['style'], $this->borderstyles) > array_search($cbdb['style'], $this->borderstyles)) { + $cbdb = $tableb; + $this->setBorder($cellb, $bval); + } + } + + // FIX BORDERS ******************************************** + function _fixTableBorders(&$table) + { + if (!$table['borders_separate'] && $table['border_details']['L']['w']) { + $table['max_cell_border_width']['L'] = $table['border_details']['L']['w']; + } + if (!$table['borders_separate'] && $table['border_details']['R']['w']) { + $table['max_cell_border_width']['R'] = $table['border_details']['R']['w']; + } + if (!$table['borders_separate'] && $table['border_details']['T']['w']) { + $table['max_cell_border_width']['T'] = $table['border_details']['T']['w']; + } + if (!$table['borders_separate'] && $table['border_details']['B']['w']) { + $table['max_cell_border_width']['B'] = $table['border_details']['B']['w']; + } + if ($this->simpleTables) { + return; + } + $cells = &$table['cells']; + $numcols = $table['nc']; + $numrows = $table['nr']; + /* -- TABLES-ADVANCED-BORDERS -- */ + if (isset($table['topntail']) && $table['topntail']) { + $tntborddet = $this->border_details($table['topntail']); + } + if (isset($table['thead-underline']) && $table['thead-underline']) { + $thuborddet = $this->border_details($table['thead-underline']); + } + /* -- END TABLES-ADVANCED-BORDERS -- */ + + for ($i = 0; $i < $numrows; $i++) { // Rows + for ($j = 0; $j < $numcols; $j++) { // Columns + if (isset($cells[$i][$j]) && $cells[$i][$j]) { + $cell = &$cells[$i][$j]; + if ($this->packTableData) { + $cbord = $this->_unpackCellBorder($cell['borderbin']); + } else { + $cbord = &$cells[$i][$j]; + } + + // mPDF 5.7.3 + if (!$cbord['border'] && $cbord['border'] !== 0 && isset($table['border']) && $table['border'] && $this->table_border_attr_set) { + $cbord['border'] = $table['border']; + $cbord['border_details'] = $table['border_details']; + } + + if (isset($cell['colspan']) && $cell['colspan'] > 1) { + $ccolsp = $cell['colspan']; + } else { + $ccolsp = 1; + } + if (isset($cell['rowspan']) && $cell['rowspan'] > 1) { + $crowsp = $cell['rowspan']; + } else { + $crowsp = 1; + } + + $cbord['border_details']['cellposdom'] = ((($i + 1) / $numrows) / 10000 ) + ((($j + 1) / $numcols) / 10 ); + // Inherit Cell border from Table border + if ($this->table_border_css_set && !$table['borders_separate']) { + if ($i == 0) { + $this->_table2cellBorder($table['border_details']['T'], $cbord['border_details']['T'], $cbord['border'], Border::TOP); + } + if ($i == ($numrows - 1) || ($i + $crowsp) == ($numrows)) { + $this->_table2cellBorder($table['border_details']['B'], $cbord['border_details']['B'], $cbord['border'], Border::BOTTOM); + } + if ($j == 0) { + $this->_table2cellBorder($table['border_details']['L'], $cbord['border_details']['L'], $cbord['border'], Border::LEFT); + } + if ($j == ($numcols - 1) || ($j + $ccolsp) == ($numcols)) { + $this->_table2cellBorder($table['border_details']['R'], $cbord['border_details']['R'], $cbord['border'], Border::RIGHT); + } + } + + /* -- TABLES-ADVANCED-BORDERS -- */ + $fixbottom = true; + if (isset($table['topntail']) && $table['topntail']) { + if ($i == 0) { + $cbord['border_details']['T'] = $tntborddet; + $this->setBorder($cbord['border'], Border::TOP); + } + if ($this->tableLevel == 1 && $table['headernrows'] > 0 && $i == $table['headernrows'] - 1) { + $cbord['border_details']['B'] = $tntborddet; + $this->setBorder($cbord['border'], Border::BOTTOM); + $fixbottom = false; + } elseif ($this->tableLevel == 1 && $table['headernrows'] > 0 && $i == $table['headernrows']) { + if (!$table['borders_separate']) { + $cbord['border_details']['T'] = $tntborddet; + $this->setBorder($cbord['border'], Border::TOP); + } + } + if ($this->tableLevel == 1 && $table['footernrows'] > 0 && $i == ($numrows - $table['footernrows'] - 1)) { + if (!$table['borders_separate']) { + $cbord['border_details']['B'] = $tntborddet; + $this->setBorder($cbord['border'], Border::BOTTOM); + $fixbottom = false; + } + } elseif ($this->tableLevel == 1 && $table['footernrows'] > 0 && $i == ($numrows - $table['footernrows'])) { + $cbord['border_details']['T'] = $tntborddet; + $this->setBorder($cbord['border'], Border::TOP); + } + if ($this->tabletheadjustfinished) { // $this->tabletheadjustfinished called from tableheader + if (!$table['borders_separate']) { + $cbord['border_details']['T'] = $tntborddet; + $this->setBorder($cbord['border'], Border::TOP); + } + } + if ($i == ($numrows - 1) || ($i + $crowsp) == ($numrows)) { + $cbord['border_details']['B'] = $tntborddet; + $this->setBorder($cbord['border'], Border::BOTTOM); + } + } + if (isset($table['thead-underline']) && $table['thead-underline']) { + if ($table['borders_separate']) { + if ($i == 0) { + $cbord['border_details']['B'] = $thuborddet; + $this->setBorder($cbord['border'], Border::BOTTOM); + $fixbottom = false; + } + } else { + if ($this->tableLevel == 1 && $table['headernrows'] > 0 && $i == $table['headernrows'] - 1) { + $cbord['border_details']['T'] = $thuborddet; + $this->setBorder($cbord['border'], Border::TOP); + } elseif ($this->tabletheadjustfinished) { // $this->tabletheadjustfinished called from tableheader + $cbord['border_details']['T'] = $thuborddet; + $this->setBorder($cbord['border'], Border::TOP); + } + } + } + + // Collapse Border - Algorithm for conflicting borders + // Hidden >> Width >> double>solid>dashed>dotted... >> style set on cell>table >> top/left>bottom/right + // Do not turn off border which is overridden + // Needed for page break for TOP/BOTTOM both to be defined in Collapsed borders + // Means it is painted twice. (Left/Right can still disable overridden border) + if (!$table['borders_separate']) { + + if (($i < ($numrows - 1) || ($i + $crowsp) < $numrows ) && $fixbottom) { // Bottom + + for ($cspi = 0; $cspi < $ccolsp; $cspi++) { + + // already defined Top for adjacent cell below + if (isset($cells[($i + $crowsp)][$j + $cspi])) { + if ($this->packTableData) { + $adjc = $cells[($i + $crowsp)][$j + $cspi]; + $celladj = $this->_unpackCellBorder($adjc['borderbin']); + } else { + $celladj = & $cells[($i + $crowsp)][$j + $cspi]; + } + } else { + $celladj = false; + } + + if (isset($celladj['border_details']['T']['s']) && $celladj['border_details']['T']['s'] == 1) { + + $csadj = $celladj['border_details']['T']['w']; + $csthis = $cbord['border_details']['B']['w']; + + // Hidden + if ($cbord['border_details']['B']['style'] == 'hidden') { + + $celladj['border_details']['T'] = $cbord['border_details']['B']; + $this->setBorder($celladj['border'], Border::TOP, false); + $this->setBorder($cbord['border'], Border::BOTTOM, false); + + } elseif ($celladj['border_details']['T']['style'] == 'hidden') { + + $cbord['border_details']['B'] = $celladj['border_details']['T']; + $this->setBorder($cbord['border'], Border::BOTTOM, false); + $this->setBorder($celladj['border'], Border::TOP, false); + + } elseif ($csthis > $csadj) { // Width + + if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['T'] = $cbord['border_details']['B']; + $this->setBorder($cbord['border'], Border::BOTTOM); + } + + } elseif ($csadj > $csthis) { + + if ($ccolsp < 2) { // don't overwrite this cell if it spans + $cbord['border_details']['B'] = $celladj['border_details']['T']; + $this->setBorder($celladj['border'], Border::TOP); + } + + } elseif (array_search($cbord['border_details']['B']['style'], $this->borderstyles) > array_search($celladj['border_details']['T']['style'], $this->borderstyles)) { // double>solid>dashed>dotted... + + if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['T'] = $cbord['border_details']['B']; + $this->setBorder($cbord['border'], Border::BOTTOM); + } + + } elseif (array_search($celladj['border_details']['T']['style'], $this->borderstyles) > array_search($cbord['border_details']['B']['style'], $this->borderstyles)) { + + if ($ccolsp < 2) { // don't overwrite this cell if it spans + $cbord['border_details']['B'] = $celladj['border_details']['T']; + $this->setBorder($celladj['border'], Border::TOP); + } + + } elseif ($celladj['border_details']['T']['dom'] > $celladj['border_details']['B']['dom']) { // Style set on cell vs. table + + if ($ccolsp < 2) { // don't overwrite this cell if it spans + $cbord['border_details']['B'] = $celladj['border_details']['T']; + $this->setBorder($celladj['border'], Border::TOP); + } + + } else { // Style set on cell vs. table - OR - LEFT/TOP (cell) in preference to BOTTOM/RIGHT + + if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['T'] = $cbord['border_details']['B']; + $this->setBorder($cbord['border'], Border::BOTTOM); + } + + } + + } elseif ($celladj) { + + if (!isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) || (isset($cells[($i + $crowsp)][$j + $cspi]['colspan']) && $cells[($i + $crowsp)][$j + $cspi]['colspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['T'] = $cbord['border_details']['B']; + } + + } + + // mPDF 5.7.4 + if ($celladj && $this->packTableData) { + $cells[$i + $crowsp][$j + $cspi]['borderbin'] = $this->_packCellBorder($celladj); + } + + unset($celladj); + } + } + + if ($j < ($numcols - 1) || ($j + $ccolsp) < $numcols) { // Right-Left + + for ($cspi = 0; $cspi < $crowsp; $cspi++) { + + // already defined Left for adjacent cell to R + if (isset($cells[($i + $cspi)][$j + $ccolsp])) { + if ($this->packTableData) { + $adjc = $cells[($i + $cspi)][$j + $ccolsp]; + $celladj = $this->_unpackCellBorder($adjc['borderbin']); + } else { + $celladj = & $cells[$i + $cspi][$j + $ccolsp]; + } + } else { + $celladj = false; + } + if ($celladj && $celladj['border_details']['L']['s'] == 1) { + $csadj = $celladj['border_details']['L']['w']; + $csthis = $cbord['border_details']['R']['w']; + // Hidden + if ($cbord['border_details']['R']['style'] == 'hidden') { + $celladj['border_details']['L'] = $cbord['border_details']['R']; + $this->setBorder($celladj['border'], Border::LEFT, false); + $this->setBorder($cbord['border'], Border::RIGHT, false); + } elseif ($celladj['border_details']['L']['style'] == 'hidden') { + $cbord['border_details']['R'] = $celladj['border_details']['L']; + $this->setBorder($cbord['border'], Border::RIGHT, false); + $this->setBorder($celladj['border'], Border::LEFT, false); + } // Width + elseif ($csthis > $csadj) { + if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['L'] = $cbord['border_details']['R']; + $this->setBorder($cbord['border'], Border::RIGHT); + $this->setBorder($celladj['border'], Border::LEFT, false); + } + } elseif ($csadj > $csthis) { + if ($crowsp < 2) { // don't overwrite this cell if it spans + $cbord['border_details']['R'] = $celladj['border_details']['L']; + $this->setBorder($cbord['border'], Border::RIGHT, false); + $this->setBorder($celladj['border'], Border::LEFT); + } + } // double>solid>dashed>dotted... + elseif (array_search($cbord['border_details']['R']['style'], $this->borderstyles) > array_search($celladj['border_details']['L']['style'], $this->borderstyles)) { + if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['L'] = $cbord['border_details']['R']; + $this->setBorder($celladj['border'], Border::LEFT, false); + $this->setBorder($cbord['border'], Border::RIGHT); + } + } elseif (array_search($celladj['border_details']['L']['style'], $this->borderstyles) > array_search($cbord['border_details']['R']['style'], $this->borderstyles)) { + if ($crowsp < 2) { // don't overwrite this cell if it spans + $cbord['border_details']['R'] = $celladj['border_details']['L']; + $this->setBorder($cbord['border'], Border::RIGHT, false); + $this->setBorder($celladj['border'], Border::LEFT); + } + } // Style set on cell vs. table + elseif ($celladj['border_details']['L']['dom'] > $cbord['border_details']['R']['dom']) { + if ($crowsp < 2) { // don't overwrite this cell if it spans + $cbord['border_details']['R'] = $celladj['border_details']['L']; + $this->setBorder($celladj['border'], Border::LEFT); + } + } // Style set on cell vs. table - OR - LEFT/TOP (cell) in preference to BOTTOM/RIGHT + else { + if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['L'] = $cbord['border_details']['R']; + $this->setBorder($cbord['border'], Border::RIGHT); + } + } + } elseif ($celladj) { + // if right-cell border is not set + if (!isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) || (isset($cells[($i + $cspi)][$j + $ccolsp]['rowspan']) && $cells[($i + $cspi)][$j + $ccolsp]['rowspan'] < 2)) { // don't overwrite bordering cells that span + $celladj['border_details']['L'] = $cbord['border_details']['R']; + } + } + // mPDF 5.7.4 + if ($celladj && $this->packTableData) { + $cells[$i + $cspi][$j + $ccolsp]['borderbin'] = $this->_packCellBorder($celladj); + } + unset($celladj); + } + } + } + + + // Set maximum cell border width meeting at LRTB edges of cell - used for extended cell border + // ['border_details']['mbw']['LT'] = meeting border width - Left border - Top end + if (!$table['borders_separate']) { + + $cbord['border_details']['mbw']['BL'] = max($cbord['border_details']['mbw']['BL'], $cbord['border_details']['L']['w']); + $cbord['border_details']['mbw']['BR'] = max($cbord['border_details']['mbw']['BR'], $cbord['border_details']['R']['w']); + $cbord['border_details']['mbw']['RT'] = max($cbord['border_details']['mbw']['RT'], $cbord['border_details']['T']['w']); + $cbord['border_details']['mbw']['RB'] = max($cbord['border_details']['mbw']['RB'], $cbord['border_details']['B']['w']); + $cbord['border_details']['mbw']['TL'] = max($cbord['border_details']['mbw']['TL'], $cbord['border_details']['L']['w']); + $cbord['border_details']['mbw']['TR'] = max($cbord['border_details']['mbw']['TR'], $cbord['border_details']['R']['w']); + $cbord['border_details']['mbw']['LT'] = max($cbord['border_details']['mbw']['LT'], $cbord['border_details']['T']['w']); + $cbord['border_details']['mbw']['LB'] = max($cbord['border_details']['mbw']['LB'], $cbord['border_details']['B']['w']); + + if (($i + $crowsp) < $numrows && isset($cells[$i + $crowsp][$j])) { // Has Bottom adjoining cell + + if ($this->packTableData) { + $adjc = $cells[$i + $crowsp][$j]; + $celladj = $this->_unpackCellBorder($adjc['borderbin']); + } else { + $celladj = & $cells[$i + $crowsp][$j]; + } + + $cbord['border_details']['mbw']['BL'] = max( + $cbord['border_details']['mbw']['BL'], + $celladj ? $celladj['border_details']['L']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['TL']: 0 + ); + + $cbord['border_details']['mbw']['BR'] = max( + $cbord['border_details']['mbw']['BR'], + $celladj ? $celladj['border_details']['R']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['TR']: 0 + ); + + $cbord['border_details']['mbw']['LB'] = max( + $cbord['border_details']['mbw']['LB'], + $celladj ? $celladj['border_details']['mbw']['LT'] : 0 + ); + + $cbord['border_details']['mbw']['RB'] = max( + $cbord['border_details']['mbw']['RB'], + $celladj ? $celladj['border_details']['mbw']['RT'] : 0 + ); + + unset($celladj); + } + + if (($j + $ccolsp) < $numcols && isset($cells[$i][$j + $ccolsp])) { // Has Right adjoining cell + + if ($this->packTableData) { + $adjc = $cells[$i][$j + $ccolsp]; + $celladj = $this->_unpackCellBorder($adjc['borderbin']); + } else { + $celladj = & $cells[$i][$j + $ccolsp]; + } + + $cbord['border_details']['mbw']['RT'] = max( + $cbord['border_details']['mbw']['RT'], + $celladj ? $celladj['border_details']['T']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['LT'] : 0 + ); + + $cbord['border_details']['mbw']['RB'] = max( + $cbord['border_details']['mbw']['RB'], + $celladj ? $celladj['border_details']['B']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['LB'] : 0 + ); + + $cbord['border_details']['mbw']['TR'] = max( + $cbord['border_details']['mbw']['TR'], + $celladj ? $celladj['border_details']['mbw']['TL'] : 0 + ); + + $cbord['border_details']['mbw']['BR'] = max( + $cbord['border_details']['mbw']['BR'], + $celladj ? $celladj['border_details']['mbw']['BL'] : 0 + ); + + unset($celladj); + } + + if ($i > 0 && isset($cells[$i - 1][$j]) && is_array($cells[$i - 1][$j]) && (($this->packTableData && $cells[$i - 1][$j]['borderbin']) || $cells[$i - 1][$j]['border'])) { // Has Top adjoining cell + + if ($this->packTableData) { + $adjc = $cells[$i - 1][$j]; + $celladj = $this->_unpackCellBorder($adjc['borderbin']); + } else { + $celladj = & $cells[$i - 1][$j]; + } + + $cbord['border_details']['mbw']['TL'] = max( + $cbord['border_details']['mbw']['TL'], + $celladj ? $celladj['border_details']['L']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['BL'] : 0 + ); + + $cbord['border_details']['mbw']['TR'] = max( + $cbord['border_details']['mbw']['TR'], + $celladj ? $celladj['border_details']['R']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['BR'] : 0 + ); + + $cbord['border_details']['mbw']['LT'] = max( + $cbord['border_details']['mbw']['LT'], + $celladj ? $celladj['border_details']['mbw']['LB'] : 0 + ); + + $cbord['border_details']['mbw']['RT'] = max( + $cbord['border_details']['mbw']['RT'], + $celladj ? $celladj['border_details']['mbw']['RB'] : 0 + ); + + if ($celladj['border_details']['mbw']['BL']) { + $celladj['border_details']['mbw']['BL'] = max($cbord['border_details']['mbw']['TL'], $celladj['border_details']['mbw']['BL']); + } + + if ($celladj['border_details']['mbw']['BR']) { + $celladj['border_details']['mbw']['BR'] = max($celladj['border_details']['mbw']['BR'], $cbord['border_details']['mbw']['TR']); + } + + if ($this->packTableData) { + $cells[$i - 1][$j]['borderbin'] = $this->_packCellBorder($celladj); + } + unset($celladj); + } + + if ($j > 0 && isset($cells[$i][$j - 1]) && is_array($cells[$i][$j - 1]) && (($this->packTableData && $cells[$i][$j - 1]['borderbin']) || $cells[$i][$j - 1]['border'])) { // Has Left adjoining cell + + if ($this->packTableData) { + $adjc = $cells[$i][$j - 1]; + $celladj = $this->_unpackCellBorder($adjc['borderbin']); + } else { + $celladj = & $cells[$i][$j - 1]; + } + + $cbord['border_details']['mbw']['LT'] = max( + $cbord['border_details']['mbw']['LT'], + $celladj ? $celladj['border_details']['T']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['RT'] : 0 + ); + + $cbord['border_details']['mbw']['LB'] = max( + $cbord['border_details']['mbw']['LB'], + $celladj ? $celladj['border_details']['B']['w'] : 0, + $celladj ? $celladj['border_details']['mbw']['RB'] : 0 + ); + + $cbord['border_details']['mbw']['BL'] = max( + $cbord['border_details']['mbw']['BL'], + $celladj ? $celladj['border_details']['mbw']['BR'] : 0 + ); + + $cbord['border_details']['mbw']['TL'] = max( + $cbord['border_details']['mbw']['TL'], + $celladj ? $celladj['border_details']['mbw']['TR'] : 0 + ); + + if ($celladj['border_details']['mbw']['RT']) { + $celladj['border_details']['mbw']['RT'] = max($celladj['border_details']['mbw']['RT'], $cbord['border_details']['mbw']['LT']); + } + + if ($celladj['border_details']['mbw']['RB']) { + $celladj['border_details']['mbw']['RB'] = max($celladj['border_details']['mbw']['RB'], $cbord['border_details']['mbw']['LB']); + } + + if ($this->packTableData) { + $cells[$i][$j - 1]['borderbin'] = $this->_packCellBorder($celladj); + } + + unset($celladj); + } + + + // Update maximum cell border width at LRTB edges of table - used for overall table width + if ($j == 0 && $cbord['border_details']['L']['w']) { + $table['max_cell_border_width']['L'] = max($table['max_cell_border_width']['L'], $cbord['border_details']['L']['w']); + } + if (($j == ($numcols - 1) || ($j + $ccolsp) == $numcols ) && $cbord['border_details']['R']['w']) { + $table['max_cell_border_width']['R'] = max($table['max_cell_border_width']['R'], $cbord['border_details']['R']['w']); + } + if ($i == 0 && $cbord['border_details']['T']['w']) { + $table['max_cell_border_width']['T'] = max($table['max_cell_border_width']['T'], $cbord['border_details']['T']['w']); + } + if (($i == ($numrows - 1) || ($i + $crowsp) == $numrows ) && $cbord['border_details']['B']['w']) { + $table['max_cell_border_width']['B'] = max($table['max_cell_border_width']['B'], $cbord['border_details']['B']['w']); + } + } + /* -- END TABLES-ADVANCED-BORDERS -- */ + + if ($this->packTableData) { + $cell['borderbin'] = $this->_packCellBorder($cbord); + } + + unset($cbord); + + unset($cell); + } + } + } + unset($cell); + } + + // END FIX BORDERS ************************************************************************************ + + function _reverseTableDir(&$table) + { + $cells = &$table['cells']; + $numcols = $table['nc']; + $numrows = $table['nr']; + for ($i = 0; $i < $numrows; $i++) { // Rows + $row = []; + for ($j = ($numcols - 1); $j >= 0; $j--) { // Columns + if (isset($cells[$i][$j]) && $cells[$i][$j]) { + $cell = &$cells[$i][$j]; + $col = $numcols - $j - 1; + if (isset($cell['colspan']) && $cell['colspan'] > 1) { + $col -= ($cell['colspan'] - 1); + } + // Nested content + if (isset($cell['textbuffer'])) { + for ($n = 0; $n < count($cell['textbuffer']); $n++) { + $t = $cell['textbuffer'][$n][0]; + if (substr($t, 0, 19) == "\xbb\xa4\xactype=nestedtable") { + $objattr = $this->_getObjAttr($t); + $objattr['col'] = $col; + $cell['textbuffer'][$n][0] = "\xbb\xa4\xactype=nestedtable,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + $this->table[($this->tableLevel + 1)][$objattr['nestedcontent']]['nestedpos'][1] = $col; + } + } + } + $row[$col] = $cells[$i][$j]; + unset($cell); + } + } + for ($f = 0; $f < $numcols; $f++) { + if (!isset($row[$f])) { + $row[$f] = 0; + } + } + $table['cells'][$i] = $row; + } + } + + function _tableWrite(&$table, $split = false, $startrow = 0, $startcol = 0, $splitpg = 0, $rety = 0) + { + $level = $table['level']; + $levelid = $table['levelid']; + + $cells = &$table['cells']; + $numcols = $table['nc']; + $numrows = $table['nr']; + $maxbwtop = 0; + if ($this->ColActive && $level == 1) { + $this->breakpoints[$this->CurrCol][] = $this->y; + } // *COLUMNS* + + if (!$split || ($startrow == 0 && $splitpg == 0) || $startrow > 0) { + // TABLE TOP MARGIN + if ($table['margin']['T']) { + if (!$this->table_rotate && $level == 1) { + $this->DivLn($table['margin']['T'], $this->blklvl, true, 1); // collapsible + } else { + $this->y += ($table['margin']['T']); + } + } + // Advance down page by half width of top border + if ($table['borders_separate']) { + if ($startrow > 0 && (!isset($table['is_thead']) || count($table['is_thead']) == 0)) { + $adv = $table['border_spacing_V'] / 2; + } else { + $adv = $table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2; + } + } else { + $adv = $table['max_cell_border_width']['T'] / 2; + } + if (!$this->table_rotate && $level == 1) { + $this->DivLn($adv); + } else { + $this->y += $adv; + } + } + + if ($level == 1) { + $this->x = $this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['padding_left'] + $this->blk[$this->blklvl]['border_left']['w']; + $x0 = $this->x; + $y0 = $this->y; + $right = $x0 + $this->blk[$this->blklvl]['inner_width']; + $outerfilled = $this->y; // Keep track of how far down the outer DIV bgcolor is painted (NB rowspans) + $this->outerfilled = $this->y; + $this->colsums = []; + } else { + $x0 = $this->x; + $y0 = $this->y; + $right = $x0 + $table['w']; + } + + if ($this->table_rotate) { + $temppgwidth = $this->tbrot_maxw; + $this->PageBreakTrigger = $pagetrigger = $y0 + ($this->blk[$this->blklvl]['inner_width']); + if ($level == 1) { + $this->tbrot_y0 = $this->y - $adv - $table['margin']['T']; + $this->tbrot_x0 = $this->x; + $this->tbrot_w = $table['w']; + if ($table['borders_separate']) { + $this->tbrot_h = $table['margin']['T'] + $table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2; + } else { + $this->tbrot_h = $table['margin']['T'] + $table['padding']['T'] + $table['max_cell_border_width']['T']; + } + } + } else { + $this->PageBreakTrigger = $pagetrigger = ($this->h - $this->bMargin); + if ($level == 1) { + $temppgwidth = $this->blk[$this->blklvl]['inner_width']; + if (isset($table['a']) and ( $table['w'] < $this->blk[$this->blklvl]['inner_width'])) { + if ($table['a'] == 'C') { + $x0 += ((($right - $x0) - $table['w']) / 2); + } elseif ($table['a'] == 'R') { + $x0 = $right - $table['w']; + } + } + } else { + $temppgwidth = $table['w']; + } + } + if (!isset($table['overflow'])) { + $table['overflow'] = null; + } + if ($table['overflow'] == 'hidden' && $level == 1 && !$this->table_rotate && !$this->ColActive) { + // Bounding rectangle to clip + $this->tableClipPath = sprintf('q %.3F %.3F %.3F %.3F re W n', $x0 * Mpdf::SCALE, $this->h * Mpdf::SCALE, $this->blk[$this->blklvl]['inner_width'] * Mpdf::SCALE, -$this->h * Mpdf::SCALE); + $this->writer->write($this->tableClipPath); + } else { + $this->tableClipPath = ''; + } + + + if ($table['borders_separate']) { + $indent = $table['margin']['L'] + $table['border_details']['L']['w'] + $table['padding']['L'] + $table['border_spacing_H'] / 2; + } else { + $indent = $table['margin']['L'] + $table['max_cell_border_width']['L'] / 2; + } + $x0 += $indent; + + $returny = 0; + $lastCol = 0; + $tableheader = []; + $tablefooter = []; + $tableheaderrowheight = 0; + $tablefooterrowheight = 0; + $footery = 0; + + // mPD 3.0 Set the Page & Column where table starts + if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN + $tablestartpage = 'EVEN'; + } elseif (($this->mirrorMargins) && (($this->page) % 2 == 1)) { // ODD + $tablestartpage = 'ODD'; + } else { + $tablestartpage = ''; + } + if ($this->ColActive) { + $tablestartcolumn = $this->CurrCol; + } else { + $tablestartcolumn = ''; + } + + $y = $h = 0; + for ($i = 0; $i < $numrows; $i++) { // Rows + if (isset($table['is_tfoot'][$i]) && $table['is_tfoot'][$i] && $level == 1) { + $tablefooterrowheight += $table['hr'][$i]; + $tablefooter[$i][0]['trbackground-images'] = $table['trbackground-images'][$i]; + $tablefooter[$i][0]['trgradients'] = $table['trgradients'][$i]; + $tablefooter[$i][0]['trbgcolor'] = $table['bgcolor'][$i]; + for ($j = $startcol; $j < $numcols; $j++) { // Columns + if (isset($cells[$i][$j]) && $cells[$i][$j]) { + $cell = &$cells[$i][$j]; + if ($split) { + if ($table['colPg'][$j] != $splitpg) { + continue; + } + list($x, $w) = $this->_splitTableGetWidth($table, $i, $j); + $js = $j - $startcol; + } else { + list($x, $w) = $this->_tableGetWidth($table, $i, $j); + $js = $j; + } + + list($y, $h) = $this->_tableGetHeight($table, $i, $j); + $x += $x0; + $y += $y0; + // Get info of tfoot ==>> table footer + $tablefooter[$i][$js]['x'] = $x; + $tablefooter[$i][$js]['y'] = $y; + $tablefooter[$i][$js]['h'] = $h; + $tablefooter[$i][$js]['w'] = $w; + if (isset($cell['textbuffer'])) { + $tablefooter[$i][$js]['textbuffer'] = $cell['textbuffer']; + } else { + $tablefooter[$i][$js]['textbuffer'] = ''; + } + $tablefooter[$i][$js]['a'] = $cell['a']; + $tablefooter[$i][$js]['R'] = $cell['R']; + $tablefooter[$i][$js]['va'] = $cell['va']; + $tablefooter[$i][$js]['mih'] = $cell['mih']; + if (isset($cell['gradient'])) { + $tablefooter[$i][$js]['gradient'] = $cell['gradient']; // *BACKGROUNDS* + } + if (isset($cell['background-image'])) { + $tablefooter[$i][$js]['background-image'] = $cell['background-image']; // *BACKGROUNDS* + } + + // CELL FILL BGCOLOR + if (!$this->simpleTables) { + if ($this->packTableData) { + $c = $this->_unpackCellBorder($cell['borderbin']); + $tablefooter[$i][$js]['border'] = $c['border']; + $tablefooter[$i][$js]['border_details'] = $c['border_details']; + } else { + $tablefooter[$i][$js]['border'] = $cell['border']; + $tablefooter[$i][$js]['border_details'] = $cell['border_details']; + } + } elseif ($this->simpleTables) { + $tablefooter[$i][$js]['border'] = $table['simple']['border']; + $tablefooter[$i][$js]['border_details'] = $table['simple']['border_details']; + } + $tablefooter[$i][$js]['bgcolor'] = $cell['bgcolor']; + $tablefooter[$i][$js]['padding'] = $cell['padding']; + if (isset($cell['rowspan'])) { + $tablefooter[$i][$js]['rowspan'] = $cell['rowspan']; + } + if (isset($cell['colspan'])) { + $tablefooter[$i][$js]['colspan'] = $cell['colspan']; + } + if (isset($cell['direction'])) { + $tablefooter[$i][$js]['direction'] = $cell['direction']; + } + if (isset($cell['cellLineHeight'])) { + $tablefooter[$i][$js]['cellLineHeight'] = $cell['cellLineHeight']; + } + if (isset($cell['cellLineStackingStrategy'])) { + $tablefooter[$i][$js]['cellLineStackingStrategy'] = $cell['cellLineStackingStrategy']; + } + if (isset($cell['cellLineStackingShift'])) { + $tablefooter[$i][$js]['cellLineStackingShift'] = $cell['cellLineStackingShift']; + } + } + } + } + } + + if ($level == 1) { + $this->writer->write('___TABLE___BACKGROUNDS' . $this->uniqstr); + } + $tableheaderadj = 0; + $tablefooteradj = 0; + + $tablestartpageno = $this->page; + + // Draw Table Contents and Borders + for ($i = 0; $i < $numrows; $i++) { // Rows + if ($split && $startrow > 0) { + $thnr = (isset($table['is_thead']) ? count($table['is_thead']) : 0); + if ($i >= $thnr && $i < $startrow) { + continue; + } + if ($i == $startrow) { + $returny = $rety - $tableheaderrowheight; + } + } + + // Get Maximum row/cell height in row - including rowspan>1 + 1 overlapping + $maxrowheight = $this->_tableGetMaxRowHeight($table, $i); + + $skippage = false; + $newpagestarted = false; + for ($j = $startcol; $j < $numcols; $j++) { // Columns + if ($split) { + if ($table['colPg'][$j] > $splitpg) { + break; + } + $lastCol = $j; + } + if (isset($cells[$i][$j]) && $cells[$i][$j]) { + $cell = &$cells[$i][$j]; + if ($split) { + $lastCol = $j + (isset($cell['colspan']) ? ($cell['colspan'] - 1) : 0); + list($x, $w) = $this->_splitTableGetWidth($table, $i, $j); + } else { + list($x, $w) = $this->_tableGetWidth($table, $i, $j); + } + + list($y, $h) = $this->_tableGetHeight($table, $i, $j); + $x += $x0; + $y += $y0; + $y -= $returny; + + if ($table['borders_separate']) { + if (!empty($tablefooter) || $i == ($numrows - 1) || (isset($cell['rowspan']) && ($i + $cell['rowspan']) == $numrows) || (!isset($cell['rowspan']) && ($i + 1) == $numrows)) { + $extra = $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2; + // $extra = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V']/2; + } else { + $extra = $table['border_spacing_V'] / 2; + } + } else { + $extra = $table['max_cell_border_width']['B'] / 2; + } + + if ($j == $startcol && ((($y + $maxrowheight + $extra ) > ($pagetrigger + 0.001)) || (($this->keepColumns || !$this->ColActive) && !empty($tablefooter) && ($y + $maxrowheight + $tablefooterrowheight + $extra) > $pagetrigger) && ($this->tableLevel == 1 && $i < ($numrows - $table['headernrows']))) && ($y0 > 0 || $x0 > 0) && !$this->InFooter && $this->autoPageBreak) { + if (!$skippage) { + $finalSpread = true; + $firstSpread = true; + if ($split) { + for ($t = $startcol; $t < $numcols; $t++) { + // Are there more columns to print on a next page? + if ($table['colPg'][$t] > $splitpg) { + $finalSpread = false; + break; + } + } + if ($startcol > 0) { + $firstSpread = false; + } + } + + if (($this->keepColumns || !$this->ColActive) && !empty($tablefooter) && $i > 0) { + $this->y = $y; + $ya = $this->y; + $this->TableHeaderFooter($tablefooter, $tablestartpage, $tablestartcolumn, 'F', $level, $firstSpread, $finalSpread); + if ($this->table_rotate) { + $this->tbrot_h += $this->y - $ya; + } + $tablefooteradj = $this->y - $ya; + } + $y -= $y0; + $returny += $y; + + $oldcolumn = $this->CurrCol; + if ($this->AcceptPageBreak()) { + $newpagestarted = true; + $this->y = $y + $y0; + + // Move down to account for border-spacing or + // extra half border width in case page breaks in middle + if ($i > 0 && !$this->table_rotate && $level == 1 && !$this->ColActive) { + if ($table['borders_separate']) { + $adv = $table['border_spacing_V'] / 2; + // If table footer + if (($this->keepColumns || !$this->ColActive) && !empty($tablefooter) && $i > 0) { + $adv += ($table['padding']['B'] + $table['border_details']['B']['w']); + } + } else { + $maxbwtop = 0; + $maxbwbottom = 0; + if (!$this->simpleTables) { + if (!empty($tablefooter)) { + $maxbwbottom = $table['max_cell_border_width']['B']; + } else { + $brow = $i - 1; + for ($ctj = 0; $ctj < $numcols; $ctj++) { + if (isset($cells[$brow][$ctj]) && $cells[$brow][$ctj]) { + if ($this->packTableData) { + list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cells[$brow][$ctj]['borderbin']); + } else { + $bb = $cells[$brow][$ctj]['border_details']['B']['w']; + } + $maxbwbottom = max($maxbwbottom, $bb); + } + } + } + if (!empty($tableheader)) { + $maxbwtop = $table['max_cell_border_width']['T']; + } else { + $trow = $i - 1; + for ($ctj = 0; $ctj < $numcols; $ctj++) { + if (isset($cells[$trow][$ctj]) && $cells[$trow][$ctj]) { + if ($this->packTableData) { + list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cells[$trow][$ctj]['borderbin']); + } else { + $bt = $cells[$trow][$ctj]['border_details']['T']['w']; + } + $maxbwtop = max($maxbwtop, $bt); + } + } + } + } elseif ($this->simpleTables) { + $maxbwtop = $table['simple']['border_details']['T']['w']; + $maxbwbottom = $table['simple']['border_details']['B']['w']; + } + $adv = $maxbwbottom / 2; + } + $this->y += $adv; + } + + // Rotated table split over pages - needs this->y for borders/backgrounds + if ($i > 0 && $this->table_rotate && $level == 1) { + // $this->y = $y0 + $this->tbrot_w; + } + + if ($this->tableClipPath) { + $this->writer->write("Q"); + } + + $bx = $x0; + $by = $y0; + + if ($table['borders_separate']) { + $bx -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['border_spacing_H'] / 2); + if ($tablestartpageno != $this->page) { // IF already broken across a previous pagebreak + $by += $table['max_cell_border_width']['T'] / 2; + if (empty($tableheader)) { + $by -= ($table['border_spacing_V'] / 2); + } + } else { + $by -= ($table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2); + } + } elseif ($tablestartpageno != $this->page && !empty($tableheader)) { + $by += $maxbwtop / 2; + } + + $by -= $tableheaderadj; + $bh = $this->y - $by + $tablefooteradj; + if (!$table['borders_separate']) { + $bh -= $adv; + } + if ($split) { + $bw = 0; + for ($t = $startcol; $t < $numcols; $t++) { + if ($table['colPg'][$t] == $splitpg) { + $bw += $table['wc'][$t]; + } + if ($table['colPg'][$t] > $splitpg) { + break; + } + } + if ($table['borders_separate']) { + if ($firstSpread) { + $bw += $table['padding']['L'] + $table['border_details']['L']['w'] + $table['border_spacing_H']; + } else { + $bx += ($table['padding']['L'] + $table['border_details']['L']['w']); + $bw += $table['border_spacing_H']; + } + if ($finalSpread) { + $bw += $table['padding']['R'] + $table['border_details']['R']['w'] / 2 + $table['border_spacing_H']; + } + } + } else { + $bw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + } + + if ($this->splitTableBorderWidth && ($this->keepColumns || !$this->ColActive) && empty($tablefooter) && $i > 0 && $table['border_details']['B']['w']) { + $prevDrawColor = $this->DrawColor; + $lw = $this->LineWidth; + $this->SetLineWidth($this->splitTableBorderWidth); + $this->SetDColor($table['border_details']['B']['c']); + $this->SetLineJoin(0); + $this->SetLineCap(0); + $blx = $bx; + $blw = $bw; + if (!$table['borders_separate']) { + $blx -= ($table['max_cell_border_width']['L'] / 2); + $blw += ($table['max_cell_border_width']['L'] / 2 + $table['max_cell_border_width']['R'] / 2); + } + $this->Line($blx, $this->y + ($this->splitTableBorderWidth / 2), $blx + $blw, $this->y + ($this->splitTableBorderWidth / 2)); + $this->DrawColor = $prevDrawColor; + $this->writer->write($this->DrawColor); + $this->SetLineWidth($lw); + $this->SetLineJoin(2); + $this->SetLineCap(2); + } + + if (!$this->ColActive && ($i > 0 || $j > 0)) { + if (isset($table['bgcolor'][-1])) { + $color = $this->colorConverter->convert($table['bgcolor'][-1], $this->PDFAXwarnings); + if ($color) { + if (!$table['borders_separate']) { + $bh -= $table['max_cell_border_width']['B'] / 2; + } + $this->tableBackgrounds[$level * 9][] = ['gradient' => false, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'col' => $color]; + } + } + + /* -- BACKGROUNDS -- */ + if (isset($table['gradient'])) { + $g = $this->gradient->parseBackgroundGradient($table['gradient']); + if ($g) { + $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + + if (isset($table['background-image'])) { + if ($table['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['background-image']['gradient'])) { + $g = $this->gradient->parseMozGradient($table['background-image']['gradient']); + if ($g) { + $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } else { + $image_id = $table['background-image']['image_id']; + $orig_w = $table['background-image']['orig_w']; + $orig_h = $table['background-image']['orig_h']; + $x_pos = $table['background-image']['x_pos']; + $y_pos = $table['background-image']['y_pos']; + $x_repeat = $table['background-image']['x_repeat']; + $y_repeat = $table['background-image']['y_repeat']; + $resize = $table['background-image']['resize']; + $opacity = $table['background-image']['opacity']; + $itype = $table['background-image']['itype']; + $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype]; + } + } + /* -- END BACKGROUNDS -- */ + } + + // $this->AcceptPageBreak() has moved tablebuffer to $this->pages content + if ($this->tableBackgrounds) { + $s = $this->PrintTableBackgrounds(); + if ($this->bufferoutput) { + $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->headerbuffer); + $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->headerbuffer); + } else { + $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]); + $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->pages[$this->page]); + } + $this->tableBackgrounds = []; + } + + if ($split) { + if ($i == 0 && $j == 0) { + $y0 = -1; + } elseif ($finalSpread) { + $splitpg = 0; + $startcol = 0; + $startrow = $i; + } else { + $splitpg++; + $startcol = $t; + $returny -= $y; + } + return [false, $startrow, $startcol, $splitpg, $returny, $y0]; + } + + $this->AddPage($this->CurOrientation); + + $this->writer->write('___TABLE___BACKGROUNDS' . $this->uniqstr); + + + if ($this->tableClipPath) { + $this->writer->write($this->tableClipPath); + } + + // Added to correct for OddEven Margins + $x = $x + $this->MarginCorrection; + $x0 = $x0 + $this->MarginCorrection; + + if ($this->splitTableBorderWidth && ($this->keepColumns || !$this->ColActive) && empty($tableheader) && $i > 0 && $table['border_details']['T']['w']) { + $prevDrawColor = $this->DrawColor; + $lw = $this->LineWidth; + $this->SetLineWidth($this->splitTableBorderWidth); + $this->SetDColor($table['border_details']['T']['c']); + $this->SetLineJoin(0); + $this->SetLineCap(0); + $blx += $this->MarginCorrection; + $this->Line($blx, $this->y - ($this->splitTableBorderWidth / 2), $blx + $blw, $this->y - ($this->splitTableBorderWidth / 2)); + $this->DrawColor = $prevDrawColor; + $this->writer->write($this->DrawColor); + $this->SetLineWidth($lw); + $this->SetLineJoin(2); + $this->SetLineCap(2); + } + + // Move down to account for half of top border-spacing or + // extra half border width in case page was broken in middle + if ($i > 0 && !$this->table_rotate && $level == 1 && $table['headernrows'] == 0) { + if ($table['borders_separate']) { + $adv = $table['border_spacing_V'] / 2; + } else { + $maxbwtop = 0; + for ($ctj = 0; $ctj < $numcols; $ctj++) { + if (isset($cells[$i][$ctj]) && $cells[$i][$ctj]) { + if (!$this->simpleTables) { + if ($this->packTableData) { + list($bt, $br, $bb, $bl) = $this->_getBorderWidths($cells[$i][$ctj]['borderbin']); + } else { + $bt = $cells[$i][$ctj]['border_details']['T']['w']; + } + $maxbwtop = max($maxbwtop, $bt); + } elseif ($this->simpleTables) { + $maxbwtop = max($maxbwtop, $table['simple']['border_details']['T']['w']); + } + } + } + $adv = $maxbwtop / 2; + } + $this->y += $adv; + } + + + if ($this->table_rotate) { + $this->tbrot_x0 = $this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['padding_left'] + $this->blk[$this->blklvl]['border_left']['w']; + if ($table['borders_separate']) { + $this->tbrot_h = $table['margin']['T'] + $table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2; + } else { + $this->tbrot_h = $table['margin']['T'] + $table['max_cell_border_width']['T']; + } + $this->tbrot_y0 = $this->y; + $pagetrigger = $y0 - $tableheaderadj + ($this->blk[$this->blklvl]['inner_width']); + } else { + $pagetrigger = $this->PageBreakTrigger; + } + + if ($this->kwt_saved && $level == 1) { + $this->kwt_moved = true; + } + + + if (!empty($tableheader)) { + $ya = $this->y; + $this->TableHeaderFooter($tableheader, $tablestartpage, $tablestartcolumn, 'H', $level); + if ($this->table_rotate) { + $this->tbrot_h = $this->y - $ya; + } + $tableheaderadj = $this->y - $ya; + } elseif ($i == 0 && !$this->table_rotate && $level == 1 && !$this->ColActive) { + // Advance down page + if ($table['borders_separate']) { + $adv = $table['border_spacing_V'] / 2 + $table['border_details']['T']['w'] + $table['padding']['T']; + } else { + $adv = $table['max_cell_border_width']['T'] / 2; + } + if ($adv) { + if ($this->table_rotate) { + $this->y += ($adv); + } else { + $this->DivLn($adv, $this->blklvl, true); + } + } + } + + $outerfilled = 0; + $y = $y0 = $this->y; + } + + /* -- COLUMNS -- */ + // COLS + // COLUMN CHANGE + if ($this->CurrCol != $oldcolumn) { + // Added to correct for Columns + $x += $this->ChangeColumn * ($this->ColWidth + $this->ColGap); + $x0 += $this->ChangeColumn * ($this->ColWidth + $this->ColGap); + if ($this->CurrCol == 0) { // just added a page - possibly with tableheader + $y0 = $this->y; // this->y0 is global used by Columns - $y0 is internal to tablewrite + } else { + $y0 = $this->y0; // this->y0 is global used by Columns - $y0 is internal to tablewrite + } + $y = $y0; + $outerfilled = 0; + if ($this->CurrCol != 0 && ($this->keepColumns && $this->ColActive) && !empty($tableheader) && $i > 0) { + $this->x = $x; + $this->y = $y; + $this->TableHeaderFooter($tableheader, $tablestartpage, $tablestartcolumn, 'H', $level); + $y0 = $y = $this->y; + } + } + /* -- END COLUMNS -- */ + } + $skippage = true; + } + + $this->x = $x; + $this->y = $y; + + if ($this->kwt_saved && $level == 1) { + $this->printkwtbuffer(); + $x0 = $x = $this->x; + $y0 = $y = $this->y; + $this->kwt_moved = false; + $this->kwt_saved = false; + } + + + // Set the Page & Column where table actually starts + if ($i == 0 && $j == 0 && $level == 1) { + if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN + $tablestartpage = 'EVEN'; + } elseif (($this->mirrorMargins) && (($this->page) % 2 == 1)) { // ODD + $tablestartpage = 'ODD'; + } else { + $tablestartpage = ''; + } + $tablestartpageno = $this->page; + if ($this->ColActive) { + $tablestartcolumn = $this->CurrCol; + } // *COLUMNS* + } + + // ALIGN + $align = $cell['a']; + + /* -- COLUMNS -- */ + // If outside columns, this is done in PaintDivBB + if ($this->ColActive) { + // OUTER FILL BGCOLOR of DIVS + if ($this->blklvl > 0 && ($j == 0) && !$this->table_rotate && $level == 1) { + $firstblockfill = $this->GetFirstBlockFill(); + if ($firstblockfill && $this->blklvl >= $firstblockfill) { + $divh = $maxrowheight; + // Last row + if ((!isset($cell['rowspan']) && $i == $numrows - 1) || (isset($cell['rowspan']) && (($i == $numrows - 1 && $cell['rowspan'] < 2) || ($cell['rowspan'] > 1 && ($i + $cell['rowspan'] - 1) == $numrows - 1)))) { + if ($table['borders_separate']) { + $adv = $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2; + } else { + $adv = $table['margin']['B'] + $table['max_cell_border_width']['B'] / 2; + } + $divh += $adv; // last row: fill bottom half of bottom border (y advanced at end) + } + + if (($this->y + $divh) > $outerfilled) { // if not already painted by previous rowspan + $bak_x = $this->x; + $bak_y = $this->y; + if ($outerfilled > $this->y) { + $divh = ($this->y + $divh) - $outerfilled; + $this->y = $outerfilled; + } + + $this->DivLn($divh, -3, false); + $outerfilled = $this->y + $divh; + // Reset current block fill + $bcor = $this->blk[$this->blklvl]['bgcolorarray']; + if ($bcor) { + $this->SetFColor($bcor); + } + $this->x = $bak_x; + $this->y = $bak_y; + } + } + } + } + + // TABLE BACKGROUND FILL BGCOLOR - for cellSpacing + if ($this->ColActive) { + if ($table['borders_separate']) { + $fill = isset($table['bgcolor'][-1]) ? $table['bgcolor'][-1] : 0; + if ($fill) { + $color = $this->colorConverter->convert($fill, $this->PDFAXwarnings); + if ($color) { + $xadj = ($table['border_spacing_H'] / 2); + $yadj = ($table['border_spacing_V'] / 2); + $wadj = $table['border_spacing_H']; + $hadj = $table['border_spacing_V']; + if ($i == 0) { // Top + $yadj += $table['padding']['T'] + $table['border_details']['T']['w']; + $hadj += $table['padding']['T'] + $table['border_details']['T']['w']; + } + if ($j == 0) { // Left + $xadj += $table['padding']['L'] + $table['border_details']['L']['w']; + $wadj += $table['padding']['L'] + $table['border_details']['L']['w']; + } + if ($i == ($numrows - 1) || (isset($cell['rowspan']) && ($i + $cell['rowspan']) == $numrows) || (!isset($cell['rowspan']) && ($i + 1) == $numrows)) { // Bottom + $hadj += $table['padding']['B'] + $table['border_details']['B']['w']; + } + if ($j == ($numcols - 1) || (isset($cell['colspan']) && ($j + $cell['colspan']) == $numcols) || (!isset($cell['colspan']) && ($j + 1) == $numcols)) { // Right + $wadj += $table['padding']['R'] + $table['border_details']['R']['w']; + } + $this->SetFColor($color); + $this->Rect($x - $xadj, $y - $yadj, $w + $wadj, $h + $hadj, 'F'); + } + } + } + } + /* -- END COLUMNS -- */ + + if ($table['empty_cells'] != 'hide' || !empty($cell['textbuffer']) || (isset($cell['nestedcontent']) && $cell['nestedcontent']) || !$table['borders_separate']) { + $paintcell = true; + } else { + $paintcell = false; + } + + // Set Borders + $bord = 0; + $bord_det = []; + + if (!$this->simpleTables) { + if ($this->packTableData) { + $c = $this->_unpackCellBorder($cell['borderbin']); + $bord = $c['border']; + $bord_det = $c['border_details']; + } else { + $bord = $cell['border']; + $bord_det = $cell['border_details']; + } + } elseif ($this->simpleTables) { + $bord = $table['simple']['border']; + $bord_det = $table['simple']['border_details']; + } + + // TABLE ROW OR CELL FILL BGCOLOR + $fill = 0; + if (isset($cell['bgcolor']) && $cell['bgcolor'] && $cell['bgcolor'] != 'transparent') { + $fill = $cell['bgcolor']; + $leveladj = 6; + } elseif (isset($table['bgcolor'][$i]) && $table['bgcolor'][$i] && $table['bgcolor'][$i] != 'transparent') { // Row color + $fill = $table['bgcolor'][$i]; + $leveladj = 3; + } + if ($fill && $paintcell) { + $color = $this->colorConverter->convert($fill, $this->PDFAXwarnings); + if ($color) { + if ($table['borders_separate']) { + if ($this->ColActive) { + $this->SetFColor($color); + $this->Rect($x + ($table['border_spacing_H'] / 2), $y + ($table['border_spacing_V'] / 2), $w - $table['border_spacing_H'], $h - $table['border_spacing_V'], 'F'); + } else { + $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => ($x + ($table['border_spacing_H'] / 2)), 'y' => ($y + ($table['border_spacing_V'] / 2)), 'w' => ($w - $table['border_spacing_H']), 'h' => ($h - $table['border_spacing_V']), 'col' => $color]; + } + } else { + if ($this->ColActive) { + $this->SetFColor($color); + $this->Rect($x, $y, $w, $h, 'F'); + } else { + $this->tableBackgrounds[$level * 9 + $leveladj][] = ['gradient' => false, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'col' => $color]; + } + } + } + } + + /* -- BACKGROUNDS -- */ + if (isset($cell['gradient']) && $cell['gradient'] && $paintcell) { + $g = $this->gradient->parseBackgroundGradient($cell['gradient']); + if ($g) { + if ($table['borders_separate']) { + $px = $x + ($table['border_spacing_H'] / 2); + $py = $y + ($table['border_spacing_V'] / 2); + $pw = $w - $table['border_spacing_H']; + $ph = $h - $table['border_spacing_V']; + } else { + $px = $x; + $py = $y; + $pw = $w; + $ph = $h; + } + if ($this->ColActive) { + $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']); + } else { + $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } + + if (isset($cell['background-image']) && $paintcell) { + if (isset($cell['background-image']['gradient']) && $cell['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $cell['background-image']['gradient'])) { + $g = $this->gradient->parseMozGradient($cell['background-image']['gradient']); + if ($g) { + if ($table['borders_separate']) { + $px = $x + ($table['border_spacing_H'] / 2); + $py = $y + ($table['border_spacing_V'] / 2); + $pw = $w - $table['border_spacing_H']; + $ph = $h - $table['border_spacing_V']; + } else { + $px = $x; + $py = $y; + $pw = $w; + $ph = $h; + } + if ($this->ColActive) { + $this->gradient->Gradient($px, $py, $pw, $ph, $g['type'], $g['stops'], $g['colorspace'], $g['coords'], $g['extend']); + } else { + $this->tableBackgrounds[$level * 9 + 7][] = ['gradient' => true, 'x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } elseif (isset($cell['background-image']['image_id']) && $cell['background-image']['image_id']) { // Background pattern + $n = count($this->patterns) + 1; + if ($table['borders_separate']) { + $px = $x + ($table['border_spacing_H'] / 2); + $py = $y + ($table['border_spacing_V'] / 2); + $pw = $w - $table['border_spacing_H']; + $ph = $h - $table['border_spacing_V']; + } else { + $px = $x; + $py = $y; + $pw = $w; + $ph = $h; + } + if ($this->ColActive) { + list($orig_w, $orig_h, $x_repeat, $y_repeat) = $this->_resizeBackgroundImage($cell['background-image']['orig_w'], $cell['background-image']['orig_h'], $pw, $ph, $cell['background-image']['resize'], $cell['background-image']['x_repeat'], $cell['background-image']['y_repeat']); + $this->patterns[$n] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'pgh' => $this->h, 'image_id' => $cell['background-image']['image_id'], 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $cell['background-image']['x_pos'], 'y_pos' => $cell['background-image']['y_pos'], 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat]; + if ($cell['background-image']['opacity'] > 0 && $cell['background-image']['opacity'] < 1) { + $opac = $this->SetAlpha($cell['background-image']['opacity'], 'Normal', true); + } else { + $opac = ''; + } + $this->writer->write(sprintf('q /Pattern cs /P%d scn %s %.3F %.3F %.3F %.3F re f Q', $n, $opac, $px * Mpdf::SCALE, ($this->h - $py) * Mpdf::SCALE, $pw * Mpdf::SCALE, -$ph * Mpdf::SCALE)); + } else { + $image_id = $cell['background-image']['image_id']; + $orig_w = $cell['background-image']['orig_w']; + $orig_h = $cell['background-image']['orig_h']; + $x_pos = $cell['background-image']['x_pos']; + $y_pos = $cell['background-image']['y_pos']; + $x_repeat = $cell['background-image']['x_repeat']; + $y_repeat = $cell['background-image']['y_repeat']; + $resize = $cell['background-image']['resize']; + $opacity = $cell['background-image']['opacity']; + $itype = $cell['background-image']['itype']; + $this->tableBackgrounds[$level * 9 + 8][] = ['x' => $px, 'y' => $py, 'w' => $pw, 'h' => $ph, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype]; + } + } + } + /* -- END BACKGROUNDS -- */ + + if (isset($cell['colspan']) && $cell['colspan'] > 1) { + $ccolsp = $cell['colspan']; + } else { + $ccolsp = 1; + } + if (isset($cell['rowspan']) && $cell['rowspan'] > 1) { + $crowsp = $cell['rowspan']; + } else { + $crowsp = 1; + } + + + // but still need to do this for repeated headers... + if (!$table['borders_separate'] && $this->tabletheadjustfinished && !$this->simpleTables) { + if (isset($table['topntail']) && $table['topntail']) { + $bord_det['T'] = $this->border_details($table['topntail']); + $bord_det['T']['w'] /= $this->shrin_k; + $this->setBorder($bord, Border::TOP); + } + if (isset($table['thead-underline']) && $table['thead-underline']) { + $bord_det['T'] = $this->border_details($table['thead-underline']); + $bord_det['T']['w'] /= $this->shrin_k; + $this->setBorder($bord, Border::TOP); + } + } + + + // Get info of first row ==>> table header + // Use > 1 row if THEAD + if (isset($table['is_thead'][$i]) && $table['is_thead'][$i] && $level == 1) { + if ($j == 0) { + $tableheaderrowheight += $table['hr'][$i]; + } + $tableheader[$i][0]['trbackground-images'] = (isset($table['trbackground-images'][$i]) ? $table['trbackground-images'][$i] : null); + $tableheader[$i][0]['trgradients'] = (isset($table['trgradients'][$i]) ? $table['trgradients'][$i] : null); + $tableheader[$i][0]['trbgcolor'] = (isset($table['bgcolor'][$i]) ? $table['bgcolor'][$i] : null); + $tableheader[$i][$j]['x'] = $x; + $tableheader[$i][$j]['y'] = $y; + $tableheader[$i][$j]['h'] = $h; + $tableheader[$i][$j]['w'] = $w; + if (isset($cell['textbuffer'])) { + $tableheader[$i][$j]['textbuffer'] = $cell['textbuffer']; + } else { + $tableheader[$i][$j]['textbuffer'] = ''; + } + $tableheader[$i][$j]['a'] = $cell['a']; + $tableheader[$i][$j]['R'] = $cell['R']; + + $tableheader[$i][$j]['va'] = $cell['va']; + $tableheader[$i][$j]['mih'] = $cell['mih']; + $tableheader[$i][$j]['gradient'] = (isset($cell['gradient']) ? $cell['gradient'] : null); // *BACKGROUNDS* + $tableheader[$i][$j]['background-image'] = (isset($cell['background-image']) ? $cell['background-image'] : null); // *BACKGROUNDS* + $tableheader[$i][$j]['rowspan'] = (isset($cell['rowspan']) ? $cell['rowspan'] : null); + $tableheader[$i][$j]['colspan'] = (isset($cell['colspan']) ? $cell['colspan'] : null); + $tableheader[$i][$j]['bgcolor'] = $cell['bgcolor']; + + if (!$this->simpleTables) { + $tableheader[$i][$j]['border'] = $bord; + $tableheader[$i][$j]['border_details'] = $bord_det; + } elseif ($this->simpleTables) { + $tableheader[$i][$j]['border'] = $table['simple']['border']; + $tableheader[$i][$j]['border_details'] = $table['simple']['border_details']; + } + $tableheader[$i][$j]['padding'] = $cell['padding']; + if (isset($cell['direction'])) { + $tableheader[$i][$j]['direction'] = $cell['direction']; + } + if (isset($cell['cellLineHeight'])) { + $tableheader[$i][$j]['cellLineHeight'] = $cell['cellLineHeight']; + } + if (isset($cell['cellLineStackingStrategy'])) { + $tableheader[$i][$j]['cellLineStackingStrategy'] = $cell['cellLineStackingStrategy']; + } + if (isset($cell['cellLineStackingShift'])) { + $tableheader[$i][$j]['cellLineStackingShift'] = $cell['cellLineStackingShift']; + } + } + + // CELL BORDER + if ($bord) { + if ($table['borders_separate'] && $paintcell) { + $this->_tableRect($x + ($table['border_spacing_H'] / 2) + ($bord_det['L']['w'] / 2), $y + ($table['border_spacing_V'] / 2) + ($bord_det['T']['w'] / 2), $w - $table['border_spacing_H'] - ($bord_det['L']['w'] / 2) - ($bord_det['R']['w'] / 2), $h - $table['border_spacing_V'] - ($bord_det['T']['w'] / 2) - ($bord_det['B']['w'] / 2), $bord, $bord_det, false, $table['borders_separate']); + } elseif (!$table['borders_separate']) { + $this->_tableRect($x, $y, $w, $h, $bord, $bord_det, true, $table['borders_separate']); // true causes buffer + } + } + + // VERTICAL ALIGN + if ($cell['R'] && intval($cell['R']) > 0 && intval($cell['R']) < 90 && isset($cell['va']) && $cell['va'] != 'B') { + $cell['va'] = 'B'; + } + if (!isset($cell['va']) || $cell['va'] == 'M') { + $this->y += ($h - $cell['mih']) / 2; + } elseif (isset($cell['va']) && $cell['va'] == 'B') { + $this->y += $h - $cell['mih']; + } + + // NESTED CONTENT + // TEXT (and nested tables) + + $this->divwidth = $w; + if (!empty($cell['textbuffer'])) { + $this->cellTextAlign = $align; + $this->cellLineHeight = $cell['cellLineHeight']; + $this->cellLineStackingStrategy = $cell['cellLineStackingStrategy']; + $this->cellLineStackingShift = $cell['cellLineStackingShift']; + if ($level == 1) { + if (isset($table['is_tfoot'][$i]) && $table['is_tfoot'][$i]) { + if (preg_match('/{colsum([0-9]*)[_]*}/', $cell['textbuffer'][0][0], $m)) { + $rep = sprintf("%01." . intval($m[1]) . "f", $this->colsums[$j]); + $cell['textbuffer'][0][0] = preg_replace('/{colsum[0-9_]*}/', $rep, $cell['textbuffer'][0][0]); + } + } elseif (!isset($table['is_thead'][$i])) { + if (isset($this->colsums[$j])) { + $this->colsums[$j] += $this->toFloat($cell['textbuffer'][0][0]); + } else { + $this->colsums[$j] = $this->toFloat($cell['textbuffer'][0][0]); + } + } + } + $opy = $this->y; + // mPDF ITERATION + if ($this->iterationCounter) { + foreach ($cell['textbuffer'] as $k => $t) { + if (preg_match('/{iteration ([a-zA-Z0-9_]+)}/', $t[0], $m)) { + $vname = '__' . $m[1] . '_'; + if (!isset($this->$vname)) { + $this->$vname = 1; + } else { + $this->$vname++; + } + $cell['textbuffer'][$k][0] = preg_replace('/{iteration ' . $m[1] . '}/', $this->$vname, $cell['textbuffer'][$k][0]); + } + } + } + + + if ($cell['R']) { + $cellPtSize = $cell['textbuffer'][0][11] / $this->shrin_k; + if (!$cellPtSize) { + $cellPtSize = $this->default_font_size; + } + $cellFontHeight = ($cellPtSize / Mpdf::SCALE); + $opx = $this->x; + $angle = intval($cell['R']); + // Only allow 45 to 89 degrees (when bottom-aligned) or exactly 90 or -90 + if ($angle > 90) { + $angle = 90; + } elseif ($angle > 0 && $angle < 45) { + $angle = 45; + } elseif ($angle < 0) { + $angle = -90; + } + $offset = ((sin(deg2rad($angle))) * 0.37 * $cellFontHeight); + if (isset($cell['a']) && $cell['a'] == 'R') { + $this->x += ($w) + ($offset) - ($cellFontHeight / 3) - ($cell['padding']['R'] + ($table['border_spacing_H'] / 2)); + } elseif (!isset($cell['a']) || $cell['a'] == 'C') { + $this->x += ($w / 2) + ($offset); + } else { + $this->x += ($offset) + ($cellFontHeight / 3) + ($cell['padding']['L'] + ($table['border_spacing_H'] / 2)); + } + $str = ''; + foreach ($cell['textbuffer'] as $t) { + $str .= $t[0] . ' '; + } + $str = rtrim($str); + if (!isset($cell['va']) || $cell['va'] == 'M') { + $this->y -= ($h - $cell['mih']) / 2; // Undo what was added earlier VERTICAL ALIGN + if ($angle > 0) { + $this->y += (($h - $cell['mih']) / 2) + $cell['padding']['T'] + ($cell['mih'] - ($cell['padding']['T'] + $cell['padding']['B'])); + } elseif ($angle < 0) { + $this->y += (($h - $cell['mih']) / 2) + ($cell['padding']['T'] + ($table['border_spacing_V'] / 2)); + } + } elseif (isset($cell['va']) && $cell['va'] == 'B') { + $this->y -= $h - $cell['mih']; // Undo what was added earlier VERTICAL ALIGN + if ($angle > 0) { + $this->y += $h - ($cell['padding']['B'] + ($table['border_spacing_V'] / 2)); + } elseif ($angle < 0) { + $this->y += $h - $cell['mih'] + ($cell['padding']['T'] + ($table['border_spacing_V'] / 2)); + } + } elseif (isset($cell['va']) && $cell['va'] == 'T') { + if ($angle > 0) { + $this->y += $cell['mih'] - ($cell['padding']['B'] + ($table['border_spacing_V'] / 2)); + } elseif ($angle < 0) { + $this->y += ($cell['padding']['T'] + ($table['border_spacing_V'] / 2)); + } + } + $this->Rotate($angle, $this->x, $this->y); + $s_fs = $this->FontSizePt; + $s_f = $this->FontFamily; + $s_st = $this->FontStyle; + if (!empty($cell['textbuffer'][0][3])) { // Font Color + $cor = $cell['textbuffer'][0][3]; + $this->SetTColor($cor); + } + $this->SetFont($cell['textbuffer'][0][4], $cell['textbuffer'][0][2], $cellPtSize, true, true); + + $this->magic_reverse_dir($str, $this->directionality, $cell['textbuffer'][0][18]); + $this->Text($this->x, $this->y, $str, $cell['textbuffer'][0][18], $cell['textbuffer'][0][8]); // textvar + $this->Rotate(0); + $this->SetFont($s_f, $s_st, $s_fs, true, true); + $this->SetTColor(0); + $this->x = $opx; + } else { + if (!$this->simpleTables) { + if ($bord_det) { + $btlw = $bord_det['L']['w']; + $btrw = $bord_det['R']['w']; + $bttw = $bord_det['T']['w']; + } else { + $btlw = 0; + $btrw = 0; + $bttw = 0; + } + if ($table['borders_separate']) { + $xadj = $btlw + $cell['padding']['L'] + ($table['border_spacing_H'] / 2); + $wadj = $btlw + $btrw + $cell['padding']['L'] + $cell['padding']['R'] + $table['border_spacing_H']; + $yadj = $bttw + $cell['padding']['T'] + ($table['border_spacing_H'] / 2); + } else { + $xadj = $btlw / 2 + $cell['padding']['L']; + $wadj = ($btlw + $btrw) / 2 + $cell['padding']['L'] + $cell['padding']['R']; + $yadj = $bttw / 2 + $cell['padding']['T']; + } + } elseif ($this->simpleTables) { + if ($table['borders_separate']) { // NB twice border width + $xadj = $table['simple']['border_details']['L']['w'] + $cell['padding']['L'] + ($table['border_spacing_H'] / 2); + $wadj = $table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w'] + $cell['padding']['L'] + $cell['padding']['R'] + $table['border_spacing_H']; + $yadj = $table['simple']['border_details']['T']['w'] + $cell['padding']['T'] + ($table['border_spacing_H'] / 2); + } else { + $xadj = $table['simple']['border_details']['L']['w'] / 2 + $cell['padding']['L']; + $wadj = ($table['simple']['border_details']['L']['w'] + $table['simple']['border_details']['R']['w']) / 2 + $cell['padding']['L'] + $cell['padding']['R']; + $yadj = $table['simple']['border_details']['T']['w'] / 2 + $cell['padding']['T']; + } + } + $this->decimal_offset = 0; + if (substr($cell['a'], 0, 1) == 'D') { + if (isset($cell['colspan']) && $cell['colspan'] > 1) { + $this->cellTextAlign = $c['a'] = substr($cell['a'], 2, 1); + } else { + $smax = $table['decimal_align'][$j]['maxs0']; + $d_content = $table['decimal_align'][$j]['maxs0'] + $table['decimal_align'][$j]['maxs1']; + $this->decimal_offset = $smax; + $extra = ($w - $d_content - $wadj); + if ($extra > 0) { + if (substr($cell['a'], 2, 1) == 'R') { + $this->decimal_offset += $extra; + } elseif (substr($cell['a'], 2, 1) == 'C') { + $this->decimal_offset += ($extra) / 2; + } + } + } + } + $this->divwidth = $w - $wadj; + if ($this->divwidth == 0) { + $this->divwidth = 0.0001; + } + $this->x += $xadj; + $this->y += $yadj; + $this->printbuffer($cell['textbuffer'], '', true, false, $cell['direction']); + } + $this->y = $opy; + } + + /* -- BACKGROUNDS -- */ + if (!$this->ColActive) { + if (isset($table['trgradients'][$i]) && ($j == 0 || $table['borders_separate'])) { + $g = $this->gradient->parseBackgroundGradient($table['trgradients'][$i]); + if ($g) { + $gx = $x0; + $gy = $y; + $gh = $h; + $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + if ($table['borders_separate']) { + $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']); + $clx = $x + ($table['border_spacing_H'] / 2); + $cly = $y + ($table['border_spacing_V'] / 2); + $clw = $w - $table['border_spacing_H']; + $clh = $h - $table['border_spacing_V']; + // Set clipping path + $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6 + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s]; + } else { + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } + if (isset($table['trbackground-images'][$i]) && ($j == 0 || $table['borders_separate'])) { + if (isset($table['trbackground-images'][$i]['gradient']) && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['trbackground-images'][$i]['gradient'])) { + $g = $this->gradient->parseMozGradient($table['trbackground-images'][$i]['gradient']); + if ($g) { + $gx = $x0; + $gy = $y; + $gh = $h; + $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + if ($table['borders_separate']) { + $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']); + $clx = $x + ($table['border_spacing_H'] / 2); + $cly = $y + ($table['border_spacing_V'] / 2); + $clw = $w - $table['border_spacing_H']; + $clh = $h - $table['border_spacing_V']; + // Set clipping path + $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6 + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => $s]; + } else { + $this->tableBackgrounds[$level * 9 + 4][] = ['gradient' => true, 'x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + } else { + $image_id = $table['trbackground-images'][$i]['image_id']; + $orig_w = $table['trbackground-images'][$i]['orig_w']; + $orig_h = $table['trbackground-images'][$i]['orig_h']; + $x_pos = $table['trbackground-images'][$i]['x_pos']; + $y_pos = $table['trbackground-images'][$i]['y_pos']; + $x_repeat = $table['trbackground-images'][$i]['x_repeat']; + $y_repeat = $table['trbackground-images'][$i]['y_repeat']; + $resize = $table['trbackground-images'][$i]['resize']; + $opacity = $table['trbackground-images'][$i]['opacity']; + $itype = $table['trbackground-images'][$i]['itype']; + $clippath = ''; + $gx = $x0; + $gy = $y; + $gh = $h; + $gw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + if ($table['borders_separate']) { + $gw -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['padding']['R'] + $table['border_details']['R']['w'] + $table['border_spacing_H']); + $clx = $x + ($table['border_spacing_H'] / 2); + $cly = $y + ($table['border_spacing_V'] / 2); + $clw = $w - $table['border_spacing_H']; + $clh = $h - $table['border_spacing_V']; + // Set clipping path + $s = $this->_setClippingPath($clx, $cly, $clw, $clh); // mPDF 6 + $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx + ($table['border_spacing_H'] / 2), 'y' => $gy + ($table['border_spacing_V'] / 2), 'w' => $gw - $table['border_spacing_V'], 'h' => $gh - $table['border_spacing_H'], 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => $s, 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype]; + } else { + $this->tableBackgrounds[$level * 9 + 5][] = ['x' => $gx, 'y' => $gy, 'w' => $gw, 'h' => $gh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype]; + } + } + } + } + + /* -- END BACKGROUNDS -- */ + + // TABLE BORDER - if separate + if (($table['borders_separate'] || ($this->simpleTables && !$table['simple']['border'])) && $table['border']) { + $halfspaceL = $table['padding']['L'] + ($table['border_spacing_H'] / 2); + $halfspaceR = $table['padding']['R'] + ($table['border_spacing_H'] / 2); + $halfspaceT = $table['padding']['T'] + ($table['border_spacing_V'] / 2); + $halfspaceB = $table['padding']['B'] + ($table['border_spacing_V'] / 2); + $tbx = $x; + $tby = $y; + $tbw = $w; + $tbh = $h; + $tab_bord = 0; + + $corner = ''; + if ($i == 0) { // Top + $tby -= $halfspaceT + ($table['border_details']['T']['w'] / 2); + $tbh += $halfspaceT + ($table['border_details']['T']['w'] / 2); + $this->setBorder($tab_bord, Border::TOP); + $corner .= 'T'; + } + if ($i == ($numrows - 1) || (isset($cell['rowspan']) && ($i + $cell['rowspan']) == $numrows)) { // Bottom + $tbh += $halfspaceB + ($table['border_details']['B']['w'] / 2); + $this->setBorder($tab_bord, Border::BOTTOM); + $corner .= 'B'; + } + if ($j == 0) { // Left + $tbx -= $halfspaceL + ($table['border_details']['L']['w'] / 2); + $tbw += $halfspaceL + ($table['border_details']['L']['w'] / 2); + $this->setBorder($tab_bord, Border::LEFT); + $corner .= 'L'; + } + if ($j == ($numcols - 1) || (isset($cell['colspan']) && ($j + $cell['colspan']) == $numcols)) { // Right + $tbw += $halfspaceR + ($table['border_details']['R']['w'] / 2); + $this->setBorder($tab_bord, Border::RIGHT); + $corner .= 'R'; + } + $this->_tableRect($tbx, $tby, $tbw, $tbh, $tab_bord, $table['border_details'], false, $table['borders_separate'], 'table', $corner, $table['border_spacing_V'], $table['border_spacing_H']); + } + + unset($cell); + // Reset values + $this->Reset(); + }//end of (if isset(cells)...) + }// end of columns + + $newpagestarted = false; + $this->tabletheadjustfinished = false; + + /* -- COLUMNS -- */ + if ($this->ColActive) { + if (!$this->table_keep_together && $i < $numrows - 1 && $level == 1) { + $this->breakpoints[$this->CurrCol][] = $y + $h; + } // mPDF 6 + if (count($this->cellBorderBuffer)) { + $this->printcellbuffer(); + } + } + /* -- END COLUMNS -- */ + + if ($i == $numrows - 1) { + $this->y = $y + $h; + } // last row jump (update this->y position) + if ($this->table_rotate && $level == 1) { + $this->tbrot_h += $h; + } + } // end of rows + + if (count($this->cellBorderBuffer)) { + $this->printcellbuffer(); + } + + + if ($this->tableClipPath) { + $this->writer->write("Q"); + } + $this->tableClipPath = ''; + + // Advance down page by half width of bottom border + if ($table['borders_separate']) { + $this->y += $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2; + } else { + $this->y += $table['max_cell_border_width']['B'] / 2; + } + + if ($table['borders_separate'] && $level == 1) { + $this->tbrot_h += $table['margin']['B'] + $table['padding']['B'] + $table['border_details']['B']['w'] + $table['border_spacing_V'] / 2; + } elseif ($level == 1) { + $this->tbrot_h += $table['margin']['B'] + $table['max_cell_border_width']['B'] / 2; + } + + $bx = $x0; + $by = $y0; + if ($table['borders_separate']) { + $bx -= ($table['padding']['L'] + $table['border_details']['L']['w'] + $table['border_spacing_H'] / 2); + if ($tablestartpageno != $this->page) { // IF broken across page + $by += $table['max_cell_border_width']['T'] / 2; + if (empty($tableheader)) { + $by -= ($table['border_spacing_V'] / 2); + } + } elseif ($split && $startrow > 0 && empty($tableheader)) { + $by -= ($table['border_spacing_V'] / 2); + } else { + $by -= ($table['padding']['T'] + $table['border_details']['T']['w'] + $table['border_spacing_V'] / 2); + } + } elseif ($tablestartpageno != $this->page && !empty($tableheader)) { + $by += $maxbwtop / 2; + } + $by -= $tableheaderadj; + $bh = $this->y - $by; + if (!$table['borders_separate']) { + $bh -= $table['max_cell_border_width']['B'] / 2; + } + + if ($split) { + $bw = 0; + $finalSpread = true; + for ($t = $startcol; $t < $numcols; $t++) { + if ($table['colPg'][$t] == $splitpg) { + $bw += $table['wc'][$t]; + } + if ($table['colPg'][$t] > $splitpg) { + $finalSpread = false; + break; + } + } + if ($startcol == 0) { + $firstSpread = true; + } else { + $firstSpread = false; + } + if ($table['borders_separate']) { + $bw += $table['border_spacing_H']; + if ($firstSpread) { + $bw += $table['padding']['L'] + $table['border_details']['L']['w']; + } else { + $bx += ($table['padding']['L'] + $table['border_details']['L']['w']); + } + if ($finalSpread) { + $bw += $table['padding']['R'] + $table['border_details']['R']['w']; + } + } + } else { + $bw = $table['w'] - ($table['max_cell_border_width']['L'] / 2) - ($table['max_cell_border_width']['R'] / 2) - $table['margin']['L'] - $table['margin']['R']; + } + + if (!$this->ColActive) { + if (isset($table['bgcolor'][-1])) { + $color = $this->colorConverter->convert($table['bgcolor'][-1], $this->PDFAXwarnings); + if ($color) { + $this->tableBackgrounds[$level * 9][] = ['gradient' => false, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'col' => $color]; + } + } + + /* -- BACKGROUNDS -- */ + if (isset($table['gradient'])) { + $g = $this->gradient->parseBackgroundGradient($table['gradient']); + if ($g) { + $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } + + if (isset($table['background-image'])) { + if (isset($table['background-image']['gradient']) && $table['background-image']['gradient'] && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $table['background-image']['gradient'])) { + $g = $this->gradient->parseMozGradient($table['background-image']['gradient']); + if ($g) { + $this->tableBackgrounds[$level * 9 + 1][] = ['gradient' => true, 'x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'gradtype' => $g['type'], 'stops' => $g['stops'], 'colorspace' => $g['colorspace'], 'coords' => $g['coords'], 'extend' => $g['extend'], 'clippath' => '']; + } + } else { + $image_id = $table['background-image']['image_id']; + $orig_w = $table['background-image']['orig_w']; + $orig_h = $table['background-image']['orig_h']; + $x_pos = $table['background-image']['x_pos']; + $y_pos = $table['background-image']['y_pos']; + $x_repeat = $table['background-image']['x_repeat']; + $y_repeat = $table['background-image']['y_repeat']; + $resize = $table['background-image']['resize']; + $opacity = $table['background-image']['opacity']; + $itype = $table['background-image']['itype']; + $this->tableBackgrounds[$level * 9 + 2][] = ['x' => $bx, 'y' => $by, 'w' => $bw, 'h' => $bh, 'image_id' => $image_id, 'orig_w' => $orig_w, 'orig_h' => $orig_h, 'x_pos' => $x_pos, 'y_pos' => $y_pos, 'x_repeat' => $x_repeat, 'y_repeat' => $y_repeat, 'clippath' => '', 'resize' => $resize, 'opacity' => $opacity, 'itype' => $itype]; + } + } + /* -- END BACKGROUNDS -- */ + } + + if ($this->tableBackgrounds && $level == 1) { + $s = $this->PrintTableBackgrounds(); + if ($this->table_rotate && !$this->processingHeader && !$this->processingFooter) { + $this->tablebuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->tablebuffer); + if ($level == 1) { + $this->tablebuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->tablebuffer); + } + } elseif ($this->bufferoutput) { + $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->headerbuffer); + if ($level == 1) { + $this->headerbuffer = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->headerbuffer); + } + } else { + $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->pages[$this->page]); + if ($level == 1) { + $this->pages[$this->page] = preg_replace('/(___TABLE___BACKGROUNDS' . $this->uniqstr . ')/', " ", $this->pages[$this->page]); + } + } + $this->tableBackgrounds = []; + } + + + // TABLE BOTTOM MARGIN + if ($table['margin']['B']) { + if (!$this->table_rotate && $level == 1) { + $this->DivLn($table['margin']['B'], $this->blklvl, true); // collapsible + } else { + $this->y += ($table['margin']['B']); + } + } + + if ($this->ColActive && $level == 1) { + $this->breakpoints[$this->CurrCol][] = $this->y; + } // *COLUMNS* + + if ($split) { + // Are there more columns to print on a next page? + if ($lastCol < $numcols - 1) { + $splitpg++; + $startcol = $lastCol + 1; + return [false, $startrow, $startcol, $splitpg, $returny, $y0]; + } else { + return [true, 0, 0, 0, false, false]; + } + } + } + + // END OF FUNCTION _tableWrite() + /////////////////////////END OF TABLE CODE////////////////////////////////// + /* -- END TABLES -- */ + + function _putextgstates() + { + for ($i = 1; $i <= count($this->extgstates); $i++) { + $this->writer->object(); + $this->extgstates[$i]['n'] = $this->n; + $this->writer->write('<</Type /ExtGState'); + foreach ($this->extgstates[$i]['parms'] as $k => $v) { + $this->writer->write('/' . $k . ' ' . $v); + } + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + } + + function SetProtection($permissions = [], $user_pass = '', $owner_pass = null, $length = 40) + { + $this->encrypted = $this->protection->setProtection($permissions, $user_pass, $owner_pass, $length); + } + + // ========================================= + // FROM class PDF_Bookmark + function Bookmark($txt, $level = 0, $y = 0) + { + $txt = $this->purify_utf8_text($txt); + if ($this->text_input_as_HTML) { + $txt = $this->all_entities_to_utf8($txt); + } + if ($y == -1) { + if (!$this->ColActive) { + $y = $this->y; + } else { + $y = $this->y0; + } // If columns are on - mark top of columns + } + + // else y is used as set, or =0 i.e. top of page + // DIRECTIONALITY RTL + $bmo = ['t' => $txt, 'l' => $level, 'y' => $y, 'p' => $this->page]; + + if ($this->keep_block_together) { + // do nothing + } elseif ($this->table_rotate) { + $this->tbrot_BMoutlines[] = $bmo; + } elseif ($this->kwt) { + $this->kwt_BMoutlines[] = $bmo; + } elseif ($this->ColActive) { + $this->col_BMoutlines[] = $bmo; + } else { + $this->BMoutlines[] = $bmo; + } + } + + /** + * Initiate, and Mark a place for the Table of Contents to be inserted + */ + function TOC( + $tocfont = '', + $tocfontsize = 0, + $tocindent = 0, + $resetpagenum = '', + $pagenumstyle = '', + $suppress = '', + $toc_orientation = '', + $TOCusePaging = true, + $TOCuseLinking = false, + $toc_id = 0, + $tocoutdent = '' + ) { + + $this->tableOfContents->TOC( + $tocfont, + $tocfontsize, + $tocindent, + $resetpagenum, + $pagenumstyle, + $suppress, + $toc_orientation, + $TOCusePaging, + $TOCuseLinking, + $toc_id, + $tocoutdent + ); + } + + function TOCpagebreakByArray($a) + { + if (!is_array($a)) { + $a = []; + } + $tocoutdent = (isset($a['tocoutdent']) ? $a['tocoutdent'] : (isset($a['outdent']) ? $a['outdent'] : '')); + $TOCusePaging = (isset($a['TOCusePaging']) ? $a['TOCusePaging'] : (isset($a['paging']) ? $a['paging'] : true)); + $TOCuseLinking = (isset($a['TOCuseLinking']) ? $a['TOCuseLinking'] : (isset($a['links']) ? $a['links'] : '')); + $toc_orientation = (isset($a['toc_orientation']) ? $a['toc_orientation'] : (isset($a['toc-orientation']) ? $a['toc-orientation'] : '')); + $toc_mgl = (isset($a['toc_mgl']) ? $a['toc_mgl'] : (isset($a['toc-margin-left']) ? $a['toc-margin-left'] : '')); + $toc_mgr = (isset($a['toc_mgr']) ? $a['toc_mgr'] : (isset($a['toc-margin-right']) ? $a['toc-margin-right'] : '')); + $toc_mgt = (isset($a['toc_mgt']) ? $a['toc_mgt'] : (isset($a['toc-margin-top']) ? $a['toc-margin-top'] : '')); + $toc_mgb = (isset($a['toc_mgb']) ? $a['toc_mgb'] : (isset($a['toc-margin-bottom']) ? $a['toc-margin-bottom'] : '')); + $toc_mgh = (isset($a['toc_mgh']) ? $a['toc_mgh'] : (isset($a['toc-margin-header']) ? $a['toc-margin-header'] : '')); + $toc_mgf = (isset($a['toc_mgf']) ? $a['toc_mgf'] : (isset($a['toc-margin-footer']) ? $a['toc-margin-footer'] : '')); + $toc_ohname = (isset($a['toc_ohname']) ? $a['toc_ohname'] : (isset($a['toc-odd-header-name']) ? $a['toc-odd-header-name'] : '')); + $toc_ehname = (isset($a['toc_ehname']) ? $a['toc_ehname'] : (isset($a['toc-even-header-name']) ? $a['toc-even-header-name'] : '')); + $toc_ofname = (isset($a['toc_ofname']) ? $a['toc_ofname'] : (isset($a['toc-odd-footer-name']) ? $a['toc-odd-footer-name'] : '')); + $toc_efname = (isset($a['toc_efname']) ? $a['toc_efname'] : (isset($a['toc-even-footer-name']) ? $a['toc-even-footer-name'] : '')); + $toc_ohvalue = (isset($a['toc_ohvalue']) ? $a['toc_ohvalue'] : (isset($a['toc-odd-header-value']) ? $a['toc-odd-header-value'] : 0)); + $toc_ehvalue = (isset($a['toc_ehvalue']) ? $a['toc_ehvalue'] : (isset($a['toc-even-header-value']) ? $a['toc-even-header-value'] : 0)); + $toc_ofvalue = (isset($a['toc_ofvalue']) ? $a['toc_ofvalue'] : (isset($a['toc-odd-footer-value']) ? $a['toc-odd-footer-value'] : 0)); + $toc_efvalue = (isset($a['toc_efvalue']) ? $a['toc_efvalue'] : (isset($a['toc-even-footer-value']) ? $a['toc-even-footer-value'] : 0)); + $toc_preHTML = (isset($a['toc_preHTML']) ? $a['toc_preHTML'] : (isset($a['toc-preHTML']) ? $a['toc-preHTML'] : '')); + $toc_postHTML = (isset($a['toc_postHTML']) ? $a['toc_postHTML'] : (isset($a['toc-postHTML']) ? $a['toc-postHTML'] : '')); + $toc_bookmarkText = (isset($a['toc_bookmarkText']) ? $a['toc_bookmarkText'] : (isset($a['toc-bookmarkText']) ? $a['toc-bookmarkText'] : '')); + $resetpagenum = (isset($a['resetpagenum']) ? $a['resetpagenum'] : ''); + $pagenumstyle = (isset($a['pagenumstyle']) ? $a['pagenumstyle'] : ''); + $suppress = (isset($a['suppress']) ? $a['suppress'] : ''); + $orientation = (isset($a['orientation']) ? $a['orientation'] : ''); + $mgl = (isset($a['mgl']) ? $a['mgl'] : (isset($a['margin-left']) ? $a['margin-left'] : '')); + $mgr = (isset($a['mgr']) ? $a['mgr'] : (isset($a['margin-right']) ? $a['margin-right'] : '')); + $mgt = (isset($a['mgt']) ? $a['mgt'] : (isset($a['margin-top']) ? $a['margin-top'] : '')); + $mgb = (isset($a['mgb']) ? $a['mgb'] : (isset($a['margin-bottom']) ? $a['margin-bottom'] : '')); + $mgh = (isset($a['mgh']) ? $a['mgh'] : (isset($a['margin-header']) ? $a['margin-header'] : '')); + $mgf = (isset($a['mgf']) ? $a['mgf'] : (isset($a['margin-footer']) ? $a['margin-footer'] : '')); + $ohname = (isset($a['ohname']) ? $a['ohname'] : (isset($a['odd-header-name']) ? $a['odd-header-name'] : '')); + $ehname = (isset($a['ehname']) ? $a['ehname'] : (isset($a['even-header-name']) ? $a['even-header-name'] : '')); + $ofname = (isset($a['ofname']) ? $a['ofname'] : (isset($a['odd-footer-name']) ? $a['odd-footer-name'] : '')); + $efname = (isset($a['efname']) ? $a['efname'] : (isset($a['even-footer-name']) ? $a['even-footer-name'] : '')); + $ohvalue = (isset($a['ohvalue']) ? $a['ohvalue'] : (isset($a['odd-header-value']) ? $a['odd-header-value'] : 0)); + $ehvalue = (isset($a['ehvalue']) ? $a['ehvalue'] : (isset($a['even-header-value']) ? $a['even-header-value'] : 0)); + $ofvalue = (isset($a['ofvalue']) ? $a['ofvalue'] : (isset($a['odd-footer-value']) ? $a['odd-footer-value'] : 0)); + $efvalue = (isset($a['efvalue']) ? $a['efvalue'] : (isset($a['even-footer-value']) ? $a['even-footer-value'] : 0)); + $toc_id = (isset($a['toc_id']) ? $a['toc_id'] : (isset($a['name']) ? $a['name'] : 0)); + $pagesel = (isset($a['pagesel']) ? $a['pagesel'] : (isset($a['pageselector']) ? $a['pageselector'] : '')); + $toc_pagesel = (isset($a['toc_pagesel']) ? $a['toc_pagesel'] : (isset($a['toc-pageselector']) ? $a['toc-pageselector'] : '')); + $sheetsize = (isset($a['sheetsize']) ? $a['sheetsize'] : (isset($a['sheet-size']) ? $a['sheet-size'] : '')); + $toc_sheetsize = (isset($a['toc_sheetsize']) ? $a['toc_sheetsize'] : (isset($a['toc-sheet-size']) ? $a['toc-sheet-size'] : '')); + + $this->TOCpagebreak('', '', '', $TOCusePaging, $TOCuseLinking, $toc_orientation, $toc_mgl, $toc_mgr, $toc_mgt, $toc_mgb, $toc_mgh, $toc_mgf, $toc_ohname, $toc_ehname, $toc_ofname, $toc_efname, $toc_ohvalue, $toc_ehvalue, $toc_ofvalue, $toc_efvalue, $toc_preHTML, $toc_postHTML, $toc_bookmarkText, $resetpagenum, $pagenumstyle, $suppress, $orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $toc_id, $pagesel, $toc_pagesel, $sheetsize, $toc_sheetsize, $tocoutdent); + } + + function TOCpagebreak($tocfont = '', $tocfontsize = '', $tocindent = '', $TOCusePaging = true, $TOCuseLinking = '', $toc_orientation = '', $toc_mgl = '', $toc_mgr = '', $toc_mgt = '', $toc_mgb = '', $toc_mgh = '', $toc_mgf = '', $toc_ohname = '', $toc_ehname = '', $toc_ofname = '', $toc_efname = '', $toc_ohvalue = 0, $toc_ehvalue = 0, $toc_ofvalue = 0, $toc_efvalue = 0, $toc_preHTML = '', $toc_postHTML = '', $toc_bookmarkText = '', $resetpagenum = '', $pagenumstyle = '', $suppress = '', $orientation = '', $mgl = '', $mgr = '', $mgt = '', $mgb = '', $mgh = '', $mgf = '', $ohname = '', $ehname = '', $ofname = '', $efname = '', $ohvalue = 0, $ehvalue = 0, $ofvalue = 0, $efvalue = 0, $toc_id = 0, $pagesel = '', $toc_pagesel = '', $sheetsize = '', $toc_sheetsize = '', $tocoutdent = '') + { + // Start a new page + if ($this->state == 0) { + $this->AddPage(); + } + if ($this->y == $this->tMargin && (!$this->mirrorMargins || ($this->mirrorMargins && $this->page % 2 == 1))) { + // Don't add a page + if ($this->page == 1 && count($this->PageNumSubstitutions) == 0) { + if (!$suppress) { + $suppress = 'off'; + } + // $this->PageNumSubstitutions[] = array('from'=>1, 'reset'=> $resetpagenum, 'type'=>$pagenumstyle, 'suppress'=> $suppress); + } + $this->PageNumSubstitutions[] = ['from' => $this->page, 'reset' => $resetpagenum, 'type' => $pagenumstyle, 'suppress' => $suppress]; + } else { + $this->AddPage($orientation, 'NEXT-ODD', $resetpagenum, $pagenumstyle, $suppress, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $pagesel, $sheetsize); + } + $this->tableOfContents->TOCpagebreak($tocfont, $tocfontsize, $tocindent, $TOCusePaging, $TOCuseLinking, $toc_orientation, $toc_mgl, $toc_mgr, $toc_mgt, $toc_mgb, $toc_mgh, $toc_mgf, $toc_ohname, $toc_ehname, $toc_ofname, $toc_efname, $toc_ohvalue, $toc_ehvalue, $toc_ofvalue, $toc_efvalue, $toc_preHTML, $toc_postHTML, $toc_bookmarkText, $resetpagenum, $pagenumstyle, $suppress, $orientation, $mgl, $mgr, $mgt, $mgb, $mgh, $mgf, $ohname, $ehname, $ofname, $efname, $ohvalue, $ehvalue, $ofvalue, $efvalue, $toc_id, $pagesel, $toc_pagesel, $sheetsize, $toc_sheetsize, $tocoutdent); + } + + function TOC_Entry($txt, $level = 0, $toc_id = 0) + { + if ($this->ColActive) { + $ily = $this->y0; + } else { + $ily = $this->y; + } // use top of columns + + $linkn = $this->AddLink(); + $uid = '__mpdfinternallink_' . $linkn; + if ($this->table_rotate) { + $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page, "tbrot" => true]; + } elseif ($this->kwt) { + $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page, "kwt" => true]; + } elseif ($this->ColActive) { + $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page, "col" => $this->CurrCol]; + } elseif (!$this->keep_block_together) { + $this->internallink[$uid] = ["Y" => $ily, "PAGE" => $this->page]; + } + $this->internallink['#' . $uid] = $linkn; + $this->SetLink($linkn, $ily, $this->page); + + if (strtoupper($toc_id) == 'ALL') { + $toc_id = '_mpdf_all'; + } elseif (!$toc_id) { + $toc_id = 0; + } else { + $toc_id = strtolower($toc_id); + } + $btoc = ['t' => $txt, 'l' => $level, 'p' => $this->page, 'link' => $linkn, 'toc_id' => $toc_id]; + if ($this->keep_block_together) { + // do nothing + } /* -- TABLES -- */ elseif ($this->table_rotate) { + $this->tbrot_toc[] = $btoc; + } elseif ($this->kwt) { + $this->kwt_toc[] = $btoc; + } /* -- END TABLES -- */ elseif ($this->ColActive) { // *COLUMNS* + $this->col_toc[] = $btoc; // *COLUMNS* + } // *COLUMNS* + else { + $this->tableOfContents->_toc[] = $btoc; + } + } + + /* -- END TOC -- */ + + // ====================================================== + function MovePages($target_page, $start_page, $end_page = -1) + { + // move a page/pages EARLIER in the document + if ($end_page < 1) { + $end_page = $start_page; + } + $n_toc = $end_page - $start_page + 1; + + // Set/Update PageNumSubstitutions changes before moving anything + if (count($this->PageNumSubstitutions)) { + $tp_present = false; + $sp_present = false; + $ep_present = false; + foreach ($this->PageNumSubstitutions as $k => $v) { + if ($this->PageNumSubstitutions[$k]['from'] == $target_page) { + $tp_present = true; + if ($this->PageNumSubstitutions[$k]['suppress'] != 'on' && $this->PageNumSubstitutions[$k]['suppress'] != 1) { + $this->PageNumSubstitutions[$k]['suppress'] = 'off'; + } + } + if ($this->PageNumSubstitutions[$k]['from'] == $start_page) { + $sp_present = true; + if ($this->PageNumSubstitutions[$k]['suppress'] != 'on' && $this->PageNumSubstitutions[$k]['suppress'] != 1) { + $this->PageNumSubstitutions[$k]['suppress'] = 'off'; + } + } + if ($this->PageNumSubstitutions[$k]['from'] == ($end_page + 1)) { + $ep_present = true; + if ($this->PageNumSubstitutions[$k]['suppress'] != 'on' && $this->PageNumSubstitutions[$k]['suppress'] != 1) { + $this->PageNumSubstitutions[$k]['suppress'] = 'off'; + } + } + } + + if (!$tp_present) { + list($tp_type, $tp_suppress, $tp_reset) = $this->docPageSettings($target_page); + } + if (!$sp_present) { + list($sp_type, $sp_suppress, $sp_reset) = $this->docPageSettings($start_page); + } + if (!$ep_present) { + list($ep_type, $ep_suppress, $ep_reset) = $this->docPageSettings($start_page - 1); + } + } + + $last = []; + // store pages + for ($i = $start_page; $i <= $end_page; $i++) { + $last[] = $this->pages[$i]; + } + // move pages + for ($i = $start_page - 1; $i >= ($target_page); $i--) { + $this->pages[$i + $n_toc] = $this->pages[$i]; + } + // Put toc pages at insert point + for ($i = 0; $i < $n_toc; $i++) { + $this->pages[$target_page + $i] = $last[$i]; + } + + /* -- BOOKMARKS -- */ + // Update Bookmarks + foreach ($this->BMoutlines as $i => $o) { + if ($o['p'] >= $target_page) { + $this->BMoutlines[$i]['p'] += $n_toc; + } + } + /* -- END BOOKMARKS -- */ + + // Update Page Links + if (count($this->PageLinks)) { + $newarr = []; + foreach ($this->PageLinks as $i => $o) { + foreach ($this->PageLinks[$i] as $key => $pl) { + if (strpos($pl[4], '@') === 0) { + $p = substr($pl[4], 1); + if ($p >= $start_page && $p <= $end_page) { + $this->PageLinks[$i][$key][4] = '@' . ($p + ($target_page - $start_page)); + } elseif ($p >= $target_page && $p < $start_page) { + $this->PageLinks[$i][$key][4] = '@' . ($p + $n_toc); + } + } + } + if ($i >= $start_page && $i <= $end_page) { + $newarr[($i + ($target_page - $start_page))] = $this->PageLinks[$i]; + } elseif ($i >= $target_page && $i < $start_page) { + $newarr[($i + $n_toc)] = $this->PageLinks[$i]; + } else { + $newarr[$i] = $this->PageLinks[$i]; + } + } + $this->PageLinks = $newarr; + } + + // OrientationChanges + if (count($this->OrientationChanges)) { + $newarr = []; + foreach ($this->OrientationChanges as $p => $v) { + if ($p >= $start_page && $p <= $end_page) { + $newarr[($p + ($target_page - $start_page))] = $this->OrientationChanges[$p]; + } elseif ($p >= $target_page && $p < $start_page) { + $newarr[$p + $n_toc] = $this->OrientationChanges[$p]; + } else { + $newarr[$p] = $this->OrientationChanges[$p]; + } + } + ksort($newarr); + $this->OrientationChanges = $newarr; + } + + // Page Dimensions + if (count($this->pageDim)) { + $newarr = []; + foreach ($this->pageDim as $p => $v) { + if ($p >= $start_page && $p <= $end_page) { + $newarr[($p + ($target_page - $start_page))] = $this->pageDim[$p]; + } elseif ($p >= $target_page && $p < $start_page) { + $newarr[$p + $n_toc] = $this->pageDim[$p]; + } else { + $newarr[$p] = $this->pageDim[$p]; + } + } + ksort($newarr); + $this->pageDim = $newarr; + } + + // HTML Headers & Footers + if (count($this->saveHTMLHeader)) { + $newarr = []; + foreach ($this->saveHTMLHeader as $p => $v) { + if ($p >= $start_page && $p <= $end_page) { + $newarr[($p + ($target_page - $start_page))] = $this->saveHTMLHeader[$p]; + } elseif ($p >= $target_page && $p < $start_page) { + $newarr[$p + $n_toc] = $this->saveHTMLHeader[$p]; + } else { + $newarr[$p] = $this->saveHTMLHeader[$p]; + } + } + ksort($newarr); + $this->saveHTMLHeader = $newarr; + } + if (count($this->saveHTMLFooter)) { + $newarr = []; + foreach ($this->saveHTMLFooter as $p => $v) { + if ($p >= $start_page && $p <= $end_page) { + $newarr[($p + ($target_page - $start_page))] = $this->saveHTMLFooter[$p]; + } elseif ($p >= $target_page && $p < $start_page) { + $newarr[$p + $n_toc] = $this->saveHTMLFooter[$p]; + } else { + $newarr[$p] = $this->saveHTMLFooter[$p]; + } + } + ksort($newarr); + $this->saveHTMLFooter = $newarr; + } + + // Update Internal Links + if (count($this->internallink)) { + foreach ($this->internallink as $key => $o) { + if (is_array($o) && $o['PAGE'] >= $start_page && $o['PAGE'] <= $end_page) { + $this->internallink[$key]['PAGE'] += ($target_page - $start_page); + } elseif (is_array($o) && $o['PAGE'] >= $target_page && $o['PAGE'] < $start_page) { + $this->internallink[$key]['PAGE'] += $n_toc; + } + } + } + + // Update Links + if (count($this->links)) { + foreach ($this->links as $key => $o) { + if ($o[0] >= $start_page && $o[0] <= $end_page) { + $this->links[$key][0] += ($target_page - $start_page); + } + if ($o[0] >= $target_page && $o[0] < $start_page) { + $this->links[$key][0] += $n_toc; + } + } + } + + // Update Form fields + if (count($this->form->forms)) { + foreach ($this->form->forms as $key => $f) { + if ($f['page'] >= $start_page && $f['page'] <= $end_page) { + $this->form->forms[$key]['page'] += ($target_page - $start_page); + } + if ($f['page'] >= $target_page && $f['page'] < $start_page) { + $this->form->forms[$key]['page'] += $n_toc; + } + } + } + + /* -- ANNOTATIONS -- */ + // Update Annotations + if (count($this->PageAnnots)) { + $newarr = []; + foreach ($this->PageAnnots as $p => $anno) { + if ($p >= $start_page && $p <= $end_page) { + $np = $p + ($target_page - $start_page); + foreach ($anno as $o) { + $newarr[$np][] = $o; + } + } elseif ($p >= $target_page && $p < $start_page) { + $np = $p + $n_toc; + foreach ($anno as $o) { + $newarr[$np][] = $o; + } + } else { + $newarr[$p] = $this->PageAnnots[$p]; + } + } + $this->PageAnnots = $newarr; + unset($newarr); + } + /* -- END ANNOTATIONS -- */ + + // Update TOC pages + if (count($this->tableOfContents->_toc)) { + foreach ($this->tableOfContents->_toc as $key => $t) { + if ($t['p'] >= $start_page && $t['p'] <= $end_page) { + $this->tableOfContents->_toc[$key]['p'] += ($target_page - $start_page); + } + if ($t['p'] >= $target_page && $t['p'] < $start_page) { + $this->tableOfContents->_toc[$key]['p'] += $n_toc; + } + } + } + + // Update PageNumSubstitutions + if (count($this->PageNumSubstitutions)) { + $newarr = []; + foreach ($this->PageNumSubstitutions as $k => $v) { + if ($this->PageNumSubstitutions[$k]['from'] >= $start_page && $this->PageNumSubstitutions[$k]['from'] <= $end_page) { + $this->PageNumSubstitutions[$k]['from'] += ($target_page - $start_page); + $newarr[$this->PageNumSubstitutions[$k]['from']] = $this->PageNumSubstitutions[$k]; + } elseif ($this->PageNumSubstitutions[$k]['from'] >= $target_page && $this->PageNumSubstitutions[$k]['from'] < $start_page) { + $this->PageNumSubstitutions[$k]['from'] += $n_toc; + $newarr[$this->PageNumSubstitutions[$k]['from']] = $this->PageNumSubstitutions[$k]; + } else { + $newarr[$this->PageNumSubstitutions[$k]['from']] = $this->PageNumSubstitutions[$k]; + } + } + + if (!$sp_present) { + $newarr[$target_page] = ['from' => $target_page, 'suppress' => $sp_suppress, 'reset' => $sp_reset, 'type' => $sp_type]; + } + if (!$tp_present) { + $newarr[($target_page + $n_toc)] = ['from' => ($target_page + $n_toc), 'suppress' => $tp_suppress, 'reset' => $tp_reset, 'type' => $tp_type]; + } + if (!$ep_present && $end_page > count($this->pages)) { + $newarr[($end_page + 1)] = ['from' => ($end_page + 1), 'suppress' => $ep_suppress, 'reset' => $ep_reset, 'type' => $ep_type]; + } + ksort($newarr); + $this->PageNumSubstitutions = []; + foreach ($newarr as $v) { + $this->PageNumSubstitutions[] = $v; + } + } + } + + function DeletePages($start_page, $end_page = -1) + { + // move a page/pages EARLIER in the document + if ($end_page < 1) { + $end_page = $start_page; + } + $n_tod = $end_page - $start_page + 1; + $last_page = count($this->pages); + $n_atend = $last_page - $end_page + 1; + + // move pages + for ($i = 0; $i < $n_atend; $i++) { + $this->pages[$start_page + $i] = $this->pages[$end_page + 1 + $i]; + } + // delete pages + for ($i = 0; $i < $n_tod; $i++) { + unset($this->pages[$last_page - $i]); + } + + + /* -- BOOKMARKS -- */ + // Update Bookmarks + foreach ($this->BMoutlines as $i => $o) { + if ($o['p'] >= $end_page) { + $this->BMoutlines[$i]['p'] -= $n_tod; + } elseif ($p < $start_page) { + unset($this->BMoutlines[$i]); + } + } + /* -- END BOOKMARKS -- */ + + // Update Page Links + if (count($this->PageLinks)) { + $newarr = []; + foreach ($this->PageLinks as $i => $o) { + foreach ($this->PageLinks[$i] as $key => $pl) { + if (strpos($pl[4], '@') === 0) { + $p = substr($pl[4], 1); + if ($p > $end_page) { + $this->PageLinks[$i][$key][4] = '@' . ($p - $n_tod); + } elseif ($p < $start_page) { + unset($this->PageLinks[$i][$key]); + } + } + } + if ($i > $end_page) { + $newarr[($i - $n_tod)] = $this->PageLinks[$i]; + } elseif ($p < $start_page) { + $newarr[$i] = $this->PageLinks[$i]; + } + } + $this->PageLinks = $newarr; + } + + // OrientationChanges + if (count($this->OrientationChanges)) { + $newarr = []; + foreach ($this->OrientationChanges as $p => $v) { + if ($p > $end_page) { + $newarr[($p - $t_tod)] = $this->OrientationChanges[$p]; + } elseif ($p < $start_page) { + $newarr[$p] = $this->OrientationChanges[$p]; + } + } + ksort($newarr); + $this->OrientationChanges = $newarr; + } + + // Page Dimensions + if (count($this->pageDim)) { + $newarr = []; + foreach ($this->pageDim as $p => $v) { + if ($p > $end_page) { + $newarr[($p - $n_tod)] = $this->pageDim[$p]; + } elseif ($p < $start_page) { + $newarr[$p] = $this->pageDim[$p]; + } + } + ksort($newarr); + $this->pageDim = $newarr; + } + + // HTML Headers & Footers + if (count($this->saveHTMLHeader)) { + foreach ($this->saveHTMLHeader as $p => $v) { + if ($p > $end_page) { + $newarr[($p - $n_tod)] = $this->saveHTMLHeader[$p]; + } // mPDF 5.7.3 + elseif ($p < $start_page) { + $newarr[$p] = $this->saveHTMLHeader[$p]; + } + } + ksort($newarr); + $this->saveHTMLHeader = $newarr; + } + if (count($this->saveHTMLFooter)) { + $newarr = []; + foreach ($this->saveHTMLFooter as $p => $v) { + if ($p > $end_page) { + $newarr[($p - $n_tod)] = $this->saveHTMLFooter[$p]; + } elseif ($p < $start_page) { + $newarr[$p] = $this->saveHTMLFooter[$p]; + } + } + ksort($newarr); + $this->saveHTMLFooter = $newarr; + } + + // Update Internal Links + foreach ($this->internallink as $key => $o) { + if ($o['PAGE'] > $end_page) { + $this->internallink[$key]['PAGE'] -= $n_tod; + } elseif ($o['PAGE'] < $start_page) { + unset($this->internallink[$key]); + } + } + + // Update Links + foreach ($this->links as $key => $o) { + if ($o[0] > $end_page) { + $this->links[$key][0] -= $n_tod; + } elseif ($o[0] < $start_page) { + unset($this->links[$key]); + } + } + + // Update Form fields + foreach ($this->form->forms as $key => $f) { + if ($f['page'] > $end_page) { + $this->form->forms[$key]['page'] -= $n_tod; + } elseif ($f['page'] < $start_page) { + unset($this->form->forms[$key]); + } + } + + /* -- ANNOTATIONS -- */ + // Update Annotations + if (count($this->PageAnnots)) { + $newarr = []; + foreach ($this->PageAnnots as $p => $anno) { + if ($p > $end_page) { + foreach ($anno as $o) { + $newarr[($p - $n_tod)][] = $o; + } + } elseif ($p < $start_page) { + $newarr[$p] = $this->PageAnnots[$p]; + } + } + ksort($newarr); + $this->PageAnnots = $newarr; + } + /* -- END ANNOTATIONS -- */ + + // Update PageNumSubstitutions + foreach ($this->PageNumSubstitutions as $k => $v) { + if ($this->PageNumSubstitutions[$k]['from'] > $end_page) { + $this->PageNumSubstitutions[$k]['from'] -= $n_tod; + } elseif ($this->PageNumSubstitutions[$k]['from'] < $start_page) { + unset($this->PageNumSubstitutions[$k]); + } + } + + unset($newarr); + $this->page = count($this->pages); + } + + // ====================================================== + /* -- INDEX -- */ + // FROM class PDF_Ref == INDEX + + function IndexEntry($txt, $xref = '') + { + if ($xref) { + $this->IndexEntrySee($txt, $xref); + return; + } + + // Search the reference (AND Ref/PageNo) in the array + $Present = false; + if ($this->keep_block_together) { + // do nothing + } /* -- TABLES -- */ elseif ($this->kwt) { + $size = count($this->kwt_Reference); + for ($i = 0; $i < $size; $i++) { + if (isset($this->kwt_Reference[$i]['t']) && $this->kwt_Reference[$i]['t'] == $txt) { + $Present = true; + if ($this->page != $this->kwt_Reference[$i]['op']) { + $this->kwt_Reference[$i]['op'] = $this->page; + } + } + } + if (!$Present) { // If not found, add it + $this->kwt_Reference[] = ['t' => $txt, 'op' => $this->page]; + } + } /* -- END TABLES -- */ else { + $size = count($this->Reference); + for ($i = 0; $i < $size; $i++) { + if (isset($this->Reference[$i]['t']) && $this->Reference[$i]['t'] == $txt) { + $Present = true; + if (!in_array($this->page, $this->Reference[$i]['p'])) { + $this->Reference[$i]['p'][] = $this->page; + } + } + } + if (!$Present) { // If not found, add it + $this->Reference[] = ['t' => $txt, 'p' => [$this->page]]; + } + } + } + + // Added function to add a reference "Elephants. See Chickens" + function IndexEntrySee($txta, $txtb) + { + if ($this->directionality == 'rtl') { // *OTL* + // ONLY DO THIS IF NOT IN TAGS + if ($txta == strip_tags($txta)) { + $txta = str_replace(':', ' - ', $txta); // *OTL* + } + if ($txtb == strip_tags($txtb)) { + $txtb = str_replace(':', ' - ', $txtb); // *OTL* + } + } // *OTL* + else { // *OTL* + if ($txta == strip_tags($txta)) { + $txta = str_replace(':', ', ', $txta); + } + if ($txtb == strip_tags($txtb)) { + $txtb = str_replace(':', ', ', $txtb); + } + } // *OTL* + $this->Reference[] = ['t' => $txta . ' - see ' . $txtb, 'p' => []]; + } + + private function filesInDir($directory) + { + $files = []; + foreach ((new \DirectoryIterator($directory)) as $v) { + if ($v->isDir() || $v->isDot()) { + continue; + } + + $files[] = $v->getPathname(); + } + + return $files; + } + + function InsertIndex($usedivletters = 1, $useLinking = false, $indexCollationLocale = '', $indexCollationGroup = '') + { + $size = count($this->Reference); + if ($size == 0) { + return false; + } + + // $spacer used after named entry + // $sep separates number [groups], $joiner joins numbers in range + // e.g. "elephant 73, 97-99" = elephant[$spacer]73[$sep]97[$joiner]99 + // $subEntrySeparator separates main and subentry (if $this->indexUseSubentries == false;) e.g. + // Mammal:elephant => Mammal[$subEntrySeparator]elephant + // $subEntryInset specifies what precedes a subentry (if $this->indexUseSubentries == true;) e.g. + // Mammal:elephant => [$subEntryInset]elephant + $spacer = "\xc2\xa0 "; + if ($this->directionality == 'rtl') { + $sep = '، '; + $joiner = '-'; + $subEntrySeparator = '، '; + $subEntryInset = ' - '; + } else { + $sep = ', '; + $joiner = '-'; + $subEntrySeparator = ', '; + $subEntryInset = ' - '; + } + + for ($i = 0; $i < $size; $i++) { + $txt = $this->Reference[$i]['t']; + $txt = strip_tags($txt); // mPDF 6 + $txt = $this->purify_utf8($txt); + $this->Reference[$i]['uf'] = $txt; // Unformatted e.g. pure utf-8 encoded characters, no mark-up/tags + // Used for ordering and collation + } + + if ($usedivletters) { + if ($indexCollationGroup && \in_array(strtolower($indexCollationGroup), array_map(function ($v) { + return strtolower(basename($v, '.php')); + }, $this->filesInDir(__DIR__ . '/../data/collations/')))) { + $collation = require __DIR__ . '/../data/collations/' . $indexCollationGroup . '.php'; + } else { + $collation = []; + } + for ($i = 0; $i < $size; $i++) { + if ($this->Reference[$i]['uf']) { + $l = mb_substr($this->Reference[$i]['uf'], 0, 1, 'UTF-8'); + if (isset($indexCollationGroup) && $indexCollationGroup) { + $uni = $this->UTF8StringToArray($l); + $ucode = $uni[0]; + if (isset($collation[$ucode])) { + $this->Reference[$i]['d'] = UtfString::code2utf($collation[$ucode]); + } else { + $this->Reference[$i]['d'] = mb_strtolower($l, 'UTF-8'); + } + } else { + $this->Reference[$i]['d'] = mb_strtolower($l, 'UTF-8'); + } + } + } + } + + // Alphabetic sort of the references + $originalLocale = setlocale(LC_COLLATE, 0); + if ($indexCollationLocale) { + setlocale(LC_COLLATE, $indexCollationLocale); + } + + usort($this->Reference, function ($a, $b) { + return strcoll(strtolower($a['uf']), strtolower($b['uf'])); + }); + + if ($indexCollationLocale) { + setlocale(LC_COLLATE, $originalLocale); + } + + $html = '<div class="mpdf_index_main">'; + + $lett = ''; + $last_lett = ''; + $mainentry = ''; + for ($i = 0; $i < $size; $i++) { + if ($this->Reference[$i]['t']) { + if ($usedivletters) { + $lett = $this->Reference[$i]['d']; + if ($lett != $last_lett) { + $html .= '<div class="mpdf_index_letter">' . $lett . '</div>'; + } + } + $txt = $this->Reference[$i]['t']; + + // Sub-entries e.g. Mammals:elephant + // But allow for tags e.g. <b>Mammal</b>:elephants + $a = preg_split('/(<.*?>)/', $txt, -1, PREG_SPLIT_DELIM_CAPTURE); + $txt = ''; + $marker = false; + foreach ($a as $k => $e) { + if ($k % 2 == 0 && !$marker) { + if (strpos($e, ':') !== false) { // == SubEntry + if ($this->indexUseSubentries) { + // If the Main entry does not have any page numbers associated with it + // create and insert an entry + list($txtmain, $sub) = preg_split('/[:]/', $e, 2); + if (strip_tags($txt . $txtmain) != $mainentry) { + $html .= '<div class="mpdf_index_entry">' . $txt . $txtmain . '</div>'; + $mainentry = strip_tags($txt . $txtmain); + } + + $txt = $subEntryInset; + $e = $sub; // Only replace first one + } else { + $e = preg_replace('/[:]/', $subEntrySeparator, $e, 1); // Only replace first one + } + $marker = true; // Don't replace any more once the subentry marker has been found + } + } + $txt .= $e; + } + + if (!$marker) { + $mainentry = strip_tags($txt); + } + + $html .= '<div class="mpdf_index_entry">'; + $html .= $txt; + $ppp = $this->Reference[$i]['p']; // = array of page numbers to point to + if (count($ppp)) { + sort($ppp); + $newarr = []; + $range_start = $ppp[0]; + $range_end = 0; + + $html .= $spacer; + + for ($zi = 1; $zi < count($ppp); $zi++) { + if ($ppp[$zi] == ($ppp[($zi - 1)] + 1)) { + $range_end = $ppp[$zi]; + } else { + if ($range_end) { + if ($range_end == $range_start + 1) { + if ($useLinking) { + $html .= '<a class="mpdf_index_link" href="@' . $range_start . '">'; + } + $html .= $this->docPageNum($range_start); + if ($useLinking) { + $html .= '</a>'; + } + $html .= $sep; + + if ($useLinking) { + $html .= '<a class="mpdf_index_link" href="@' . $ppp[$zi - 1] . '">'; + } + $html .= $this->docPageNum($ppp[$zi - 1]); + if ($useLinking) { + $html .= '</a>'; + } + $html .= $sep; + } + } else { + if ($useLinking) { + $html .= '<a class="mpdf_index_link" href="@' . $ppp[$zi - 1] . '">'; + } + $html .= $this->docPageNum($ppp[$zi - 1]); + if ($useLinking) { + $html .= '</a>'; + } + $html .= $sep; + } + $range_start = $ppp[$zi]; + $range_end = 0; + } + } + + if ($range_end) { + if ($useLinking) { + $html .= '<a class="mpdf_index_link" href="@' . $range_start . '">'; + } + $html .= $this->docPageNum($range_start); + if ($range_end == $range_start + 1) { + if ($useLinking) { + $html .= '</a>'; + } + $html .= $sep; + if ($useLinking) { + $html .= '<a class="mpdf_index_link" href="@' . $range_end . '">'; + } + $html .= $this->docPageNum($range_end); + if ($useLinking) { + $html .= '</a>'; + } + } else { + $html .= $joiner; + $html .= $this->docPageNum($range_end); + if ($useLinking) { + $html .= '</a>'; + } + } + } else { + if ($useLinking) { + $html .= '<a class="mpdf_index_link" href="@' . $ppp[(count($ppp) - 1)] . '">'; + } + $html .= $this->docPageNum($ppp[(count($ppp) - 1)]); + if ($useLinking) { + $html .= '</a>'; + } + } + } + } + $html .= '</div>'; + $last_lett = $lett; + } + $html .= '</div>'; + $save_fpb = $this->fixedPosBlockSave; + $this->WriteHTML($html); + $this->fixedPosBlockSave = $save_fpb; + + $this->breakpoints[$this->CurrCol][] = $this->y; // *COLUMNS* + } + + /* -- END INDEX -- */ + + function AcceptPageBreak() + { + if (count($this->cellBorderBuffer)) { + $this->printcellbuffer(); + } // *TABLES* + /* -- COLUMNS -- */ + if ($this->ColActive == 1) { + if ($this->CurrCol < $this->NbCol - 1) { + // Go to the next column + $this->CurrCol++; + $this->SetCol($this->CurrCol); + $this->y = $this->y0; + $this->ChangeColumn = 1; // Number (and direction) of columns changed +1, +2, -2 etc. + // DIRECTIONALITY RTL + if ($this->directionality == 'rtl') { + $this->ChangeColumn = -($this->ChangeColumn); + } // *OTL* + // Stay on the page + return false; + } else { + // Go back to the first column - NEW PAGE + if (count($this->columnbuffer)) { + $this->printcolumnbuffer(); + } + $this->SetCol(0); + $this->y0 = $this->tMargin; + $this->ChangeColumn = -($this->NbCol - 1); + // DIRECTIONALITY RTL + if ($this->directionality == 'rtl') { + $this->ChangeColumn = -($this->ChangeColumn); + } // *OTL* + // Page break + return true; + } + } /* -- END COLUMNS -- */ + /* -- TABLES -- */ elseif ($this->table_rotate) { + if ($this->tablebuffer) { + $this->printtablebuffer(); + } + return true; + } /* -- END TABLES -- */ else { // *COLUMNS* + $this->ChangeColumn = 0; + return $this->autoPageBreak; + } // *COLUMNS* + return $this->autoPageBreak; + } + + // ----------- COLUMNS --------------------- + /* -- COLUMNS -- */ + + function SetColumns($NbCol, $vAlign = '', $gap = 5) + { + // NbCol = number of columns + // Anything less than 2 turns columns off + if ($NbCol < 2) { // SET COLUMNS OFF + if ($this->ColActive) { + $this->ColActive = 0; + if (count($this->columnbuffer)) { + $this->printcolumnbuffer(); + } + $this->NbCol = 1; + $this->ResetMargins(); + $this->pgwidth = $this->w - $this->lMargin - $this->rMargin; + $this->divwidth = 0; + $this->Ln(); + } + $this->ColActive = 0; + $this->columnbuffer = []; + $this->ColDetails = []; + $this->columnLinks = []; + $this->columnAnnots = []; + $this->columnForms = []; + $this->col_BMoutlines = []; + $this->col_toc = []; + $this->breakpoints = []; + } else { // SET COLUMNS ON + if ($this->ColActive) { + $this->ColActive = 0; + if (count($this->columnbuffer)) { + $this->printcolumnbuffer(); + } + $this->ResetMargins(); + } + if (isset($this->y) && $this->y > $this->tMargin) { + $this->Ln(); + } + $this->NbCol = $NbCol; + $this->ColGap = $gap; + $this->divwidth = 0; + $this->ColActive = 1; + $this->ColumnAdjust = true; // enables column height adjustment for the page + $this->columnbuffer = []; + $this->ColDetails = []; + $this->columnLinks = []; + $this->columnAnnots = []; + $this->columnForms = []; + $this->col_BMoutlines = []; + $this->col_toc = []; + $this->breakpoints = []; + if ((strtoupper($vAlign) == 'J') || (strtoupper($vAlign) == 'JUSTIFY')) { + $vAlign = 'J'; + } else { + $vAlign = ''; + } + $this->colvAlign = $vAlign; + // Save the ordinate + $absL = $this->DeflMargin - ($gap / 2); + $absR = $this->DefrMargin - ($gap / 2); + $PageWidth = $this->w - $absL - $absR; // virtual pagewidth for calculation only + $ColWidth = (($PageWidth - ($gap * ($NbCol))) / $NbCol); + $this->ColWidth = $ColWidth; + /* -- OTL -- */ + + if ($this->directionality == 'rtl') { + for ($i = 0; $i < $this->NbCol; $i++) { + $this->ColL[$i] = $absL + ($gap / 2) + (($NbCol - ($i + 1)) * ($PageWidth / $NbCol)); + $this->ColR[$i] = $this->ColL[$i] + $ColWidth; // NB This is not R margin -> R pos + } + } else { + /* -- END OTL -- */ + for ($i = 0; $i < $this->NbCol; $i++) { + $this->ColL[$i] = $absL + ($gap / 2) + ($i * ($PageWidth / $NbCol) ); + $this->ColR[$i] = $this->ColL[$i] + $ColWidth; // NB This is not R margin -> R pos + } + } // *OTL* + $this->pgwidth = $ColWidth; + $this->SetCol(0); + $this->y0 = $this->y; + } + $this->x = $this->lMargin; + } + + function SetCol($CurrCol) + { + // Used internally to set column by number: 0 is 1st column + // Set position on a column + $this->CurrCol = $CurrCol; + $x = $this->ColL[$CurrCol]; + $xR = $this->ColR[$CurrCol]; // NB This is not R margin -> R pos + if (($this->mirrorMargins) && (($this->page) % 2 == 0)) { // EVEN + $x += $this->MarginCorrection; + $xR += $this->MarginCorrection; + } + $this->SetMargins($x, ($this->w - $xR), $this->tMargin); + } + + function AddColumn() + { + $this->NewColumn(); + $this->ColumnAdjust = false; // disables all column height adjustment for the page. + } + + function NewColumn() + { + if ($this->ColActive == 1) { + if ($this->CurrCol < $this->NbCol - 1) { + // Go to the next column + $this->CurrCol++; + $this->SetCol($this->CurrCol); + $this->y = $this->y0; + $this->ChangeColumn = 1; + // DIRECTIONALITY RTL + if ($this->directionality == 'rtl') { + $this->ChangeColumn = -($this->ChangeColumn); + } // *OTL* + // Stay on the page + } else { + // Go back to the first column + // Page break + if (count($this->columnbuffer)) { + $this->printcolumnbuffer(); + } + $this->AddPage($this->CurOrientation); + $this->SetCol(0); + $this->y0 = $this->tMargin; + $this->ChangeColumn = -($this->NbCol - 1); + // DIRECTIONALITY RTL + if ($this->directionality == 'rtl') { + $this->ChangeColumn = -($this->ChangeColumn); + } // *OTL* + } + $this->x = $this->lMargin; + } else { + $this->AddPage($this->CurOrientation); + } + } + + function printcolumnbuffer() + { + // Columns ended (but page not ended) -> try to match all columns - unless disabled by using a custom column-break + if (!$this->ColActive && $this->ColumnAdjust && !$this->keepColumns) { + // Calculate adjustment to add to each column to calculate rel_y value + $this->ColDetails[0]['add_y'] = 0; + $last_col = 0; + // Recursively add previous column's height + for ($i = 1; $i < $this->NbCol; $i++) { + if (isset($this->ColDetails[$i]['bottom_margin']) && $this->ColDetails[$i]['bottom_margin']) { // If any entries in the column + $this->ColDetails[$i]['add_y'] = ($this->ColDetails[$i - 1]['bottom_margin'] - $this->y0) + $this->ColDetails[$i - 1]['add_y']; + $last_col = $i; // Last column actually printed + } + } + + // Calculate value for each position sensitive entry as though for one column + foreach ($this->columnbuffer as $key => $s) { + $t = $s['s']; + if ($t == 'ACROFORM') { + $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0; + $this->columnbuffer[$key]['s'] = ''; + } elseif (preg_match('/BT \d+\.\d\d+ (\d+\.\d\d+) Td/', $t)) { + $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0; + } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ [\-]{0,1}\d+\.\d\d+ re/', $t)) { + $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0; + } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) m/', $t)) { + $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0; + } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) l/', $t)) { + $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0; + } elseif (preg_match('/q \d+\.\d\d+ 0 0 \d+\.\d\d+ \d+\.\d\d+ (\d+\.\d\d+) cm \/(I|FO)\d+ Do Q/', $t)) { + $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0; + } elseif (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ c/', $t)) { + $this->columnbuffer[$key]['rel_y'] = $s['y'] + $this->ColDetails[$s['col']]['add_y'] - $this->y0; + } + } + foreach ($this->internallink as $key => $f) { + if (is_array($f) && isset($f['col'])) { + $this->internallink[$key]['rel_y'] = $f['Y'] + $this->ColDetails[$f['col']]['add_y'] - $this->y0; + } + } + + $breaks = []; + foreach ($this->breakpoints as $c => $bpa) { + foreach ($bpa as $rely) { + $breaks[] = $rely + $this->ColDetails[$c]['add_y'] - $this->y0; + } + } + + + if (isset($this->ColDetails[$last_col]['bottom_margin'])) { + $lcbm = $this->ColDetails[$last_col]['bottom_margin']; + } else { + $lcbm = 0; + } + $sum_h = $this->ColDetails[$last_col]['add_y'] + $lcbm - $this->y0; + // $sum_h = max($this->ColDetails[$last_col]['add_y'] + $this->ColDetails[$last_col]['bottom_margin'] - $this->y0, end($breaks)); + $target_h = ($sum_h / $this->NbCol); + + $cbr = []; + for ($i = 1; $i < $this->NbCol; $i++) { + $th = ($sum_h * $i / $this->NbCol); + foreach ($breaks as $bk => $val) { + if ($val > $th) { + if (($val - $th) < ($th - $breaks[$bk - 1])) { + $cbr[$i - 1] = $val; + } else { + $cbr[$i - 1] = $breaks[$bk - 1]; + } + break; + } + } + } + $cbr[($this->NbCol - 1)] = $sum_h; + + // mPDF 6 + // Avoid outputing with 1st column empty + if (isset($cbr[0]) && $cbr[0] == 0) { + for ($i = 0; $i < $this->NbCol - 1; $i++) { + $cbr[$i] = $cbr[$i + 1]; + } + } + + // Now update the columns - divide into columns of approximately equal value + $last_new_col = 0; + $yadj = 0; // mm + $xadj = 0; + $last_col_bottom = 0; + $lowest_bottom_y = 0; + $block_bottom = 0; + $newcolumn = 0; + foreach ($this->columnbuffer as $key => $s) { + if (isset($s['rel_y'])) { // only process position sensitive data + if ($s['rel_y'] >= $cbr[$newcolumn]) { + $newcolumn++; + } else { + $newcolumn = $last_new_col; + } + + + $block_bottom = max($block_bottom, ($s['rel_y'] + $s['h'])); + + if ($this->directionality == 'rtl') { // *OTL* + $xadj = -(($newcolumn - $s['col']) * ($this->ColWidth + $this->ColGap)); // *OTL* + } // *OTL* + else { // *OTL* + $xadj = ($newcolumn - $s['col']) * ($this->ColWidth + $this->ColGap); + } // *OTL* + + if ($last_new_col != $newcolumn) { // Added new column + $last_col_bottom = $this->columnbuffer[$key]['rel_y']; + $block_bottom = 0; + } + $yadj = ($s['rel_y'] - $s['y']) - ($last_col_bottom) + $this->y0; + // callback function + $t = $s['s']; + + // mPDF 5.7+ + $t = $this->columnAdjustPregReplace('Td', $xadj, $yadj, '/BT (\d+\.\d\d+) (\d+\.\d\d+) Td/', $t); + $t = $this->columnAdjustPregReplace('re', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) ([\-]{0,1}\d+\.\d\d+) re/', $t); + $t = $this->columnAdjustPregReplace('l', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) l/', $t); + $t = $this->columnAdjustPregReplace('img', $xadj, $yadj, '/q (\d+\.\d\d+) 0 0 (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) cm \/(I|FO)/', $t); + $t = $this->columnAdjustPregReplace('draw', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) m/', $t); + $t = $this->columnAdjustPregReplace('bezier', $xadj, $yadj, '/(\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) (\d+\.\d\d+) c/', $t); + + $this->columnbuffer[$key]['s'] = $t; + $this->columnbuffer[$key]['newcol'] = $newcolumn; + $this->columnbuffer[$key]['newy'] = $s['y'] + $yadj; + $last_new_col = $newcolumn; + $clb = $s['y'] + $yadj + $s['h']; // bottom_margin of current + if ((isset($this->ColDetails[$newcolumn]['max_bottom']) && $clb > $this->ColDetails[$newcolumn]['max_bottom']) || (!isset($this->ColDetails[$newcolumn]['max_bottom']) && $clb)) { + $this->ColDetails[$newcolumn]['max_bottom'] = $clb; + } + if ($clb > $lowest_bottom_y) { + $lowest_bottom_y = $clb; + } + // Adjust LINKS + if (isset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->PageLinks[$this->page][$ref][0] += ($xadj * Mpdf::SCALE); + $this->PageLinks[$this->page][$ref][1] -= ($yadj * Mpdf::SCALE); + unset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]); + } + // Adjust FORM FIELDS + if (isset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->form->forms[$ref]['x'] += ($xadj); + $this->form->forms[$ref]['y'] += ($yadj); + unset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]); + } + /* -- ANNOTATIONS -- */ + if (isset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]; + if ($this->PageAnnots[$this->page][$ref]['x'] < 0) { + $this->PageAnnots[$this->page][$ref]['x'] -= ($xadj); + } else { + $this->PageAnnots[$this->page][$ref]['x'] += ($xadj); + } + $this->PageAnnots[$this->page][$ref]['y'] += ($yadj); // unlike PageLinks, Page annots has y values from top in mm + unset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]); + } + /* -- END ANNOTATIONS -- */ + } + } + + /* -- BOOKMARKS -- */ + // Adjust Bookmarks + foreach ($this->col_BMoutlines as $v) { + $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $this->y0, 'p' => $v['p']]; + } + /* -- END BOOKMARKS -- */ + + /* -- TOC -- */ + + // Adjust ToC + foreach ($this->col_toc as $v) { + $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']]; + $this->links[$v['link']][1] = $this->y0; + } + /* -- END TOC -- */ + + // Adjust column length to be equal + if ($this->colvAlign == 'J') { + foreach ($this->columnbuffer as $key => $s) { + if (isset($s['rel_y'])) { // only process position sensitive data + // Set ratio to expand y values or heights + if (isset($this->ColDetails[$s['newcol']]['max_bottom']) && $this->ColDetails[$s['newcol']]['max_bottom'] && $this->ColDetails[$s['newcol']]['max_bottom'] != $this->y0) { + $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['newcol']]['max_bottom'] - ($this->y0)); + } else { + $ratio = 1; + } + if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) { + $yadj = ($s['newy'] - $this->y0) * ($ratio - 1); + + // Adjust LINKS + if (isset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->PageLinks[$this->page][$ref][1] -= ($yadj * Mpdf::SCALE); // y value + $this->PageLinks[$this->page][$ref][3] *= $ratio; // height + unset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]); + } + // Adjust FORM FIELDS + if (isset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->form->forms[$ref]['x'] += ($xadj); + $this->form->forms[$ref]['y'] += ($yadj); + unset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]); + } + /* -- ANNOTATIONS -- */ + if (isset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->PageAnnots[$this->page][$ref]['y'] += ($yadj); + unset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]); + } + /* -- END ANNOTATIONS -- */ + } + } + } + foreach ($this->internallink as $key => $f) { + if (is_array($f) && isset($f['col'])) { + $last_col_bottom = 0; + for ($nbc = 0; $nbc < $this->NbCol; $nbc++) { + if ($f['rel_y'] >= $cbr[$nbc]) { + $last_col_bottom = $cbr[$nbc]; + } + } + $yadj = ($f['rel_y'] - $f['Y']) - $last_col_bottom + $this->y0; + $f['Y'] += $yadj; + unset($f['col']); + unset($f['rel_y']); + $this->internallink[$key] = $f; + } + } + + $last_col = -1; + $trans_on = false; + foreach ($this->columnbuffer as $key => $s) { + if (isset($s['rel_y'])) { // only process position sensitive data + // Set ratio to expand y values or heights + if (isset($this->ColDetails[$s['newcol']]['max_bottom']) && $this->ColDetails[$s['newcol']]['max_bottom'] && $this->ColDetails[$s['newcol']]['max_bottom'] != $this->y0) { + $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['newcol']]['max_bottom'] - ($this->y0)); + } else { + $ratio = 1; + } + if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) { + // Start Transformation + $this->pages[$this->page] .= $this->StartTransform(true) . "\n"; + $this->pages[$this->page] .= $this->transformScale(100, $ratio * 100, $x = '', $this->y0, true) . "\n"; + $trans_on = true; + } + } + // Now output the adjusted values + $this->pages[$this->page] .= $s['s'] . "\n"; + if (isset($s['rel_y']) && ($ratio > 1) && ($ratio <= $this->max_colH_correction)) { // only process position sensitive data + // Stop Transformation + $this->pages[$this->page] .= $this->StopTransform(true) . "\n"; + $trans_on = false; + } + } + if ($trans_on) { + $this->pages[$this->page] .= $this->StopTransform(true) . "\n"; + } + } else { // if NOT $this->colvAlign == 'J' + // Now output the adjusted values + foreach ($this->columnbuffer as $s) { + $this->pages[$this->page] .= $s['s'] . "\n"; + } + } + if ($lowest_bottom_y > 0) { + $this->y = $lowest_bottom_y; + } + } // Columns not ended but new page -> align columns (can leave the columns alone - just tidy up the height) + elseif ($this->colvAlign == 'J' && $this->ColumnAdjust && !$this->keepColumns) { + // calculate the lowest bottom margin + $lowest_bottom_y = 0; + foreach ($this->columnbuffer as $key => $s) { + // Only process output data + $t = $s['s']; + if ($t == 'ACROFORM' || (preg_match('/BT \d+\.\d\d+ (\d+\.\d\d+) Td/', $t)) || (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ [\-]{0,1}\d+\.\d\d+ re/', $t)) || + (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) l/', $t)) || + (preg_match('/q \d+\.\d\d+ 0 0 \d+\.\d\d+ \d+\.\d\d+ (\d+\.\d\d+) cm \/(I|FO)\d+ Do Q/', $t)) || + (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) m/', $t)) || + (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ c/', $t))) { + $clb = $s['y'] + $s['h']; + if ((isset($this->ColDetails[$s['col']]['max_bottom']) && $clb > $this->ColDetails[$s['col']]['max_bottom']) || !isset($this->ColDetails[$s['col']]['max_bottom'])) { + $this->ColDetails[$s['col']]['max_bottom'] = $clb; + } + if ($clb > $lowest_bottom_y) { + $lowest_bottom_y = $clb; + } + $this->columnbuffer[$key]['rel_y'] = $s['y']; // Marks position sensitive data to process later + if ($t == 'ACROFORM') { + $this->columnbuffer[$key]['s'] = ''; + } + } + } + // Adjust column length equal + foreach ($this->columnbuffer as $key => $s) { + // Set ratio to expand y values or heights + if (isset($this->ColDetails[$s['col']]['max_bottom']) && $this->ColDetails[$s['col']]['max_bottom']) { + $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['col']]['max_bottom'] - ($this->y0)); + } else { + $ratio = 1; + } + if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) { + $yadj = ($s['y'] - $this->y0) * ($ratio - 1); + + // Adjust LINKS + if (isset($s['rel_y'])) { // only process position sensitive data + // otherwise triggers for all entries in column buffer (.e.g. formatting) and makes below adjustments more than once + if (isset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->PageLinks[$this->page][$ref][1] -= ($yadj * Mpdf::SCALE); // y value + $this->PageLinks[$this->page][$ref][3] *= $ratio; // height + unset($this->columnLinks[$s['col']][intval($s['x'])][intval($s['y'])]); + } + // Adjust FORM FIELDS + if (isset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->form->forms[$ref]['x'] += ($xadj); + $this->form->forms[$ref]['y'] += ($yadj); + unset($this->columnForms[$s['col']][intval($s['x'])][intval($s['y'])]); + } + /* -- ANNOTATIONS -- */ + if (isset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])])) { + $ref = $this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]; + $this->PageAnnots[$this->page][$ref]['y'] += ($yadj); + unset($this->columnAnnots[$s['col']][intval($s['x'])][intval($s['y'])]); + } + /* -- END ANNOTATIONS -- */ + } + } + } + + /* -- BOOKMARKS -- */ + + // Adjust Bookmarks + foreach ($this->col_BMoutlines as $v) { + $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $this->y0, 'p' => $v['p']]; + } + /* -- END BOOKMARKS -- */ + + /* -- TOC -- */ + + // Adjust ToC + foreach ($this->col_toc as $v) { + $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']]; + $this->links[$v['link']][1] = $this->y0; + } + /* -- END TOC -- */ + + $trans_on = false; + foreach ($this->columnbuffer as $key => $s) { + + if (isset($s['rel_y'])) { // only process position sensitive data + + // Set ratio to expand y values or heights + if (isset($this->ColDetails[$s['col']]['max_bottom']) && $this->ColDetails[$s['col']]['max_bottom']) { + $ratio = ($lowest_bottom_y - ($this->y0)) / ($this->ColDetails[$s['col']]['max_bottom'] - ($this->y0)); + } else { + $ratio = 1; + } + + if (($ratio > 1) && ($ratio <= $this->max_colH_correction)) { + // Start Transformation + $this->pages[$this->page] .= $this->StartTransform(true) . "\n"; + $this->pages[$this->page] .= $this->transformScale(100, $ratio * 100, $x = '', $this->y0, true) . "\n"; + $trans_on = true; + } + } + + // Now output the adjusted values + $this->pages[$this->page] .= $s['s'] . "\n"; + if (isset($s['rel_y']) && ($ratio > 1) && ($ratio <= $this->max_colH_correction)) { + // Stop Transformation + $this->pages[$this->page] .= $this->StopTransform(true) . "\n"; + $trans_on = false; + } + } + + if ($trans_on) { + $this->pages[$this->page] .= $this->StopTransform(true) . "\n"; + } + + if ($lowest_bottom_y > 0) { + $this->y = $lowest_bottom_y; + } + + } else { // Just reproduce the page as it was + + // If page has not ended but height adjustment was disabled by custom column-break - adjust y + $lowest_bottom_y = 0; + + if (!$this->ColActive && (!$this->ColumnAdjust || $this->keepColumns)) { + + // calculate the lowest bottom margin + foreach ($this->columnbuffer as $key => $s) { + + // Only process output data + $t = $s['s']; + if ($t === 'ACROFORM' + || (preg_match('/BT \d+\.\d\d+ (\d+\.\d\d+) Td/', $t)) + || (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ [\-]{0,1}\d+\.\d\d+ re/', $t)) + || (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) l/', $t)) + || (preg_match('/q \d+\.\d\d+ 0 0 \d+\.\d\d+ \d+\.\d\d+ (\d+\.\d\d+) cm \/(I|FO)\d+ Do Q/', $t)) + || (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) m/', $t)) + || (preg_match('/\d+\.\d\d+ (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ c/', $t))) { + + $clb = $s['y'] + $s['h']; + + if (isset($this->ColDetails[$s['col']]['max_bottom']) && $clb > $this->ColDetails[$s['col']]['max_bottom'] || (!isset($this->ColDetails[$s['col']]['max_bottom']) && $clb)) { + $this->ColDetails[$s['col']]['max_bottom'] = $clb; + } + + if ($clb > $lowest_bottom_y) { + $lowest_bottom_y = $clb; + } + } + } + } + + foreach ($this->columnbuffer as $key => $s) { + if ($s['s'] != 'ACROFORM') { + $this->pages[$this->page] .= $s['s'] . "\n"; + } + } + + if ($lowest_bottom_y > 0) { + $this->y = $lowest_bottom_y; + } + + /* -- BOOKMARKS -- */ + // Output Bookmarks + foreach ($this->col_BMoutlines as $v) { + $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $v['p']]; + } + /* -- END BOOKMARKS -- */ + + /* -- TOC -- */ + // Output ToC + foreach ($this->col_toc as $v) { + $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']]; + } + /* -- END TOC -- */ + } + + foreach ($this->internallink as $key => $f) { + + if (isset($this->internallink[$key]['col'])) { + unset($this->internallink[$key]['col']); + } + + if (isset($this->internallink[$key]['rel_y'])) { + unset($this->internallink[$key]['rel_y']); + } + } + + $this->columnbuffer = []; + $this->ColDetails = []; + $this->columnLinks = []; + $this->columnAnnots = []; + $this->columnForms = []; + + $this->col_BMoutlines = []; + $this->col_toc = []; + $this->breakpoints = []; + } + + // mPDF 5.7+ + function columnAdjustPregReplace($type, $xadj, $yadj, $pattern, $subject) + { + preg_match($pattern, $subject, $matches); + + if (!count($matches)) { + return $subject; + } + + if (!isset($matches[3])) { + $matches[3] = 0; + } + + if (!isset($matches[4])) { + $matches[4] = 0; + } + + if (!isset($matches[5])) { + $matches[5] = 0; + } + + if (!isset($matches[6])) { + $matches[6] = 0; + } + + return str_replace($matches[0], $this->columnAdjustAdd($type, Mpdf::SCALE, $xadj, $yadj, $matches[1], $matches[2], $matches[3], $matches[4], $matches[5], $matches[6]), $subject); + } + /* -- END COLUMNS -- */ + + // ================================================================== + /* -- TABLES -- */ + function printcellbuffer() + { + if (count($this->cellBorderBuffer)) { + + sort($this->cellBorderBuffer); + + foreach ($this->cellBorderBuffer as $cbb) { + + $cba = unpack("A16dom/nbord/A1side/ns/dbw/a6ca/A10style/dx/dy/dw/dh/dmbl/dmbr/dmrt/dmrb/dmtl/dmtr/dmlt/dmlb/dcpd/dover/", $cbb); + $side = $cba['side']; + $color = str_pad($cba['ca'], 6, "\x00"); + + $details = []; + + $details[$side]['dom'] = (float) $cba['dom']; + $details[$side]['s'] = $cba['s']; + $details[$side]['w'] = $cba['bw']; + $details[$side]['c'] = $color; + $details[$side]['style'] = trim($cba['style']); + + $details['mbw']['BL'] = $cba['mbl']; + $details['mbw']['BR'] = $cba['mbr']; + $details['mbw']['RT'] = $cba['mrt']; + $details['mbw']['RB'] = $cba['mrb']; + $details['mbw']['TL'] = $cba['mtl']; + $details['mbw']['TR'] = $cba['mtr']; + $details['mbw']['LT'] = $cba['mlt']; + $details['mbw']['LB'] = $cba['mlb']; + + $details['cellposdom'] = $cba['cpd']; + + $details['p'] = $side; + + if ($cba['over'] == 1) { + $details[$side]['overlay'] = true; + } else { + $details[$side]['overlay'] = false; + } + + $this->_tableRect($cba['x'], $cba['y'], $cba['w'], $cba['h'], $cba['bord'], $details, false, false); + } + + $this->cellBorderBuffer = []; + } + } + + // ================================================================== + function printtablebuffer() + { + + if (!$this->table_rotate) { + + $this->pages[$this->page] .= $this->tablebuffer; + + foreach ($this->tbrot_Links as $p => $l) { + foreach ($l as $v) { + $this->PageLinks[$p][] = $v; + } + } + $this->tbrot_Links = []; + + /* -- ANNOTATIONS -- */ + foreach ($this->tbrot_Annots as $p => $l) { + foreach ($l as $v) { + $this->PageAnnots[$p][] = $v; + } + } + $this->tbrot_Annots = []; + /* -- END ANNOTATIONS -- */ + + /* -- BOOKMARKS -- */ + // Output Bookmarks + foreach ($this->tbrot_BMoutlines as $v) { + $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $v['p']]; + } + $this->tbrot_BMoutlines = []; + /* -- END BOOKMARKS -- */ + + /* -- TOC -- */ + // Output ToC + foreach ($this->tbrot_toc as $v) { + $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']]; + } + $this->tbrot_toc = []; + /* -- END TOC -- */ + + return; + } + + // elseif rotated + $lm = $this->lMargin + $this->blk[$this->blklvl]['outer_left_margin'] + $this->blk[$this->blklvl]['border_left']['w'] + $this->blk[$this->blklvl]['padding_left']; + $pw = $this->blk[$this->blklvl]['inner_width']; + + // Start Transformation + $this->pages[$this->page] .= $this->StartTransform(true) . "\n"; + + if ($this->table_rotate > 1) { // clockwise + + if ($this->tbrot_align == 'L') { + $xadj = $this->tbrot_h; // align L (as is) + } elseif ($this->tbrot_align == 'R') { + $xadj = $lm - $this->tbrot_x0 + ($pw); // align R + } else { + $xadj = $lm - $this->tbrot_x0 + (($pw + $this->tbrot_h) / 2); // align C + } + + $yadj = 0; + + } else { // anti-clockwise + + if ($this->tbrot_align == 'L') { + $xadj = 0; // align L (as is) + } elseif ($this->tbrot_align == 'R') { + $xadj = $lm - $this->tbrot_x0 + ($pw - $this->tbrot_h); // align R + } else { + $xadj = $lm - $this->tbrot_x0 + (($pw - $this->tbrot_h) / 2); // align C + } + + $yadj = $this->tbrot_w; + } + + + $this->pages[$this->page] .= $this->transformTranslate($xadj, $yadj, true) . "\n"; + $this->pages[$this->page] .= $this->transformRotate($this->table_rotate, $this->tbrot_x0, $this->tbrot_y0, true) . "\n"; + + // Now output the adjusted values + $this->pages[$this->page] .= $this->tablebuffer; + + foreach ($this->tbrot_Links as $p => $l) { + + foreach ($l as $v) { + + $w = $v[2] / Mpdf::SCALE; + $h = $v[3] / Mpdf::SCALE; + $ax = ($v[0] / Mpdf::SCALE) - $this->tbrot_x0; + $ay = (($this->hPt - $v[1]) / Mpdf::SCALE) - $this->tbrot_y0; + + if ($this->table_rotate > 1) { // clockwise + $bx = $this->tbrot_x0 + $xadj - $ay - $h; + $by = $this->tbrot_y0 + $yadj + $ax; + } else { + $bx = $this->tbrot_x0 + $xadj + $ay; + $by = $this->tbrot_y0 + $yadj - $ax - $w; + } + + $v[0] = $bx * Mpdf::SCALE; + $v[1] = ($this->h - $by) * Mpdf::SCALE; + $v[2] = $h * Mpdf::SCALE; // swap width and height + $v[3] = $w * Mpdf::SCALE; + + $this->PageLinks[$p][] = $v; + } + } + + $this->tbrot_Links = []; + foreach ($this->internallink as $key => $f) { + if (is_array($f) && isset($f['tbrot'])) { + $f['Y'] = $this->tbrot_y0; + $f['PAGE'] = $this->page; + unset($f['tbrot']); + $this->internallink[$key] = $f; + } + } + + /* -- ANNOTATIONS -- */ + foreach ($this->tbrot_Annots as $p => $l) { + foreach ($l as $v) { + $ax = abs($v['x']) - $this->tbrot_x0; // abs because -ve values are internally set and held for reference if annotMargin set + $ay = $v['y'] - $this->tbrot_y0; + + if ($this->table_rotate > 1) { // clockwise + $bx = $this->tbrot_x0 + $xadj - $ay; + $by = $this->tbrot_y0 + $yadj + $ax; + } else { + $bx = $this->tbrot_x0 + $xadj + $ay; + $by = $this->tbrot_y0 + $yadj - $ax; + } + + if ($v['x'] < 0) { + $v['x'] = -$bx; + } else { + $v['x'] = $bx; + } + + $v['y'] = ($by); + $this->PageAnnots[$p][] = $v; + } + } + + $this->tbrot_Annots = []; + /* -- END ANNOTATIONS -- */ + + /* -- BOOKMARKS -- */ + // Adjust Bookmarks + foreach ($this->tbrot_BMoutlines as $v) { + $v['y'] = $this->tbrot_y0; + $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $this->page]; + } + /* -- END BOOKMARKS -- */ + + /* -- TOC -- */ + // Adjust ToC - uses document page number + foreach ($this->tbrot_toc as $v) { + $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $this->page, 'link' => $v['link'], 'toc_id' => $v['toc_id']]; + $this->links[$v['link']][1] = $this->tbrot_y0; + } + /* -- END TOC -- */ + + $this->tbrot_BMoutlines = []; + $this->tbrot_toc = []; + + // Stop Transformation + $this->pages[$this->page] .= $this->StopTransform(true) . "\n"; + + $this->y = $this->tbrot_y0 + $this->tbrot_w; + $this->x = $this->lMargin; + + $this->tablebuffer = ''; + } + + /** + * Keep-with-table This buffers contents of h1-6 to keep on page with table + */ + function printkwtbuffer() + { + if (!$this->kwt_moved) { + + foreach ($this->kwt_buffer as $s) { + $this->pages[$this->page] .= $s['s'] . "\n"; + } + + foreach ($this->kwt_Links as $p => $l) { + foreach ($l as $v) { + $this->PageLinks[$p][] = $v; + } + } + + $this->kwt_Links = []; + + /* -- ANNOTATIONS -- */ + foreach ($this->kwt_Annots as $p => $l) { + foreach ($l as $v) { + $this->PageAnnots[$p][] = $v; + } + } + $this->kwt_Annots = []; + /* -- END ANNOTATIONS -- */ + + /* -- INDEX -- */ + // Output Reference (index) + foreach ($this->kwt_Reference as $v) { + + $Present = 0; + + for ($i = 0; $i < count($this->Reference); $i++) { + if ($this->Reference[$i]['t'] == $v['t']) { + $Present = 1; + if (!in_array($v['op'], $this->Reference[$i]['p'])) { + $this->Reference[$i]['p'][] = $v['op']; + } + } + } + + if ($Present == 0) { + $this->Reference[] = ['t' => $v['t'], 'p' => [$v['op']]]; + } + } + $this->kwt_Reference = []; + /* -- END INDEX -- */ + + /* -- BOOKMARKS -- */ + // Output Bookmarks + foreach ($this->kwt_BMoutlines as $v) { + $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $v['p']]; + } + $this->kwt_BMoutlines = []; + /* -- END BOOKMARKS -- */ + + /* -- TOC -- */ + // Output ToC + foreach ($this->kwt_toc as $v) { + $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $v['p'], 'link' => $v['link'], 'toc_id' => $v['toc_id']]; + } + $this->kwt_toc = []; + /* -- END TOC -- */ + + $this->pageoutput[$this->page] = []; // mPDF 6 + + return; + } + + // Start Transformation + $this->pages[$this->page] .= $this->StartTransform(true) . "\n"; + $xadj = $this->lMargin - $this->kwt_x0; + // $yadj = $this->y - $this->kwt_y0 ; + $yadj = $this->tMargin - $this->kwt_y0; + + $this->pages[$this->page] .= $this->transformTranslate($xadj, $yadj, true) . "\n"; + + // Now output the adjusted values + foreach ($this->kwt_buffer as $s) { + $this->pages[$this->page] .= $s['s'] . "\n"; + } + + // Adjust hyperLinks + foreach ($this->kwt_Links as $p => $l) { + foreach ($l as $v) { + $bx = $this->kwt_x0 + $xadj; + $by = $this->kwt_y0 + $yadj; + $v[0] = $bx * Mpdf::SCALE; + $v[1] = ($this->h - $by) * Mpdf::SCALE; + $this->PageLinks[$p][] = $v; + } + } + + foreach ($this->internallink as $key => $f) { + if (is_array($f) && isset($f['kwt'])) { + $f['Y'] += $yadj; + $f['PAGE'] = $this->page; + unset($f['kwt']); + $this->internallink[$key] = $f; + } + } + + /* -- ANNOTATIONS -- */ + foreach ($this->kwt_Annots as $p => $l) { + foreach ($l as $v) { + $bx = $this->kwt_x0 + $xadj; + $by = $this->kwt_y0 + $yadj; + if ($v['x'] < 0) { + $v['x'] = -$bx; + } else { + $v['x'] = $bx; + } + $v['y'] = $by; + $this->PageAnnots[$p][] = $v; + } + } + /* -- END ANNOTATIONS -- */ + + /* -- BOOKMARKS -- */ + + // Adjust Bookmarks + foreach ($this->kwt_BMoutlines as $v) { + if ($v['y'] != 0) { + $v['y'] += $yadj; + } + $this->BMoutlines[] = ['t' => $v['t'], 'l' => $v['l'], 'y' => $v['y'], 'p' => $this->page]; + } + /* -- END BOOKMARKS -- */ + + /* -- INDEX -- */ + // Adjust Reference (index) + foreach ($this->kwt_Reference as $v) { + + $Present = 0; + + // Search the reference (AND Ref/PageNo) in the array + for ($i = 0; $i < count($this->Reference); $i++) { + if ($this->Reference[$i]['t'] == $v['t']) { + $Present = 1; + if (!in_array($this->page, $this->Reference[$i]['p'])) { + $this->Reference[$i]['p'][] = $this->page; + } + } + } + + if ($Present == 0) { + $this->Reference[] = ['t' => $v['t'], 'p' => [$this->page]]; + } + } + /* -- END INDEX -- */ + + /* -- TOC -- */ + + // Adjust ToC + foreach ($this->kwt_toc as $v) { + $this->tableOfContents->_toc[] = ['t' => $v['t'], 'l' => $v['l'], 'p' => $this->page, 'link' => $v['link'], 'toc_id' => $v['toc_id']]; + $this->links[$v['link']][0] = $this->page; + $this->links[$v['link']][1] += $yadj; + } + /* -- END TOC -- */ + + + $this->kwt_Links = []; + $this->kwt_Annots = []; + + $this->kwt_Reference = []; + $this->kwt_BMoutlines = []; + $this->kwt_toc = []; + + // Stop Transformation + $this->pages[$this->page] .= $this->StopTransform(true) . "\n"; + + $this->kwt_buffer = []; + + $this->y += $this->kwt_height; + $this->pageoutput[$this->page] = []; // mPDF 6 + } + /* -- END TABLES -- */ + + function printfloatbuffer() + { + if (count($this->floatbuffer)) { + $this->objectbuffer = $this->floatbuffer; + $this->printobjectbuffer(false); + $this->objectbuffer = []; + $this->floatbuffer = []; + $this->floatmargins = []; + } + } + + function Circle($x, $y, $r, $style = 'S') + { + $this->Ellipse($x, $y, $r, $r, $style); + } + + function Ellipse($x, $y, $rx, $ry, $style = 'S') + { + if ($style === 'F') { + $op = 'f'; + } elseif ($style === 'FD' or $style === 'DF') { + $op = 'B'; + } else { + $op = 'S'; + } + + $lx = 4 / 3 * (M_SQRT2 - 1) * $rx; + $ly = 4 / 3 * (M_SQRT2 - 1) * $ry; + + $h = $this->h; + + $this->writer->write(sprintf('%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c', ($x + $rx) * Mpdf::SCALE, ($h - $y) * Mpdf::SCALE, ($x + $rx) * Mpdf::SCALE, ($h - ($y - $ly)) * Mpdf::SCALE, ($x + $lx) * Mpdf::SCALE, ($h - ($y - $ry)) * Mpdf::SCALE, $x * Mpdf::SCALE, ($h - ($y - $ry)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c', ($x - $lx) * Mpdf::SCALE, ($h - ($y - $ry)) * Mpdf::SCALE, ($x - $rx) * Mpdf::SCALE, ($h - ($y - $ly)) * Mpdf::SCALE, ($x - $rx) * Mpdf::SCALE, ($h - $y) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c', ($x - $rx) * Mpdf::SCALE, ($h - ($y + $ly)) * Mpdf::SCALE, ($x - $lx) * Mpdf::SCALE, ($h - ($y + $ry)) * Mpdf::SCALE, $x * Mpdf::SCALE, ($h - ($y + $ry)) * Mpdf::SCALE)); + $this->writer->write(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F c %s', ($x + $lx) * Mpdf::SCALE, ($h - ($y + $ry)) * Mpdf::SCALE, ($x + $rx) * Mpdf::SCALE, ($h - ($y + $ly)) * Mpdf::SCALE, ($x + $rx) * Mpdf::SCALE, ($h - $y) * Mpdf::SCALE, $op)); + } + + /* -- DIRECTW -- */ + function AutosizeText($text, $w, $font, $style, $szfont = 72) + { + + $text = ' ' . $text . ' '; + + $this->SetFont($font, $style, $szfont, false); + + $text = $this->purify_utf8_text($text); + if ($this->text_input_as_HTML) { + $text = $this->all_entities_to_utf8($text); + } + if ($this->usingCoreFont) { + $text = mb_convert_encoding($text, $this->mb_enc, 'UTF-8'); + } + + // DIRECTIONALITY + if (preg_match("/([" . $this->pregRTLchars . "])/u", $text)) { + $this->biDirectional = true; + } + + $textvar = 0; + $save_OTLtags = $this->OTLtags; + $this->OTLtags = []; + + if ($this->useKerning) { + if ($this->CurrentFont['haskernGPOS']) { + $this->OTLtags['Plus'] .= ' kern'; + } else { + $textvar = ($textvar | TextVars::FC_KERNING); + } + } + + /* -- OTL -- */ + // Use OTL OpenType Table Layout - GSUB & GPOS + if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) { + $text = $this->otl->applyOTL($text, $this->CurrentFont['useOTL']); + $OTLdata = $this->otl->OTLdata; + } + /* -- END OTL -- */ + + $this->OTLtags = $save_OTLtags; + + $this->magic_reverse_dir($text, $this->directionality, $OTLdata); + + $width = $this->sizeConverter->convert($w); + $loop = 0; + + while ($loop == 0) { + + $this->SetFont($font, $style, $szfont, false); + $sz = $this->GetStringWidth($text, true, $OTLdata, $textvar); + + if ($sz > $w) { + $szfont --; + } else { + $loop ++; + } + } + + $this->SetFont($font, $style, $szfont, true, true); + $this->Cell($w, 0, $text, 0, 0, "C", 0, '', 0, 0, 0, 'M', 0, false, $OTLdata, $textvar); + } + /* -- END DIRECTW -- */ + + // ==================================================== + // ==================================================== + + function magic_reverse_dir(&$chunk, $dir, &$chunkOTLdata) + { + /* -- OTL -- */ + if ($this->usingCoreFont) { + return 0; + } + + if ($chunk == '') { + return 0; + } + + if ($this->biDirectional || $dir == 'rtl') { + + // check if string contains RTL text + // including any added from OTL tables (in PUA) + $pregRTLchars = $this->pregRTLchars; + + if (isset($this->CurrentFont['rtlPUAstr']) && $this->CurrentFont['rtlPUAstr']) { + $pregRTLchars .= $this->CurrentFont['rtlPUAstr']; + } + + if (!preg_match("/[" . $pregRTLchars . "]/u", $chunk) && $dir != 'rtl') { + return 0; + } // Chunk doesn't contain RTL characters + + $unicode = $this->UTF8StringToArray($chunk, false); + + $isStrong = false; + if (empty($chunkOTLdata)) { + $this->getBasicOTLdata($chunkOTLdata, $unicode, $isStrong); + } + + $useGPOS = isset($this->CurrentFont['useOTL']) && ($this->CurrentFont['useOTL'] & 0x80); + + // NB Returned $chunk may be a shorter string (with adjusted $cOTLdata) by removal of LRE, RLE etc embedding codes. + list($chunk, $rtl_content) = $this->otl->bidiSort($unicode, $chunk, $dir, $chunkOTLdata, $useGPOS); + + return $rtl_content; + } + + /* -- END OTL -- */ + return 0; + } + + /* -- OTL -- */ + + function getBasicOTLdata(&$chunkOTLdata, $unicode, &$is_strong) + { + if (empty($this->otl)) { + $this->otl = new Otl($this, $this->fontCache); + } + + $chunkOTLdata['group'] = ''; + $chunkOTLdata['GPOSinfo'] = []; + $chunkOTLdata['char_data'] = []; + + foreach ($unicode as $char) { + + $ucd_record = Ucdn::get_ucd_record($char); + $chunkOTLdata['char_data'][] = ['bidi_class' => $ucd_record[2], 'uni' => $char]; + + if ($ucd_record[2] == 0 || $ucd_record[2] == 3 || $ucd_record[2] == 4) { + $is_strong = true; + } // contains strong character + + if ($ucd_record[0] == Ucdn::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) { + $chunkOTLdata['group'] .= 'M'; + } elseif ($char == 32 || $char == 12288) { + $chunkOTLdata['group'] .= 'S'; + } else { + $chunkOTLdata['group'] .= 'C'; + } + } + } + + function _setBidiCodes($mode = 'start', $bdf = '') + { + $s = ''; + + if ($mode == 'end') { + + // PDF comes before PDI to close isolate-override (e.g. "LRILROPDFPDI") + if (strpos($bdf, 'PDF') !== false) { + $s .= UtfString::code2utf(0x202C); + } // POP DIRECTIONAL FORMATTING + + if (strpos($bdf, 'PDI') !== false) { + $s .= UtfString::code2utf(0x2069); + } // POP DIRECTIONAL ISOLATE + + } elseif ($mode == 'start') { + + // LRI comes before LRO to open isolate-override (e.g. "LRILROPDFPDI") + if (strpos($bdf, 'LRI') !== false) { // U+2066 LRI + $s .= UtfString::code2utf(0x2066); + } elseif (strpos($bdf, 'RLI') !== false) { // U+2067 RLI + $s .= UtfString::code2utf(0x2067); + } elseif (strpos($bdf, 'FSI') !== false) { // U+2068 FSI + $s .= UtfString::code2utf(0x2068); + } + + if (strpos($bdf, 'LRO') !== false) { // U+202D LRO + $s .= UtfString::code2utf(0x202D); + } elseif (strpos($bdf, 'RLO') !== false) { // U+202E RLO + $s .= UtfString::code2utf(0x202E); + } elseif (strpos($bdf, 'LRE') !== false) { // U+202A LRE + $s .= UtfString::code2utf(0x202A); + } elseif (strpos($bdf, 'RLE') !== false) { // U+202B RLE + $s .= UtfString::code2utf(0x202B); + } + } + + return $s; + } + /* -- END OTL -- */ + + function SetSubstitutions() + { + $subsarray = []; + require __DIR__ . '/../data/subs_win-1252.php'; + $this->substitute = []; + foreach ($subsarray as $key => $val) { + $this->substitute[UtfString::code2utf($key)] = $val; + } + } + + function SubstituteChars($html) + { + // only substitute characters between tags + if (count($this->substitute)) { + $a = preg_split('/(<.*?>)/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE); + $html = ''; + foreach ($a as $i => $e) { + if ($i % 2 == 0) { + $e = strtr($e, $this->substitute); + } + $html .= $e; + } + } + + return $html; + } + + function SubstituteCharsSIP(&$writehtml_a, &$writehtml_i, &$writehtml_e) + { + if (preg_match("/^(.*?)([\x{20000}-\x{2FFFF}]+)(.*)/u", $writehtml_e, $m)) { + if (isset($this->CurrentFont['sipext']) && $this->CurrentFont['sipext']) { + $font = $this->CurrentFont['sipext']; + if (!in_array($font, $this->available_unifonts)) { + return 0; + } + $writehtml_a[$writehtml_i] = $writehtml_e = $m[1]; + array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]); + $this->subPos = $writehtml_i; + return 4; + } + } + + return 0; + } + + /** + * If core font is selected in document which is not onlyCoreFonts - substitute with non-core font + */ + function SubstituteCharsNonCore(&$writehtml_a, &$writehtml_i, &$writehtml_e) + { + // Ignore if in Textarea + if ($writehtml_i > 0 && strtolower(substr($writehtml_a[$writehtml_i - 1], 0, 8)) == 'textarea') { + return 0; + } + + if (mb_convert_encoding(mb_convert_encoding($writehtml_e, $this->mb_enc, "UTF-8"), "UTF-8", $this->mb_enc) == $writehtml_e) { + return 0; + } + + $cw = &$this->CurrentFont['cw']; + $unicode = $this->UTF8StringToArray($writehtml_e, false); + $start = -1; + $end = 0; + $flag = 0; + $ftype = ''; + $u = []; + + if (!$this->subArrMB) { + + require __DIR__ . '/../data/subs_core.php'; + + $this->subArrMB['a'] = $aarr; + $this->subArrMB['s'] = $sarr; + $this->subArrMB['z'] = $zarr; + } + + foreach ($unicode as $c => $char) { + + if (($char > 127 || ($flag == 1 && $char == 32)) && $char != 173 && (!isset($this->subArrMB['a'][$char]) || ($flag == 1 && $char == 32)) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) { + if ($flag == 0) { + $start = $c; + } + $flag = 1; + $u[] = $char; + } elseif ($flag > 0) { + $end = $c - 1; + break; + } + } + + if ($flag > 0 && !$end) { + $end = count($unicode) - 1; + } + + if ($start == -1) { + return 0; + } + + // Try in backup subs font + if (!is_array($this->backupSubsFont)) { + $this->backupSubsFont = ["$this->backupSubsFont"]; + } + + foreach ($this->backupSubsFont as $bsfctr => $bsf) { + + if ($this->fonttrans[$bsf] == 'chelvetica' || $this->fonttrans[$bsf] == 'ctimes' || $this->fonttrans[$bsf] == 'ccourier') { + continue; + } + + $font = $bsf; + unset($cw); + $cw = ''; + + if (isset($this->fonts[$font])) { + $cw = &$this->fonts[$font]['cw']; + } elseif ($this->fontCache->has($font . '.cw.dat')) { + $cw = $this->fontCache->load($font . '.cw.dat'); + } else { + $prevFontFamily = $this->FontFamily; + $prevFontStyle = $this->currentfontstyle; + $prevFontSizePt = $this->FontSizePt; + $this->SetFont($bsf, '', '', false); + $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false); + } + + if (!$cw) { + continue; + } + + $l = 0; + foreach ($u as $char) { + if ($char == 173 || $this->_charDefined($cw, $char) || ($char > 1536 && $char < 1791) || ($char > 2304 && $char < 3455 )) { + $l++; + } else { + if ($l == 0 && $bsfctr == (count($this->backupSubsFont) - 1)) { // Not found even in last backup font + $cont = mb_substr($writehtml_e, $start + 1); + $writehtml_e = mb_substr($writehtml_e, 0, $start + 1, 'UTF-8'); + array_splice($writehtml_a, $writehtml_i + 1, 0, ['', $cont]); + $this->subPos = $writehtml_i + 1; + + return 2; + } else { + break; + } + } + } + + if ($l > 0) { + $patt = mb_substr($writehtml_e, $start, $l, 'UTF-8'); + if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) { + $writehtml_e = $m[1]; + array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]); + $this->subPos = $writehtml_i + 3; + + return 4; + } + } + } + + unset($cw); + + return 0; + } + + function SubstituteCharsMB(&$writehtml_a, &$writehtml_i, &$writehtml_e) + { + // Ignore if in Textarea + if ($writehtml_i > 0 && strtolower(substr($writehtml_a[$writehtml_i - 1], 0, 8)) == 'textarea') { + return 0; + } + + $cw = &$this->CurrentFont['cw']; + $unicode = $this->UTF8StringToArray($writehtml_e, false); + $start = -1; + $end = 0; + $flag = 0; + $ftype = ''; + $u = []; + + foreach ($unicode as $c => $char) { + + if (($flag == 0 || $flag == 2) && (!$this->_charDefined($cw, $char) || ($flag == 2 && $char == 32)) && $this->checkSIP && $char > 131071) { // Unicode Plane 2 (SIP) + + if (in_array($this->FontFamily, $this->available_CJK_fonts)) { + return 0; + } + + if ($flag == 0) { + $start = $c; + } + + $flag = 2; + $u[] = $char; + + // elseif (($flag == 0 || $flag==1) && $char != 173 && !$this->_charDefined($cw,$char) && ($char<1423 || ($char>3583 && $char < 11263))) { + + } elseif (($flag == 0 || $flag == 1) && $char != 173 && (!$this->_charDefined($cw, $char) || ($flag == 1 && $char == 32)) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) { + + if ($flag == 0) { + $start = $c; + } + + $flag = 1; + $u[] = $char; + + } elseif ($flag > 0) { + + $end = $c - 1; + break; + + } + } + + if ($flag > 0 && !$end) { + $end = count($unicode) - 1; + } + + if ($start == -1) { + return 0; + } + + if ($flag == 2) { // SIP + + // Check if current CJK font has a ext-B related font + if (isset($this->CurrentFont['sipext']) && $this->CurrentFont['sipext']) { + $font = $this->CurrentFont['sipext']; + unset($cw); + $cw = ''; + + if (isset($this->fonts[$font])) { + $cw = &$this->fonts[$font]['cw']; + } elseif ($this->fontCache->has($font . '.cw.dat')) { + $cw = $this->fontCache->load($font . '.cw.dat'); + } else { + $prevFontFamily = $this->FontFamily; + $prevFontStyle = $this->currentfontstyle; + $prevFontSizePt = $this->FontSizePt; + $this->SetFont($font, '', '', false); + $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false); + } + + if (!$cw) { + return 0; + } + + $l = 0; + foreach ($u as $char) { + if ($this->_charDefined($cw, $char) || $char > 131071) { + $l++; + } else { + break; + } + } + + if ($l > 0) { + $patt = mb_substr($writehtml_e, $start, $l); + if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) { + $writehtml_e = $m[1]; + array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]); + $this->subPos = $writehtml_i + 3; + return 4; + } + } + } + + // Check Backup SIP font (defined in Config\FontVariables) + if (isset($this->backupSIPFont) && $this->backupSIPFont) { + + if ($this->currentfontfamily != $this->backupSIPFont) { + $font = $this->backupSIPFont; + } else { + unset($cw); + return 0; + } + + unset($cw); + $cw = ''; + + if (isset($this->fonts[$font])) { + $cw = &$this->fonts[$font]['cw']; + } elseif ($this->fontCache->has($font . '.cw.dat')) { + $cw = $this->fontCache->load($font . '.cw.dat'); + } else { + $prevFontFamily = $this->FontFamily; + $prevFontStyle = $this->currentfontstyle; + $prevFontSizePt = $this->FontSizePt; + $this->SetFont($this->backupSIPFont, '', '', false); + $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false); + } + + if (!$cw) { + return 0; + } + + $l = 0; + foreach ($u as $char) { + if ($this->_charDefined($cw, $char) || $char > 131071) { + $l++; + } else { + break; + } + } + + if ($l > 0) { + $patt = mb_substr($writehtml_e, $start, $l); + if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) { + $writehtml_e = $m[1]; + array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]); + $this->subPos = $writehtml_i + 3; + return 4; + } + } + } + + return 0; + } + + // FIRST TRY CORE FONTS (when appropriate) + if (!$this->PDFA && !$this->PDFX && !$this->biDirectional) { // mPDF 6 + $repl = []; + if (!$this->subArrMB) { + require __DIR__ . '/../data/subs_core.php'; + $this->subArrMB['a'] = $aarr; + $this->subArrMB['s'] = $sarr; + $this->subArrMB['z'] = $zarr; + } + if (isset($this->subArrMB['a'][$u[0]])) { + $font = 'tta'; + $ftype = 'C'; + foreach ($u as $char) { + if (isset($this->subArrMB['a'][$char])) { + $repl[] = $this->subArrMB['a'][$char]; + } else { + break; + } + } + } elseif (isset($this->subArrMB['z'][$u[0]])) { + $font = 'ttz'; + $ftype = 'C'; + foreach ($u as $char) { + if (isset($this->subArrMB['z'][$char])) { + $repl[] = $this->subArrMB['z'][$char]; + } else { + break; + } + } + } elseif (isset($this->subArrMB['s'][$u[0]])) { + $font = 'tts'; + $ftype = 'C'; + foreach ($u as $char) { + if (isset($this->subArrMB['s'][$char])) { + $repl[] = $this->subArrMB['s'][$char]; + } else { + break; + } + } + } + if ($ftype == 'C') { + $patt = mb_substr($writehtml_e, $start, count($repl)); + if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) { + $writehtml_e = $m[1]; + array_splice($writehtml_a, $writehtml_i + 1, 0, [$font, implode('|', $repl), '/' . $font, $m[3]]); // e.g. <tts> + $this->subPos = $writehtml_i + 3; + return 4; + } + return 0; + } + } + + // LASTLY TRY IN BACKUP SUBS FONT + if (!is_array($this->backupSubsFont)) { + $this->backupSubsFont = ["$this->backupSubsFont"]; + } + + foreach ($this->backupSubsFont as $bsfctr => $bsf) { + if ($this->currentfontfamily != $bsf) { + $font = $bsf; + } else { + continue; + } + + unset($cw); + $cw = ''; + + if (isset($this->fonts[$font])) { + $cw = &$this->fonts[$font]['cw']; + } elseif ($this->fontCache->has($font . '.cw.dat')) { + $cw = $this->fontCache->load($font . '.cw.dat'); + } else { + $prevFontFamily = $this->FontFamily; + $prevFontStyle = $this->currentfontstyle; + $prevFontSizePt = $this->FontSizePt; + $this->SetFont($bsf, '', '', false); + $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt, false); + if ($this->fontCache->has($font . '.cw.dat')) { + $cw = $this->fontCache->load($font . '.cw.dat'); + } + } + + if (!$cw) { + continue; + } + + $l = 0; + foreach ($u as $char) { + if ($char == 173 || $this->_charDefined($cw, $char) || ($char > 1536 && $char < 1791) || ($char > 2304 && $char < 3455 )) { // Arabic and Indic + $l++; + } else { + if ($l == 0 && $bsfctr == (count($this->backupSubsFont) - 1)) { // Not found even in last backup font + $cont = mb_substr($writehtml_e, $start + 1); + $writehtml_e = mb_substr($writehtml_e, 0, $start + 1); + array_splice($writehtml_a, $writehtml_i + 1, 0, ['', $cont]); + $this->subPos = $writehtml_i + 1; + return 2; + } else { + break; + } + } + } + + if ($l > 0) { + $patt = mb_substr($writehtml_e, $start, $l); + if (preg_match("/(.*?)(" . preg_quote($patt, '/') . ")(.*)/u", $writehtml_e, $m)) { + $writehtml_e = $m[1]; + array_splice($writehtml_a, $writehtml_i + 1, 0, ['span style="font-family: ' . $font . '"', $m[2], '/span', $m[3]]); + $this->subPos = $writehtml_i + 3; + return 4; + } + } + } + + unset($cw); + + return 0; + } + + function setHiEntitySubstitutions() + { + $entarr = include __DIR__ . '/../data/entity_substitutions.php'; + + foreach ($entarr as $key => $val) { + $this->entsearch[] = '&' . $key . ';'; + $this->entsubstitute[] = UtfString::code2utf($val); + } + } + + function SubstituteHiEntities($html) + { + // converts html_entities > ASCII 127 to unicode + // Leaves in particular < to distinguish from tag marker + if (count($this->entsearch)) { + $html = str_replace($this->entsearch, $this->entsubstitute, $html); + } + + return $html; + } + + /** + * Edited v1.2 Pass by reference; option to continue if invalid UTF-8 chars + */ + function is_utf8(&$string) + { + if ($string === mb_convert_encoding(mb_convert_encoding($string, "UTF-32", "UTF-8"), "UTF-8", "UTF-32")) { + return true; + } + + if ($this->ignore_invalid_utf8) { + $string = mb_convert_encoding(mb_convert_encoding($string, "UTF-32", "UTF-8"), "UTF-8", "UTF-32"); + return true; + } + + return false; + } + + /** + * For HTML + * + * Checks string is valid UTF-8 encoded + * converts html_entities > ASCII 127 to UTF-8 + * Only exception - leaves low ASCII entities e.g. < & etc. + * Leaves in particular < to distinguish from tag marker + */ + function purify_utf8($html, $lo = true) + { + if (!$this->is_utf8($html)) { + + while (mb_convert_encoding(mb_convert_encoding($html, "UTF-32", "UTF-8"), "UTF-8", "UTF-32") != $html) { + + $a = @iconv('UTF-8', 'UTF-8', $html); + $error = error_get_last(); + if ($error && $error['message'] === 'iconv(): Detected an illegal character in input string') { + throw new \Mpdf\MpdfException('Invalid input characters. Did you set $mpdf->in_charset properly?'); + } + + $pos = $start = strlen($a); + $err = ''; + while (ord(substr($html, $pos, 1)) > 128) { + $err .= '[[#' . ord(substr($html, $pos, 1)) . ']]'; + $pos++; + } + + $this->logger->error($err, ['context' => LogContext::UTF8]); + $html = substr($html, $pos); + } + + throw new \Mpdf\MpdfException("HTML contains invalid UTF-8 character(s). See log for further details"); + } + + $html = preg_replace("/\r/", "", $html); + + // converts html_entities > ASCII 127 to UTF-8 + // Leaves in particular < to distinguish from tag marker + $html = $this->SubstituteHiEntities($html); + + // converts all &#nnn; or &#xHHH; to UTF-8 multibyte + // If $lo==true then includes ASCII < 128 + $html = UtfString::strcode2utf($html, $lo); + + return $html; + } + + /** + * For TEXT + */ + function purify_utf8_text($txt) + { + // Make sure UTF-8 string of characters + if (!$this->is_utf8($txt)) { + throw new \Mpdf\MpdfException("Text contains invalid UTF-8 character(s)"); + } + + $txt = preg_replace("/\r/", "", $txt); + + return ($txt); + } + + function all_entities_to_utf8($txt) + { + // converts txt_entities > ASCII 127 to UTF-8 + // Leaves in particular < to distinguish from tag marker + $txt = $this->SubstituteHiEntities($txt); + + // converts all &#nnn; or &#xHHH; to UTF-8 multibyte + $txt = UtfString::strcode2utf($txt); + + $txt = $this->lesser_entity_decode($txt); + return ($txt); + } + + /* -- BARCODES -- */ + /** + * UPC/EAN barcode + * + * EAN13, EAN8, UPCA, UPCE, ISBN, ISSN + * Accepts 12 or 13 digits with or without - hyphens + */ + function WriteBarcode($code, $showtext = 1, $x = '', $y = '', $size = 1, $border = 0, $paddingL = 1, $paddingR = 1, $paddingT = 2, $paddingB = 2, $height = 1, $bgcol = false, $col = false, $btype = 'ISBN', $supplement = '0', $supplement_code = '', $k = 1) + { + if (empty($code)) { + return; + } + + $codestr = $code; + $code = preg_replace('/\-/', '', $code); + + $this->barcode = new Barcode(); + if ($btype == 'ISSN' || $btype == 'ISBN') { + $arrcode = $this->barcode->getBarcodeArray($code, 'EAN13'); + } else { + $arrcode = $this->barcode->getBarcodeArray($code, $btype); + } + + if ($arrcode === false) { + throw new \Mpdf\MpdfException('Error in barcode string: ' . $codestr); + } + + if ((($btype === 'EAN13' || $btype === 'ISBN' || $btype === 'ISSN') && strlen($code) === 12) + || ($btype == 'UPCA' && strlen($code) === 11) + || ($btype == 'UPCE' && strlen($code) === 11) + || ($btype == 'EAN8' && strlen($code) === 7)) { + + $code .= $arrcode['checkdigit']; + + if (stristr($codestr, '-')) { + $codestr .= '-' . $arrcode['checkdigit']; + } else { + $codestr .= $arrcode['checkdigit']; + } + } + + if ($btype === 'ISBN') { + $codestr = 'ISBN ' . $codestr; + } + + if ($btype === 'ISSN') { + $codestr = 'ISSN ' . $codestr; + } + + if (empty($x)) { + $x = $this->x; + } + + if (empty($y)) { + $y = $this->y; + } + + // set foreground color + $prevDrawColor = $this->DrawColor; + $prevTextColor = $this->TextColor; + $prevFillColor = $this->FillColor; + + $lw = $this->LineWidth; + $this->SetLineWidth(0.01); + + $size /= $k; // in case resized in a table + + $xres = $arrcode['nom-X'] * $size; + $llm = $arrcode['lightmL'] * $arrcode['nom-X'] * $size; // Left Light margin + $rlm = $arrcode['lightmR'] * $arrcode['nom-X'] * $size; // Right Light margin + + $bcw = ($arrcode["maxw"] * $xres); // Barcode width = Should always be 31.35mm * $size + + $fbw = $bcw + $llm + $rlm; // Full barcode width incl. light margins + $ow = $fbw + $paddingL + $paddingR; // Full overall width incl. user-defined padding + + $fbwi = $fbw - 2; // Full barcode width incl. light margins - 2mm - for isbn string + // cf. http://www.gs1uk.org/downloads/bar_code/Bar coding getting it right.pdf + $num_height = 3 * $size; // Height of numerals + $fbh = $arrcode['nom-H'] * $size * $height; // Full barcode height incl. numerals + $bch = $fbh - (1.5 * $size); // Barcode height of bars (3mm for numerals) + + if (($btype == 'EAN13' && $showtext) || $btype == 'ISSN' || $btype == 'ISBN') { // Add height for ISBN string + margin from top of bars + $tisbnm = 1.5 * $size; // Top margin between isbn (if shown) & bars + $codestr_fontsize = 2.1 * $size; + $paddingT += $codestr_fontsize + $tisbnm; + } + + $oh = $fbh + $paddingT + $paddingB; // Full overall height incl. user-defined padding + + // PRINT border background color + $xpos = $x; + $ypos = $y; + + if ($col) { + $this->SetDColor($col); + $this->SetTColor($col); + } else { + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + + if ($bgcol) { + $this->SetFColor($bgcol); + } else { + $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings)); + } + + if (!$bgcol && !$col) { // fn. called directly - not via HTML + + if ($border) { + $fillb = 'DF'; + } else { + $fillb = 'F'; + } + + $this->Rect($xpos, $ypos, $ow, $oh, $fillb); + } + + + // PRINT BARS + $xpos = $x + $paddingL + $llm; + $ypos = $y + $paddingT; + + if ($col) { + $this->SetFColor($col); + } else { + $this->SetFColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + + if ($arrcode !== false) { + foreach ($arrcode["bcode"] as $v) { + $bw = ($v["w"] * $xres); + if ($v["t"]) { + // draw a vertical bar + $this->Rect($xpos, $ypos, $bw, $bch, 'F'); + } + $xpos += $bw; + } + } + + // print text + $prevFontFamily = $this->FontFamily; + $prevFontStyle = $this->FontStyle; + $prevFontSizePt = $this->FontSizePt; + + // ISBN string + if (($btype === 'EAN13' && $showtext) || $btype === 'ISBN' || $btype === 'ISSN') { + + if ($this->onlyCoreFonts) { + $this->SetFont('chelvetica'); + } else { + $this->SetFont('sans'); + } + + if ($bgcol) { + $this->SetFColor($bgcol); + } else { + $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings)); + } + + $this->x = $x + $paddingL + 1; // 1mm left margin (cf. $fbwi above) + + // max width is $fbwi + $loop = 0; + while ($loop == 0) { + $this->SetFontSize($codestr_fontsize * 1.4 * Mpdf::SCALE, false); // don't write + $sz = $this->GetStringWidth($codestr); + + if ($sz > $fbwi) { + $codestr_fontsize -= 0.1; + } else { + $loop ++; + } + } + + $this->SetFont('', '', $codestr_fontsize * 1.4 * Mpdf::SCALE, true, true); // * 1.4 because font height is only 7/10 of given mm + // WORD SPACING + if ($fbwi > $sz) { + $xtra = $fbwi - $sz; + $charspacing = $xtra / (strlen($codestr) - 1); + if ($charspacing) { + $this->writer->write(sprintf('BT %.3F Tc ET', $charspacing * Mpdf::SCALE)); + } + } + + $this->y = $y + $paddingT - ($codestr_fontsize ) - $tisbnm; + $this->Cell($fbw, $codestr_fontsize, $codestr); + + if ($charspacing) { + $this->writer->write('BT 0 Tc ET'); + } + } + + + // Bottom NUMERALS + // mPDF 5.7.4 + if ($this->onlyCoreFonts) { + $this->SetFont('ccourier'); + $fh = 1.3; + } else { + $this->SetFont('ocrb'); + $fh = 1.06; + } + + $charRO = ''; + + if ($btype === 'EAN13' || $btype === 'ISBN' || $btype === 'ISSN') { + + $outerfontsize = 3; // Inner fontsize = 3 + $outerp = $xres * 4; + $innerp = $xres * 2.5; + $textw = ($bcw * 0.5) - $outerp - $innerp; + $chars = 6; // number of numerals in each half + $charLO = substr($code, 0, 1); // Left Outer + $charLI = substr($code, 1, 6); // Left Inner + $charRI = substr($code, 7, 6); // Right Inner + + if (!$supplement) { + $charRO = '>'; // Right Outer + } + + } elseif ($btype === 'UPCA') { + + $outerfontsize = 2.3; // Inner fontsize = 3 + $outerp = $xres * 10; + $innerp = $xres * 2.5; + $textw = ($bcw * 0.5) - $outerp - $innerp; + $chars = 5; + $charLO = substr($code, 0, 1); // Left Outer + $charLI = substr($code, 1, 5); // Left Inner + $charRI = substr($code, 6, 5); // Right Inner + $charRO = substr($code, 11, 1); // Right Outer + + } elseif ($btype === 'UPCE') { + + $outerfontsize = 2.3; // Inner fontsize = 3 + $outerp = $xres * 4; + $innerp = 0; + $textw = ($bcw * 0.5) - $outerp - $innerp; + $chars = 3; + $upce_code = $arrcode['code']; + $charLO = substr($code, 0, 1); // Left Outer + $charLI = substr($upce_code, 0, 3); // Left Inner + $charRI = substr($upce_code, 3, 3); // Right Inner + $charRO = substr($code, 11, 1); // Right Outer + + } elseif ($btype === 'EAN8') { + + $outerfontsize = 3; // Inner fontsize = 3 + $outerp = $xres * 4; + $innerp = $xres * 2.5; + $textw = ($bcw * 0.5) - $outerp - $innerp; + $chars = 4; + $charLO = '<'; // Left Outer + $charLI = substr($code, 0, 4); // Left Inner + $charRI = substr($code, 4, 4); // Right Inner + + if (!$supplement) { + $charRO = '>'; // Right Outer + } + } + + $this->SetFontSize(($outerfontsize / 3) * 3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters) + + if (!$this->usingCoreFont) { // character width at 3mm + $cw = $this->_getCharWidth($this->CurrentFont['cw'], 32) * 3 * $fh * $size / 1000; + } else { + $cw = 600 * 3 * $fh * $size / 1000; + } + + // Outer left character + $y_text = $y + $paddingT + $bch - ($num_height / 2); + $y_text_outer = $y + $paddingT + $bch - ($num_height * ($outerfontsize / 3) / 2); + + $this->x = $x + $paddingL - ($cw * ($outerfontsize / 3) * 0.1); // 0.1 is correction as char does not fill full width; + $this->y = $y_text_outer; + $this->Cell($cw, $num_height, $charLO); + + // WORD SPACING for inner chars + $xtra = $textw - ($cw * $chars); + $charspacing = $xtra / ($chars - 1); + if ($charspacing) { + $this->writer->write(sprintf('BT %.3F Tc ET', $charspacing * Mpdf::SCALE)); + } + + if ($bgcol) { + $this->SetFColor($bgcol); + } else { + $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings)); + } + + $this->SetFontSize(3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters) + + // Inner left half characters + $this->x = $x + $paddingL + $llm + $outerp; + $this->y = $y_text; + $this->Cell($textw, $num_height, $charLI, 0, 0, '', 1); + + // Inner right half characters + $this->x = $x + $paddingL + $llm + ($bcw * 0.5) + $innerp; + $this->y = $y_text; + $this->Cell($textw, $num_height, $charRI, 0, 0, '', 1); + + if ($charspacing) { + $this->writer->write('BT 0 Tc ET'); + } + + // Outer Right character + $this->SetFontSize(($outerfontsize / 3) * 3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters) + + $this->x = $x + $paddingL + $llm + $bcw + $rlm - ($cw * ($outerfontsize / 3) * 0.9); // 0.9 is correction as char does not fill full width + $this->y = $y_text_outer; + $this->Cell($cw * ($outerfontsize / 3), $num_height, $charRO, 0, 0, 'R'); + + if ($supplement) { // EAN-2 or -5 Supplement + // PRINT BARS + $supparrcode = $this->barcode->getBarcodeArray($supplement_code, 'EAN' . $supplement); + + if ($supparrcode === false) { + throw new \Mpdf\MpdfException('Error in barcode string (supplement): ' . $codestr . ' ' . $supplement_code); + } + + if (strlen($supplement_code) != $supplement) { + throw new \Mpdf\MpdfException('Barcode supplement incorrect: ' . $supplement_code); + } + + $llm = $fbw - (($arrcode['lightmR'] - $supparrcode['sepM']) * $arrcode['nom-X'] * $size); // Left Light margin + $rlm = $arrcode['lightmR'] * $arrcode['nom-X'] * $size; // Right Light margin + + $bcw = ($supparrcode["maxw"] * $xres); // Barcode width = Should always be 31.35mm * $size + + $fbw = $bcw + $llm + $rlm; // Full barcode width incl. light margins + $ow = $fbw + $paddingL + $paddingR; // Full overall width incl. user-defined padding + $bch = $fbh - (1.5 * $size) - ($num_height + 0.5); // Barcode height of bars (3mm for numerals) + + $xpos = $x + $paddingL + $llm; + $ypos = $y + $paddingT + $num_height + 0.5; + + if ($col) { + $this->SetFColor($col); + } else { + $this->SetFColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + + if ($supparrcode !== false) { + foreach ($supparrcode["bcode"] as $v) { + $bw = ($v["w"] * $xres); + if ($v["t"]) { + // draw a vertical bar + $this->Rect($xpos, $ypos, $bw, $bch, 'F'); + } + $xpos += $bw; + } + } + + // Characters + if ($bgcol) { + $this->SetFColor($bgcol); + } else { + $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings)); + } + + $this->SetFontSize(3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters) + $this->x = $x + $paddingL + $llm; + $this->y = $y + $paddingT; + $this->Cell($bcw, $num_height, $supplement_code, 0, 0, 'C'); + + // Outer Right character (light margin) + $this->SetFontSize(($outerfontsize / 3) * 3 * $fh * $size * Mpdf::SCALE); // 3mm numerals (FontSize is larger to account for space above/below characters) + $this->x = $x + $paddingL + $llm + $bcw + $rlm - ($cw * 0.9); // 0.9 is correction as char does not fill full width + $this->y = $y + $paddingT; + $this->Cell($cw * ($outerfontsize / 3), $num_height, '>', 0, 0, 'R'); + } + + // Restore ************** + $this->SetFont($prevFontFamily, $prevFontStyle, $prevFontSizePt); + $this->DrawColor = $prevDrawColor; + $this->TextColor = $prevTextColor; + $this->FillColor = $prevFillColor; + $this->SetLineWidth($lw); + $this->SetY($y); + } + + /** + * POSTAL and OTHER barcodes + */ + function WriteBarcode2($code, $x = '', $y = '', $size = 1, $height = 1, $bgcol = false, $col = false, $btype = 'IMB', $print_ratio = '', $k = 1) + { + if (empty($code)) { + return; + } + + $this->barcode = new Barcode(); + $arrcode = $this->barcode->getBarcodeArray($code, $btype, $print_ratio); + + if (empty($x)) { + $x = $this->x; + } + + if (empty($y)) { + $y = $this->y; + } + + $prevDrawColor = $this->DrawColor; + $prevTextColor = $this->TextColor; + $prevFillColor = $this->FillColor; + $lw = $this->LineWidth; + $this->SetLineWidth(0.01); + $size /= $k; // in case resized in a table + $xres = $arrcode['nom-X'] * $size; + + if ($btype === 'IMB' || $btype === 'RM4SCC' || $btype === 'KIX' || $btype === 'POSTNET' || $btype === 'PLANET') { + $llm = $arrcode['quietL'] / $k; // Left Quiet margin + $rlm = $arrcode['quietR'] / $k; // Right Quiet margin + $tlm = $blm = $arrcode['quietTB'] / $k; + $height = 1; // Overrides + } elseif (in_array($btype, ['C128A', 'C128B', 'C128C', 'EAN128A', 'EAN128B', 'EAN128C', 'C39', 'C39+', 'C39E', 'C39E+', 'S25', 'S25+', 'I25', 'I25+', 'I25B', 'I25B+', 'C93', 'MSI', 'MSI+', 'CODABAR', 'CODE11'])) { + $llm = $arrcode['lightmL'] * $xres; // Left Quiet margin + $rlm = $arrcode['lightmR'] * $xres; // Right Quiet margin + $tlm = $blm = $arrcode['lightTB'] * $xres * $height; + } + + $bcw = ($arrcode["maxw"] * $xres); + $fbw = $bcw + $llm + $rlm; // Full barcode width incl. light margins + + $bch = ($arrcode["nom-H"] * $size * $height); + $fbh = $bch + $tlm + $blm; // Full barcode height + + // PRINT border background color + $xpos = $x; + $ypos = $y; + + if ($col) { + $this->SetDColor($col); + $this->SetTColor($col); + } else { + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $this->SetTColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + + if ($bgcol) { + $this->SetFColor($bgcol); + } else { + $this->SetFColor($this->colorConverter->convert(255, $this->PDFAXwarnings)); + } + + // PRINT BARS + if ($col) { + $this->SetFColor($col); + } else { + $this->SetFColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + } + $xpos = $x + $llm; + + if ($arrcode !== false) { + foreach ($arrcode["bcode"] as $v) { + $bw = ($v["w"] * $xres); + if ($v["t"]) { + $ypos = $y + $tlm + ($bch * $v['p'] / $arrcode['maxh']); + $this->Rect($xpos, $ypos, $bw, ($v['h'] * $bch / $arrcode['maxh']), 'F'); + } + $xpos += $bw; + } + } + + // PRINT BEARER BARS + if ($btype == 'I25B' || $btype == 'I25B+') { + $this->Rect($x, $y, $fbw, ($arrcode['lightTB'] * $xres * $height), 'F'); + $this->Rect($x, $y + $tlm + $bch, $fbw, ($arrcode['lightTB'] * $xres * $height), 'F'); + } + + // Restore ************** + $this->DrawColor = $prevDrawColor; + $this->TextColor = $prevTextColor; + $this->FillColor = $prevFillColor; + $this->SetLineWidth($lw); + $this->SetY($y); + } + /* -- END BARCODES -- */ + + function StartTransform($returnstring = false) + { + if ($returnstring) { + return('q'); + } else { + $this->writer->write('q'); + } + } + + function StopTransform($returnstring = false) + { + if ($returnstring) { + return('Q'); + } else { + $this->writer->write('Q'); + } + } + + function transformScale($s_x, $s_y, $x = '', $y = '', $returnstring = false) + { + if ($x === '') { + $x = $this->x; + } + + if ($y === '') { + $y = $this->y; + } + + if (($s_x == 0) or ( $s_y == 0)) { + throw new \Mpdf\MpdfException('Please do not use values equal to zero for scaling'); + } + + $y = ($this->h - $y) * Mpdf::SCALE; + $x *= Mpdf::SCALE; + + // calculate elements of transformation matrix + $s_x /= 100; + $s_y /= 100; + $tm = []; + $tm[0] = $s_x; + $tm[1] = 0; + $tm[2] = 0; + $tm[3] = $s_y; + $tm[4] = $x * (1 - $s_x); + $tm[5] = $y * (1 - $s_y); + + // scale the coordinate system + if ($returnstring) { + return($this->_transform($tm, true)); + } else { + $this->_transform($tm); + } + } + + function transformTranslate($t_x, $t_y, $returnstring = false) + { + // calculate elements of transformation matrix + $tm = []; + $tm[0] = 1; + $tm[1] = 0; + $tm[2] = 0; + $tm[3] = 1; + $tm[4] = $t_x * Mpdf::SCALE; + $tm[5] = -$t_y * Mpdf::SCALE; + + // translate the coordinate system + if ($returnstring) { + return($this->_transform($tm, true)); + } else { + $this->_transform($tm); + } + } + + function transformRotate($angle, $x = '', $y = '', $returnstring = false) + { + if ($x === '') { + $x = $this->x; + } + + if ($y === '') { + $y = $this->y; + } + + $angle = -$angle; + $y = ($this->h - $y) * Mpdf::SCALE; + $x *= Mpdf::SCALE; + + // calculate elements of transformation matrix + $tm = []; + $tm[0] = cos(deg2rad($angle)); + $tm[1] = sin(deg2rad($angle)); + $tm[2] = -$tm[1]; + $tm[3] = $tm[0]; + $tm[4] = $x + $tm[1] * $y - $tm[0] * $x; + $tm[5] = $y - $tm[0] * $y - $tm[1] * $x; + + // rotate the coordinate system around ($x,$y) + if ($returnstring) { + return $this->_transform($tm, true); + } else { + $this->_transform($tm); + } + } + + /** + * mPDF 5.7.3 TRANSFORMS + */ + function transformSkew($angle_x, $angle_y, $x = '', $y = '', $returnstring = false) + { + if ($x === '') { + $x = $this->x; + } + + if ($y === '') { + $y = $this->y; + } + + $angle_x = -$angle_x; + $angle_y = -$angle_y; + + $x *= Mpdf::SCALE; + $y = ($this->h - $y) * Mpdf::SCALE; + + // calculate elements of transformation matrix + $tm = []; + $tm[0] = 1; + $tm[1] = tan(deg2rad($angle_y)); + $tm[2] = tan(deg2rad($angle_x)); + $tm[3] = 1; + $tm[4] = -$tm[2] * $y; + $tm[5] = -$tm[1] * $x; + + // skew the coordinate system + if ($returnstring) { + return $this->_transform($tm, true); + } else { + $this->_transform($tm); + } + } + + function _transform($tm, $returnstring = false) + { + if ($returnstring) { + return(sprintf('%.4F %.4F %.4F %.4F %.4F %.4F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5])); + } else { + $this->writer->write(sprintf('%.4F %.4F %.4F %.4F %.4F %.4F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5])); + } + } + + // AUTOFONT ========================= + function markScriptToLang($html) + { + if ($this->onlyCoreFonts) { + return $html; + } + + $n = ''; + $a = preg_split('/<(.*?)>/ms', $html, -1, PREG_SPLIT_DELIM_CAPTURE); + foreach ($a as $i => $e) { + if ($i % 2 == 0) { + + // ignore if in Textarea + if ($i > 0 && strtolower(substr($a[$i - 1], 1, 8)) == 'textarea') { + $a[$i] = $e; + continue; + } + + $e = UtfString::strcode2utf($e); + $e = $this->lesser_entity_decode($e); + + $earr = $this->UTF8StringToArray($e, false); + + $scriptblock = 0; + $scriptblocks = []; + $scriptblocks[0] = 0; + $chardata = []; + $subchunk = 0; + $charctr = 0; + + foreach ($earr as $char) { + + $ucd_record = Ucdn::get_ucd_record($char); + $sbl = $ucd_record[6]; + + if ($sbl && $sbl != 40 && $sbl != 102) { + if ($scriptblock == 0) { + $scriptblock = $sbl; + $scriptblocks[$subchunk] = $scriptblock; + } elseif ($scriptblock > 0 && $scriptblock != $sbl) { + // NEW (non-common) Script encountered in this chunk. + // Start a new subchunk + $subchunk++; + $scriptblock = $sbl; + $charctr = 0; + $scriptblocks[$subchunk] = $scriptblock; + } + } + + $chardata[$subchunk][$charctr]['script'] = $sbl; + $chardata[$subchunk][$charctr]['uni'] = $char; + $charctr++; + } + + // If scriptblock[x] = common & non-baseScript + // and scriptblock[x+1] = baseScript + // Move common script from end of x to start of x+1 + for ($sch = 0; $sch < $subchunk; $sch++) { + if ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->baseScript && $scriptblocks[$sch + 1] == $this->baseScript) { + $end = count($chardata[$sch]) - 1; + while ($chardata[$sch][$end]['script'] == 0 && $end > 1) { // common script + $tmp = array_pop($chardata[$sch]); + array_unshift($chardata[$sch + 1], $tmp); + $end--; + } + } + } + + $o = ''; + for ($sch = 0; $sch <= $subchunk; $sch++) { + + if (isset($chardata[$sch])) { + $s = ''; + for ($j = 0; $j < count($chardata[$sch]); $j++) { + $s .= UtfString::code2utf($chardata[$sch][$j]['uni']); + } + + // ZZZ99 Undo lesser_entity_decode as above - but only for <>& + $s = str_replace("&", "&", $s); + $s = str_replace("<", "<", $s); + $s = str_replace(">", ">", $s); + + // Check Vietnamese if Latin script - even if Basescript + if ($scriptblocks[$sch] == Ucdn::SCRIPT_LATIN && $this->autoVietnamese && preg_match("/([" . $this->scriptToLanguage->getLanguageDelimiters('viet') . "])/u", $s)) { + $o .= '<span lang="vi" class="lang_vi">' . $s . '</span>'; + } elseif ($scriptblocks[$sch] == Ucdn::SCRIPT_ARABIC && $this->autoArabic) { // Check Arabic for different languages if Arabic script - even if Basescript + if (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('sindhi') . "]/u", $s)) { + $o .= '<span lang="sd" class="lang_sd">' . $s . '</span>'; + } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('urdu') . "]/u", $s)) { + $o .= '<span lang="ur" class="lang_ur">' . $s . '</span>'; + } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('pashto') . "]/u", $s)) { + $o .= '<span lang="ps" class="lang_ps">' . $s . '</span>'; + } elseif (preg_match("/[" . $this->scriptToLanguage->getLanguageDelimiters('persian') . "]/u", $s)) { + $o .= '<span lang="fa" class="lang_fa">' . $s . '</span>'; + } elseif ($this->baseScript != Ucdn::SCRIPT_ARABIC && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) { + $o .= '<span lang="' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '" class="lang_' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '">' . $s . '</span>'; + } else { + // Just output chars + $o .= $s; + } + } elseif ($scriptblocks[$sch] > 0 && $scriptblocks[$sch] != $this->baseScript && $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch])) { // Identify Script block if not Basescript, and mark up as language + // Encase in <span> + $o .= '<span lang="' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '" class="lang_' . $this->scriptToLanguage->getLanguageByScript($scriptblocks[$sch]) . '">'; + $o .= $s; + $o .= '</span>'; + } else { + // Just output chars + $o .= $s; + } + } + } + + $a[$i] = $o; + } else { + $a[$i] = '<' . $e . '>'; + } + } + + $n = implode('', $a); + + return $n; + } + + /* -- COLUMNS -- */ + /** + * Callback function from function printcolumnbuffer in mpdf + */ + function columnAdjustAdd($type, $k, $xadj, $yadj, $a, $b, $c = 0, $d = 0, $e = 0, $f = 0) + { + if ($type === 'Td') { // xpos,ypos + + $a += ($xadj * $k); + $b -= ($yadj * $k); + + return 'BT ' . sprintf('%.3F %.3F', $a, $b) . ' Td'; + + } elseif ($type === 're') { // xpos,ypos,width,height + + $a += ($xadj * $k); + $b -= ($yadj * $k); + + return sprintf('%.3F %.3F %.3F %.3F', $a, $b, $c, $d) . ' re'; + + } elseif ($type === 'l') { // xpos,ypos,x2pos,y2pos + + $a += ($xadj * $k); + $b -= ($yadj * $k); + + return sprintf('%.3F %.3F l', $a, $b); + + } elseif ($type === 'img') { // width,height,xpos,ypos + + $c += ($xadj * $k); + $d -= ($yadj * $k); + + return sprintf('q %.3F 0 0 %.3F %.3F %.3F', $a, $b, $c, $d) . ' cm /' . $e; + + } elseif ($type === 'draw') { // xpos,ypos + + $a += ($xadj * $k); + $b -= ($yadj * $k); + + return sprintf('%.3F %.3F m', $a, $b); + + } elseif ($type === 'bezier') { // xpos,ypos,x2pos,y2pos,x3pos,y3pos + + $a += ($xadj * $k); + $b -= ($yadj * $k); + $c += ($xadj * $k); + $d -= ($yadj * $k); + $e += ($xadj * $k); + $f -= ($yadj * $k); + + return sprintf('%.3F %.3F %.3F %.3F %.3F %.3F', $a, $b, $c, $d, $e, $f) . ' c'; + } + } + + /* -- END COLUMNS -- */ + + // mPDF 5.7.3 TRANSFORMS + function ConvertAngle($s, $makepositive = true) + { + if (preg_match('/([\-]*[0-9\.]+)(deg|grad|rad)/i', $s, $m)) { + + $angle = $m[1] + 0; + + if (strtolower($m[2]) == 'deg') { + $angle = $angle; + } elseif (strtolower($m[2]) == 'grad') { + $angle *= (360 / 400); + } elseif (strtolower($m[2]) == 'rad') { + $angle = rad2deg($angle); + } + + while ($angle >= 360) { + $angle -= 360; + } + + while ($angle <= -360) { + $angle += 360; + } + + if ($makepositive) { // always returns an angle between 0 and 360deg + if ($angle < 0) { + $angle += 360; + } + } + + } else { + $angle = $s + 0; + } + + return $angle; + } + + function lesser_entity_decode($html) + { + // supports the most used entity codes (only does ascii safe characters) + $html = str_replace("<", "<", $html); + $html = str_replace(">", ">", $html); + + $html = str_replace("'", "'", $html); + $html = str_replace(""", '"', $html); + $html = str_replace("&", "&", $html); + + return $html; + } + + function AdjustHTML($html, $tabSpaces = 8) + { + $limit = ini_get('pcre.backtrack_limit'); + if (strlen($html) > $limit) { + throw new \Mpdf\MpdfException(sprintf( + 'The HTML code size is larger than pcre.backtrack_limit %d. You should use WriteHTML() with smaller string lengths.', + $limit + )); + } + + preg_match_all("/(<annotation.*?>)/si", $html, $m); + if (count($m[1])) { + for ($i = 0; $i < count($m[1]); $i++) { + $sub = preg_replace("/\n/si", "\xbb\xa4\xac", $m[1][$i]); + $html = preg_replace('/' . preg_quote($m[1][$i], '/') . '/si', $sub, $html); + } + } + + preg_match_all("/(<svg.*?<\/svg>)/si", $html, $svgi); + if (count($svgi[0])) { + for ($i = 0; $i < count($svgi[0]); $i++) { + $file = $this->cache->write('/_tempSVG' . uniqid(random_int(1, 100000), true) . '_' . $i . '.svg', $svgi[0][$i]); + $html = str_replace($svgi[0][$i], '<img src="' . $file . '" />', $html); + } + } + + // Remove javascript code from HTML (should not appear in the PDF file) + $html = preg_replace('/<script.*?<\/script>/is', '', $html); + + // Remove special comments + $html = preg_replace('/<!--mpdf/i', '', $html); + $html = preg_replace('/mpdf-->/i', '', $html); + + // Remove comments from HTML (should not appear in the PDF file) + $html = preg_replace('/<!--.*?-->/s', '', $html); + + $html = preg_replace('/\f/', '', $html); // replace formfeed by nothing + $html = preg_replace('/\r/', '', $html); // replace carriage return by nothing + + // Well formed XHTML end tags + $html = preg_replace('/<(br|hr)>/i', "<\\1 />", $html); // mPDF 6 + $html = preg_replace('/<(br|hr)\/>/i', "<\\1 />", $html); + + // Get rid of empty <thead></thead> etc + $html = preg_replace('/<tr>\s*<\/tr>/i', '', $html); + $html = preg_replace('/<thead>\s*<\/thead>/i', '', $html); + $html = preg_replace('/<tfoot>\s*<\/tfoot>/i', '', $html); + $html = preg_replace('/<table[^>]*>\s*<\/table>/i', '', $html); + + // Remove spaces at end of table cells + $html = preg_replace("/[ \n\r]+<\/t(d|h)/", '</t\\1', $html); + + $html = preg_replace("/[ ]*<dottab\s*[\/]*>[ ]*/", '<dottab />', $html); + + // Concatenates any Substitute characters from symbols/dingbats + $html = str_replace('</tts><tts>', '|', $html); + $html = str_replace('</ttz><ttz>', '|', $html); + $html = str_replace('</tta><tta>', '|', $html); + + $html = preg_replace('/<br \/>\s*/is', "<br />", $html); + + $html = preg_replace('/<wbr[ \/]*>\s*/is', "­", $html); + + // Preserve '\n's in content between the tags <pre> and </pre> + if (preg_match('/<pre/', $html)) { + + $html_a = preg_split('/(\<\/?pre[^\>]*\>)/', $html, -1, 2); + $h = []; + $c = 0; + + foreach ($html_a as $s) { + if ($c > 1 && preg_match('/^<\/pre/i', $s)) { + $c--; + $s = preg_replace('/<\/pre/i', '</innerpre', $s); + } elseif ($c > 0 && preg_match('/^<pre/i', $s)) { + $c++; + $s = preg_replace('/<pre/i', '<innerpre', $s); + } elseif (preg_match('/^<pre/i', $s)) { + $c++; + } elseif (preg_match('/^<\/pre/i', $s)) { + $c--; + } + array_push($h, $s); + } + + $html = implode('', $h); + } + + $thereispre = preg_match_all('#<pre(.*?)>(.*?)</pre>#si', $html, $temp); + + // Preserve '\n's in content between the tags <textarea> and </textarea> + $thereistextarea = preg_match_all('#<textarea(.*?)>(.*?)</textarea>#si', $html, $temp2); + $html = preg_replace('/[\n]/', ' ', $html); // replace linefeed by spaces + $html = preg_replace('/[\t]/', ' ', $html); // replace tabs by spaces + + // Converts < to < when not a tag + $html = preg_replace('/<([^!\/a-zA-Z_:])/i', '<\\1', $html); // mPDF 5.7.3 + $html = preg_replace("/[ ]+/", ' ', $html); + + $html = preg_replace('/\/li>\s+<\/(u|o)l/i', '/li></\\1l', $html); + $html = preg_replace('/\/(u|o)l>\s+<\/li/i', '/\\1l></li', $html); + $html = preg_replace('/\/li>\s+<\/(u|o)l/i', '/li></\\1l', $html); + $html = preg_replace('/\/li>\s+<li/i', '/li><li', $html); + $html = preg_replace('/<(u|o)l([^>]*)>[ ]+/i', '<\\1l\\2>', $html); + $html = preg_replace('/[ ]+<(u|o)l/i', '<\\1l', $html); + + // Make self closing tabs valid XHTML + // Tags which are self-closing: 1) Replaceable and 2) Non-replaced items + $selftabs = 'input|hr|img|br|barcode|dottab'; + $selftabs2 = 'indexentry|indexinsert|bookmark|watermarktext|watermarkimage|column_break|columnbreak|newcolumn|newpage|page_break|pagebreak|formfeed|columns|toc|tocpagebreak|setpageheader|setpagefooter|sethtmlpageheader|sethtmlpagefooter|annotation'; + + // Fix self-closing tags which don't close themselves + $html = preg_replace('/(<(' . $selftabs . '|' . $selftabs2 . ')[^>\/]*)>/i', '\\1 />', $html); + + // Fix self-closing tags that don't include a space between the tag name and the closing slash + $html = preg_replace('/(<(' . $selftabs . '|' . $selftabs2 . '))\/>/i', '\\1 />', $html); + + $iterator = 0; + while ($thereispre) { // Recover <pre attributes>content</pre> + $temp[2][$iterator] = preg_replace('/<([^!\/a-zA-Z_:])/', '<\\1', $temp[2][$iterator]); // mPDF 5.7.2 // mPDF 5.7.3 + + $temp[2][$iterator] = preg_replace_callback("/^([^\n\t]*?)\t/m", [$this, 'tabs2spaces_callback'], $temp[2][$iterator]); // mPDF 5.7+ + $temp[2][$iterator] = preg_replace('/\t/', str_repeat(" ", $tabSpaces), $temp[2][$iterator]); + + $temp[2][$iterator] = preg_replace('/\n/', "<br />", $temp[2][$iterator]); + $temp[2][$iterator] = str_replace('\\', "\\\\", $temp[2][$iterator]); + // $html = preg_replace('#<pre(.*?)>(.*?)</pre>#si','<erp'.$temp[1][$iterator].'>'.$temp[2][$iterator].'</erp>',$html,1); + $html = preg_replace('#<pre(.*?)>(.*?)</pre>#si', '<erp' . $temp[1][$iterator] . '>' . str_replace('$', '\$', $temp[2][$iterator]) . '</erp>', $html, 1); // mPDF 5.7+ + $thereispre--; + $iterator++; + } + + $iterator = 0; + while ($thereistextarea) { // Recover <textarea attributes>content</textarea> + $temp2[2][$iterator] = preg_replace('/\t/', str_repeat(" ", $tabSpaces), $temp2[2][$iterator]); + $temp2[2][$iterator] = str_replace('\\', "\\\\", $temp2[2][$iterator]); + $html = preg_replace('#<textarea(.*?)>(.*?)</textarea>#si', '<aeratxet' . $temp2[1][$iterator] . '>' . trim($temp2[2][$iterator]) . '</aeratxet>', $html, 1); + $thereistextarea--; + $iterator++; + } + + // Restore original tag names + $html = str_replace("<erp", "<pre", $html); + $html = str_replace("</erp>", "</pre>", $html); + $html = str_replace("<aeratxet", "<textarea", $html); + $html = str_replace("</aeratxet>", "</textarea>", $html); + $html = str_replace("</innerpre", "</pre", $html); + $html = str_replace("<innerpre", "<pre", $html); + + $html = preg_replace('/<textarea([^>]*)><\/textarea>/si', '<textarea\\1> </textarea>', $html); + $html = preg_replace('/(<table[^>]*>)\s*(<caption)(.*?<\/caption>)(.*?<\/table>)/si', '\\2 position="top"\\3\\1\\4\\2 position="bottom"\\3', $html); // *TABLES* + $html = preg_replace('/<(h[1-6])([^>]*)(>(?:(?!h[1-6]).)*?<\/\\1>\s*<table)/si', '<\\1\\2 keep-with-table="1"\\3', $html); // *TABLES* + $html = preg_replace("/\xbb\xa4\xac/", "\n", $html); + + // Fixes <p>₹</p> which browser copes with even though it is wrong! + $html = preg_replace("/(&#[x]{0,1}[0-9a-f]{1,5})</i", "\\1;<", $html); + + return $html; + } + + // mPDF 5.7+ + function tabs2spaces_callback($matches) + { + return (stripslashes($matches[1]) . str_repeat(' ', $this->tabSpaces - (mb_strlen(stripslashes($matches[1])) % $this->tabSpaces))); + } + + // mPDF 5.7+ + function date_callback($matches) + { + return date($matches[1]); + } + + // ========== OVERWRITE SEARCH STRING IN A PDF FILE ================ + function OverWrite($file_in, $search, $replacement, $dest = Destination::DOWNLOAD, $file_out = "mpdf") + { + $pdf = file_get_contents($file_in); + + if (!is_array($search)) { + $x = $search; + $search = [$x]; + } + if (!is_array($replacement)) { + $x = $replacement; + $replacement = [$x]; // mPDF 5.7.4 + } + + if (!$this->onlyCoreFonts && !$this->usingCoreFont) { + foreach ($search as $k => $val) { + $search[$k] = $this->writer->utf8ToUtf16BigEndian($search[$k], false); + $search[$k] = $this->writer->escape($search[$k]); + $replacement[$k] = $this->writer->utf8ToUtf16BigEndian($replacement[$k], false); + $replacement[$k] = $this->writer->escape($replacement[$k]); + } + } else { + foreach ($replacement as $k => $val) { + $replacement[$k] = mb_convert_encoding($replacement[$k], $this->mb_enc, 'utf-8'); + $replacement[$k] = $this->writer->escape($replacement[$k]); + } + } + + // Get xref into array + $xref = []; + preg_match("/xref\n0 (\d+)\n(.*?)\ntrailer/s", $pdf, $m); + $xref_objid = $m[1]; + preg_match_all('/(\d{10}) (\d{5}) (f|n)/', $m[2], $x); + for ($i = 0; $i < count($x[0]); $i++) { + $xref[] = [intval($x[1][$i]), $x[2][$i], $x[3][$i]]; + } + + $changes = []; + preg_match("/<<\s*\/Type\s*\/Pages\s*\/Kids\s*\[(.*?)\]\s*\/Count/s", $pdf, $m); + preg_match_all("/(\d+) 0 R /s", $m[1], $o); + $objlist = $o[1]; + + foreach ($objlist as $obj) { + if ($this->compress) { + preg_match("/" . ($obj + 1) . " 0 obj\n<<\s*\/Filter\s*\/FlateDecode\s*\/Length (\d+)>>\nstream\n(.*?)\nendstream\n/s", $pdf, $m); + } else { + preg_match("/" . ($obj + 1) . " 0 obj\n<<\s*\/Length (\d+)>>\nstream\n(.*?)\nendstream\n/s", $pdf, $m); + } + + $s = $m[2]; + if (!$s) { + continue; + } + + $oldlen = $m[1]; + + if ($this->encrypted) { + $s = $this->protection->rc4($this->protection->objectKey($obj + 1), $s); + } + + if ($this->compress) { + $s = gzuncompress($s); + } + + foreach ($search as $k => $val) { + $s = str_replace($search[$k], $replacement[$k], $s); + } + + if ($this->compress) { + $s = gzcompress($s); + } + + if ($this->encrypted) { + $s = $this->protection->rc4($this->protection->objectKey($obj + 1), $s); + } + + $newlen = strlen($s); + + $changes[($xref[$obj + 1][0])] = ($newlen - $oldlen) + (strlen($newlen) - strlen($oldlen)); + + if ($this->compress) { + $newstr = ($obj + 1) . " 0 obj\n<</Filter /FlateDecode /Length " . $newlen . ">>\nstream\n" . $s . "\nendstream\n"; + } else { + $newstr = ($obj + 1) . " 0 obj\n<</Length " . $newlen . ">>\nstream\n" . $s . "\nendstream\n"; + } + + $pdf = str_replace($m[0], $newstr, $pdf); + } + + // Update xref in PDF + krsort($changes); + $newxref = "xref\n0 " . $xref_objid . "\n"; + foreach ($xref as $v) { + foreach ($changes as $ck => $cv) { + if ($v[0] > $ck) { + $v[0] += $cv; + } + } + $newxref .= sprintf('%010d', $v[0]) . ' ' . $v[1] . ' ' . $v[2] . " \n"; + } + $newxref .= "trailer"; + $pdf = preg_replace("/xref\n0 \d+\n.*?\ntrailer/s", $newxref, $pdf); + + // Update startxref in PDF + preg_match("/startxref\n(\d+)\n%%EOF/s", $pdf, $m); + $startxref = $m[1]; + $startxref += array_sum($changes); + $pdf = preg_replace("/startxref\n(\d+)\n%%EOF/s", "startxref\n" . $startxref . "\n%%EOF", $pdf); + + // OUTPUT + switch ($dest) { + case Destination::INLINE: + if (isset($_SERVER['SERVER_NAME'])) { + // We send to a browser + header('Content-Type: application/pdf'); + header('Content-Length: ' . strlen($pdf)); + header('Content-disposition: inline; filename=' . $file_out); + } + + echo $pdf; + + break; + + case Destination::FILE: + if (!$file_out) { + $file_out = 'mpdf.pdf'; + } + + $f = fopen($file_out, 'wb'); + + if (!$f) { + throw new \Mpdf\MpdfException('Unable to create output file: ' . $file_out); + } + + fwrite($f, $pdf, strlen($pdf)); + + fclose($f); + + break; + + case Destination::STRING_RETURN: + return $pdf; + + case Destination::DOWNLOAD: // Download file + default: + if (isset($_SERVER['HTTP_USER_AGENT']) and strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) { + header('Content-Type: application/force-download'); + } else { + header('Content-Type: application/octet-stream'); + } + + header('Content-Length: ' . strlen($pdf)); + header('Content-disposition: attachment; filename=' . $file_out); + + echo $pdf; + + break; + } + } + + + function Thumbnail($file, $npr = 3, $spacing = 10) + { + // $npr = number per row + $w = (($this->pgwidth + $spacing) / $npr) - $spacing; + $oldlinewidth = $this->LineWidth; + $this->SetLineWidth(0.02); + $this->SetDColor($this->colorConverter->convert(0, $this->PDFAXwarnings)); + $h = 0; + $maxh = 0; + $x = $_x = $this->lMargin; + $_y = $this->tMargin; + + if ($this->y == 0) { + $y = $_y; + } else { + $y = $this->y; + } + + $pagecount = $this->setSourceFile($file); + + for ($n = 1; $n <= $pagecount; $n++) { + $tplidx = $this->importPage($n); + $size = $this->useTemplate($tplidx, $x, $y, $w); + $this->Rect($x, $y, $size['width'], $size['height']); + $h = max($h, $size['height']); + $maxh = max($h, $maxh); + + if ($n % $npr == 0) { + if (($y + $h + $spacing + $maxh) > $this->PageBreakTrigger && $n != $pagecount) { + $this->AddPage(); + $x = $_x; + $y = $_y; + } else { + $y += $h + $spacing; + $x = $_x; + $h = 0; + } + } else { + $x += $w + $spacing; + } + } + $this->SetLineWidth($oldlinewidth); + } + + function SetPageTemplate($tplidx = '') + { + if (!isset($this->importedPages[$tplidx])) { + $this->pageTemplate = ''; + return false; + } + $this->pageTemplate = $tplidx; + } + + function SetDocTemplate($file = '', $continue = 0) + { + $this->docTemplate = $file; + $this->docTemplateContinue = $continue; + } + + /* -- END IMPORTS -- */ + + // JAVASCRIPT + function _set_object_javascript($string) + { + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/S /JavaScript '); + $this->writer->write('/JS ' . $this->writer->string($string)); + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + + function SetJS($script) + { + $this->js = $script; + } + + /** + * This function takes the last comma or dot (if any) to make a clean float, ignoring thousand separator, currency or any other letter + * + * @param string $num + * @see http://php.net/manual/de/function.floatval.php#114486 + * @return float + */ + public function toFloat($num) + { + $dotPos = strrpos($num, '.'); + $commaPos = strrpos($num, ','); + $sep = (($dotPos > $commaPos) && $dotPos) ? $dotPos : ((($commaPos > $dotPos) && $commaPos) ? $commaPos : false); + + if (!$sep) { + return floatval(preg_replace('/[^0-9]/', '', $num)); + } + + return floatval( + preg_replace('/[^0-9]/', '', substr($num, 0, $sep)) . '.' . + preg_replace('/[^0-9]/', '', substr($num, $sep+1, strlen($num))) + ); + } + + public function getFontDescriptor() + { + return $this->fontDescriptor; + } + + /** + * Temporarily return the method to preserve example 44 yearbook + */ + public function _out($s) + { + $this->writer->write($s); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/MpdfException.php b/lib/MPDF/vendor/mpdf/mpdf/src/MpdfException.php new file mode 100644 index 0000000..f845354 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/MpdfException.php @@ -0,0 +1,8 @@ +<?php + +namespace Mpdf; + +class MpdfException extends \ErrorException +{ + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/MpdfImageException.php b/lib/MPDF/vendor/mpdf/mpdf/src/MpdfImageException.php new file mode 100644 index 0000000..c8f4ca0 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/MpdfImageException.php @@ -0,0 +1,8 @@ +<?php + +namespace Mpdf; + +class MpdfImageException extends \Mpdf\MpdfException +{ + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Otl.php b/lib/MPDF/vendor/mpdf/mpdf/src/Otl.php new file mode 100644 index 0000000..4603996 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Otl.php @@ -0,0 +1,6211 @@ +<?php + +namespace Mpdf; + +use Mpdf\Strict; + +use Mpdf\Css\TextVars; +use Mpdf\Fonts\FontCache; + +use Mpdf\Shaper\Indic; +use Mpdf\Shaper\Myanmar; +use Mpdf\Shaper\Sea; + +use Mpdf\Utils\UtfString; + +class Otl +{ + + use Strict; + + const _OTL_OLD_SPEC_COMPAT_1 = true; + const _DICT_NODE_TYPE_SPLIT = 0x01; + const _DICT_NODE_TYPE_LINEAR = 0x02; + const _DICT_INTERMEDIATE_MATCH = 0x03; + const _DICT_FINAL_MATCH = 0x04; + + private $mpdf; + + private $fontCache; + + var $arabLeftJoining; + + var $arabRightJoining; + + var $arabTransparentJoin; + + var $arabTransparent; + + var $GSUBdata; + + var $GPOSdata; + + var $GSUBfont; + + var $fontkey; + + var $ttfOTLdata; + + var $glyphIDtoUni; + + var $_pos; + + var $GSUB_offset; + + var $GPOS_offset; + + var $MarkAttachmentType; + + var $MarkGlyphSets; + + var $GlyphClassMarks; + + var $GlyphClassLigatures; + + var $GlyphClassBases; + + var $GlyphClassComponents; + + var $Ignores; + + var $LuCoverage; + + var $OTLdata; + + var $assocLigs; + + var $assocMarks; + + var $shaper; + + var $restrictToSyllable; + + var $lbdicts; // Line-breaking dictionaries + + var $LuDataCache; + + var $arabGlyphs; + + var $current_fh; + + var $Entry; + + var $Exit; + + var $GDEFdata; + + var $GPOSLookups; + + var $GSLuCoverage; + + var $GSUB_length; + + var $GSUBLookups; + + var $schOTLdata; + + var $lastBidiStrongType; + + var $debugOTL = false; + + public function __construct(Mpdf $mpdf, FontCache $fontCache) + { + $this->mpdf = $mpdf; + $this->fontCache = $fontCache; + + $this->current_fh = ''; + + $this->lbdicts = []; + $this->LuDataCache = []; + } + + function applyOTL($str, $useOTL) + { + if (!$this->arabLeftJoining) { + $this->arabic_initialise(); + } + + $this->OTLdata = []; + if (trim($str) == '') { + return $str; + } + if (!$useOTL) { + return $str; + } + + // 1. Load GDEF data + //============================== + $this->fontkey = $this->mpdf->CurrentFont['fontkey']; + $this->glyphIDtoUni = $this->mpdf->CurrentFont['glyphIDtoUni']; + $fontCacheFilename = $this->fontkey . '.GDEFdata.json'; + if (!isset($this->GDEFdata[$this->fontkey]) && $this->fontCache->jsonHas($fontCacheFilename)) { + $font = $this->fontCache->jsonLoad($fontCacheFilename); + + $this->GSUB_offset = $this->GDEFdata[$this->fontkey]['GSUB_offset'] = $font['GSUB_offset']; + $this->GPOS_offset = $this->GDEFdata[$this->fontkey]['GPOS_offset'] = $font['GPOS_offset']; + $this->GSUB_length = $this->GDEFdata[$this->fontkey]['GSUB_length'] = $font['GSUB_length']; + $this->MarkAttachmentType = $this->GDEFdata[$this->fontkey]['MarkAttachmentType'] = $font['MarkAttachmentType']; + $this->MarkGlyphSets = $this->GDEFdata[$this->fontkey]['MarkGlyphSets'] = $font['MarkGlyphSets']; + $this->GlyphClassMarks = $this->GDEFdata[$this->fontkey]['GlyphClassMarks'] = $font['GlyphClassMarks']; + $this->GlyphClassLigatures = $this->GDEFdata[$this->fontkey]['GlyphClassLigatures'] = $font['GlyphClassLigatures']; + $this->GlyphClassComponents = $this->GDEFdata[$this->fontkey]['GlyphClassComponents'] = $font['GlyphClassComponents']; + $this->GlyphClassBases = $this->GDEFdata[$this->fontkey]['GlyphClassBases'] = $font['GlyphClassBases']; + } else { + $this->GSUB_offset = $this->GDEFdata[$this->fontkey]['GSUB_offset']; + $this->GPOS_offset = $this->GDEFdata[$this->fontkey]['GPOS_offset']; + $this->GSUB_length = $this->GDEFdata[$this->fontkey]['GSUB_length']; + $this->MarkAttachmentType = $this->GDEFdata[$this->fontkey]['MarkAttachmentType']; + $this->MarkGlyphSets = $this->GDEFdata[$this->fontkey]['MarkGlyphSets']; + $this->GlyphClassMarks = $this->GDEFdata[$this->fontkey]['GlyphClassMarks']; + $this->GlyphClassLigatures = $this->GDEFdata[$this->fontkey]['GlyphClassLigatures']; + $this->GlyphClassComponents = $this->GDEFdata[$this->fontkey]['GlyphClassComponents']; + $this->GlyphClassBases = $this->GDEFdata[$this->fontkey]['GlyphClassBases']; + } + + // 2. Prepare string as HEX string and Analyse character properties + //================================================================= + $earr = $this->mpdf->UTF8StringToArray($str, false); + + $scriptblock = 0; + $scriptblocks = []; + $scriptblocks[0] = 0; + $vstr = ''; + $OTLdata = []; + $subchunk = 0; + $charctr = 0; + foreach ($earr as $char) { + $ucd_record = Ucdn::get_ucd_record($char); + $sbl = $ucd_record[6]; + + // Special case - Arabic End of Ayah + if ($char == 1757) { + $sbl = Ucdn::SCRIPT_ARABIC; + } + + if ($sbl && $sbl != 40 && $sbl != 102) { + if ($scriptblock == 0) { + $scriptblock = $sbl; + $scriptblocks[$subchunk] = $scriptblock; + } elseif ($scriptblock > 0 && $scriptblock != $sbl) { + // ************************************************* + // NEW (non-common) Script encountered in this chunk. Start a new subchunk + $subchunk++; + $scriptblock = $sbl; + $charctr = 0; + $scriptblocks[$subchunk] = $scriptblock; + } + } + + $OTLdata[$subchunk][$charctr]['general_category'] = $ucd_record[0]; + $OTLdata[$subchunk][$charctr]['bidi_type'] = $ucd_record[2]; + + //$OTLdata[$subchunk][$charctr]['combining_class'] = $ucd_record[1]; + //$OTLdata[$subchunk][$charctr]['bidi_type'] = $ucd_record[2]; + //$OTLdata[$subchunk][$charctr]['mirrored'] = $ucd_record[3]; + //$OTLdata[$subchunk][$charctr]['east_asian_width'] = $ucd_record[4]; + //$OTLdata[$subchunk][$charctr]['normalization_check'] = $ucd_record[5]; + //$OTLdata[$subchunk][$charctr]['script'] = $ucd_record[6]; + + $charasstr = $this->unicode_hex($char); + + if (strpos($this->GlyphClassMarks, $charasstr) !== false) { + $OTLdata[$subchunk][$charctr]['group'] = 'M'; + } elseif ($char == 32 || $char == 12288) { + $OTLdata[$subchunk][$charctr]['group'] = 'S'; + } // 12288 = 0x3000 = CJK space + else { + $OTLdata[$subchunk][$charctr]['group'] = 'C'; + } + + $OTLdata[$subchunk][$charctr]['uni'] = $char; + $OTLdata[$subchunk][$charctr]['hex'] = $charasstr; + $charctr++; + } + + /* PROCESS EACH SUBCHUNK WITH DIFFERENT SCRIPTS */ + for ($sch = 0; $sch <= $subchunk; $sch++) { + $this->OTLdata = $OTLdata[$sch]; + $scriptblock = $scriptblocks[$sch]; + + // 3. Get Appropriate Scripts, and Shaper engine from analysing text and list of available scripts/langsys in font + //============================== + // Based on actual script block of text, select shaper (and line-breaking dictionaries) + if (Ucdn::SCRIPT_DEVANAGARI <= $scriptblock && $scriptblock <= Ucdn::SCRIPT_MALAYALAM) { + $this->shaper = "I"; + } // INDIC shaper + elseif ($scriptblock == Ucdn::SCRIPT_ARABIC || $scriptblock == Ucdn::SCRIPT_SYRIAC) { + $this->shaper = "A"; + } // ARABIC shaper + elseif ($scriptblock == Ucdn::SCRIPT_NKO || $scriptblock == Ucdn::SCRIPT_MANDAIC) { + $this->shaper = "A"; + } // ARABIC shaper + elseif ($scriptblock == Ucdn::SCRIPT_KHMER) { + $this->shaper = "K"; + } // KHMER shaper + elseif ($scriptblock == Ucdn::SCRIPT_THAI) { + $this->shaper = "T"; + } // THAI shaper + elseif ($scriptblock == Ucdn::SCRIPT_LAO) { + $this->shaper = "L"; + } // LAO shaper + elseif ($scriptblock == Ucdn::SCRIPT_SINHALA) { + $this->shaper = "S"; + } // SINHALA shaper + elseif ($scriptblock == Ucdn::SCRIPT_MYANMAR) { + $this->shaper = "M"; + } // MYANMAR shaper + elseif ($scriptblock == Ucdn::SCRIPT_NEW_TAI_LUE) { + $this->shaper = "E"; + } // SEA South East Asian shaper + elseif ($scriptblock == Ucdn::SCRIPT_CHAM) { + $this->shaper = "E"; + } // SEA South East Asian shaper + elseif ($scriptblock == Ucdn::SCRIPT_TAI_THAM) { + $this->shaper = "E"; + } // SEA South East Asian shaper + else { + $this->shaper = ""; + } + // Get scripttag based on actual text script + $scripttag = Ucdn::$uni_scriptblock[$scriptblock]; + + $GSUBscriptTag = ''; + $GSUBlangsys = ''; + $GPOSscriptTag = ''; + $GPOSlangsys = ''; + $is_old_spec = false; + + $ScriptLang = $this->mpdf->CurrentFont['GSUBScriptLang']; + if (count($ScriptLang)) { + list($GSUBscriptTag, $is_old_spec) = $this->_getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL, 'GSUB'); + if ($this->mpdf->fontLanguageOverride && strpos($ScriptLang[$GSUBscriptTag], $this->mpdf->fontLanguageOverride) !== false) { + $GSUBlangsys = str_pad($this->mpdf->fontLanguageOverride, 4); + } elseif ($GSUBscriptTag && isset($ScriptLang[$GSUBscriptTag]) && $ScriptLang[$GSUBscriptTag] != '') { + $GSUBlangsys = $this->_getOTLLangTag($this->mpdf->currentLang, $ScriptLang[$GSUBscriptTag]); + } + } + $ScriptLang = $this->mpdf->CurrentFont['GPOSScriptLang']; + + // NB If after GSUB, the same script/lang exist for GPOS, just use these... + if ($GSUBscriptTag && $GSUBlangsys && isset($ScriptLang[$GSUBscriptTag]) && strpos($ScriptLang[$GSUBscriptTag], $GSUBlangsys) !== false) { + $GPOSlangsys = $GSUBlangsys; + $GPOSscriptTag = $GSUBscriptTag; + } // else repeat for GPOS + // [Font XBRiyaz has GSUB tables for latn, but not GPOS for latn] + elseif (count($ScriptLang)) { + list($GPOSscriptTag, $dummy) = $this->_getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL, 'GPOS'); + if ($GPOSscriptTag && $this->mpdf->fontLanguageOverride && strpos($ScriptLang[$GPOSscriptTag], $this->mpdf->fontLanguageOverride) !== false) { + $GPOSlangsys = str_pad($this->mpdf->fontLanguageOverride, 4); + } elseif ($GPOSscriptTag && isset($ScriptLang[$GPOSscriptTag]) && $ScriptLang[$GPOSscriptTag] != '') { + $GPOSlangsys = $this->_getOTLLangTag($this->mpdf->currentLang, $ScriptLang[$GPOSscriptTag]); + } + } + + // This is just for the font_dump_OTL utility to set script and langsys override + // $mpdf->overrideOTLsettings does not exist, this is never called + /*if (isset($this->mpdf->overrideOTLsettings) && isset($this->mpdf->overrideOTLsettings[$this->fontkey])) { + $GSUBscriptTag = $GPOSscriptTag = $this->mpdf->overrideOTLsettings[$this->fontkey]['script']; + $GSUBlangsys = $GPOSlangsys = $this->mpdf->overrideOTLsettings[$this->fontkey]['lang']; + }*/ + + if (!$GSUBscriptTag && !$GSUBlangsys && !$GPOSscriptTag && !$GPOSlangsys) { + // Remove ZWJ and ZWNJ + for ($i = 0; $i < count($this->OTLdata); $i++) { + if ($this->OTLdata[$i]['uni'] == 8204 || $this->OTLdata[$i]['uni'] == 8205) { + array_splice($this->OTLdata, $i, 1); + } + } + $this->schOTLdata[$sch] = $this->OTLdata; + $this->OTLdata = []; + continue; + } + + // Don't use MYANMAR shaper unless using v2 scripttag + if ($this->shaper == 'M' && $GSUBscriptTag != 'mym2') { + $this->shaper = ''; + } + + $GSUBFeatures = (isset($this->mpdf->CurrentFont['GSUBFeatures'][$GSUBscriptTag][$GSUBlangsys]) ? $this->mpdf->CurrentFont['GSUBFeatures'][$GSUBscriptTag][$GSUBlangsys] : false); + $GPOSFeatures = (isset($this->mpdf->CurrentFont['GPOSFeatures'][$GPOSscriptTag][$GPOSlangsys]) ? $this->mpdf->CurrentFont['GPOSFeatures'][$GPOSscriptTag][$GPOSlangsys] : false); + + $this->assocLigs = []; // Ligatures[$posarr lpos] => nc + $this->assocMarks = []; // assocMarks[$posarr mpos] => array(compID, ligPos) + + if (!isset($this->GDEFdata[$this->fontkey]['GSUBGPOStables'])) { + $this->ttfOTLdata = $this->GDEFdata[$this->fontkey]['GSUBGPOStables'] = $this->fontCache->load($this->fontkey . '.GSUBGPOStables.dat', 'rb'); + if (!$this->ttfOTLdata) { + throw new \Mpdf\MpdfException('Can\'t open file ' . $this->fontCache->tempFilename($this->fontkey . '.GSUBGPOStables.dat')); + } + } else { + $this->ttfOTLdata = $this->GDEFdata[$this->fontkey]['GSUBGPOStables']; + } + + if ($this->debugOTL) { + $this->_dumpproc('BEGIN', '-', '-', '-', '-', -1, '-', 0); + } + + //////////////////////////////////////////////////////////////// + ///////// LINE BREAKING FOR KHMER, THAI + LAO ///////////////// + //////////////////////////////////////////////////////////////// + // Insert U+200B at word boundaries using dictionaries + if ($this->mpdf->useDictionaryLBR && ($this->shaper == "K" || $this->shaper == "T" || $this->shaper == "L")) { + // Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries + $this->seaLineBreaking(); + } // Insert U+200B at word boundaries for Tibetan + elseif ($this->mpdf->useTibetanLBR && $scriptblock == Ucdn::SCRIPT_TIBETAN) { + // Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries + $this->tibetanLineBreaking(); + } + + + //////////////////////////////////////////////////////////////// + ////////// GSUB ///////////////////////////////// + //////////////////////////////////////////////////////////////// + if (($useOTL & 0xFF) && $GSUBscriptTag && $GSUBlangsys && $GSUBFeatures) { + // 4. Load GSUB data, Coverage & Lookups + //================================================================= + + $this->GSUBfont = $this->fontkey . '.GSUB.' . $GSUBscriptTag . '.' . $GSUBlangsys; + + if (!isset($this->GSUBdata[$this->GSUBfont])) { + $fontCacheFilename = $this->GSUBfont . '.json'; + if ($this->fontCache->jsonHas($fontCacheFilename)) { + $font = $this->fontCache->jsonLoad($fontCacheFilename); + + $this->GSUBdata[$this->GSUBfont]['rtlSUB'] = $font['rtlSUB']; + $this->GSUBdata[$this->GSUBfont]['finals'] = $font['finals']; + if ($this->shaper == 'I') { + $this->GSUBdata[$this->GSUBfont]['rphf'] = $font['rphf']; + $this->GSUBdata[$this->GSUBfont]['half'] = $font['half']; + $this->GSUBdata[$this->GSUBfont]['pref'] = $font['pref']; + $this->GSUBdata[$this->GSUBfont]['blwf'] = $font['blwf']; + $this->GSUBdata[$this->GSUBfont]['pstf'] = $font['pstf']; + } + } else { + $this->GSUBdata[$this->GSUBfont] = ['rtlSUB' => [], 'rphf' => [], 'rphf' => [], + 'pref' => [], 'blwf' => [], 'pstf' => [], 'finals' => '' + ]; + } + } + + $fontCacheFilename = $this->fontkey . '.GSUBdata.json'; + if (!isset($this->GSUBdata[$this->fontkey]) && $this->fontCache->jsonHas($fontCacheFilename)) { + $this->GSLuCoverage = $this->GSUBdata[$this->fontkey]['GSLuCoverage'] = $this->fontCache->jsonLoad($fontCacheFilename); + } else { + $this->GSLuCoverage = $this->GSUBdata[$this->fontkey]['GSLuCoverage']; + } + + $this->GSUBLookups = $this->mpdf->CurrentFont['GSUBLookups']; + + + // 5(A). GSUB - Shaper - ARABIC + //============================== + if ($this->shaper == 'A') { + //----------------------------------------------------------------------------------- + // a. Apply initial GSUB Lookups (in order specified in lookup list but only selecting from certain tags) + //----------------------------------------------------------------------------------- + $tags = 'locl ccmp'; + $omittags = ''; + $usetags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, true); + } + $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys); + + //----------------------------------------------------------------------------------- + // b. Apply context-specific forms GSUB Lookups (initial, isolated, medial, final) + //----------------------------------------------------------------------------------- + // Arab and Syriac are the only scripts requiring the special joining - which takes the place of + // isol fina medi init rules in GSUB (+ fin2 fin3 med2 in Syriac syrc) + $tags = 'isol fina fin2 fin3 medi med2 init'; + $omittags = ''; + $usetags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, true); + } + + $this->arabGlyphs = $this->GSUBdata[$this->GSUBfont]['rtlSUB']; + + $gcms = explode("| ", $this->GlyphClassMarks); + $gcm = []; + foreach ($gcms as $g) { + $gcm[hexdec($g)] = 1; + } + $this->arabTransparentJoin = $this->arabTransparent + $gcm; + $this->arabic_shaper($usetags, $GSUBscriptTag); + + //----------------------------------------------------------------------------------- + // c. Set Kashida points (after joining occurred - medi, fina, init) but before other substitutions + //----------------------------------------------------------------------------------- + //if ($scriptblock == Ucdn::SCRIPT_ARABIC ) { + for ($i = 0; $i < count($this->OTLdata); $i++) { + // Put the kashida marker on the character BEFORE which is inserted the kashida + // Kashida marker is inverse of priority i.e. Priority 1 => 7, Priority 7 => 1. + // Priority 1 User-inserted Kashida 0640 = Tatweel + // The user entered a Kashida in a position + // Position: Before the user-inserted kashida + if ($this->OTLdata[$i]['uni'] == 0x0640) { + $this->OTLdata[$i]['GPOSinfo']['kashida'] = 8; // Put before the next character + } // Priority 2 Seen (0633) FEB3, FEB4; Sad (0635) FEBB, FEBC + // Initial or medial form + // Connecting to the next character + // Position: After the character + elseif ($this->OTLdata[$i]['uni'] == 0xFEB3 || $this->OTLdata[$i]['uni'] == 0xFEB4 || $this->OTLdata[$i]['uni'] == 0xFEBB || $this->OTLdata[$i]['uni'] == 0xFEBC) { + $checkpos = $i + 1; + while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + if (isset($this->OTLdata[$checkpos])) { + $this->OTLdata[$checkpos]['GPOSinfo']['kashida'] = 7; // Put after marks on next character + } + } // Priority 3 Taa Marbutah (0629) FE94; Haa (062D) FEA2; Dal (062F) FEAA + // Final form + // Connecting to previous character + // Position: Before the character + elseif ($this->OTLdata[$i]['uni'] == 0xFE94 || $this->OTLdata[$i]['uni'] == 0xFEA2 || $this->OTLdata[$i]['uni'] == 0xFEAA) { + $this->OTLdata[$i]['GPOSinfo']['kashida'] = 6; + } // Priority 4 Alef (0627) FE8E; Tah (0637) FEC2; Lam (0644) FEDE; Kaf (0643) FEDA; Gaf (06AF) FB93 + // Final form + // Connecting to previous character + // Position: Before the character + elseif ($this->OTLdata[$i]['uni'] == 0xFE8E || $this->OTLdata[$i]['uni'] == 0xFEC2 || $this->OTLdata[$i]['uni'] == 0xFEDE || $this->OTLdata[$i]['uni'] == 0xFEDA || $this->OTLdata[$i]['uni'] == 0xFB93) { + $this->OTLdata[$i]['GPOSinfo']['kashida'] = 5; + } // Priority 5 RA (0631) FEAE; Ya (064A) FEF2 FEF4; Alef Maqsurah (0649) FEF0 FBE9 + // Final or Medial form + // Connected to preceding medial BAA (0628) = FE92 + // Position: Before preceding medial Baa + // Although not mentioned in spec, added Farsi Yeh (06CC) FBFD FBFF; equivalent to 064A or 0649 + elseif ($this->OTLdata[$i]['uni'] == 0xFEAE || $this->OTLdata[$i]['uni'] == 0xFEF2 || $this->OTLdata[$i]['uni'] == 0xFEF0 || $this->OTLdata[$i]['uni'] == 0xFEF4 || $this->OTLdata[$i]['uni'] == 0xFBE9 || $this->OTLdata[$i]['uni'] == 0xFBFD || $this->OTLdata[$i]['uni'] == 0xFBFF + ) { + $checkpos = $i - 1; + while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos--; + } + if (isset($this->OTLdata[$checkpos]) && $this->OTLdata[$checkpos]['uni'] == 0xFE92) { + $this->OTLdata[$checkpos]['GPOSinfo']['kashida'] = 4; // ******* Before preceding BAA + } + } // Priority 6 WAW (0648) FEEE; Ain (0639) FECA; Qaf (0642) FED6; Fa (0641) FED2 + // Final form + // Connecting to previous character + // Position: Before the character + elseif ($this->OTLdata[$i]['uni'] == 0xFEEE || $this->OTLdata[$i]['uni'] == 0xFECA || $this->OTLdata[$i]['uni'] == 0xFED6 || $this->OTLdata[$i]['uni'] == 0xFED2) { + $this->OTLdata[$i]['GPOSinfo']['kashida'] = 3; + } + + // Priority 7 Other connecting characters + // Final form + // Connecting to previous character + // Position: Before the character + /* This isn't in the spec, but using MS WORD as a basis, give a lower priority to the 3 characters already checked + in (5) above. Test case: + خْرَىٰ + فَتُذَكِّر + */ + + if (!isset($this->OTLdata[$i]['GPOSinfo']['kashida'])) { + if (strpos($this->GSUBdata[$this->GSUBfont]['finals'], $this->OTLdata[$i]['hex']) !== false) { // ANY OTHER FINAL FORM + $this->OTLdata[$i]['GPOSinfo']['kashida'] = 2; + } elseif (strpos('0FEAE 0FEF0 0FEF2', $this->OTLdata[$i]['hex']) !== false) { // not already included in 5 above + $this->OTLdata[$i]['GPOSinfo']['kashida'] = 1; + } + } + } + + //----------------------------------------------------------------------------------- + // d. Apply Presentation Forms GSUB Lookups (+ any discretionary) - Apply one at a time in Feature order + //----------------------------------------------------------------------------------- + $tags = 'rlig calt liga clig mset'; + + $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo'; + $usetags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false); + } + + $ts = explode(' ', $usetags); + foreach ($ts as $ut) { // - Apply one at a time in Feature order + $this->_applyGSUBrules($ut, $GSUBscriptTag, $GSUBlangsys); + } + //----------------------------------------------------------------------------------- + // e. NOT IN SPEC + // If space precedes a mark -> substitute a   before the Mark, to prevent line breaking Test: + //----------------------------------------------------------------------------------- + for ($ptr = 1; $ptr < count($this->OTLdata); $ptr++) { + if ($this->OTLdata[$ptr]['general_category'] == Ucdn::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK && $this->OTLdata[$ptr - 1]['uni'] == 32) { + $this->OTLdata[$ptr - 1]['uni'] = 0xa0; + $this->OTLdata[$ptr - 1]['hex'] = '000A0'; + } + } + } // 5(I). GSUB - Shaper - INDIC and SINHALA and KHMER + //=================================== + elseif ($this->shaper == 'I' || $this->shaper == 'K' || $this->shaper == 'S') { + $this->restrictToSyllable = true; + //----------------------------------------------------------------------------------- + // a. First decompose/compose split mattras + // (normalize) ??????? Nukta/Halant order etc ?????????????????????????????????????????????????????????????????????????? + //----------------------------------------------------------------------------------- + for ($ptr = 0; $ptr < count($this->OTLdata); $ptr++) { + $char = $this->OTLdata[$ptr]['uni']; + $sub = Indic::decompose_indic($char); + if ($sub) { + $newinfo = []; + for ($i = 0; $i < count($sub); $i++) { + $newinfo[$i] = []; + $ucd_record = Ucdn::get_ucd_record($sub[$i]); + $newinfo[$i]['general_category'] = $ucd_record[0]; + $newinfo[$i]['bidi_type'] = $ucd_record[2]; + $charasstr = $this->unicode_hex($sub[$i]); + if (strpos($this->GlyphClassMarks, $charasstr) !== false) { + $newinfo[$i]['group'] = 'M'; + } else { + $newinfo[$i]['group'] = 'C'; + } + $newinfo[$i]['uni'] = $sub[$i]; + $newinfo[$i]['hex'] = $charasstr; + } + array_splice($this->OTLdata, $ptr, 1, $newinfo); + $ptr += count($sub) - 1; + } + /* Only Composition-exclusion exceptions that we want to recompose. */ + if ($this->shaper == 'I') { + if ($char == 0x09AF && isset($this->OTLdata[$ptr + 1]) && $this->OTLdata[$ptr + 1]['uni'] == 0x09BC) { + $sub = 0x09DF; + $newinfo = []; + $newinfo[0] = []; + $ucd_record = Ucdn::get_ucd_record($sub); + $newinfo[0]['general_category'] = $ucd_record[0]; + $newinfo[0]['bidi_type'] = $ucd_record[2]; + $newinfo[0]['group'] = 'C'; + $newinfo[0]['uni'] = $sub; + $newinfo[0]['hex'] = $this->unicode_hex($sub); + array_splice($this->OTLdata, $ptr, 2, $newinfo); + } + } + } + //----------------------------------------------------------------------------------- + // b. Analyse characters - group as syllables/clusters (Indic); invalid diacritics; add dotted circle + //----------------------------------------------------------------------------------- + $indic_category_string = ''; + foreach ($this->OTLdata as $eid => $c) { + Indic::set_indic_properties($this->OTLdata[$eid], $scriptblock); // sets ['indic_category'] and ['indic_position'] + //$c['general_category'] + //$c['combining_class'] + //$c['uni'] = $char; + + $indic_category_string .= Indic::$indic_category_char[$this->OTLdata[$eid]['indic_category']]; + } + + $broken_syllables = false; + if ($this->shaper == 'I') { + Indic::set_syllables($this->OTLdata, $indic_category_string, $broken_syllables); + } elseif ($this->shaper == 'S') { + Indic::set_syllables_sinhala($this->OTLdata, $indic_category_string, $broken_syllables); + } elseif ($this->shaper == 'K') { + Indic::set_syllables_khmer($this->OTLdata, $indic_category_string, $broken_syllables); + } + $indic_category_string = ''; + + //----------------------------------------------------------------------------------- + // c. Initial Re-ordering (Indic / Khmer / Sinhala) + //----------------------------------------------------------------------------------- + // Find base consonant + // Decompose/compose and reorder Matras + // Reorder marks to canonical order + + $indic_config = Indic::$indic_configs[$scriptblock]; + $dottedcircle = false; + if ($broken_syllables) { + if ($this->mpdf->_charDefined($this->mpdf->fonts[$this->fontkey]['cw'], 0x25CC)) { + $dottedcircle = []; + $ucd_record = Ucdn::get_ucd_record(0x25CC); + $dottedcircle[0]['general_category'] = $ucd_record[0]; + $dottedcircle[0]['bidi_type'] = $ucd_record[2]; + $dottedcircle[0]['group'] = 'C'; + $dottedcircle[0]['uni'] = 0x25CC; + $dottedcircle[0]['indic_category'] = Indic::OT_DOTTEDCIRCLE; + $dottedcircle[0]['indic_position'] = Indic::POS_BASE_C; + + $dottedcircle[0]['hex'] = '025CC'; // TEMPORARY ***** + } + } + Indic::initial_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $broken_syllables, $indic_config, $scriptblock, $is_old_spec, $dottedcircle); + + //----------------------------------------------------------------------------------- + // d. Apply initial and basic shaping forms GSUB Lookups (one at a time) + //----------------------------------------------------------------------------------- + if ($this->shaper == 'I' || $this->shaper == 'S') { + $tags = 'locl ccmp nukt akhn rphf rkrf pref blwf half pstf vatu cjct'; + } elseif ($this->shaper == 'K') { + $tags = 'locl ccmp pref blwf abvf pstf cfar'; + } + $this->_applyGSUBrulesIndic($tags, $GSUBscriptTag, $GSUBlangsys, $is_old_spec); + + //----------------------------------------------------------------------------------- + // e. Final Re-ordering (Indic / Khmer / Sinhala) + //----------------------------------------------------------------------------------- + // Reorder matras + // Reorder reph + // Reorder pre-base reordering consonants: + + Indic::final_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $indic_config, $scriptblock, $is_old_spec); + + //----------------------------------------------------------------------------------- + // f. Apply 'init' feature to first syllable in word (indicated by ['mask']) Indic::FLAG(Indic::INIT); + //----------------------------------------------------------------------------------- + if ($this->shaper == 'I' || $this->shaper == 'S') { + $tags = 'init'; + $this->_applyGSUBrulesIndic($tags, $GSUBscriptTag, $GSUBlangsys, $is_old_spec); + } + + //----------------------------------------------------------------------------------- + // g. Apply Presentation Forms GSUB Lookups (+ any discretionary) + //----------------------------------------------------------------------------------- + $tags = 'pres abvs blws psts haln rlig calt liga clig mset'; + + $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo'; + $usetags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false); + } + if ($this->shaper == 'K') { // Features are applied one at a time, working through each codepoint + $this->_applyGSUBrulesSingly($usetags, $GSUBscriptTag, $GSUBlangsys); + } else { + $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys); + } + $this->restrictToSyllable = false; + } // 5(M). GSUB - Shaper - MYANMAR (ONLY mym2) + //============================== + // NB Old style 'mymr' is left to go through the default shaper + elseif ($this->shaper == 'M') { + $this->restrictToSyllable = true; + //----------------------------------------------------------------------------------- + // a. Analyse characters - group as syllables/clusters (Myanmar); invalid diacritics; add dotted circle + //----------------------------------------------------------------------------------- + $myanmar_category_string = ''; + foreach ($this->OTLdata as $eid => $c) { + Myanmar::set_myanmar_properties($this->OTLdata[$eid]); // sets ['myanmar_category'] and ['myanmar_position'] + $myanmar_category_string .= Myanmar::$myanmar_category_char[$this->OTLdata[$eid]['myanmar_category']]; + } + $broken_syllables = false; + Myanmar::set_syllables($this->OTLdata, $myanmar_category_string, $broken_syllables); + $myanmar_category_string = ''; + + //----------------------------------------------------------------------------------- + // b. Re-ordering (Myanmar mym2) + //----------------------------------------------------------------------------------- + $dottedcircle = false; + if ($broken_syllables) { + if ($this->mpdf->_charDefined($this->mpdf->fonts[$this->fontkey]['cw'], 0x25CC)) { + $dottedcircle = []; + $ucd_record = Ucdn::get_ucd_record(0x25CC); + $dottedcircle[0]['general_category'] = $ucd_record[0]; + $dottedcircle[0]['bidi_type'] = $ucd_record[2]; + $dottedcircle[0]['group'] = 'C'; + $dottedcircle[0]['uni'] = 0x25CC; + $dottedcircle[0]['myanmar_category'] = Myanmar::OT_DOTTEDCIRCLE; + $dottedcircle[0]['myanmar_position'] = Myanmar::POS_BASE_C; + $dottedcircle[0]['hex'] = '025CC'; + } + } + Myanmar::reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $broken_syllables, $dottedcircle); + + //----------------------------------------------------------------------------------- + // c. Apply initial and basic shaping forms GSUB Lookups (one at a time) + //----------------------------------------------------------------------------------- + + $tags = 'locl ccmp rphf pref blwf pstf'; + $this->_applyGSUBrulesMyanmar($tags, $GSUBscriptTag, $GSUBlangsys); + + //----------------------------------------------------------------------------------- + // d. Apply Presentation Forms GSUB Lookups (+ any discretionary) + //----------------------------------------------------------------------------------- + $tags = 'pres abvs blws psts haln rlig calt liga clig mset'; + $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo'; + $usetags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false); + } + $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys); + $this->restrictToSyllable = false; + } // 5(E). GSUB - Shaper - SEA South East Asian (New Tai Lue, Cham, Tai Tam) + //============================== + elseif ($this->shaper == 'E') { + /* HarfBuzz says: If the designer designed the font for the 'DFLT' script, + * use the default shaper. Otherwise, use the SEA shaper. + * Note that for some simple scripts, there may not be *any* + * GSUB/GPOS needed, so there may be no scripts found! */ + + $this->restrictToSyllable = true; + //----------------------------------------------------------------------------------- + // a. Analyse characters - group as syllables/clusters (Indic); invalid diacritics; add dotted circle + //----------------------------------------------------------------------------------- + $sea_category_string = ''; + foreach ($this->OTLdata as $eid => $c) { + Sea::set_sea_properties($this->OTLdata[$eid], $scriptblock); // sets ['sea_category'] and ['sea_position'] + //$c['general_category'] + //$c['combining_class'] + //$c['uni'] = $char; + + $sea_category_string .= Sea::$sea_category_char[$this->OTLdata[$eid]['sea_category']]; + } + + $broken_syllables = false; + Sea::set_syllables($this->OTLdata, $sea_category_string, $broken_syllables); + $sea_category_string = ''; + + //----------------------------------------------------------------------------------- + // b. Apply locl and ccmp shaping forms - before initial re-ordering; GSUB Lookups (one at a time) + //----------------------------------------------------------------------------------- + $tags = 'locl ccmp'; + $this->_applyGSUBrulesSingly($tags, $GSUBscriptTag, $GSUBlangsys); + + //----------------------------------------------------------------------------------- + // c. Initial Re-ordering + //----------------------------------------------------------------------------------- + // Find base consonant + // Decompose/compose and reorder Matras + // Reorder marks to canonical order + + $dottedcircle = false; + if ($broken_syllables) { + if ($this->mpdf->_charDefined($this->mpdf->fonts[$this->fontkey]['cw'], 0x25CC)) { + $dottedcircle = []; + $ucd_record = Ucdn::get_ucd_record(0x25CC); + $dottedcircle[0]['general_category'] = $ucd_record[0]; + $dottedcircle[0]['bidi_type'] = $ucd_record[2]; + $dottedcircle[0]['group'] = 'C'; + $dottedcircle[0]['uni'] = 0x25CC; + $dottedcircle[0]['sea_category'] = Sea::OT_GB; + $dottedcircle[0]['sea_position'] = Sea::POS_BASE_C; + + $dottedcircle[0]['hex'] = '025CC'; // TEMPORARY ***** + } + } + Sea::initial_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $broken_syllables, $scriptblock, $dottedcircle); + + //----------------------------------------------------------------------------------- + // d. Apply basic shaping forms GSUB Lookups (one at a time) + //----------------------------------------------------------------------------------- + $tags = 'pref abvf blwf pstf'; + $this->_applyGSUBrulesSingly($tags, $GSUBscriptTag, $GSUBlangsys); + + //----------------------------------------------------------------------------------- + // e. Final Re-ordering + //----------------------------------------------------------------------------------- + + Sea::final_reordering($this->OTLdata, $this->GSUBdata[$this->GSUBfont], $scriptblock); + + //----------------------------------------------------------------------------------- + // f. Apply Presentation Forms GSUB Lookups (+ any discretionary) + //----------------------------------------------------------------------------------- + $tags = 'pres abvs blws psts'; + + $omittags = 'locl ccmp nukt akhn rphf rkrf pref blwf abvf half pstf cfar vatu cjct init medi fina isol med2 fin2 fin3 ljmo vjmo tjmo'; + $usetags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $usetags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false); + } + $this->_applyGSUBrules($usetags, $GSUBscriptTag, $GSUBlangsys); + $this->restrictToSyllable = false; + } // 5(D). GSUB - Shaper - DEFAULT (including THAI and LAO and MYANMAR v1 [mymr] and TIBETAN) + //============================== + else { // DEFAULT + //----------------------------------------------------------------------------------- + // a. First decompose/compose in Thai / Lao - Tibetan + //----------------------------------------------------------------------------------- + // Decomposition for THAI or LAO + /* This function implements the shaping logic documented here: + * + * http://linux.thai.net/~thep/th-otf/shaping.html + * + * The first shaping rule listed there is needed even if the font has Thai + * OpenType tables. + * + * + * The following is NOT specified in the MS OT Thai spec, however, it seems + * to be what Uniscribe and other engines implement. According to Eric Muller: + * + * When you have a SARA AM, decompose it in NIKHAHIT + SARA AA, *and* move the + * NIKHAHIT backwards over any tone mark (0E48-0E4B). + * + * <0E14, 0E4B, 0E33> -> <0E14, 0E4D, 0E4B, 0E32> + * + * This reordering is legit only when the NIKHAHIT comes from a SARA AM, not + * when it's there to start with. The string <0E14, 0E4B, 0E4D> is probably + * not what a user wanted, but the rendering is nevertheless nikhahit above + * chattawa. + * + * Same for Lao. + * + * Thai Lao + * SARA AM: U+0E33 U+0EB3 + * SARA AA: U+0E32 U+0EB2 + * Nikhahit: U+0E4D U+0ECD + * + * Testing shows that Uniscribe reorder the following marks: + * Thai: <0E31,0E34..0E37,0E47..0E4E> + * Lao: <0EB1,0EB4..0EB7,0EC7..0ECE> + * + * Lao versions are the same as Thai + 0x80. + */ + if ($this->shaper == 'T' || $this->shaper == 'L') { + for ($ptr = 0; $ptr < count($this->OTLdata); $ptr++) { + $char = $this->OTLdata[$ptr]['uni']; + if (($char & ~0x0080) == 0x0E33) { // if SARA_AM (U+0E33 or U+0EB3) + $NIKHAHIT = $char + 0x1A; + $SARA_AA = $char - 1; + $sub = [$SARA_AA, $NIKHAHIT]; + + $newinfo = []; + $ucd_record = Ucdn::get_ucd_record($sub[0]); + $newinfo[0]['general_category'] = $ucd_record[0]; + $newinfo[0]['bidi_type'] = $ucd_record[2]; + $charasstr = $this->unicode_hex($sub[0]); + if (strpos($this->GlyphClassMarks, $charasstr) !== false) { + $newinfo[0]['group'] = 'M'; + } else { + $newinfo[0]['group'] = 'C'; + } + $newinfo[0]['uni'] = $sub[0]; + $newinfo[0]['hex'] = $charasstr; + $this->OTLdata[$ptr] = $newinfo[0]; // Substitute SARA_AM => SARA_AA + + $ntones = 0; // number of (preceding) tone marks + // IS_TONE_MARK ((x) & ~0x0080, 0x0E34 - 0x0E37, 0x0E47 - 0x0E4E, 0x0E31) + while (isset($this->OTLdata[$ptr - 1 - $ntones]) && ( + ($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) == 0x0E31 || + (($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) >= 0x0E34 && + ($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) <= 0x0E37) || + (($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) >= 0x0E47 && + ($this->OTLdata[$ptr - 1 - $ntones]['uni'] & ~0x0080) <= 0x0E4E) + ) + ) { + $ntones++; + } + + $newinfo = []; + $ucd_record = Ucdn::get_ucd_record($sub[1]); + $newinfo[0]['general_category'] = $ucd_record[0]; + $newinfo[0]['bidi_type'] = $ucd_record[2]; + $charasstr = $this->unicode_hex($sub[1]); + if (strpos($this->GlyphClassMarks, $charasstr) !== false) { + $newinfo[0]['group'] = 'M'; + } else { + $newinfo[0]['group'] = 'C'; + } + $newinfo[0]['uni'] = $sub[1]; + $newinfo[0]['hex'] = $charasstr; + // Insert NIKAHIT + array_splice($this->OTLdata, $ptr - $ntones, 0, $newinfo); + + $ptr++; + } + } + } + + if ($scriptblock == Ucdn::SCRIPT_TIBETAN) { + // ========================= + // Reordering TIBETAN + // ========================= + // Tibetan does not need to need a shaper generally, as long as characters are presented in the correct order + // so we will do one minor change here: + // From ICU: If the present character is a number, and the next character is a pre-number combining mark + // then the two characters are reordered + // From MS OTL spec the following are Digit modifiers (Md): 0F18–0F19, 0F3E–0F3F + // Digits: 0F20–0F33 + // On testing only 0x0F3F (pre-based mark) seems to need re-ordering + for ($ptr = 0; $ptr < count($this->OTLdata) - 1; $ptr++) { + if (Indic::in_range($this->OTLdata[$ptr]['uni'], 0x0F20, 0x0F33) && $this->OTLdata[$ptr + 1]['uni'] == 0x0F3F) { + $tmp = $this->OTLdata[$ptr + 1]; + $this->OTLdata[$ptr + 1] = $this->OTLdata[$ptr]; + $this->OTLdata[$ptr] = $tmp; + } + } + + + // ========================= + // Decomposition for TIBETAN + // ========================= + /* Recommended, but does not seem to change anything... + for($ptr=0; $ptr<count($this->OTLdata); $ptr++) { + $char = $this->OTLdata[$ptr]['uni']; + $sub = Indic::decompose_indic($char); + if ($sub) { + $newinfo = array(); + for($i=0;$i<count($sub);$i++) { + $newinfo[$i] = array(); + $ucd_record = Ucdn::get_ucd_record($sub[$i]); + $newinfo[$i]['general_category'] = $ucd_record[0]; + $newinfo[$i]['bidi_type'] = $ucd_record[2]; + $charasstr = $this->unicode_hex($sub[$i]); + if (strpos($this->GlyphClassMarks, $charasstr)!==false) { $newinfo[$i]['group'] = 'M'; } + else { $newinfo[$i]['group'] = 'C'; } + $newinfo[$i]['uni'] = $sub[$i]; + $newinfo[$i]['hex'] = $charasstr; + } + array_splice($this->OTLdata, $ptr, 1, $newinfo); + $ptr += count($sub)-1; + } + } + */ + } + + + //----------------------------------------------------------------------------------- + // b. Apply all GSUB Lookups (in order specified in lookup list) + //----------------------------------------------------------------------------------- + $tags = 'locl ccmp pref blwf abvf pstf pres abvs blws psts haln rlig calt liga clig mset RQD'; + // pref blwf abvf pstf required for Tibetan + // " RQD" is a non-standard tag in Garuda font - presumably intended to be used by default ? "ReQuireD" + // Being a 3 letter tag is non-standard, and does not allow it to be set by font-feature-settings + + + /* ?Add these until shapers witten? + Hangul: ljmo vjmo tjmo + */ + + $omittags = ''; + $useGSUBtags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $useGSUBtags = $this->_applyTagSettings($tags, $GSUBFeatures, $omittags, false); + } + // APPLY GSUB rules (as long as not Latin + SmallCaps - but not OTL smcp) + if (!(($this->mpdf->textvar & TextVars::FC_SMALLCAPS) && $scriptblock == Ucdn::SCRIPT_LATIN && strpos($useGSUBtags, 'smcp') === false)) { + $this->_applyGSUBrules($useGSUBtags, $GSUBscriptTag, $GSUBlangsys); + } + } + } + + // Shapers - KHMER & THAI & LAO - Replace Word boundary marker with U+200B + // Also TIBETAN (no shaper) + //======================================================= + if (($this->shaper == "K" || $this->shaper == "T" || $this->shaper == "L") || $scriptblock == Ucdn::SCRIPT_TIBETAN) { + // Set up properties to insert a U+200B character + $newinfo = []; + //$newinfo[0] = array('general_category' => 1, 'bidi_type' => 14, 'group' => 'S', 'uni' => 0x200B, 'hex' => '0200B'); + $newinfo[0] = [ + 'general_category' => Ucdn::UNICODE_GENERAL_CATEGORY_FORMAT, + 'bidi_type' => Ucdn::BIDI_CLASS_BN, + 'group' => 'S', 'uni' => 0x200B, 'hex' => '0200B']; + // Then insert U+200B at (after) all word end boundaries + for ($i = count($this->OTLdata) - 1; $i > 0; $i--) { + // Make sure after GSUB that wordend has not been moved - check next char is not in the same syllable + if (isset($this->OTLdata[$i]['wordend']) && $this->OTLdata[$i]['wordend'] && + isset($this->OTLdata[$i + 1]['uni']) && (!isset($this->OTLdata[$i + 1]['syllable']) || !isset($this->OTLdata[$i + 1]['syllable']) || $this->OTLdata[$i + 1]['syllable'] != $this->OTLdata[$i]['syllable'])) { + array_splice($this->OTLdata, $i + 1, 0, $newinfo); + $this->_updateLigatureMarks($i, 1); + } elseif ($this->OTLdata[$i]['uni'] == 0x2e) { // Word end if Full-stop. + array_splice($this->OTLdata, $i + 1, 0, $newinfo); + $this->_updateLigatureMarks($i, 1); + } + } + } + + + // Shapers - INDIC & ARABIC & KHMER & SINHALA & MYANMAR - Remove ZWJ and ZWNJ + //======================================================= + if ($this->shaper == 'I' || $this->shaper == 'S' || $this->shaper == 'A' || $this->shaper == 'K' || $this->shaper == 'M') { + // Remove ZWJ and ZWNJ + for ($i = 0; $i < count($this->OTLdata); $i++) { + if ($this->OTLdata[$i]['uni'] == 8204 || $this->OTLdata[$i]['uni'] == 8205) { + array_splice($this->OTLdata, $i, 1); + $this->_updateLigatureMarks($i, -1); + } + } + } + + + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + ////////// GPOS ///////////////////////////////// + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + if (($useOTL & 0xFF) && $GPOSscriptTag && $GPOSlangsys && $GPOSFeatures) { + $this->Entry = []; + $this->Exit = []; + + // 6. Load GPOS data, Coverage & Lookups + //================================================================= + $fontCacheFilename = $this->mpdf->CurrentFont['fontkey'] . '.GPOSdata.json'; + if (!isset($this->GPOSdata[$this->fontkey]) && $this->fontCache->jsonHas($fontCacheFilename)) { + $this->LuCoverage = $this->GPOSdata[$this->fontkey]['LuCoverage'] = $this->fontCache->jsonLoad($fontCacheFilename); + } else { + $this->LuCoverage = $this->GPOSdata[$this->fontkey]['LuCoverage']; + } + + $this->GPOSLookups = $this->mpdf->CurrentFont['GPOSLookups']; + + + // 7. Select Feature tags to use (incl optional) + //============================== + $tags = 'abvm blwm mark mkmk curs cpsp dist requ'; // Default set + // 'requ' is not listed in the Microsoft registry of Feature tags + // Found in Arial Unicode MS, it repositions the baseline for punctuation in Kannada script + + // ZZZ96 + // Set kern to be included by default in non-Latin script (? just when shapers used) + // Kern is used in some fonts to reposition marks etc. and is essential for correct display + //if ($this->shaper) {$tags .= ' kern'; } + if ($scriptblock != Ucdn::SCRIPT_LATIN) { + $tags .= ' kern'; + } + + $omittags = ''; + $usetags = $tags; + if (!empty($this->mpdf->OTLtags)) { + $usetags = $this->_applyTagSettings($tags, $GPOSFeatures, $omittags, false); + } + + + + // 8. Get GPOS LookupList from Feature tags + //============================== + $LookupList = []; + foreach ($GPOSFeatures as $tag => $arr) { + if (strpos($usetags, $tag) !== false) { + foreach ($arr as $lu) { + $LookupList[$lu] = $tag; + } + } + } + ksort($LookupList); + + + // 9. Apply GPOS Lookups (in order specified in lookup list but selecting from specified tags) + //============================== + // APPLY THE GPOS RULES (as long as not Latin + SmallCaps - but not OTL smcp) + if (!(($this->mpdf->textvar & TextVars::FC_SMALLCAPS) && $scriptblock == Ucdn::SCRIPT_LATIN && strpos($useGSUBtags, 'smcp') === false)) { + $this->_applyGPOSrules($LookupList, $is_old_spec); + // (sets: $this->OTLdata[n]['GPOSinfo'] XPlacement YPlacement XAdvance Entry Exit ) + } + + // 10. Process cursive text + //============================== + if (count($this->Entry) || count($this->Exit)) { + // RTL + $incurs = false; + for ($i = (count($this->OTLdata) - 1); $i >= 0; $i--) { + if (isset($this->Entry[$i]) && isset($this->Entry[$i]['Y']) && $this->Entry[$i]['dir'] == 'RTL') { + $nextbase = $i - 1; // Set as next base ignoring marks (next base reading RTL in logical oder + while (isset($this->OTLdata[$nextbase]['hex']) && strpos($this->GlyphClassMarks, $this->OTLdata[$nextbase]['hex']) !== false) { + $nextbase--; + } + if (isset($this->Exit[$nextbase]) && isset($this->Exit[$nextbase]['Y'])) { + $diff = $this->Entry[$i]['Y'] - $this->Exit[$nextbase]['Y']; + if ($incurs === false) { + $incurs = $diff; + } else { + $incurs += $diff; + } + for ($j = ($i - 1); $j >= $nextbase; $j--) { + if (isset($this->OTLdata[$j]['GPOSinfo']['YPlacement'])) { + $this->OTLdata[$j]['GPOSinfo']['YPlacement'] += $incurs; + } else { + $this->OTLdata[$j]['GPOSinfo']['YPlacement'] = $incurs; + } + } + if (isset($this->Exit[$i]['X']) && isset($this->Entry[$nextbase]['X'])) { + $adj = -($this->Entry[$i]['X'] - $this->Exit[$nextbase]['X']); + // If XAdvance is aplied - in order for PDF to position the Advance correctly need to place it on: + // in RTL - the current glyph or the last of any associated marks + if (isset($this->OTLdata[$nextbase + 1]['GPOSinfo']['XAdvance'])) { + $this->OTLdata[$nextbase + 1]['GPOSinfo']['XAdvance'] += $adj; + } else { + $this->OTLdata[$nextbase + 1]['GPOSinfo']['XAdvance'] = $adj; + } + } + } else { + $incurs = false; + } + } elseif (strpos($this->GlyphClassMarks, $this->OTLdata[$i]['hex']) !== false) { + continue; + } // ignore Marks + else { + $incurs = false; + } + } + // LTR + $incurs = false; + for ($i = 0; $i < count($this->OTLdata); $i++) { + if (isset($this->Exit[$i]) && isset($this->Exit[$i]['Y']) && $this->Exit[$i]['dir'] == 'LTR') { + $nextbase = $i + 1; // Set as next base ignoring marks + while (strpos($this->GlyphClassMarks, $this->OTLdata[$nextbase]['hex']) !== false) { + $nextbase++; + } + if (isset($this->Entry[$nextbase]) && isset($this->Entry[$nextbase]['Y'])) { + $diff = $this->Exit[$i]['Y'] - $this->Entry[$nextbase]['Y']; + if ($incurs === false) { + $incurs = $diff; + } else { + $incurs += $diff; + } + for ($j = ($i + 1); $j <= $nextbase; $j++) { + if (isset($this->OTLdata[$j]['GPOSinfo']['YPlacement'])) { + $this->OTLdata[$j]['GPOSinfo']['YPlacement'] += $incurs; + } else { + $this->OTLdata[$j]['GPOSinfo']['YPlacement'] = $incurs; + } + } + if (isset($this->Exit[$i]['X']) && isset($this->Entry[$nextbase]['X'])) { + $adj = -($this->Exit[$i]['X'] - $this->Entry[$nextbase]['X']); + // If XAdvance is aplied - in order for PDF to position the Advance correctly need to place it on: + // in LTR - the next glyph, ignoring marks + if (isset($this->OTLdata[$nextbase]['GPOSinfo']['XAdvance'])) { + $this->OTLdata[$nextbase]['GPOSinfo']['XAdvance'] += $adj; + } else { + $this->OTLdata[$nextbase]['GPOSinfo']['XAdvance'] = $adj; + } + } + } else { + $incurs = false; + } + } elseif (strpos($this->GlyphClassMarks, $this->OTLdata[$i]['hex']) !== false) { + continue; + } // ignore Marks + else { + $incurs = false; + } + } + } + } // end GPOS + + if ($this->debugOTL) { + $this->_dumpproc('END', '-', '-', '-', '-', 0, '-', 0); + exit; + } + + $this->schOTLdata[$sch] = $this->OTLdata; + $this->OTLdata = []; + } // END foreach subchunk + // 11. Re-assemble and return text string + //============================== + $newGPOSinfo = []; + $newOTLdata = []; + $newchar_data = []; + $newgroup = ''; + $e = ''; + $ectr = 0; + + for ($sch = 0; $sch <= $subchunk; $sch++) { + for ($i = 0; $i < count($this->schOTLdata[$sch]); $i++) { + if (isset($this->schOTLdata[$sch][$i]['GPOSinfo'])) { + $newGPOSinfo[$ectr] = $this->schOTLdata[$sch][$i]['GPOSinfo']; + } + $newchar_data[$ectr] = ['bidi_class' => $this->schOTLdata[$sch][$i]['bidi_type'], 'uni' => $this->schOTLdata[$sch][$i]['uni']]; + $newgroup .= $this->schOTLdata[$sch][$i]['group']; + $e .= UtfString::code2utf($this->schOTLdata[$sch][$i]['uni']); + if (isset($this->mpdf->CurrentFont['subset'])) { + $this->mpdf->CurrentFont['subset'][$this->schOTLdata[$sch][$i]['uni']] = $this->schOTLdata[$sch][$i]['uni']; + } + $ectr++; + } + } + $this->OTLdata['GPOSinfo'] = $newGPOSinfo; + $this->OTLdata['char_data'] = $newchar_data; + $this->OTLdata['group'] = $newgroup; + + // This leaves OTLdata::GPOSinfo, ::bidi_type, & ::group + + return $e; + } + + function _applyTagSettings($tags, $Features, $omittags = '', $onlytags = false) + { + if (empty($this->mpdf->OTLtags['Plus']) && empty($this->mpdf->OTLtags['Minus']) && empty($this->mpdf->OTLtags['FFPlus']) && empty($this->mpdf->OTLtags['FFMinus'])) { + return $tags; + } + + // Use $tags as starting point + $usetags = $tags; + + // Only set / unset tags which are in the font + // Ignore tags which are in $omittags + // If $onlytags, then just unset tags which are already in the Tag list + + $fp = $fm = $ffp = $ffm = ''; + + // Font features to enable - set by font-variant-xx + if (isset($this->mpdf->OTLtags['Plus'])) { + $fp = $this->mpdf->OTLtags['Plus']; + } + preg_match_all('/([a-zA-Z0-9]{4})/', $fp, $m); + for ($i = 0; $i < count($m[0]); $i++) { + $t = $m[1][$i]; + // Is it a valid tag? + if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) { + $usetags .= ' ' . $t; + } + } + + // Font features to disable - set by font-variant-xx + if (isset($this->mpdf->OTLtags['Minus'])) { + $fm = $this->mpdf->OTLtags['Minus']; + } + preg_match_all('/([a-zA-Z0-9]{4})/', $fm, $m); + for ($i = 0; $i < count($m[0]); $i++) { + $t = $m[1][$i]; + // Is it a valid tag? + if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) { + $usetags = str_replace($t, '', $usetags); + } + } + + // Font features to enable - set by font-feature-settings + if (isset($this->mpdf->OTLtags['FFPlus'])) { + $ffp = $this->mpdf->OTLtags['FFPlus']; // Font Features - may include integer: salt4 + } + preg_match_all('/([a-zA-Z0-9]{4})([\d+]*)/', $ffp, $m); + for ($i = 0; $i < count($m[0]); $i++) { + $t = $m[1][$i]; + // Is it a valid tag? + if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) { + $usetags .= ' ' . $m[0][$i]; // - may include integer: salt4 + } + } + + // Font features to disable - set by font-feature-settings + if (isset($this->mpdf->OTLtags['FFMinus'])) { + $ffm = $this->mpdf->OTLtags['FFMinus']; + } + preg_match_all('/([a-zA-Z0-9]{4})/', $ffm, $m); + for ($i = 0; $i < count($m[0]); $i++) { + $t = $m[1][$i]; + // Is it a valid tag? + if (isset($Features[$t]) && strpos($omittags, $t) === false && (!$onlytags || strpos($tags, $t) !== false )) { + $usetags = str_replace($t, '', $usetags); + } + } + return $usetags; + } + + function _applyGSUBrules($usetags, $scriptTag, $langsys) + { + // Features from all Tags are applied together, in Lookup List order. + // For Indic - should be applied one syllable at a time + // - Implemented in functions checkContextMatch and checkContextMatchMultiple by failing to match if outside scope of current 'syllable' + // if $this->restrictToSyllable is true + + $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys]; + $LookupList = []; + foreach ($GSUBFeatures as $tag => $arr) { + if (strpos($usetags, $tag) !== false) { + foreach ($arr as $lu) { + $LookupList[$lu] = $tag; + } + } + } + ksort($LookupList); + + foreach ($LookupList as $lu => $tag) { + $Type = $this->GSUBLookups[$lu]['Type']; + $Flag = $this->GSUBLookups[$lu]['Flag']; + $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + $tagInt = 1; + if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) { + $tagInt = $m[1]; + } + $ptr = 0; + // Test each glyph sequentially + while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064 + $currGlyph = $this->OTLdata[$ptr]['hex']; + $currGID = $this->OTLdata[$ptr]['uni']; + $shift = 1; + foreach ($this->GSUBLookups[$lu]['Subtables'] as $c => $subtable_offset) { + // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3) + if (isset($this->GSLuCoverage[$lu][$c][$currGID])) { + // Get rules from font GSUB subtable + $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $tag, 0, $tagInt); + + if ($shift) { + break; + } + } + } + if ($shift == 0) { + $shift = 1; + } + $ptr += $shift; + } + } + } + + function _applyGSUBrulesSingly($usetags, $scriptTag, $langsys) + { + // Features are applied one at a time, working through each codepoint + + $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys]; + + $tags = explode(' ', $usetags); + foreach ($tags as $usetag) { + $LookupList = []; + foreach ($GSUBFeatures as $tag => $arr) { + if (strpos($usetags, $tag) !== false) { + foreach ($arr as $lu) { + $LookupList[$lu] = $tag; + } + } + } + ksort($LookupList); + + $ptr = 0; + // Test each glyph sequentially + while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064 + $currGlyph = $this->OTLdata[$ptr]['hex']; + $currGID = $this->OTLdata[$ptr]['uni']; + $shift = 1; + + foreach ($LookupList as $lu => $tag) { + $Type = $this->GSUBLookups[$lu]['Type']; + $Flag = $this->GSUBLookups[$lu]['Flag']; + $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + $tagInt = 1; + if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) { + $tagInt = $m[1]; + } + + foreach ($this->GSUBLookups[$lu]['Subtables'] as $c => $subtable_offset) { + // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3) + if (isset($this->GSLuCoverage[$lu][$c][$currGID])) { + // Get rules from font GSUB subtable + $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $tag, 0, $tagInt); + + if ($shift) { + break 2; + } + } + } + } + if ($shift == 0) { + $shift = 1; + } + $ptr += $shift; + } + } + } + + function _applyGSUBrulesMyanmar($usetags, $scriptTag, $langsys) + { + // $usetags = locl ccmp rphf pref blwf pstf'; + // applied to all characters + + $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys]; + + // ALL should be applied one syllable at a time + // Implemented in functions checkContextMatch and checkContextMatchMultiple by failing to match if outside scope of current 'syllable' + $tags = explode(' ', $usetags); + foreach ($tags as $usetag) { + $LookupList = []; + foreach ($GSUBFeatures as $tag => $arr) { + if ($tag == $usetag) { + foreach ($arr as $lu) { + $LookupList[$lu] = $tag; + } + } + } + ksort($LookupList); + + foreach ($LookupList as $lu => $tag) { + $Type = $this->GSUBLookups[$lu]['Type']; + $Flag = $this->GSUBLookups[$lu]['Flag']; + $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + $tagInt = 1; + if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) { + $tagInt = $m[1]; + } + + $ptr = 0; + // Test each glyph sequentially + while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064 + $currGlyph = $this->OTLdata[$ptr]['hex']; + $currGID = $this->OTLdata[$ptr]['uni']; + $shift = 1; + foreach ($this->GSUBLookups[$lu]['Subtables'] as $c => $subtable_offset) { + // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3) + if (isset($this->GSLuCoverage[$lu][$c][$currGID])) { + // Get rules from font GSUB subtable + $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $usetag, 0, $tagInt); + + if ($shift) { + break; + } + } + } + if ($shift == 0) { + $shift = 1; + } + $ptr += $shift; + } + } + } + } + + function _applyGSUBrulesIndic($usetags, $scriptTag, $langsys, $is_old_spec) + { + // $usetags = 'locl ccmp nukt akhn rphf rkrf pref blwf half pstf vatu cjct'; then later - init + // rphf, pref, blwf, half, abvf, pstf, and init are only applied where ['mask'] indicates: Indic::FLAG(Indic::RPHF); + // The rest are applied to all characters + + $GSUBFeatures = $this->mpdf->CurrentFont['GSUBFeatures'][$scriptTag][$langsys]; + + // ALL should be applied one syllable at a time + // Implemented in functions checkContextMatch and checkContextMatchMultiple by failing to match if outside scope of current 'syllable' + $tags = explode(' ', $usetags); + foreach ($tags as $usetag) { + $LookupList = []; + foreach ($GSUBFeatures as $tag => $arr) { + if ($tag == $usetag) { + foreach ($arr as $lu) { + $LookupList[$lu] = $tag; + } + } + } + ksort($LookupList); + + foreach ($LookupList as $lu => $tag) { + $Type = $this->GSUBLookups[$lu]['Type']; + $Flag = $this->GSUBLookups[$lu]['Flag']; + $MarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + $tagInt = 1; + if (preg_match('/' . $tag . '([0-9]{1,2})/', $usetags, $m)) { + $tagInt = $m[1]; + } + + $ptr = 0; + // Test each glyph sequentially + while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064 + $currGlyph = $this->OTLdata[$ptr]['hex']; + $currGID = $this->OTLdata[$ptr]['uni']; + $shift = 1; + foreach ($this->GSUBLookups[$lu]['Subtables'] as $c => $subtable_offset) { + // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3) + if (isset($this->GSLuCoverage[$lu][$c][$currGID])) { + if (strpos('rphf pref blwf half pstf cfar init', $usetag) !== false) { // only apply when mask indicates + $mask = 0; + switch ($usetag) { + case 'rphf': + $mask = (1 << (Indic::RPHF)); + break; + case 'pref': + $mask = (1 << (Indic::PREF)); + break; + case 'blwf': + $mask = (1 << (Indic::BLWF)); + break; + case 'half': + $mask = (1 << (Indic::HALF)); + break; + case 'pstf': + $mask = (1 << (Indic::PSTF)); + break; + case 'cfar': + $mask = (1 << (Indic::CFAR)); + break; + case 'init': + $mask = (1 << (Indic::INIT)); + break; + } + if (!($this->OTLdata[$ptr]['mask'] & $mask)) { + continue; + } + } + // Get rules from font GSUB subtable + $shift = $this->_applyGSUBsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GSUB_offset), $Type, $Flag, $MarkFilteringSet, $this->GSLuCoverage[$lu][$c], 0, $usetag, $is_old_spec, $tagInt); + + if ($shift) { + break; + } + } // Special case for Indic ZZZ99S + // Check to substitute Halant-Consonant in PREF, BLWF or PSTF + // i.e. new spec but GSUB tables have Consonant-Halant in Lookups e.g. FreeSerif, which + // incorrectly just moved old spec tables to new spec. Uniscribe seems to cope with this + // See also ttffontsuni.php + // First check if current glyph is a Halant/Virama + elseif (static::_OTL_OLD_SPEC_COMPAT_1 && $Type == 4 && !$is_old_spec && strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $currGlyph) !== false) { + // only apply when 'pref blwf pstf' tags, and when mask indicates + if (strpos('pref blwf pstf', $usetag) !== false) { + $mask = 0; + switch ($usetag) { + case 'pref': + $mask = (1 << (Indic::PREF)); + break; + case 'blwf': + $mask = (1 << (Indic::BLWF)); + break; + case 'pstf': + $mask = (1 << (Indic::PSTF)); + break; + } + if (!($this->OTLdata[$ptr]['mask'] & $mask)) { + continue; + } + + $nextGlyph = $this->OTLdata[$ptr + 1]['hex']; + $nextGID = $this->OTLdata[$ptr + 1]['uni']; + if (isset($this->GSLuCoverage[$lu][$c][$nextGID])) { + // Get rules from font GSUB subtable + $shift = $this->_applyGSUBsubtableSpecial($lu, $c, $ptr, $currGlyph, $currGID, $nextGlyph, $nextGID, ($subtable_offset - $this->GSUB_offset), $Type, $this->GSLuCoverage[$lu][$c]); + + if ($shift) { + break; + } + } + } + } + } + if ($shift == 0) { + $shift = 1; + } + $ptr += $shift; + } + } + } + } + + function _applyGSUBsubtableSpecial($lookupID, $subtable, $ptr, $currGlyph, $currGID, $nextGlyph, $nextGID, $subtable_offset, $Type, $LuCoverage) + { + + // Special case for Indic + // Check to substitute Halant-Consonant in PREF, BLWF or PSTF + // i.e. new spec but GSUB tables have Consonant-Halant in Lookups e.g. FreeSerif, which + // incorrectly just moved old spec tables to new spec. Uniscribe seems to cope with this + // See also ttffontsuni.php + + $this->seek($subtable_offset); + $SubstFormat = $this->read_ushort(); + + // Subtable contains Consonant - Halant + // Text string contains Halant ($CurrGlyph) - Consonant ($nextGlyph) + // Halant has already been matched, and already checked that $nextGID is in Coverage table + //////////////////////////////////////////////////////////////////////////////// + // Only does: LookupType 4: Ligature Substitution Subtable : n to 1 + //////////////////////////////////////////////////////////////////////////////// + $Coverage = $subtable_offset + $this->read_ushort(); + $NextGlyphPos = $LuCoverage[$nextGID]; + $LigSetCount = $this->read_short(); + + $this->skip($NextGlyphPos * 2); + $LigSet = $subtable_offset + $this->read_short(); + + $this->seek($LigSet); + $LigCount = $this->read_short(); + // LigatureSet i.e. all starting with the same Glyph $nextGlyph [Consonant] + $LigatureOffset = []; + for ($g = 0; $g < $LigCount; $g++) { + $LigatureOffset[$g] = $LigSet + $this->read_ushort(); + } + for ($g = 0; $g < $LigCount; $g++) { + // Ligature tables + $this->seek($LigatureOffset[$g]); + $LigGlyph = $this->read_ushort(); + $substitute = $this->glyphToChar($LigGlyph); + $CompCount = $this->read_ushort(); + + if ($CompCount != 2) { + return 0; + } // Only expecting to work with 2:1 (and no ignore characters in between) + + + $gid = $this->read_ushort(); + $checkGlyph = $this->glyphToChar($gid); // Other component/input Glyphs starting at position 2 (arrayindex 1) + + if ($currGID == $checkGlyph) { + $match = true; + } else { + $match = false; + break; + } + + $GlyphPos = []; + $GlyphPos[] = $ptr; + $GlyphPos[] = $ptr + 1; + + + if ($match) { + $shift = $this->GSUBsubstitute($ptr, $substitute, 4, $GlyphPos); // GlyphPos contains positions to set null + if ($shift) { + return 1; + } + } + } + return 0; + } + + function _applyGSUBsubtable($lookupID, $subtable, $ptr, $currGlyph, $currGID, $subtable_offset, $Type, $Flag, $MarkFilteringSet, $LuCoverage, $level, $currentTag, $is_old_spec, $tagInt) + { + $ignore = $this->_getGCOMignoreString($Flag, $MarkFilteringSet); + + // Lets start + $this->seek($subtable_offset); + $SubstFormat = $this->read_ushort(); + + //////////////////////////////////////////////////////////////////////////////// + // LookupType 1: Single Substitution Subtable : 1 to 1 + //////////////////////////////////////////////////////////////////////////////// + if ($Type == 1) { + // Flag = Ignore + if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) { + return 0; + } + $CoverageOffset = $subtable_offset + $this->read_ushort(); + $GlyphPos = $LuCoverage[$currGID]; + //=========== + // Format 1: + //=========== + if ($SubstFormat == 1) { // Calculated output glyph indices + $DeltaGlyphID = $this->read_short(); + $this->seek($CoverageOffset); + $glyphs = $this->_getCoverageGID(); + $GlyphID = $glyphs[$GlyphPos] + $DeltaGlyphID; + } //=========== + // Format 2: + //=========== + elseif ($SubstFormat == 2) { // Specified output glyph indices + $GlyphCount = $this->read_ushort(); + $this->skip($GlyphPos * 2); + $GlyphID = $this->read_ushort(); + } + + $substitute = $this->glyphToChar($GlyphID); + $shift = $this->GSUBsubstitute($ptr, $substitute, $Type); + if ($this->debugOTL && $shift) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + if ($shift) { + return 1; + } + return 0; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 2: Multiple Substitution Subtable : 1 to n + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 2) { + // Flag = Ignore + if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) { + return 0; + } + $Coverage = $subtable_offset + $this->read_ushort(); + $GlyphPos = $LuCoverage[$currGID]; + $this->skip(2); + $this->skip($GlyphPos * 2); + $Sequences = $subtable_offset + $this->read_short(); + + $this->seek($Sequences); + $GlyphCount = $this->read_short(); + $SubstituteGlyphs = []; + for ($g = 0; $g < $GlyphCount; $g++) { + $sgid = $this->read_ushort(); + $SubstituteGlyphs[] = $this->glyphToChar($sgid); + } + + $shift = $this->GSUBsubstitute($ptr, $SubstituteGlyphs, $Type); + if ($this->debugOTL && $shift) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + if ($shift) { + return $shift; + } + return 0; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 3: Alternate Forms : 1 to 1(n) + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 3) { + // Flag = Ignore + if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) { + return 0; + } + $Coverage = $subtable_offset + $this->read_ushort(); + $AlternateSetCount = $this->read_short(); + ///////////////////////////////////////////////////////////////////////////////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Need to set alternate IF set by CSS3 font-feature for a tag + // i.e. if this is 'salt' alternate may be set to 2 + // default value will be $alt=1 ( === index of 0 in list of alternates) + $alt = 1; // $alt=1 points to Alternative[0] + if ($tagInt > 1) { + $alt = $tagInt; + } + ///////////////////////////////////////////////////////////////////////////////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + if ($alt == 0) { + return 0; + } // If specified alternate not present, cancel [ or could default $alt = 1 ?] + + $GlyphPos = $LuCoverage[$currGID]; + $this->skip($GlyphPos * 2); + + $AlternateSets = $subtable_offset + $this->read_short(); + $this->seek($AlternateSets); + + $AlternateGlyphCount = $this->read_short(); + if ($alt > $AlternateGlyphCount) { + return 0; + } // If specified alternate not present, cancel [ or could default $alt = 1 ?] + + $this->skip(($alt - 1) * 2); + $GlyphID = $this->read_ushort(); + + $substitute = $this->glyphToChar($GlyphID); + $shift = $this->GSUBsubstitute($ptr, $substitute, $Type); + if ($this->debugOTL && $shift) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + if ($shift) { + return 1; + } + return 0; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 4: Ligature Substitution Subtable : n to 1 + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 4) { + // Flag = Ignore + if ($this->_checkGCOMignore($Flag, $currGlyph, $MarkFilteringSet)) { + return 0; + } + $Coverage = $subtable_offset + $this->read_ushort(); + $FirstGlyphPos = $LuCoverage[$currGID]; + + $LigSetCount = $this->read_short(); + + $this->skip($FirstGlyphPos * 2); + $LigSet = $subtable_offset + $this->read_short(); + + $this->seek($LigSet); + $LigCount = $this->read_short(); + // LigatureSet i.e. all starting with the same first Glyph $currGlyph + $LigatureOffset = []; + for ($g = 0; $g < $LigCount; $g++) { + $LigatureOffset[$g] = $LigSet + $this->read_ushort(); + } + for ($g = 0; $g < $LigCount; $g++) { + // Ligature tables + $this->seek($LigatureOffset[$g]); + $LigGlyph = $this->read_ushort(); // Output Ligature GlyphID + $substitute = $this->glyphToChar($LigGlyph); + $CompCount = $this->read_ushort(); + + $spos = $ptr; + $match = true; + $GlyphPos = []; + $GlyphPos[] = $spos; + for ($l = 1; $l < $CompCount; $l++) { + $gid = $this->read_ushort(); + $checkGlyph = $this->glyphToChar($gid); // Other component/input Glyphs starting at position 2 (arrayindex 1) + + $spos++; + //while $this->OTLdata[$spos]['uni'] is an "ignore" => spos++ + while (isset($this->OTLdata[$spos]) && strpos($ignore, $this->OTLdata[$spos]['hex']) !== false) { + $spos++; + } + + if (isset($this->OTLdata[$spos]) && $this->OTLdata[$spos]['uni'] == $checkGlyph) { + $GlyphPos[] = $spos; + } else { + $match = false; + break; + } + } + + + if ($match) { + $shift = $this->GSUBsubstitute($ptr, $substitute, $Type, $GlyphPos); // GlyphPos contains positions to set null + if ($this->debugOTL && $shift) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + if ($shift) { + return ($spos - $ptr + 1 - ($CompCount - 1)); + } + } + } + return 0; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 5: Contextual Substitution Subtable + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 5) { + //=========== + // Format 1: Simple Context Glyph Substitution + //=========== + if ($SubstFormat == 1) { + $CoverageTableOffset = $subtable_offset + $this->read_ushort(); + $SubRuleSetCount = $this->read_ushort(); + $SubRuleSetOffset = []; + for ($b = 0; $b < $SubRuleSetCount; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $SubRuleSetOffset[] = $offset; + } else { + $SubRuleSetOffset[] = $subtable_offset + $offset; + } + } + + // SubRuleSet tables: All contexts beginning with the same glyph + // Select the SubRuleSet required using the position of the glyph in the coverage table + $GlyphPos = $LuCoverage[$currGID]; + if ($SubRuleSetOffset[$GlyphPos] > 0) { + $this->seek($SubRuleSetOffset[$GlyphPos]); + $SubRuleCnt = $this->read_ushort(); + $SubRule = []; + for ($b = 0; $b < $SubRuleCnt; $b++) { + $SubRule[$b] = $SubRuleSetOffset[$GlyphPos] + $this->read_ushort(); + } + for ($b = 0; $b < $SubRuleCnt; $b++) { // EACH RULE + $this->seek($SubRule[$b]); + $InputGlyphCount = $this->read_ushort(); + $SubstCount = $this->read_ushort(); + + $Backtrack = []; + $Lookahead = []; + $Input = []; + $Input[0] = $this->OTLdata[$ptr]['uni']; + for ($r = 1; $r < $InputGlyphCount; $r++) { + $gid = $this->read_ushort(); + $Input[$r] = $this->glyphToChar($gid); + } + $matched = $this->checkContextMatch($Input, $Backtrack, $Lookahead, $ignore, $ptr); + if ($matched) { + if ($this->debugOTL) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + for ($p = 0; $p < $SubstCount; $p++) { // EACH LOOKUP + $SequenceIndex[$p] = $this->read_ushort(); + $LookupListIndex[$p] = $this->read_ushort(); + } + + for ($p = 0; $p < $SubstCount; $p++) { + // Apply $LookupListIndex at $SequenceIndex + if ($SequenceIndex[$p] >= $InputGlyphCount) { + continue; + } + $lu = $LookupListIndex[$p]; + $luType = $this->GSUBLookups[$lu]['Type']; + $luFlag = $this->GSUBLookups[$lu]['Flag']; + $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + + $luptr = $matched[$SequenceIndex[$p]]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GSUBLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt); + if ($shift) { + break; + } + } + } + + if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) { + return $shift; + } /* OTL_FIX_3 */ + else { + return $InputGlyphCount; // should be + matched ignores in Input Sequence + } + } + } + } + return 0; + } //=========== + // Format 2: + //=========== + // Format 2: Class-based Context Glyph Substitution + elseif ($SubstFormat == 2) { + $CoverageTableOffset = $subtable_offset + $this->read_ushort(); + $InputClassDefOffset = $subtable_offset + $this->read_ushort(); + $SubClassSetCnt = $this->read_ushort(); + $SubClassSetOffset = []; + for ($b = 0; $b < $SubClassSetCnt; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $SubClassSetOffset[] = $offset; + } else { + $SubClassSetOffset[] = $subtable_offset + $offset; + } + } + + $InputClasses = $this->_getClasses($InputClassDefOffset); + + for ($s = 0; $s < $SubClassSetCnt; $s++) { // $SubClassSet is ordered by input class-may be NULL + // Select $SubClassSet if currGlyph is in First Input Class + if ($SubClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) { + $this->seek($SubClassSetOffset[$s]); + $SubClassRuleCnt = $this->read_ushort(); + $SubClassRule = []; + for ($b = 0; $b < $SubClassRuleCnt; $b++) { + $SubClassRule[$b] = $SubClassSetOffset[$s] + $this->read_ushort(); + } + + for ($b = 0; $b < $SubClassRuleCnt; $b++) { // EACH RULE + $this->seek($SubClassRule[$b]); + $InputGlyphCount = $this->read_ushort(); + $SubstCount = $this->read_ushort(); + $Input = []; + for ($r = 1; $r < $InputGlyphCount; $r++) { + $Input[$r] = $this->read_ushort(); + } + + $inputClass = $s; + + $inputGlyphs = []; + $inputGlyphs[0] = $InputClasses[$inputClass]; + + if ($InputGlyphCount > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) { + $classindex = $Input[$gcl]; + if (isset($InputClasses[$classindex])) { + $inputGlyphs[$gcl] = $InputClasses[$classindex]; + } else { + $inputGlyphs[$gcl] = ''; + } + } + } + + // Class 0 contains all the glyphs NOT in the other classes + $class0excl = []; + for ($gc = 1; $gc <= count($InputClasses); $gc++) { + if (is_array($InputClasses[$gc])) { + $class0excl = $class0excl + $InputClasses[$gc]; + } + } + + $backtrackGlyphs = []; + $lookaheadGlyphs = []; + + $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl); + if ($matched) { + if ($this->debugOTL) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + for ($p = 0; $p < $SubstCount; $p++) { // EACH LOOKUP + $SequenceIndex[$p] = $this->read_ushort(); + $LookupListIndex[$p] = $this->read_ushort(); + } + + for ($p = 0; $p < $SubstCount; $p++) { + // Apply $LookupListIndex at $SequenceIndex + if ($SequenceIndex[$p] >= $InputGlyphCount) { + continue; + } + $lu = $LookupListIndex[$p]; + $luType = $this->GSUBLookups[$lu]['Type']; + $luFlag = $this->GSUBLookups[$lu]['Flag']; + $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + + $luptr = $matched[$SequenceIndex[$p]]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GSUBLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt); + if ($shift) { + break; + } + } + } + + if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) { + return $shift; + } /* OTL_FIX_3 */ + else { + return $InputGlyphCount; // should be + matched ignores in Input Sequence + } + } + } + } + } + + return 0; + } //=========== + // Format 3: + //=========== + // Format 3: Coverage-based Context Glyph Substitution + elseif ($SubstFormat == 3) { + throw new \Mpdf\MpdfException("GSUB Lookup Type " . $Type . " Format " . $SubstFormat . " not TESTED YET."); + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 6: Chaining Contextual Substitution Subtable + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 6) { + //=========== + // Format 1: + //=========== + // Format 1: Simple Chaining Context Glyph Substitution + if ($SubstFormat == 1) { + $Coverage = $subtable_offset + $this->read_ushort(); + $GlyphPos = $LuCoverage[$currGID]; + $ChainSubRuleSetCount = $this->read_ushort(); + // All of the ChainSubRule tables defining contexts that begin with the same first glyph are grouped together and defined in a ChainSubRuleSet table + $this->skip($GlyphPos * 2); + $ChainSubRuleSet = $subtable_offset + $this->read_ushort(); + $this->seek($ChainSubRuleSet); + $ChainSubRuleCount = $this->read_ushort(); + + for ($s = 0; $s < $ChainSubRuleCount; $s++) { + $ChainSubRule[$s] = $ChainSubRuleSet + $this->read_ushort(); + } + + for ($s = 0; $s < $ChainSubRuleCount; $s++) { + $this->seek($ChainSubRule[$s]); + + $BacktrackGlyphCount = $this->read_ushort(); + $Backtrack = []; + for ($b = 0; $b < $BacktrackGlyphCount; $b++) { + $gid = $this->read_ushort(); + $Backtrack[] = $this->glyphToChar($gid); + } + $Input = []; + $Input[0] = $this->OTLdata[$ptr]['uni']; + $InputGlyphCount = $this->read_ushort(); + for ($b = 1; $b < $InputGlyphCount; $b++) { + $gid = $this->read_ushort(); + $Input[$b] = $this->glyphToChar($gid); + } + $LookaheadGlyphCount = $this->read_ushort(); + $Lookahead = []; + for ($b = 0; $b < $LookaheadGlyphCount; $b++) { + $gid = $this->read_ushort(); + $Lookahead[] = $this->glyphToChar($gid); + } + + $matched = $this->checkContextMatch($Input, $Backtrack, $Lookahead, $ignore, $ptr); + if ($matched) { + if ($this->debugOTL) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + $SubstCount = $this->read_ushort(); + for ($p = 0; $p < $SubstCount; $p++) { + // SubstLookupRecord + $SubstLookupRecord[$p]['SequenceIndex'] = $this->read_ushort(); + $SubstLookupRecord[$p]['LookupListIndex'] = $this->read_ushort(); + } + for ($p = 0; $p < $SubstCount; $p++) { + // Apply $SubstLookupRecord[$p]['LookupListIndex'] at $SubstLookupRecord[$p]['SequenceIndex'] + if ($SubstLookupRecord[$p]['SequenceIndex'] >= $InputGlyphCount) { + continue; + } + $lu = $SubstLookupRecord[$p]['LookupListIndex']; + $luType = $this->GSUBLookups[$lu]['Type']; + $luFlag = $this->GSUBLookups[$lu]['Flag']; + $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + + $luptr = $matched[$SubstLookupRecord[$p]['SequenceIndex']]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GSUBLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt); + if ($shift) { + break; + } + } + } + if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) { + return $shift; + } /* OTL_FIX_3 */ + else { + return $InputGlyphCount; // should be + matched ignores in Input Sequence + } + } + } + return 0; + } //=========== + // Format 2: + //=========== + // Format 2: Class-based Chaining Context Glyph Substitution p257 + elseif ($SubstFormat == 2) { + // NB Format 2 specifies fixed class assignments (identical for each position in the backtrack, input, or lookahead sequence) and exclusive classes (a glyph cannot be in more than one class at a time) + + $CoverageTableOffset = $subtable_offset + $this->read_ushort(); + $BacktrackClassDefOffset = $subtable_offset + $this->read_ushort(); + $InputClassDefOffset = $subtable_offset + $this->read_ushort(); + $LookaheadClassDefOffset = $subtable_offset + $this->read_ushort(); + $ChainSubClassSetCnt = $this->read_ushort(); + $ChainSubClassSetOffset = []; + for ($b = 0; $b < $ChainSubClassSetCnt; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $ChainSubClassSetOffset[] = $offset; + } else { + $ChainSubClassSetOffset[] = $subtable_offset + $offset; + } + } + + $BacktrackClasses = $this->_getClasses($BacktrackClassDefOffset); + $InputClasses = $this->_getClasses($InputClassDefOffset); + $LookaheadClasses = $this->_getClasses($LookaheadClassDefOffset); + + for ($s = 0; $s < $ChainSubClassSetCnt; $s++) { // $ChainSubClassSet is ordered by input class-may be NULL + // Select $ChainSubClassSet if currGlyph is in First Input Class + if ($ChainSubClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) { + $this->seek($ChainSubClassSetOffset[$s]); + $ChainSubClassRuleCnt = $this->read_ushort(); + $ChainSubClassRule = []; + for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { + $ChainSubClassRule[$b] = $ChainSubClassSetOffset[$s] + $this->read_ushort(); + } + + for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { // EACH RULE + $this->seek($ChainSubClassRule[$b]); + $BacktrackGlyphCount = $this->read_ushort(); + for ($r = 0; $r < $BacktrackGlyphCount; $r++) { + $Backtrack[$r] = $this->read_ushort(); + } + $InputGlyphCount = $this->read_ushort(); + for ($r = 1; $r < $InputGlyphCount; $r++) { + $Input[$r] = $this->read_ushort(); + } + $LookaheadGlyphCount = $this->read_ushort(); + for ($r = 0; $r < $LookaheadGlyphCount; $r++) { + $Lookahead[$r] = $this->read_ushort(); + } + + + // These contain classes of glyphs as arrays + // $InputClasses[(class)] e.g. 0x02E6,0x02E7,0x02E8 + // $LookaheadClasses[(class)] + // $BacktrackClasses[(class)] + // These contain arrays of classIndexes + // [Backtrack] [Lookahead] and [Input] (Input is from the second position only) + + + $inputClass = $s; //??? + + $inputGlyphs = []; + $inputGlyphs[0] = $InputClasses[$inputClass]; + + if ($InputGlyphCount > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) { + $classindex = $Input[$gcl]; + if (isset($InputClasses[$classindex])) { + $inputGlyphs[$gcl] = $InputClasses[$classindex]; + } else { + $inputGlyphs[$gcl] = ''; + } + } + } + + // Class 0 contains all the glyphs NOT in the other classes + $class0excl = []; + for ($gc = 1; $gc <= count($InputClasses); $gc++) { + if (isset($InputClasses[$gc])) { + $class0excl = $class0excl + $InputClasses[$gc]; + } + } + + if ($BacktrackGlyphCount) { + for ($gcl = 0; $gcl < $BacktrackGlyphCount; $gcl++) { + $classindex = $Backtrack[$gcl]; + if (isset($BacktrackClasses[$classindex])) { + $backtrackGlyphs[$gcl] = $BacktrackClasses[$classindex]; + } else { + $backtrackGlyphs[$gcl] = ''; + } + } + } else { + $backtrackGlyphs = []; + } + + // Class 0 contains all the glyphs NOT in the other classes + $bclass0excl = []; + for ($gc = 1; $gc <= count($BacktrackClasses); $gc++) { + if (isset($BacktrackClasses[$gc])) { + $bclass0excl = $bclass0excl + $BacktrackClasses[$gc]; + } + } + + + if ($LookaheadGlyphCount) { + for ($gcl = 0; $gcl < $LookaheadGlyphCount; $gcl++) { + $classindex = $Lookahead[$gcl]; + if (isset($LookaheadClasses[$classindex])) { + $lookaheadGlyphs[$gcl] = $LookaheadClasses[$classindex]; + } else { + $lookaheadGlyphs[$gcl] = ''; + } + } + } else { + $lookaheadGlyphs = []; + } + + // Class 0 contains all the glyphs NOT in the other classes + $lclass0excl = []; + for ($gc = 1; $gc <= count($LookaheadClasses); $gc++) { + if (isset($LookaheadClasses[$gc])) { + $lclass0excl = $lclass0excl + $LookaheadClasses[$gc]; + } + } + + + $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl, $bclass0excl, $lclass0excl); + if ($matched) { + if ($this->debugOTL) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + $SubstCount = $this->read_ushort(); + for ($p = 0; $p < $SubstCount; $p++) { // EACH LOOKUP + $SequenceIndex[$p] = $this->read_ushort(); + $LookupListIndex[$p] = $this->read_ushort(); + } + + for ($p = 0; $p < $SubstCount; $p++) { + // Apply $LookupListIndex at $SequenceIndex + if ($SequenceIndex[$p] >= $InputGlyphCount) { + continue; + } + $lu = $LookupListIndex[$p]; + $luType = $this->GSUBLookups[$lu]['Type']; + $luFlag = $this->GSUBLookups[$lu]['Flag']; + $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + + $luptr = $matched[$SequenceIndex[$p]]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GSUBLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt); + if ($shift) { + break; + } + } + } + + if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) { + return $shift; + } /* OTL_FIX_3 */ + else { + return $InputGlyphCount; // should be + matched ignores in Input Sequence + } + } + } + } + } + + return 0; + } //=========== + // Format 3: + //=========== + // Format 3: Coverage-based Chaining Context Glyph Substitution p259 + elseif ($SubstFormat == 3) { + $BacktrackGlyphCount = $this->read_ushort(); + for ($b = 0; $b < $BacktrackGlyphCount; $b++) { + $CoverageBacktrackOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $InputGlyphCount = $this->read_ushort(); + for ($b = 0; $b < $InputGlyphCount; $b++) { + $CoverageInputOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $LookaheadGlyphCount = $this->read_ushort(); + for ($b = 0; $b < $LookaheadGlyphCount; $b++) { + $CoverageLookaheadOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $SubstCount = $this->read_ushort(); + $save_pos = $this->_pos; // Save the point just after PosCount + + $CoverageBacktrackGlyphs = []; + for ($b = 0; $b < $BacktrackGlyphCount; $b++) { + $this->seek($CoverageBacktrackOffset[$b]); + $glyphs = $this->_getCoverage(); + $CoverageBacktrackGlyphs[$b] = implode("|", $glyphs); + } + $CoverageInputGlyphs = []; + for ($b = 0; $b < $InputGlyphCount; $b++) { + $this->seek($CoverageInputOffset[$b]); + $glyphs = $this->_getCoverage(); + $CoverageInputGlyphs[$b] = implode("|", $glyphs); + } + $CoverageLookaheadGlyphs = []; + for ($b = 0; $b < $LookaheadGlyphCount; $b++) { + $this->seek($CoverageLookaheadOffset[$b]); + $glyphs = $this->_getCoverage(); + $CoverageLookaheadGlyphs[$b] = implode("|", $glyphs); + } + + $matched = $this->checkContextMatchMultiple($CoverageInputGlyphs, $CoverageBacktrackGlyphs, $CoverageLookaheadGlyphs, $ignore, $ptr); + if ($matched) { + if ($this->debugOTL) { + $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + } + + $this->seek($save_pos); // Return to just after PosCount + for ($p = 0; $p < $SubstCount; $p++) { + // SubstLookupRecord + $SubstLookupRecord[$p]['SequenceIndex'] = $this->read_ushort(); + $SubstLookupRecord[$p]['LookupListIndex'] = $this->read_ushort(); + } + for ($p = 0; $p < $SubstCount; $p++) { + // Apply $SubstLookupRecord[$p]['LookupListIndex'] at $SubstLookupRecord[$p]['SequenceIndex'] + if ($SubstLookupRecord[$p]['SequenceIndex'] >= $InputGlyphCount) { + continue; + } + $lu = $SubstLookupRecord[$p]['LookupListIndex']; + $luType = $this->GSUBLookups[$lu]['Type']; + $luFlag = $this->GSUBLookups[$lu]['Flag']; + $luMarkFilteringSet = $this->GSUBLookups[$lu]['MarkFilteringSet']; + + $luptr = $matched[$SubstLookupRecord[$p]['SequenceIndex']]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GSUBLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGSUBsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GSUB_offset), $luType, $luFlag, $luMarkFilteringSet, $this->GSLuCoverage[$lu][$luc], 1, $currentTag, $is_old_spec, $tagInt); + if ($shift) { + break; + } + } + } + if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) { + return (isset($shift) ? $shift : 0); + } /* OTL_FIX_3 */ + else { + return $InputGlyphCount; // should be + matched ignores in Input Sequence + } + } + + return 0; + } + } else { + throw new \Mpdf\MpdfException("GSUB Lookup Type " . $Type . " not supported."); + } + } + + function _updateLigatureMarks($pos, $n) + { + if ($n > 0) { + // Update position of Ligatures and associated Marks + // Foreach lig/assocMarks + // Any position lpos or mpos > $pos + count($substitute) + // $this->assocMarks = array(); // assocMarks[$pos mpos] => array(compID, ligPos) + // $this->assocLigs = array(); // Ligatures[$pos lpos] => nc + for ($p = count($this->OTLdata) - 1; $p >= ($pos + $n); $p--) { + if (isset($this->assocLigs[$p])) { + $tmp = $this->assocLigs[$p]; + unset($this->assocLigs[$p]); + $this->assocLigs[($p + $n)] = $tmp; + } + } + for ($p = count($this->OTLdata) - 1; $p >= 0; $p--) { + if (isset($this->assocMarks[$p])) { + if ($this->assocMarks[$p]['ligPos'] >= ($pos + $n)) { + $this->assocMarks[$p]['ligPos'] += $n; + } + if ($p >= ($pos + $n)) { + $tmp = $this->assocMarks[$p]; + unset($this->assocMarks[$p]); + $this->assocMarks[($p + $n)] = $tmp; + } + } + } + } elseif ($n < 1) { // glyphs removed + $nrem = -$n; + // Update position of pre-existing Ligatures and associated Marks + for ($p = ($pos + 1); $p < count($this->OTLdata); $p++) { + if (isset($this->assocLigs[$p])) { + $tmp = $this->assocLigs[$p]; + unset($this->assocLigs[$p]); + $this->assocLigs[($p - $nrem)] = $tmp; + } + } + for ($p = 0; $p < count($this->OTLdata); $p++) { + if (isset($this->assocMarks[$p])) { + if ($this->assocMarks[$p]['ligPos'] >= ($pos)) { + $this->assocMarks[$p]['ligPos'] -= $nrem; + } + if ($p > $pos) { + $tmp = $this->assocMarks[$p]; + unset($this->assocMarks[$p]); + $this->assocMarks[($p - $nrem)] = $tmp; + } + } + } + } + } + + function GSUBsubstitute($pos, $substitute, $Type, $GlyphPos = null) + { + + // LookupType 1: Simple Substitution Subtable : 1 to 1 + // LookupType 3: Alternate Forms : 1 to 1(n) + if ($Type == 1 || $Type == 3) { + $this->OTLdata[$pos]['uni'] = $substitute; + $this->OTLdata[$pos]['hex'] = $this->unicode_hex($substitute); + return 1; + } // LookupType 2: Multiple Substitution Subtable : 1 to n + elseif ($Type == 2) { + for ($i = 0; $i < count($substitute); $i++) { + $uni = $substitute[$i]; + $newOTLdata[$i] = []; + $newOTLdata[$i]['uni'] = $uni; + $newOTLdata[$i]['hex'] = $this->unicode_hex($uni); + + + // Get types of new inserted chars - or replicate type of char being replaced + // $bt = Ucdn::get_bidi_class($uni); + // if (!$bt) { + $bt = $this->OTLdata[$pos]['bidi_type']; + // } + + if (strpos($this->GlyphClassMarks, $newOTLdata[$i]['hex']) !== false) { + $gp = 'M'; + } elseif ($uni == 32) { + $gp = 'S'; + } else { + $gp = 'C'; + } + + // Need to update matra_type ??? of new glyphs inserted ??????????????????????????????????????? + + $newOTLdata[$i]['bidi_type'] = $bt; + $newOTLdata[$i]['group'] = $gp; + + // Need to update details of new glyphs inserted + $newOTLdata[$i]['general_category'] = $this->OTLdata[$pos]['general_category']; + + if ($this->shaper == 'I' || $this->shaper == 'K' || $this->shaper == 'S') { + $newOTLdata[$i]['indic_category'] = $this->OTLdata[$pos]['indic_category']; + $newOTLdata[$i]['indic_position'] = $this->OTLdata[$pos]['indic_position']; + } elseif ($this->shaper == 'M') { + $newOTLdata[$i]['myanmar_category'] = $this->OTLdata[$pos]['myanmar_category']; + $newOTLdata[$i]['myanmar_position'] = $this->OTLdata[$pos]['myanmar_position']; + } + if (isset($this->OTLdata[$pos]['mask'])) { + $newOTLdata[$i]['mask'] = $this->OTLdata[$pos]['mask']; + } + if (isset($this->OTLdata[$pos]['syllable'])) { + $newOTLdata[$i]['syllable'] = $this->OTLdata[$pos]['syllable']; + } + } + if ($this->shaper == 'K' || $this->shaper == 'T' || $this->shaper == 'L') { + if ($this->OTLdata[$pos]['wordend']) { + $newOTLdata[count($substitute) - 1]['wordend'] = true; + } + } + + array_splice($this->OTLdata, $pos, 1, $newOTLdata); // Replace 1 with n + // Update position of Ligatures and associated Marks + // count($substitute)-1 is the number of glyphs added + $nadd = count($substitute) - 1; + $this->_updateLigatureMarks($pos, $nadd); + return count($substitute); + } // LookupType 4: Ligature Substitution Subtable : n to 1 + elseif ($Type == 4) { + // Create Ligatures and associated Marks + $firstGlyph = $this->OTLdata[$pos]['hex']; + + // If all components of the ligature are marks (and in the same syllable), we call this a mark ligature. + $contains_marks = false; + $contains_nonmarks = false; + if (isset($this->OTLdata[$pos]['syllable'])) { + $current_syllable = $this->OTLdata[$pos]['syllable']; + } else { + $current_syllable = 0; + } + for ($i = 0; $i < count($GlyphPos); $i++) { + // If subsequent components are not Marks as well - don't ligate + $unistr = $this->OTLdata[$GlyphPos[$i]]['hex']; + if ($this->restrictToSyllable && isset($this->OTLdata[$GlyphPos[$i]]['syllable']) && $this->OTLdata[$GlyphPos[$i]]['syllable'] != $current_syllable) { + return 0; + } + if (strpos($this->GlyphClassMarks, $unistr) !== false) { + $contains_marks = true; + } else { + $contains_nonmarks = true; + } + } + if ($contains_marks && !$contains_nonmarks) { + // Mark Ligature (all components are Marks) + $firstMarkAssoc = ''; + if (isset($this->assocMarks[$pos])) { + $firstMarkAssoc = $this->assocMarks[$pos]; + } + // If all components of the ligature are marks, we call this a mark ligature. + for ($i = 1; $i < count($GlyphPos); $i++) { + // If subsequent components are not Marks as well - don't ligate + // $unistr = $this->OTLdata[$GlyphPos[$i]]['hex']; + // if (strpos($this->GlyphClassMarks, $unistr )===false) { return; } + + $nextMarkAssoc = ''; + if (isset($this->assocMarks[$GlyphPos[$i]])) { + $nextMarkAssoc = $this->assocMarks[$GlyphPos[$i]]; + } + // If first component was attached to a previous ligature component, + // all subsequent components should be attached to the same ligature + // component, otherwise we shouldn't ligate them. + // If first component was NOT attached to a previous ligature component, + // all subsequent components should also NOT be attached to any ligature component, + if ($firstMarkAssoc != $nextMarkAssoc) { + // unless they are attached to the first component itself! + // if (!is_array($nextMarkAssoc) || $nextMarkAssoc['ligPos']!= $pos) { return; } + // Update/Edit - In test with myanmartext font + // င်္က္ကျြွေိ + // => Lookup 17 E003 E066B E05A 102D + // E003 and 102D should form a mark ligature, but 102D is already associated with (non-mark) ligature E05A + // So instead of disallowing the mark ligature to form, just dissociate... + if (!is_array($nextMarkAssoc) || $nextMarkAssoc['ligPos'] != $pos) { + unset($this->assocMarks[$GlyphPos[$i]]); + } + } + } + + /* + * - If it *is* a mark ligature, we don't allocate a new ligature id, and leave + * the ligature to keep its old ligature id. This will allow it to attach to + * a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH, + * and LAM,LAM,HEH form a ligature, they will leave SHADDA and FATHA wit a + * ligature id and component value of 2. Then if SHADDA,FATHA form a ligature + * later, we don't want them to lose their ligature id/component, otherwise + * GPOS will fail to correctly position the mark ligature on top of the + * LAM,LAM,HEH ligature. + */ + // So if is_array($firstMarkAssoc) - the new (Mark) ligature should keep this association + + $lastPos = $GlyphPos[(count($GlyphPos) - 1)]; + } else { + /* + * - Ligatures cannot be formed across glyphs attached to different components + * of previous ligatures. Eg. the sequence is LAM,SHADDA,LAM,FATHA,HEH, and + * LAM,LAM,HEH form a ligature, leaving SHADDA,FATHA next to eachother. + * However, it would be wrong to ligate that SHADDA,FATHA sequence. + * There is an exception to this: If a ligature tries ligating with marks that + * belong to it itself, go ahead, assuming that the font designer knows what + * they are doing (otherwise it can break Indic stuff when a matra wants to + * ligate with a conjunct...) + */ + + /* + * - If a ligature is formed of components that some of which are also ligatures + * themselves, and those ligature components had marks attached to *their* + * components, we have to attach the marks to the new ligature component + * positions! Now *that*'s tricky! And these marks may be following the + * last component of the whole sequence, so we should loop forward looking + * for them and update them. + * + * Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a + * 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature + * id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature + * form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to + * the new ligature with a component value of 2. + * + * This in fact happened to a font... See: + * https://bugzilla.gnome.org/show_bug.cgi?id=437633 + */ + + $currComp = 0; + for ($i = 0; $i < count($GlyphPos); $i++) { + if ($i > 0 && isset($this->assocLigs[$GlyphPos[$i]])) { // One of the other components is already a ligature + $nc = $this->assocLigs[$GlyphPos[$i]]; + } else { + $nc = 1; + } + // While next char to right is a mark (but not the next matched glyph) + // ?? + also include a Mark Ligature here + $ic = 1; + while ((($i == count($GlyphPos) - 1) || (isset($GlyphPos[$i + 1]) && ($GlyphPos[$i] + $ic) < $GlyphPos[$i + 1])) && isset($this->OTLdata[($GlyphPos[$i] + $ic)]) && strpos($this->GlyphClassMarks, $this->OTLdata[($GlyphPos[$i] + $ic)]['hex']) !== false) { + $newComp = $currComp; + if (isset($this->assocMarks[$GlyphPos[$i] + $ic])) { // One of the inbetween Marks is already associated with a Lig + // OK as long as it is associated with the current Lig + // if ($this->assocMarks[($GlyphPos[$i]+$ic)]['ligPos'] != ($GlyphPos[$i]+$ic)) { die("Problem #1"); } + $newComp += $this->assocMarks[($GlyphPos[$i] + $ic)]['compID']; + } + $this->assocMarks[($GlyphPos[$i] + $ic)] = ['compID' => $newComp, 'ligPos' => $pos]; + $ic++; + } + $currComp += $nc; + } + $lastPos = $GlyphPos[(count($GlyphPos) - 1)] + $ic - 1; + $this->assocLigs[$pos] = $currComp; // Number of components in new Ligature + } + + // Now remove the unwanted glyphs and associated metadata + $newOTLdata[0] = []; + + // Get types of new inserted chars - or replicate type of char being replaced + // $bt = Ucdn::get_bidi_class($substitute); + // if (!$bt) { + $bt = $this->OTLdata[$pos]['bidi_type']; + // } + + if (strpos($this->GlyphClassMarks, $this->unicode_hex($substitute)) !== false) { + $gp = 'M'; + } elseif ($substitute == 32) { + $gp = 'S'; + } else { + $gp = 'C'; + } + + // Need to update details of new glyphs inserted + $newOTLdata[0]['general_category'] = $this->OTLdata[$pos]['general_category']; + + $newOTLdata[0]['bidi_type'] = $bt; + $newOTLdata[0]['group'] = $gp; + + // KASHIDA: If forming a ligature when the last component was identified as a kashida point (final form) + // If previous/first component of ligature is a medial form, then keep this as a kashida point + // TEST (Arabic Typesetting) يَنتُم + $ka = 0; + if (isset($this->OTLdata[$GlyphPos[(count($GlyphPos) - 1)]]['GPOSinfo']['kashida'])) { + $ka = $this->OTLdata[$GlyphPos[(count($GlyphPos) - 1)]]['GPOSinfo']['kashida']; + } + if ($ka == 1 && isset($this->OTLdata[$pos]['form']) && $this->OTLdata[$pos]['form'] == 3) { + $newOTLdata[0]['GPOSinfo']['kashida'] = $ka; + } + + $newOTLdata[0]['uni'] = $substitute; + $newOTLdata[0]['hex'] = $this->unicode_hex($substitute); + + if ($this->shaper == 'I' || $this->shaper == 'K' || $this->shaper == 'S') { + $newOTLdata[0]['indic_category'] = $this->OTLdata[$pos]['indic_category']; + $newOTLdata[0]['indic_position'] = $this->OTLdata[$pos]['indic_position']; + } elseif ($this->shaper == 'M') { + $newOTLdata[0]['myanmar_category'] = $this->OTLdata[$pos]['myanmar_category']; + $newOTLdata[0]['myanmar_position'] = $this->OTLdata[$pos]['myanmar_position']; + } + if (isset($this->OTLdata[$pos]['mask'])) { + $newOTLdata[0]['mask'] = $this->OTLdata[$pos]['mask']; + } + if (isset($this->OTLdata[$pos]['syllable'])) { + $newOTLdata[0]['syllable'] = $this->OTLdata[$pos]['syllable']; + } + + $newOTLdata[0]['is_ligature'] = true; + + + array_splice($this->OTLdata, $pos, 1, $newOTLdata); + + // GlyphPos contains array of arr_pos to set null - not necessarily contiguous + // +- Remove any assocMarks or assocLigs from the main components (the ones that are deleted) + for ($i = count($GlyphPos) - 1; $i > 0; $i--) { + $gpos = $GlyphPos[$i]; + array_splice($this->OTLdata, $gpos, 1); + unset($this->assocLigs[$gpos]); + unset($this->assocMarks[$gpos]); + } + // $this->assocLigs = array(); // Ligatures[$posarr lpos] => nc + // $this->assocMarks = array(); // assocMarks[$posarr mpos] => array(compID, ligPos) + // Update position of pre-existing Ligatures and associated Marks + // Start after first GlyphPos + // count($GlyphPos)-1 is the number of glyphs removed from string + for ($p = ($GlyphPos[0] + 1); $p < (count($this->OTLdata) + count($GlyphPos) - 1); $p++) { + $nrem = 0; // Number of Glyphs removed at this point in the string + for ($i = 0; $i < count($GlyphPos); $i++) { + if ($i > 0 && $p > $GlyphPos[$i]) { + $nrem++; + } + } + if (isset($this->assocLigs[$p])) { + $tmp = $this->assocLigs[$p]; + unset($this->assocLigs[$p]); + $this->assocLigs[($p - $nrem)] = $tmp; + } + if (isset($this->assocMarks[$p])) { + $tmp = $this->assocMarks[$p]; + unset($this->assocMarks[$p]); + if ($tmp['ligPos'] > $GlyphPos[0]) { + $tmp['ligPos'] -= $nrem; + } + $this->assocMarks[($p - $nrem)] = $tmp; + } + } + return 1; + } else { + return 0; + } + } + + //////////////////////////////////////////////////////////////// + ////////// ARABIC ///////////////////////////////// + //////////////////////////////////////////////////////////////// + private function arabic_initialise() + { + // cf. http://unicode.org/Public/UNIDATA/ArabicShaping.txt + // http://unicode.org/Public/UNIDATA/extracted/DerivedJoiningType.txt + // JOIN TO FOLLOWING LETTER IN LOGICAL ORDER (i.e. AS INITIAL/MEDIAL FORM) = Unicode Left-Joining (+ Dual-Joining + Join_Causing 00640) + $this->arabLeftJoining = [ + 0x0620 => 1, 0x0626 => 1, 0x0628 => 1, 0x062A => 1, 0x062B => 1, 0x062C => 1, 0x062D => 1, 0x062E => 1, + 0x0633 => 1, 0x0634 => 1, 0x0635 => 1, 0x0636 => 1, 0x0637 => 1, 0x0638 => 1, 0x0639 => 1, 0x063A => 1, + 0x063B => 1, 0x063C => 1, 0x063D => 1, 0x063E => 1, 0x063F => 1, 0x0640 => 1, 0x0641 => 1, 0x0642 => 1, + 0x0643 => 1, 0x0644 => 1, 0x0645 => 1, 0x0646 => 1, 0x0647 => 1, 0x0649 => 1, 0x064A => 1, 0x066E => 1, + 0x066F => 1, 0x0678 => 1, 0x0679 => 1, 0x067A => 1, 0x067B => 1, 0x067C => 1, 0x067D => 1, 0x067E => 1, + 0x067F => 1, 0x0680 => 1, 0x0681 => 1, 0x0682 => 1, 0x0683 => 1, 0x0684 => 1, 0x0685 => 1, 0x0686 => 1, + 0x0687 => 1, 0x069A => 1, 0x069B => 1, 0x069C => 1, 0x069D => 1, 0x069E => 1, 0x069F => 1, 0x06A0 => 1, + 0x06A1 => 1, 0x06A2 => 1, 0x06A3 => 1, 0x06A4 => 1, 0x06A5 => 1, 0x06A6 => 1, 0x06A7 => 1, 0x06A8 => 1, + 0x06A9 => 1, 0x06AA => 1, 0x06AB => 1, 0x06AC => 1, 0x06AD => 1, 0x06AE => 1, 0x06AF => 1, 0x06B0 => 1, + 0x06B1 => 1, 0x06B2 => 1, 0x06B3 => 1, 0x06B4 => 1, 0x06B5 => 1, 0x06B6 => 1, 0x06B7 => 1, 0x06B8 => 1, + 0x06B9 => 1, 0x06BA => 1, 0x06BB => 1, 0x06BC => 1, 0x06BD => 1, 0x06BE => 1, 0x06BF => 1, 0x06C1 => 1, + 0x06C2 => 1, 0x06CC => 1, 0x06CE => 1, 0x06D0 => 1, 0x06D1 => 1, 0x06FA => 1, 0x06FB => 1, 0x06FC => 1, + 0x06FF => 1, + /* Arabic Supplement */ + 0x0750 => 1, 0x0751 => 1, 0x0752 => 1, 0x0753 => 1, 0x0754 => 1, 0x0755 => 1, 0x0756 => 1, 0x0757 => 1, + 0x0758 => 1, 0x075C => 1, 0x075D => 1, 0x075E => 1, 0x075F => 1, 0x0760 => 1, 0x0761 => 1, 0x0762 => 1, + 0x0763 => 1, 0x0764 => 1, 0x0765 => 1, 0x0766 => 1, 0x0767 => 1, 0x0768 => 1, 0x0769 => 1, 0x076A => 1, + 0x076D => 1, 0x076E => 1, 0x076F => 1, 0x0770 => 1, 0x0772 => 1, 0x0775 => 1, 0x0776 => 1, 0x0777 => 1, + 0x077A => 1, 0x077B => 1, 0x077C => 1, 0x077D => 1, 0x077E => 1, 0x077F => 1, + /* Extended Arabic */ + 0x08A0 => 1, 0x08A2 => 1, 0x08A3 => 1, 0x08A4 => 1, 0x08A5 => 1, 0x08A6 => 1, 0x08A7 => 1, 0x08A8 => 1, + 0x08A9 => 1, + /* 'syrc' Syriac */ + 0x0712 => 1, 0x0713 => 1, 0x0714 => 1, 0x071A => 1, 0x071B => 1, 0x071C => 1, 0x071D => 1, 0x071F => 1, + 0x0720 => 1, 0x0721 => 1, 0x0722 => 1, 0x0723 => 1, 0x0724 => 1, 0x0725 => 1, 0x0726 => 1, 0x0727 => 1, + 0x0729 => 1, 0x072B => 1, 0x072D => 1, 0x072E => 1, 0x074E => 1, 0x074F => 1, + /* N'Ko */ + 0x07CA => 1, 0x07CB => 1, 0x07CC => 1, 0x07CD => 1, 0x07CE => 1, 0x07CF => 1, 0x07D0 => 1, 0x07D1 => 1, + 0x07D2 => 1, 0x07D3 => 1, 0x07D4 => 1, 0x07D5 => 1, 0x07D6 => 1, 0x07D7 => 1, 0x07D8 => 1, 0x07D9 => 1, + 0x07DA => 1, 0x07DB => 1, 0x07DC => 1, 0x07DD => 1, 0x07DE => 1, 0x07DF => 1, 0x07E0 => 1, 0x07E1 => 1, + 0x07E2 => 1, 0x07E3 => 1, 0x07E4 => 1, 0x07E5 => 1, 0x07E6 => 1, 0x07E7 => 1, 0x07E8 => 1, 0x07E9 => 1, + 0x07EA => 1, 0x07FA => 1, + /* Mandaic */ + 0x0841 => 1, 0x0842 => 1, 0x0843 => 1, 0x0844 => 1, 0x0845 => 1, 0x0847 => 1, 0x0848 => 1, 0x084A => 1, + 0x084B => 1, 0x084C => 1, 0x084D => 1, 0x084E => 1, 0x0850 => 1, 0x0851 => 1, 0x0852 => 1, 0x0853 => 1, + 0x0855 => 1, + /* ZWJ U+200D */ + 0x0200D => 1]; + + /* JOIN TO PREVIOUS LETTER IN LOGICAL ORDER (i.e. AS FINAL/MEDIAL FORM) = Unicode Right-Joining (+ Dual-Joining + Join_Causing) */ + $this->arabRightJoining = [ + 0x0620 => 1, 0x0622 => 1, 0x0623 => 1, 0x0624 => 1, 0x0625 => 1, 0x0626 => 1, 0x0627 => 1, 0x0628 => 1, + 0x0629 => 1, 0x062A => 1, 0x062B => 1, 0x062C => 1, 0x062D => 1, 0x062E => 1, 0x062F => 1, 0x0630 => 1, + 0x0631 => 1, 0x0632 => 1, 0x0633 => 1, 0x0634 => 1, 0x0635 => 1, 0x0636 => 1, 0x0637 => 1, 0x0638 => 1, + 0x0639 => 1, 0x063A => 1, 0x063B => 1, 0x063C => 1, 0x063D => 1, 0x063E => 1, 0x063F => 1, 0x0640 => 1, + 0x0641 => 1, 0x0642 => 1, 0x0643 => 1, 0x0644 => 1, 0x0645 => 1, 0x0646 => 1, 0x0647 => 1, 0x0648 => 1, + 0x0649 => 1, 0x064A => 1, 0x066E => 1, 0x066F => 1, 0x0671 => 1, 0x0672 => 1, 0x0673 => 1, 0x0675 => 1, + 0x0676 => 1, 0x0677 => 1, 0x0678 => 1, 0x0679 => 1, 0x067A => 1, 0x067B => 1, 0x067C => 1, 0x067D => 1, + 0x067E => 1, 0x067F => 1, 0x0680 => 1, 0x0681 => 1, 0x0682 => 1, 0x0683 => 1, 0x0684 => 1, 0x0685 => 1, + 0x0686 => 1, 0x0687 => 1, 0x0688 => 1, 0x0689 => 1, 0x068A => 1, 0x068B => 1, 0x068C => 1, 0x068D => 1, + 0x068E => 1, 0x068F => 1, 0x0690 => 1, 0x0691 => 1, 0x0692 => 1, 0x0693 => 1, 0x0694 => 1, 0x0695 => 1, + 0x0696 => 1, 0x0697 => 1, 0x0698 => 1, 0x0699 => 1, 0x069A => 1, 0x069B => 1, 0x069C => 1, 0x069D => 1, + 0x069E => 1, 0x069F => 1, 0x06A0 => 1, 0x06A1 => 1, 0x06A2 => 1, 0x06A3 => 1, 0x06A4 => 1, 0x06A5 => 1, + 0x06A6 => 1, 0x06A7 => 1, 0x06A8 => 1, 0x06A9 => 1, 0x06AA => 1, 0x06AB => 1, 0x06AC => 1, 0x06AD => 1, + 0x06AE => 1, 0x06AF => 1, 0x06B0 => 1, 0x06B1 => 1, 0x06B2 => 1, 0x06B3 => 1, 0x06B4 => 1, 0x06B5 => 1, + 0x06B6 => 1, 0x06B7 => 1, 0x06B8 => 1, 0x06B9 => 1, 0x06BA => 1, 0x06BB => 1, 0x06BC => 1, 0x06BD => 1, + 0x06BE => 1, 0x06BF => 1, 0x06C0 => 1, 0x06C1 => 1, 0x06C2 => 1, 0x06C3 => 1, 0x06C4 => 1, 0x06C5 => 1, + 0x06C6 => 1, 0x06C7 => 1, 0x06C8 => 1, 0x06C9 => 1, 0x06CA => 1, 0x06CB => 1, 0x06CC => 1, 0x06CD => 1, + 0x06CE => 1, 0x06CF => 1, 0x06D0 => 1, 0x06D1 => 1, 0x06D2 => 1, 0x06D3 => 1, 0x06D5 => 1, 0x06EE => 1, + 0x06EF => 1, 0x06FA => 1, 0x06FB => 1, 0x06FC => 1, 0x06FF => 1, + /* Arabic Supplement */ + 0x0750 => 1, 0x0751 => 1, 0x0752 => 1, 0x0753 => 1, 0x0754 => 1, 0x0755 => 1, 0x0756 => 1, 0x0757 => 1, + 0x0758 => 1, 0x0759 => 1, 0x075A => 1, 0x075B => 1, 0x075C => 1, 0x075D => 1, 0x075E => 1, 0x075F => 1, + 0x0760 => 1, 0x0761 => 1, 0x0762 => 1, 0x0763 => 1, 0x0764 => 1, 0x0765 => 1, 0x0766 => 1, 0x0767 => 1, + 0x0768 => 1, 0x0769 => 1, 0x076A => 1, 0x076B => 1, 0x076C => 1, 0x076D => 1, 0x076E => 1, 0x076F => 1, + 0x0770 => 1, 0x0771 => 1, 0x0772 => 1, 0x0773 => 1, 0x0774 => 1, 0x0775 => 1, 0x0776 => 1, 0x0777 => 1, + 0x0778 => 1, 0x0779 => 1, 0x077A => 1, 0x077B => 1, 0x077C => 1, 0x077D => 1, 0x077E => 1, 0x077F => 1, + /* Extended Arabic */ + 0x08A0 => 1, 0x08A2 => 1, 0x08A3 => 1, 0x08A4 => 1, 0x08A5 => 1, 0x08A6 => 1, 0x08A7 => 1, 0x08A8 => 1, + 0x08A9 => 1, 0x08AA => 1, 0x08AB => 1, 0x08AC => 1, + /* 'syrc' Syriac */ + 0x0710 => 1, 0x0712 => 1, 0x0713 => 1, 0x0714 => 1, 0x0715 => 1, 0x0716 => 1, 0x0717 => 1, 0x0718 => 1, + 0x0719 => 1, 0x071A => 1, 0x071B => 1, 0x071C => 1, 0x071D => 1, 0x071E => 1, 0x071F => 1, 0x0720 => 1, + 0x0721 => 1, 0x0722 => 1, 0x0723 => 1, 0x0724 => 1, 0x0725 => 1, 0x0726 => 1, 0x0727 => 1, 0x0728 => 1, + 0x0729 => 1, 0x072A => 1, 0x072B => 1, 0x072C => 1, 0x072D => 1, 0x072E => 1, 0x072F => 1, 0x074D => 1, + 0x074E => 1, 0x074F, + /* N'Ko */ + 0x07CA => 1, 0x07CB => 1, 0x07CC => 1, 0x07CD => 1, 0x07CE => 1, 0x07CF => 1, 0x07D0 => 1, 0x07D1 => 1, + 0x07D2 => 1, 0x07D3 => 1, 0x07D4 => 1, 0x07D5 => 1, 0x07D6 => 1, 0x07D7 => 1, 0x07D8 => 1, 0x07D9 => 1, + 0x07DA => 1, 0x07DB => 1, 0x07DC => 1, 0x07DD => 1, 0x07DE => 1, 0x07DF => 1, 0x07E0 => 1, 0x07E1 => 1, + 0x07E2 => 1, 0x07E3 => 1, 0x07E4 => 1, 0x07E5 => 1, 0x07E6 => 1, 0x07E7 => 1, 0x07E8 => 1, 0x07E9 => 1, + 0x07EA => 1, 0x07FA => 1, + /* Mandaic */ + 0x0841 => 1, 0x0842 => 1, 0x0843 => 1, 0x0844 => 1, 0x0845 => 1, 0x0847 => 1, 0x0848 => 1, 0x084A => 1, + 0x084B => 1, 0x084C => 1, 0x084D => 1, 0x084E => 1, 0x0850 => 1, 0x0851 => 1, 0x0852 => 1, 0x0853 => 1, + 0x0855 => 1, + 0x0840 => 1, 0x0846 => 1, 0x0849 => 1, 0x084F => 1, 0x0854 => 1, /* Right joining */ + /* ZWJ U+200D */ + 0x0200D => 1]; + + /* VOWELS = TRANSPARENT-JOINING = Unicode Transparent-Joining type (not just vowels) */ + $this->arabTransparent = [ + 0x0610 => 1, 0x0611 => 1, 0x0612 => 1, 0x0613 => 1, 0x0614 => 1, 0x0615 => 1, 0x0616 => 1, 0x0617 => 1, + 0x0618 => 1, 0x0619 => 1, 0x061A => 1, 0x064B => 1, 0x064C => 1, 0x064D => 1, 0x064E => 1, 0x064F => 1, + 0x0650 => 1, 0x0651 => 1, 0x0652 => 1, 0x0653 => 1, 0x0654 => 1, 0x0655 => 1, 0x0656 => 1, 0x0657 => 1, + 0x0658 => 1, 0x0659 => 1, 0x065A => 1, 0x065B => 1, 0x065C => 1, 0x065D => 1, 0x065E => 1, 0x065F => 1, + 0x0670 => 1, 0x06D6 => 1, 0x06D7 => 1, 0x06D8 => 1, 0x06D9 => 1, 0x06DA => 1, 0x06DB => 1, 0x06DC => 1, + 0x06DF => 1, 0x06E0 => 1, 0x06E1 => 1, 0x06E2 => 1, 0x06E3 => 1, 0x06E4 => 1, 0x06E7 => 1, 0x06E8 => 1, + 0x06EA => 1, 0x06EB => 1, 0x06EC => 1, 0x06ED => 1, + /* Extended Arabic */ + 0x08E4 => 1, 0x08E5 => 1, 0x08E6 => 1, 0x08E7 => 1, 0x08E8 => 1, 0x08E9 => 1, 0x08EA => 1, 0x08EB => 1, + 0x08EC => 1, 0x08ED => 1, 0x08EE => 1, 0x08EF => 1, 0x08F0 => 1, 0x08F1 => 1, 0x08F2 => 1, 0x08F3 => 1, + 0x08F4 => 1, 0x08F5 => 1, 0x08F6 => 1, 0x08F7 => 1, 0x08F8 => 1, 0x08F9 => 1, 0x08FA => 1, 0x08FB => 1, + 0x08FC => 1, 0x08FD => 1, 0x08FE => 1, + /* Arabic ligatures in presentation form (converted in 'ccmp' in e.g. Arial and Times ? need to add others in this range) */ + 0xFC5E => 1, 0xFC5F => 1, 0xFC60 => 1, 0xFC61 => 1, 0xFC62 => 1, + /* 'syrc' Syriac */ + 0x070F => 1, 0x0711 => 1, 0x0730 => 1, 0x0731 => 1, 0x0732 => 1, 0x0733 => 1, 0x0734 => 1, 0x0735 => 1, + 0x0736 => 1, 0x0737 => 1, 0x0738 => 1, 0x0739 => 1, 0x073A => 1, 0x073B => 1, 0x073C => 1, 0x073D => 1, + 0x073E => 1, 0x073F => 1, 0x0740 => 1, 0x0741 => 1, 0x0742 => 1, 0x0743 => 1, 0x0744 => 1, 0x0745 => 1, + 0x0746 => 1, 0x0747 => 1, 0x0748 => 1, 0x0749 => 1, 0x074A => 1, + /* N'Ko */ + 0x07EB => 1, 0x07EC => 1, 0x07ED => 1, 0x07EE => 1, 0x07EF => 1, 0x07F0 => 1, 0x07F1 => 1, 0x07F2 => 1, + 0x07F3 => 1, + /* Mandaic */ + 0x0859 => 1, 0x085A => 1, 0x085B => 1, + ]; + } + + private function arabic_shaper($usetags, $scriptTag) + { + $chars = []; + for ($i = 0; $i < count($this->OTLdata); $i++) { + $chars[] = $this->OTLdata[$i]['hex']; + } + + $crntChar = null; + $prevChar = null; + $nextChar = null; + $output = []; + $max = count($chars); + for ($i = $max - 1; $i >= 0; $i--) { + $crntChar = $chars[$i]; + if ($i > 0) { + $prevChar = hexdec($chars[$i - 1]); + } else { + $prevChar = null; + } + if ($prevChar && isset($this->arabTransparentJoin[$prevChar]) && isset($chars[$i - 2])) { + $prevChar = hexdec($chars[$i - 2]); + if ($prevChar && isset($this->arabTransparentJoin[$prevChar]) && isset($chars[$i - 3])) { + $prevChar = hexdec($chars[$i - 3]); + if ($prevChar && isset($this->arabTransparentJoin[$prevChar]) && isset($chars[$i - 4])) { + $prevChar = hexdec($chars[$i - 4]); + } + } + } + if ($crntChar && isset($this->arabTransparentJoin[hexdec($crntChar)])) { + // If next_char = RightJoining && prev_char = LeftJoining: + if (isset($chars[$i + 1]) && $chars[$i + 1] && isset($this->arabRightJoining[hexdec($chars[$i + 1])]) && $prevChar && isset($this->arabLeftJoining[$prevChar])) { + $output[] = $this->get_arab_glyphs($crntChar, 1, $chars, $i, $scriptTag, $usetags); // <final> form + } else { + $output[] = $this->get_arab_glyphs($crntChar, 0, $chars, $i, $scriptTag, $usetags); // <isolated> form + } + continue; + } + if (hexdec($crntChar) < 128) { + $output[] = [$crntChar, 0]; + $nextChar = $crntChar; + continue; + } + // 0=ISOLATED FORM :: 1=FINAL :: 2=INITIAL :: 3=MEDIAL + $form = 0; + if ($prevChar && isset($this->arabLeftJoining[$prevChar])) { + $form++; + } + if ($nextChar && isset($this->arabRightJoining[hexdec($nextChar)])) { + $form += 2; + } + $output[] = $this->get_arab_glyphs($crntChar, $form, $chars, $i, $scriptTag, $usetags); + $nextChar = $crntChar; + } + $ra = array_reverse($output); + for ($i = 0; $i < count($this->OTLdata); $i++) { + $this->OTLdata[$i]['uni'] = hexdec($ra[$i][0]); + $this->OTLdata[$i]['hex'] = $ra[$i][0]; + $this->OTLdata[$i]['form'] = $ra[$i][1]; // Actaul form substituted 0=ISOLATED FORM :: 1=FINAL :: 2=INITIAL :: 3=MEDIAL + } + } + + private function get_arab_glyphs($char, $type, &$chars, $i, $scriptTag, $usetags) + { + // Optional Feature settings // doesn't control Syriac at present + if (($type === 0 && strpos($usetags, 'isol') === false) || ($type === 1 && strpos($usetags, 'fina') === false) || ($type === 2 && strpos($usetags, 'init') === false) || ($type === 3 && strpos($usetags, 'medi') === false)) { + return [$char, 0]; + } + + // 0=ISOLATED FORM :: 1=FINAL :: 2=INITIAL :: 3=MEDIAL (:: 4=MED2 :: 5=FIN2 :: 6=FIN3) + $retk = -1; + // Alaph 00710 in Syriac + if ($scriptTag == 'syrc' && $char == '00710') { + // if there is a preceding (base?) character *** should search back to previous base - ignoring vowels and change $n + // set $n as the position of the last base; for now we'll just do this: + $n = $i - 1; + // if the preceding (base) character cannot be joined to + // not in $this->arabLeftJoining i.e. not a char which can join to the next one + if (isset($chars[$n]) && isset($this->arabLeftJoining[hexdec($chars[$n])])) { + // if in the middle of Syriac words + if (isset($chars[$i + 1]) && preg_match('/[\x{0700}-\x{0745}]/u', UtfString::code2utf(hexdec($chars[$n]))) && preg_match('/[\x{0700}-\x{0745}]/u', UtfString::code2utf(hexdec($chars[$i + 1]))) && isset($this->arabGlyphs[$char][4])) { + $retk = 4; + } // if at the end of Syriac words + elseif (!isset($chars[$i + 1]) || !preg_match('/[\x{0700}-\x{0745}]/u', UtfString::code2utf(hexdec($chars[$i + 1])))) { + // if preceding base character IS (00715|00716|0072A) + if (strpos('0715|0716|072A', $chars[$n]) !== false && isset($this->arabGlyphs[$char][6])) { + $retk = 6; + } // elseif preceding base character is NOT (00715|00716|0072A) + elseif (isset($this->arabGlyphs[$char][5])) { + $retk = 5; + } + } + } + if ($retk != -1) { + return [$this->arabGlyphs[$char][$retk], $retk]; + } else { + return [$char, 0]; + } + } + + if (($type > 0 || $type === 0) && isset($this->arabGlyphs[$char][$type])) { + $retk = $type; + } elseif ($type == 3 && isset($this->arabGlyphs[$char][1])) { // if <medial> not defined, but <final>, return <final> + $retk = 1; + } elseif ($type == 2 && isset($this->arabGlyphs[$char][0])) { // if <initial> not defined, but <isolated>, return <isolated> + $retk = 0; + } + if ($retk != -1) { + $match = true; + // If GSUB includes a Backtrack or Lookahead condition (e.g. font ArabicTypesetting) + if (isset($this->arabGlyphs[$char]['prel'][$retk]) && $this->arabGlyphs[$char]['prel'][$retk]) { + $ig = 1; + foreach ($this->arabGlyphs[$char]['prel'][$retk] as $k => $v) { // $k starts 0, 1... + if (!isset($chars[$i - $ig - $k])) { + $match = false; + } elseif (strpos($v, $chars[$i - $ig - $k]) === false) { + while (strpos($this->arabGlyphs[$char]['ignore'][$retk], $chars[$i - $ig - $k]) !== false) { // ignore + $ig++; + } + if (!isset($chars[$i - $ig - $k])) { + $match = false; + } elseif (strpos($v, $chars[$i - $ig - $k]) === false) { + $match = false; + } + } + } + } + if (isset($this->arabGlyphs[$char]['postl'][$retk]) && $this->arabGlyphs[$char]['postl'][$retk]) { + $ig = 1; + foreach ($this->arabGlyphs[$char]['postl'][$retk] as $k => $v) { // $k starts 0, 1... + if (!isset($chars[$i + $ig + $k])) { + $match = false; + } elseif (strpos($v, $chars[$i + $ig + $k]) === false) { + while (strpos($this->arabGlyphs[$char]['ignore'][$retk], $chars[$i + $ig + $k]) !== false) { // ignore + $ig++; + } + if (!isset($chars[$i + $ig + $k])) { + $match = false; + } elseif (strpos($v, $chars[$i + $ig + $k]) === false) { + $match = false; + } + } + } + } + if ($match) { + return [$this->arabGlyphs[$char][$retk], $retk]; + } else { + return [$char, 0]; + } + } else { + return [$char, 0]; + } + } + + //////////////////////////////////////////////////////////////// + ///////////////// LINE BREAKING /////////////////////// + //////////////////////////////////////////////////////////////// + ///////////// TIBETAN LINE BREAKING /////////////////// + //////////////////////////////////////////////////////////////// + // Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries + private function tibetanLineBreaking() + { + for ($ptr = 0; $ptr < count($this->OTLdata); $ptr++) { + // Break opportunities at U+0F0B Tsheg or U=0F0D + if (isset($this->OTLdata[$ptr]['uni']) && ($this->OTLdata[$ptr]['uni'] == 0x0F0B || $this->OTLdata[$ptr]['uni'] == 0x0F0D)) { + if (isset($this->OTLdata[$ptr + 1]['uni']) && ($this->OTLdata[$ptr + 1]['uni'] == 0x0F0D || $this->OTLdata[$ptr + 1]['uni'] == 0xF0E)) { + continue; + } + // Set end of word marker in OTLdata at matchpos + $this->OTLdata[$ptr]['wordend'] = true; + } + } + } + + /** + * South East Asian Linebreaking (Thai, Khmer and Lao) using dictionary of words + * + * Sets $this->OTLdata[$i]['wordend']=true at possible end of word boundaries + */ + private function seaLineBreaking() + { + // Load Line-breaking dictionary + if (!isset($this->lbdicts[$this->shaper]) && file_exists(__DIR__ . '/../data/linebrdict' . $this->shaper . '.dat')) { + $this->lbdicts[$this->shaper] = file_get_contents(__DIR__ . '/../data/linebrdict' . $this->shaper . '.dat'); + } + + $dict = &$this->lbdicts[$this->shaper]; + + // Find all word boundaries and mark end of word $this->OTLdata[$i]['wordend']=true on last character + // If Thai, allow for possible suffixes (not in Lao or Khmer) + // repeater/ellision characters + // (0x0E2F); // Ellision character THAI_PAIYANNOI 0x0E2F UTF-8 0xE0 0xB8 0xAF + // (0x0E46); // Repeat character THAI_MAIYAMOK 0x0E46 UTF-8 0xE0 0xB9 0x86 + // (0x0EC6); // Repeat character LAO UTF-8 0xE0 0xBB 0x86 + + $rollover = []; + $ptr = 0; + + while ($ptr < count($this->OTLdata) - 3) { + if (count($rollover)) { + $matches = $rollover; + $rollover = []; + } else { + $matches = $this->checkwordmatch($dict, $ptr); + } + if (count($matches) == 1) { + $matchpos = $matches[0]; + // Check for repeaters - if so $matchpos++ + if (isset($this->OTLdata[$matchpos + 1]['uni']) && ($this->OTLdata[$matchpos + 1]['uni'] == 0x0E2F || $this->OTLdata[$matchpos + 1]['uni'] == 0x0E46 || $this->OTLdata[$matchpos + 1]['uni'] == 0x0EC6)) { + $matchpos++; + } + // Set end of word marker in OTLdata at matchpos + $this->OTLdata[$matchpos]['wordend'] = true; + $ptr = $matchpos + 1; + } elseif (empty($matches)) { + $ptr++; + // Move past any ASCII characters + while (isset($this->OTLdata[$ptr]['uni']) && ($this->OTLdata[$ptr]['uni'] >> 8) == 0) { + $ptr++; + } + } else { // Multiple matches + $secondmatch = false; + for ($m = count($matches) - 1; $m >= 0; $m--) { + //for ($m=0;$m<count($matches);$m++) { + $firstmatch = $matches[$m]; + $matches2 = $this->checkwordmatch($dict, $firstmatch + 1); + if (count($matches2)) { + // Set end of word marker in OTLdata at matchpos + $this->OTLdata[$firstmatch]['wordend'] = true; + $ptr = $firstmatch + 1; + $rollover = $matches2; + $secondmatch = true; + break; + } + } + if (!$secondmatch) { + // Set end of word marker in OTLdata at end of longest first match + $this->OTLdata[$matches[count($matches) - 1]]['wordend'] = true; + $ptr = $matches[count($matches) - 1] + 1; + // Move past any ASCII characters + while (isset($this->OTLdata[$ptr]['uni']) && ($this->OTLdata[$ptr]['uni'] >> 8) == 0) { + $ptr++; + } + } + } + } + } + + private function checkwordmatch(&$dict, $ptr) + { + /* + Node type: Split. + Divide at < 98 >= 98 + Offset for >= 98 == 79 (long 4-byte unsigned) + + Node type: Linear match. + Char = 97 + + Intermediate match + + Final match + */ + + $dictptr = 0; + $ok = true; + $matches = []; + while ($ok) { + $x = ord($dict[$dictptr]); + $c = $this->OTLdata[$ptr]['uni'] & 0xFF; + if ($x == static::_DICT_INTERMEDIATE_MATCH) { +//echo "DICT_INTERMEDIATE_MATCH: ".dechex($c).'<br />'; + // Do not match if next character in text is a Mark + if (isset($this->OTLdata[$ptr]['uni']) && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) { + $matches[] = $ptr - 1; + } + $dictptr++; + } elseif ($x == static::_DICT_FINAL_MATCH) { +//echo "DICT_FINAL_MATCH: ".dechex($c).'<br />'; + // Do not match if next character in text is a Mark + if (isset($this->OTLdata[$ptr]['uni']) && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) { + $matches[] = $ptr - 1; + } + return $matches; + } elseif ($x == static::_DICT_NODE_TYPE_LINEAR) { +//echo "DICT_NODE_TYPE_LINEAR: ".dechex($c).'<br />'; + $dictptr++; + $m = ord($dict[$dictptr]); + if ($c == $m) { + $ptr++; + if ($ptr > count($this->OTLdata) - 1) { + $next = ord($dict[$dictptr + 1]); + if ($next == static::_DICT_INTERMEDIATE_MATCH || $next == static::_DICT_FINAL_MATCH) { + // Do not match if next character in text is a Mark + if (isset($this->OTLdata[$ptr]['uni']) && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) { + $matches[] = $ptr - 1; + } + } + return $matches; + } + $dictptr++; + continue; + } else { +//echo "DICT_NODE_TYPE_LINEAR NOT: ".dechex($c).'<br />'; + return $matches; + } + } elseif ($x == static::_DICT_NODE_TYPE_SPLIT) { +//echo "DICT_NODE_TYPE_SPLIT ON ".dechex($d).": ".dechex($c).'<br />'; + $dictptr++; + $d = ord($dict[$dictptr]); + if ($c < $d) { + $dictptr += 5; + } else { + $dictptr++; + // Unsigned long 32-bit offset + $offset = (ord($dict[$dictptr]) * 16777216) + (ord($dict[$dictptr + 1]) << 16) + (ord($dict[$dictptr + 2]) << 8) + ord($dict[$dictptr + 3]); + $dictptr = $offset; + } + } else { +//echo "PROBLEM: ".($x).'<br />'; + $ok = false; // Something has gone wrong + } + } + + return $matches; + } + + //////////////////////////////////////////////////////////////// + ////////// GPOS /////////////////////////////////////// + //////////////////////////////////////////////////////////////// + private function _applyGPOSrules($LookupList, $is_old_spec = false) + { + foreach ($LookupList as $lu => $tag) { + $Type = $this->GPOSLookups[$lu]['Type']; + $Flag = $this->GPOSLookups[$lu]['Flag']; + $MarkFilteringSet = ''; + if (isset($this->GPOSLookups[$lu]['MarkFilteringSet'])) { + $MarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet']; + } + $ptr = 0; + // Test each glyph sequentially + while ($ptr < (count($this->OTLdata))) { // whilst there is another glyph ..0064 + $currGlyph = $this->OTLdata[$ptr]['hex']; + $currGID = $this->OTLdata[$ptr]['uni']; + $shift = 1; + foreach ($this->GPOSLookups[$lu]['Subtables'] as $c => $subtable_offset) { + // NB Coverage only looks at glyphs for position 1 (esp. 7.3 and 8.3) + if (isset($this->LuCoverage[$lu][$c][$currGID])) { + // Get rules from font GPOS subtable + if (isset($this->OTLdata[$ptr]['bidi_type'])) { // No need to check bidi_type - just a check that it exists + $shift = $this->_applyGPOSsubtable($lu, $c, $ptr, $currGlyph, $currGID, ($subtable_offset - $this->GPOS_offset + $this->GSUB_length), $Type, $Flag, $MarkFilteringSet, $this->LuCoverage[$lu][$c], $tag, 0, $is_old_spec); + if ($shift) { + break; + } + } + } + } + if ($shift == 0) { + $shift = 1; + } + $ptr += $shift; + } + } + } + + ////////////////////////////////////////////////////////////////////////////////// + // GPOS Types + // Lookup Type 1: Single Adjustment Positioning Subtable Adjust position of a single glyph + // Lookup Type 2: Pair Adjustment Positioning Subtable Adjust position of a pair of glyphs + // Lookup Type 3: Cursive Attachment Positioning Subtable Attach cursive glyphs + // Lookup Type 4: MarkToBase Attachment Positioning Subtable Attach a combining mark to a base glyph + // Lookup Type 5: MarkToLigature Attachment Positioning Subtable Attach a combining mark to a ligature + // Lookup Type 6: MarkToMark Attachment Positioning Subtable Attach a combining mark to another mark + // Lookup Type 7: Contextual Positioning Subtables Position one or more glyphs in context + // Lookup Type 8: Chaining Contextual Positioning Subtable Position one or more glyphs in chained context + // Lookup Type 9: Extension positioning + ////////////////////////////////////////////////////////////////////////////////// + private function _applyGPOSvaluerecord($basepos, $Value) + { + + // If current glyph is a mark with a defined width, any XAdvance is considered to REPLACE the character Advance Width + // Test case <div style="font-family:myanmartext">င်္က္ကျြွေိ</div> + if (strpos($this->GlyphClassMarks, $this->OTLdata[$basepos]['hex']) !== false) { + $cw = round($this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$basepos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000); // convert back to font design units + } else { + $cw = 0; + } + + $apos = $this->_getXAdvancePos($basepos); + + if (isset($Value['XAdvance']) && ($Value['XAdvance'] - $cw) != 0) { + // However DON'T REPLACE the character Advance Width if Advance Width is negative + // Test case <div style="font-family: dejavusansmono">ру́сский</div> + if ($Value['XAdvance'] < 0) { + $cw = 0; + } + + // For LTR apply XAdvanceL to the last mark following the base = at $apos + // For RTL apply XAdvanceR to base = at $basepos + if (isset($this->OTLdata[$apos]['GPOSinfo']['XAdvanceL'])) { + $this->OTLdata[$apos]['GPOSinfo']['XAdvanceL'] += $Value['XAdvance'] - $cw; + } else { + $this->OTLdata[$apos]['GPOSinfo']['XAdvanceL'] = $Value['XAdvance'] - $cw; + } + if (isset($this->OTLdata[$basepos]['GPOSinfo']['XAdvanceR'])) { + $this->OTLdata[$basepos]['GPOSinfo']['XAdvanceR'] += $Value['XAdvance'] - $cw; + } else { + $this->OTLdata[$basepos]['GPOSinfo']['XAdvanceR'] = $Value['XAdvance'] - $cw; + } + } + + // Any XPlacement (? and Y Placement) apply to base and marks (from basepos to apos) + for ($a = $basepos; $a <= $apos; $a++) { + if (isset($Value['XPlacement'])) { + if (isset($this->OTLdata[$a]['GPOSinfo']['XPlacement'])) { + $this->OTLdata[$a]['GPOSinfo']['XPlacement'] += $Value['XPlacement']; + } else { + $this->OTLdata[$a]['GPOSinfo']['XPlacement'] = $Value['XPlacement']; + } + } + if (isset($Value['YPlacement'])) { + if (isset($this->OTLdata[$a]['GPOSinfo']['YPlacement'])) { + $this->OTLdata[$a]['GPOSinfo']['YPlacement'] += $Value['YPlacement']; + } else { + $this->OTLdata[$a]['GPOSinfo']['YPlacement'] = $Value['YPlacement']; + } + } + } + } + + // If XAdvance is aplied to $ptr - in order for PDF to position the Advance correctly need to place it on + // the last of any Marks which immediately follow the current glyph + private function _getXAdvancePos($pos) + { + // NB Not all fonts have all marks specified in GlyphClassMarks + // If the current glyph is not a base (but a mark) then ignore this, and apply to the current position + if (strpos($this->GlyphClassMarks, $this->OTLdata[$pos]['hex']) !== false) { + return $pos; + } + + while (isset($this->OTLdata[$pos + 1]['hex']) && strpos($this->GlyphClassMarks, $this->OTLdata[$pos + 1]['hex']) !== false) { + $pos++; + } + return $pos; + } + + private function _applyGPOSsubtable($lookupID, $subtable, $ptr, $currGlyph, $currGID, $subtable_offset, $Type, $Flag, $MarkFilteringSet, $LuCoverage, $tag, $level, $is_old_spec) + { + if (($Flag & 0x0001) == 1) { + $dir = 'RTL'; + } else { // only used for Type 3 + $dir = 'LTR'; + } + + $ignore = $this->_getGCOMignoreString($Flag, $MarkFilteringSet); + + // Lets start + $this->seek($subtable_offset); + $PosFormat = $this->read_ushort(); + + //////////////////////////////////////////////////////////////////////////////// + // LookupType 1: Single adjustment Adjust position of a single glyph (e.g. SmallCaps/Sups/Subs) + //////////////////////////////////////////////////////////////////////////////// + if ($Type == 1) { + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + $Coverage = $subtable_offset + $this->read_ushort(); + $ValueFormat = $this->read_ushort(); + $Value = $this->_getValueRecord($ValueFormat); + } //=========== + // Format 2: + //=========== + elseif ($PosFormat == 2) { + $Coverage = $subtable_offset + $this->read_ushort(); + $ValueFormat = $this->read_ushort(); + $ValueCount = $this->read_ushort(); + $GlyphPos = $LuCoverage[$currGID]; + $this->skip($GlyphPos * 2 * $this->count_bits($ValueFormat)); + $Value = $this->_getValueRecord($ValueFormat); + } + $this->_applyGPOSvaluerecord($ptr, $Value); + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return 1; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 2: Pair adjustment Adjust position of a pair of glyphs (Kerning) + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 2) { + $Coverage = $subtable_offset + $this->read_ushort(); + $ValueFormat1 = $this->read_ushort(); + $ValueFormat2 = $this->read_ushort(); + $sizeOfPair = ( 2 * $this->count_bits($ValueFormat1) ) + ( 2 * $this->count_bits($ValueFormat2) ); + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + $PairSetCount = $this->read_ushort(); + $PairSetOffset = []; + for ($p = 0; $p < $PairSetCount; $p++) { + $PairSetOffset[] = $subtable_offset + $this->read_ushort(); + } + for ($p = 0; $p < $PairSetCount; $p++) { + if (isset($LuCoverage[$currGID]) && $LuCoverage[$currGID] == $p) { + $this->seek($PairSetOffset[$p]); + //PairSet table + $PairValueCount = $this->read_ushort(); + for ($pv = 0; $pv < $PairValueCount; $pv++) { + //PairValueRecord + $gid = $this->read_ushort(); + $SecondGlyph = $this->glyphToChar($gid); + $FirstGlyph = $this->OTLdata[$ptr]['uni']; + + $checkpos = $ptr; + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + if (isset($this->OTLdata[$checkpos]) && $this->OTLdata[$checkpos]['uni'] == $SecondGlyph) { + $matchedpos = $checkpos; + } else { + $matchedpos = false; + } + + if ($matchedpos !== false) { + $Value1 = $this->_getValueRecord($ValueFormat1); + $Value2 = $this->_getValueRecord($ValueFormat2); + if ($ValueFormat1) { + $this->_applyGPOSvaluerecord($ptr, $Value1); + } + if ($ValueFormat2) { + $this->_applyGPOSvaluerecord($matchedpos, $Value2); + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return $matchedpos - $ptr + 1; + } + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return $matchedpos - $ptr; + } else { + $this->skip($sizeOfPair); + } + } + } + } + return 0; + } //=========== + // Format 2: + //=========== + elseif ($PosFormat == 2) { + $ClassDef1 = $subtable_offset + $this->read_ushort(); + $ClassDef2 = $subtable_offset + $this->read_ushort(); + $Class1Count = $this->read_ushort(); + $Class2Count = $this->read_ushort(); + + $sizeOfValueRecords = $Class1Count * $Class2Count * $sizeOfPair; + + //$this->skip($sizeOfValueRecords ); ???? NOT NEEDED + // NB Class1Count includes Class 0 even though it is not defined by $ClassDef1 + // i.e. Class1Count = 5; Class1 will contain array(indices 1-4); + $Class1 = $this->_getClassDefinitionTable($ClassDef1); + $Class2 = $this->_getClassDefinitionTable($ClassDef2); + $FirstGlyph = $this->OTLdata[$ptr]['uni']; + $checkpos = $ptr; + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + if (isset($this->OTLdata[$checkpos])) { + $matchedpos = $checkpos; + } else { + return 0; + } + + $SecondGlyph = $this->OTLdata[$matchedpos]['uni']; + for ($i = 0; $i < $Class1Count; $i++) { + if (isset($Class1[$i]) && count($Class1[$i])) { + $FirstClassPos = array_search($FirstGlyph, $Class1[$i]); + if ($FirstClassPos === false) { + continue; + } else { + for ($j = 0; $j < $Class2Count; $j++) { + if (isset($Class2[$j]) && count($Class2[$j])) { + $SecondClassPos = array_search($SecondGlyph, $Class2[$j]); + if ($SecondClassPos === false) { + continue; + } + + // Get ValueRecord[$i][$j] + $offs = ($i * $Class2Count * $sizeOfPair) + ($j * $sizeOfPair); + $this->seek($subtable_offset + 16 + $offs); + + $Value1 = $this->_getValueRecord($ValueFormat1); + $Value2 = $this->_getValueRecord($ValueFormat2); + if ($ValueFormat1) { + $this->_applyGPOSvaluerecord($ptr, $Value1); + } + if ($ValueFormat2) { + $this->_applyGPOSvaluerecord($matchedpos, $Value2); + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return $matchedpos - $ptr + 1; + } + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return $matchedpos - $ptr; + } + } + } + } + } + return 0; + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 3: Cursive attachment Attach cursive glyphs + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 3) { + $this->skip(4); + // Need default XAdvance for glyph + $pdfWidth = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], hexdec($currGlyph)); // DON'T convert back to design units + + $CPos = $LuCoverage[$currGID]; + $this->skip($CPos * 4); + $EntryAnchor = $this->read_ushort(); + $ExitAnchor = $this->read_ushort(); + if ($EntryAnchor != 0) { + $EntryAnchor += $subtable_offset; + list($x, $y) = $this->_getAnchorTable($EntryAnchor); + if ($dir == 'RTL') { + if (round($pdfWidth) == round($x * 1000 / $this->mpdf->CurrentFont['unitsPerEm'])) { + $x = 0; + } else { + $x = $x - ($pdfWidth * $this->mpdf->CurrentFont['unitsPerEm'] / 1000); + } + } + + $this->Entry[$ptr] = ['X' => $x, 'Y' => $y, 'dir' => $dir]; + } + if ($ExitAnchor != 0) { + $ExitAnchor += $subtable_offset; + list($x, $y) = $this->_getAnchorTable($ExitAnchor); + if ($dir == 'LTR') { + if (round($pdfWidth) == round($x * 1000 / $this->mpdf->CurrentFont['unitsPerEm'])) { + $x = 0; + } else { + $x = $x - ($pdfWidth * $this->mpdf->CurrentFont['unitsPerEm'] / 1000); + } + } + $this->Exit[$ptr] = ['X' => $x, 'Y' => $y, 'dir' => $dir]; + } + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return 1; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 4: MarkToBase attachment Attach a combining mark to a base glyph + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 4) { + $MarkCoverage = $subtable_offset + $this->read_ushort(); + //$MarkCoverage is already set in $LuCoverage 00065|00073 etc + $BaseCoverage = $subtable_offset + $this->read_ushort(); + $ClassCount = $this->read_ushort(); // Number of classes defined for marks = Number of mark glyphs in the MarkCoverage table + $MarkArray = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table + $BaseArray = $subtable_offset + $this->read_ushort(); // Offset to BaseArray table + + $this->seek($BaseCoverage); + $BaseGlyphs = implode('|', $this->_getCoverage()); + + $checkpos = $ptr; + $checkpos--; + + // ZZZ93 + // In Lohit-Kannada font (old-spec), rules specify a Type 4 GPOS to attach below-forms to base glyph + // the repositioning does not happen in MS Word, and shouldn't happen comparing with other fonts + // ?Why not + // This Fix blocks the GPOS rule if the "mark" is not actually classified as a mark in the GlyphClasses of GDEF + // but only in Indic old-spec. + // Test cases: ನ್ನು and ಕ್ರೌ + if ($this->shaper == 'I' && $is_old_spec && strpos($this->GlyphClassMarks, $this->OTLdata[$ptr]['hex']) === false) { + return; + } + + + // "To identify the base glyph that combines with a mark, the text-processing client must look backward in the glyph string from the mark to the preceding base glyph." + while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos--; + } + + if (isset($this->OTLdata[$checkpos]) && strpos($BaseGlyphs, $this->OTLdata[$checkpos]['hex']) !== false) { + $matchedpos = $checkpos; + } else { + $matchedpos = false; + } + + if ($matchedpos !== false) { + // Get the relevant MarkRecord + $MarkPos = $LuCoverage[$currGID]; + $MarkRecord = $this->_getMarkRecord($MarkArray, $MarkPos); // e.g. Array ( [Class] => 0 [AnchorX] => -549 [AnchorY] => 1548 ) + //Mark Class is = $MarkRecord['Class'] + // Get the relevant BaseRecord + $this->seek($BaseArray); + $BaseCount = $this->read_ushort(); + $BasePos = strpos($BaseGlyphs, $this->OTLdata[$matchedpos]['hex']) / 6; + + // Move to the BaseRecord we want + $nSkip = (2 * $BasePos * $ClassCount ); + $this->skip($nSkip); + + // Read BaseRecord we want for appropriate Class + $nSkip = 2 * $MarkRecord['Class']; + $this->skip($nSkip); + $BaseRecordOffset = $BaseArray + $this->read_ushort(); + list($x, $y) = $this->_getAnchorTable($BaseRecordOffset); + $BaseRecord = ['AnchorX' => $x, 'AnchorY' => $y]; // e.g. Array ( [AnchorX] => 660 [AnchorY] => 1556 ) + // Need default XAdvance for Base glyph + $BaseWidth = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$matchedpos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units + $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $BaseWidth; + // And any intervening (ignored) characters + if (($ptr - $matchedpos) > 1) { + for ($i = $matchedpos + 1; $i < $ptr; $i++) { + $BaseWidthExtra = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$i]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units + $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] += $BaseWidthExtra; + } + } + + // Align to previous Glyph by attachment - so need to add to previous placement values + $prevXPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement'] : 0); + $prevYPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement'] : 0); + + $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $BaseRecord['AnchorX'] - $MarkRecord['AnchorX']; + $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $BaseRecord['AnchorY'] - $MarkRecord['AnchorY']; + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return 1; + } + return 0; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 5: MarkToLigature attachment Attach a combining mark to a ligature + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 5) { + $MarkCoverage = $subtable_offset + $this->read_ushort(); + //$MarkCoverage is already set in $LuCoverage 00065|00073 etc + $LigatureCoverage = $subtable_offset + $this->read_ushort(); + $ClassCount = $this->read_ushort(); // Number of classes defined for marks = Number of mark glyphs in the MarkCoverage table + $MarkArray = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table + $LigatureArray = $subtable_offset + $this->read_ushort(); // Offset to LigatureArray table + + $this->seek($LigatureCoverage); + $LigatureGlyphs = implode('|', $this->_getCoverage()); + + + $checkpos = $ptr; + $checkpos--; + + // "To position a combining mark using a MarkToLigature attachment subtable, the text-processing client must work backward from the mark to the preceding ligature glyph." + while (isset($this->OTLdata[$checkpos]) && strpos($this->GlyphClassMarks, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos--; + } + + if (isset($this->OTLdata[$checkpos]) && strpos($LigatureGlyphs, $this->OTLdata[$checkpos]['hex']) !== false) { + $matchedpos = $checkpos; + } else { + $matchedpos = false; + } + + if ($matchedpos !== false) { + // Get the relevant MarkRecord + $MarkPos = $LuCoverage[$currGID]; + $MarkRecord = $this->_getMarkRecord($MarkArray, $MarkPos); // e.g. Array ( [Class] => 0 [AnchorX] => -549 [AnchorY] => 1548 ) + //Mark Class is = $MarkRecord['Class'] + // Get the relevant LigatureRecord + $this->seek($LigatureArray); + $LigatureCount = $this->read_ushort(); + $LigaturePos = strpos($LigatureGlyphs, $this->OTLdata[$matchedpos]['hex']) / 6; + + // Move to the LigatureAttach table Record we want + $nSkip = (2 * $LigaturePos); + $this->skip($nSkip); + $LigatureAttachOffset = $LigatureArray + $this->read_ushort(); + $this->seek($LigatureAttachOffset); + $ComponentCount = $this->read_ushort(); + $offsets = []; + for ($comp = 0; $comp < $ComponentCount; $comp++) { + // ComponentRecords + for ($class = 0; $class < $ClassCount; $class++) { + $offsets[$comp][$class] = $this->read_ushort(); + } + } + + // Get the specific component for this mark attachment + if (isset($this->assocLigs[$matchedpos]) && isset($this->assocMarks[$ptr]['ligPos']) && $this->assocMarks[$ptr]['ligPos'] == $matchedpos) { + $component = $this->assocMarks[$ptr]['compID']; + } else { + $component = $ComponentCount - 1; + } + + $offset = $offsets[$component][$MarkRecord['Class']]; + if ($offset != 0) { + $LigatureRecordOffset = $offset + $LigatureAttachOffset; + list($x, $y) = $this->_getAnchorTable($LigatureRecordOffset); + $LigatureRecord = ['AnchorX' => $x, 'AnchorY' => $y]; + + // Need default XAdvance for Ligature glyph + $LigatureWidth = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$matchedpos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units + $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $LigatureWidth; + // And any intervening (ignored)characters + if (($ptr - $matchedpos) > 1) { + for ($i = $matchedpos + 1; $i < $ptr; $i++) { + $LigatureWidthExtra = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$i]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units + $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] += $LigatureWidthExtra; + } + } + + // Align to previous Ligature by attachment - so need to add to previous placement values + if (isset($this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement'])) { + $prevXPlacement = $this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement']; + } else { + $prevXPlacement = 0; + } + if (isset($this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement'])) { + $prevYPlacement = $this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement']; + } else { + $prevYPlacement = 0; + } + + $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $LigatureRecord['AnchorX'] - $MarkRecord['AnchorX']; + $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $LigatureRecord['AnchorY'] - $MarkRecord['AnchorY']; + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return 1; + } + } + return 0; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 6: MarkToMark attachment Attach a combining mark to another mark + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 6) { + $Mark1Coverage = $subtable_offset + $this->read_ushort(); // Combining Mark + //$Mark1Coverage is already set in $LuCoverage 0065|0073 etc + $Mark2Coverage = $subtable_offset + $this->read_ushort(); // Base Mark + $ClassCount = $this->read_ushort(); // Number of classes defined for marks = No. of Combining mark1 glyphs in the MarkCoverage table + $Mark1Array = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table + $Mark2Array = $subtable_offset + $this->read_ushort(); // Offset to Mark2Array table + $this->seek($Mark2Coverage); + $Mark2Glyphs = implode('|', $this->_getCoverage()); + $checkpos = $ptr; + $checkpos--; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos--; + } + if (isset($this->OTLdata[$checkpos]) && strpos($Mark2Glyphs, $this->OTLdata[$checkpos]['hex']) !== false) { + $matchedpos = $checkpos; + } else { + $matchedpos = false; + } + + if ($matchedpos !== false) { + // Get the relevant MarkRecord + $Mark1Pos = $LuCoverage[$currGID]; + $Mark1Record = $this->_getMarkRecord($Mark1Array, $Mark1Pos); // e.g. Array ( [Class] => 0 [AnchorX] => -549 [AnchorY] => 1548 ) + //Mark Class is = $Mark1Record['Class'] + // Get the relevant Mark2Record + $this->seek($Mark2Array); + $Mark2Count = $this->read_ushort(); + $Mark2Pos = strpos($Mark2Glyphs, $this->OTLdata[$matchedpos]['hex']) / 6; + + // Move to the Mark2Record we want + $nSkip = (2 * $Mark2Pos * $ClassCount ); + $this->skip($nSkip); + + // Read Mark2Record we want for appropriate Class + $nSkip = 2 * $Mark1Record['Class']; + $this->skip($nSkip); + $Mark2RecordOffset = $Mark2Array + $this->read_ushort(); + list($x, $y) = $this->_getAnchorTable($Mark2RecordOffset); + $Mark2Record = ['AnchorX' => $x, 'AnchorY' => $y]; // e.g. Array ( [AnchorX] => 660 [AnchorY] => 1556 ) + // Need default XAdvance for Mark2 glyph + $Mark2Width = $this->mpdf->_getCharWidth($this->mpdf->CurrentFont['cw'], $this->OTLdata[$matchedpos]['uni']) * $this->mpdf->CurrentFont['unitsPerEm'] / 1000; // convert back to font design units + // IF combining marks are set on different components of a ligature glyph, do not apply this rule + // Test: arabictypesetting: إِلَىٰٓ + // Test: arabictypesetting: بَّيْنَكُمْ + $prevLig = -1; + $thisLig = -1; + $prevComp = -1; + $thisComp = -1; + if (isset($this->assocMarks[$matchedpos])) { + $prevLig = $this->assocMarks[$matchedpos]['ligPos']; + $prevComp = $this->assocMarks[$matchedpos]['compID']; + } + if (isset($this->assocMarks[$ptr])) { + $thisLig = $this->assocMarks[$ptr]['ligPos']; + $thisComp = $this->assocMarks[$ptr]['compID']; + } + + // However IF Mark2 (first in logical order, i.e. being attached to) is not associated with a base, carry on + // This happens in Indic when the Mark being attached to e.g. [Halant Ma lig] -> MatraU, [U+0B4D + U+B2E as E0F5]-> U+0B41 become E135 + if (!defined("OMIT_OTL_FIX_1") || OMIT_OTL_FIX_1 != 1) { + /* OTL_FIX_1 */ + if (isset($this->assocMarks[$matchedpos]) && ($prevLig != $thisLig || $prevComp != $thisComp )) { + return 0; + } + } else { + /* Original code */ + if ($prevLig != $thisLig || $prevComp != $thisComp) { + return 0; + } + } + + + if (!defined("OMIT_OTL_FIX_2") || OMIT_OTL_FIX_2 != 1) { + /* OTL_FIX_2 */ + if (!isset($this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) || !$this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) { + $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $Mark2Width; + } + } + + // ZZZ99Q - Test Case font-family: garuda น้ำ + if (isset($this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) && $this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']) { + $this->OTLdata[$ptr]['GPOSinfo']['BaseWidth'] = $this->OTLdata[$matchedpos]['GPOSinfo']['BaseWidth']; + } + + // Align to previous Mark by attachment - so need to add the previous placement values + $prevXPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['XPlacement'] : 0); + $prevYPlacement = (isset($this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement']) ? $this->OTLdata[$matchedpos]['GPOSinfo']['YPlacement'] : 0); + $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $Mark2Record['AnchorX'] - $Mark1Record['AnchorX']; + $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $Mark2Record['AnchorY'] - $Mark1Record['AnchorY']; + if ($this->debugOTL) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + return 1; + } + return 0; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 7: Context positioning Position one or more glyphs in context + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 7) { + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET."); + } //=========== + // Format 2: + //=========== + elseif ($PosFormat == 2) { + $CoverageTableOffset = $subtable_offset + $this->read_ushort(); + $InputClassDefOffset = $subtable_offset + $this->read_ushort(); + $PosClassSetCnt = $this->read_ushort(); + $PosClassSetOffset = []; + for ($b = 0; $b < $PosClassSetCnt; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $PosClassSetOffset[] = $offset; + } else { + $PosClassSetOffset[] = $subtable_offset + $offset; + } + } + + $InputClasses = $this->_getClasses($InputClassDefOffset); + + for ($s = 0; $s < $PosClassSetCnt; $s++) { // $ChainPosClassSet is ordered by input class-may be NULL + // Select $PosClassSet if currGlyph is in First Input Class + if ($PosClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) { + $this->seek($PosClassSetOffset[$s]); + $PosClassRuleCnt = $this->read_ushort(); + $PosClassRule = []; + for ($b = 0; $b < $PosClassRuleCnt; $b++) { + $PosClassRule[$b] = $PosClassSetOffset[$s] + $this->read_ushort(); + } + + for ($b = 0; $b < $PosClassRuleCnt; $b++) { // EACH RULE + $this->seek($PosClassRule[$b]); + $InputGlyphCount = $this->read_ushort(); + $PosCount = $this->read_ushort(); + + $Input = []; + for ($r = 1; $r < $InputGlyphCount; $r++) { + $Input[$r] = $this->read_ushort(); + } + $inputClass = $s; + + $inputGlyphs = []; + $inputGlyphs[0] = $InputClasses[$inputClass]; + + if ($InputGlyphCount > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) { + $classindex = $Input[$gcl]; + if (isset($InputClasses[$classindex])) { + $inputGlyphs[$gcl] = $InputClasses[$classindex]; + } else { + $inputGlyphs[$gcl] = ''; + } + } + } + + // Class 0 contains all the glyphs NOT in the other classes + $class0excl = []; + for ($gc = 1; $gc <= count($InputClasses); $gc++) { + if (is_array($InputClasses[$gc])) { + $class0excl = $class0excl + $InputClasses[$gc]; + } + } + + $backtrackGlyphs = []; + $lookaheadGlyphs = []; + + $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl); + if ($matched) { + for ($p = 0; $p < $PosCount; $p++) { // EACH LOOKUP + $SequenceIndex[$p] = $this->read_ushort(); + $LookupListIndex[$p] = $this->read_ushort(); + } + + for ($p = 0; $p < $PosCount; $p++) { + // Apply $LookupListIndex at $SequenceIndex + if ($SequenceIndex[$p] >= $InputGlyphCount) { + continue; + } + $lu = $LookupListIndex[$p]; + $luType = $this->GPOSLookups[$lu]['Type']; + $luFlag = $this->GPOSLookups[$lu]['Flag']; + $luMarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet']; + + $luptr = $matched[$SequenceIndex[$p]]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GPOSLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGPOSsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GPOS_offset + $this->GSUB_length), $luType, $luFlag, $luMarkFilteringSet, $this->LuCoverage[$lu][$luc], $tag, 1, $is_old_spec); + if ($this->debugOTL && $shift) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + if ($shift) { + break; + } + } + } + + if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) { + return $shift; + } /* OTL_FIX_3 */ + else { + return $InputGlyphCount; // should be + matched ignores in Input Sequence + } + } + } + } + } + + return 0; + } //=========== + // Format 3: + //=========== + elseif ($PosFormat == 3) { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET."); + } else { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . ", Format " . $PosFormat . " not supported."); + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 8: Chained Context positioning Position one or more glyphs in chained context + //////////////////////////////////////////////////////////////////////////////// + elseif ($Type == 8) { + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET."); + return 0; + } //=========== + // Format 2: + //=========== + elseif ($PosFormat == 2) { + $CoverageTableOffset = $subtable_offset + $this->read_ushort(); + $BacktrackClassDefOffset = $subtable_offset + $this->read_ushort(); + $InputClassDefOffset = $subtable_offset + $this->read_ushort(); + $LookaheadClassDefOffset = $subtable_offset + $this->read_ushort(); + $ChainPosClassSetCnt = $this->read_ushort(); + $ChainPosClassSetOffset = []; + for ($b = 0; $b < $ChainPosClassSetCnt; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $ChainPosClassSetOffset[] = $offset; + } else { + $ChainPosClassSetOffset[] = $subtable_offset + $offset; + } + } + + $BacktrackClasses = $this->_getClasses($BacktrackClassDefOffset); + $InputClasses = $this->_getClasses($InputClassDefOffset); + $LookaheadClasses = $this->_getClasses($LookaheadClassDefOffset); + + for ($s = 0; $s < $ChainPosClassSetCnt; $s++) { // $ChainPosClassSet is ordered by input class-may be NULL + // Select $ChainPosClassSet if currGlyph is in First Input Class + if ($ChainPosClassSetOffset[$s] > 0 && isset($InputClasses[$s][$currGID])) { + $this->seek($ChainPosClassSetOffset[$s]); + $ChainPosClassRuleCnt = $this->read_ushort(); + $ChainPosClassRule = []; + for ($b = 0; $b < $ChainPosClassRuleCnt; $b++) { + $ChainPosClassRule[$b] = $ChainPosClassSetOffset[$s] + $this->read_ushort(); + } + + for ($b = 0; $b < $ChainPosClassRuleCnt; $b++) { // EACH RULE + $this->seek($ChainPosClassRule[$b]); + $BacktrackGlyphCount = $this->read_ushort(); + $Backtrack = []; + for ($r = 0; $r < $BacktrackGlyphCount; $r++) { + $Backtrack[$r] = $this->read_ushort(); + } + $InputGlyphCount = $this->read_ushort(); + $Input = []; + for ($r = 1; $r < $InputGlyphCount; $r++) { + $Input[$r] = $this->read_ushort(); + } + $LookaheadGlyphCount = $this->read_ushort(); + $Lookahead = []; + for ($r = 0; $r < $LookaheadGlyphCount; $r++) { + $Lookahead[$r] = $this->read_ushort(); + } + + $inputClass = $s; //??? + + $inputGlyphs = []; + $inputGlyphs[0] = $InputClasses[$inputClass]; + + if ($InputGlyphCount > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $InputGlyphCount; $gcl++) { + $classindex = $Input[$gcl]; + if (isset($InputClasses[$classindex])) { + $inputGlyphs[$gcl] = $InputClasses[$classindex]; + } else { + $inputGlyphs[$gcl] = ''; + } + } + } + + // Class 0 contains all the glyphs NOT in the other classes + $class0excl = []; + for ($gc = 1; $gc <= count($InputClasses); $gc++) { + if (isset($InputClasses[$gc]) && is_array($InputClasses[$gc])) { + $class0excl = $class0excl + $InputClasses[$gc]; + } + } + + if ($BacktrackGlyphCount) { + $backtrackGlyphs = []; + for ($gcl = 0; $gcl < $BacktrackGlyphCount; $gcl++) { + $classindex = $Backtrack[$gcl]; + if (isset($BacktrackClasses[$classindex])) { + $backtrackGlyphs[$gcl] = $BacktrackClasses[$classindex]; + } else { + $backtrackGlyphs[$gcl] = ''; + } + } + } else { + $backtrackGlyphs = []; + } + + // Class 0 contains all the glyphs NOT in the other classes + $bclass0excl = []; + for ($gc = 1; $gc <= count($BacktrackClasses); $gc++) { + if (isset($BacktrackClasses[$gc]) && is_array($BacktrackClasses[$gc])) { + $bclass0excl = $bclass0excl + $BacktrackClasses[$gc]; + } + } + + if ($LookaheadGlyphCount) { + $lookaheadGlyphs = []; + for ($gcl = 0; $gcl < $LookaheadGlyphCount; $gcl++) { + $classindex = $Lookahead[$gcl]; + if (isset($LookaheadClasses[$classindex])) { + $lookaheadGlyphs[$gcl] = $LookaheadClasses[$classindex]; + } else { + $lookaheadGlyphs[$gcl] = ''; + } + } + } else { + $lookaheadGlyphs = []; + } + + // Class 0 contains all the glyphs NOT in the other classes + $lclass0excl = []; + for ($gc = 1; $gc <= count($LookaheadClasses); $gc++) { + if (isset($LookaheadClasses[$gc]) && is_array($LookaheadClasses[$gc])) { + $lclass0excl = $lclass0excl + $LookaheadClasses[$gc]; + } + } + + $matched = $this->checkContextMatchMultipleUni($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl, $bclass0excl, $lclass0excl); + if ($matched) { + $PosCount = $this->read_ushort(); + $SequenceIndex = []; + $LookupListIndex = []; + for ($p = 0; $p < $PosCount; $p++) { // EACH LOOKUP + $SequenceIndex[$p] = $this->read_ushort(); + $LookupListIndex[$p] = $this->read_ushort(); + } + + for ($p = 0; $p < $PosCount; $p++) { + // Apply $LookupListIndex at $SequenceIndex + if ($SequenceIndex[$p] >= $InputGlyphCount) { + continue; + } + $lu = $LookupListIndex[$p]; + $luType = $this->GPOSLookups[$lu]['Type']; + $luFlag = $this->GPOSLookups[$lu]['Flag']; + $luMarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet']; + + $luptr = $matched[$SequenceIndex[$p]]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GPOSLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGPOSsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GPOS_offset + $this->GSUB_length), $luType, $luFlag, $luMarkFilteringSet, $this->LuCoverage[$lu][$luc], $tag, 1, $is_old_spec); + if ($this->debugOTL && $shift) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + if ($shift) { + break; + } + } + } + + if (!defined("OMIT_OTL_FIX_3") || OMIT_OTL_FIX_3 != 1) { + return $shift; + } /* OTL_FIX_3 */ + else { + return $InputGlyphCount; // should be + matched ignores in Input Sequence + } + } + } + } + } + + return 0; + } //=========== + // Format 3: + //=========== + elseif ($PosFormat == 3) { + $BacktrackGlyphCount = $this->read_ushort(); + for ($b = 0; $b < $BacktrackGlyphCount; $b++) { + $CoverageBacktrackOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $InputGlyphCount = $this->read_ushort(); + for ($b = 0; $b < $InputGlyphCount; $b++) { + $CoverageInputOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $LookaheadGlyphCount = $this->read_ushort(); + for ($b = 0; $b < $LookaheadGlyphCount; $b++) { + $CoverageLookaheadOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $PosCount = $this->read_ushort(); + $save_pos = $this->_pos; // Save the point just after PosCount + + $CoverageBacktrackGlyphs = []; + for ($b = 0; $b < $BacktrackGlyphCount; $b++) { + $this->seek($CoverageBacktrackOffset[$b]); + $glyphs = $this->_getCoverage(); + $CoverageBacktrackGlyphs[$b] = implode("|", $glyphs); + } + $CoverageInputGlyphs = []; + for ($b = 0; $b < $InputGlyphCount; $b++) { + $this->seek($CoverageInputOffset[$b]); + $glyphs = $this->_getCoverage(); + $CoverageInputGlyphs[$b] = implode("|", $glyphs); + } + $CoverageLookaheadGlyphs = []; + for ($b = 0; $b < $LookaheadGlyphCount; $b++) { + $this->seek($CoverageLookaheadOffset[$b]); + $glyphs = $this->_getCoverage(); + $CoverageLookaheadGlyphs[$b] = implode("|", $glyphs); + } + $matched = $this->checkContextMatchMultiple($CoverageInputGlyphs, $CoverageBacktrackGlyphs, $CoverageLookaheadGlyphs, $ignore, $ptr); + if ($matched) { + $this->seek($save_pos); // Return to just after PosCount + for ($p = 0; $p < $PosCount; $p++) { + // PosLookupRecord + $PosLookupRecord[$p]['SequenceIndex'] = $this->read_ushort(); + $PosLookupRecord[$p]['LookupListIndex'] = $this->read_ushort(); + } + for ($p = 0; $p < $PosCount; $p++) { + // Apply $PosLookupRecord[$p]['LookupListIndex'] at $PosLookupRecord[$p]['SequenceIndex'] + if ($PosLookupRecord[$p]['SequenceIndex'] >= $InputGlyphCount) { + continue; + } + $lu = $PosLookupRecord[$p]['LookupListIndex']; + $luType = $this->GPOSLookups[$lu]['Type']; + $luFlag = $this->GPOSLookups[$lu]['Flag']; + if (isset($this->GPOSLookups[$lu]['MarkFilteringSet'])) { + $luMarkFilteringSet = $this->GPOSLookups[$lu]['MarkFilteringSet']; + } else { + $luMarkFilteringSet = ''; + } + + $luptr = $matched[$PosLookupRecord[$p]['SequenceIndex']]; + $lucurrGlyph = $this->OTLdata[$luptr]['hex']; + $lucurrGID = $this->OTLdata[$luptr]['uni']; + + foreach ($this->GPOSLookups[$lu]['Subtables'] as $luc => $lusubtable_offset) { + $shift = $this->_applyGPOSsubtable($lu, $luc, $luptr, $lucurrGlyph, $lucurrGID, ($lusubtable_offset - $this->GPOS_offset + $this->GSUB_length), $luType, $luFlag, $luMarkFilteringSet, $this->LuCoverage[$lu][$luc], $tag, 1, $is_old_spec); + if ($this->debugOTL && $shift) { + $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + } + if ($shift) { + break; + } + } + } + } + } else { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . ", Format " . $PosFormat . " not supported."); + } + } else { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " not supported."); + } + } + + ////////////////////////////////////////////////////////////////////////////////// + // GPOS / GSUB / GCOM (common) functions + ////////////////////////////////////////////////////////////////////////////////// + private function checkContextMatch($Input, $Backtrack, $Lookahead, $ignore, $ptr) + { + // Input etc are single numbers - GSUB Format 6.1 + // Input starts with (1=>xxx) + // return false if no match, else an array of ptr for matches (0=>0, 1=>3,...) + + $current_syllable = (isset($this->OTLdata[$ptr]['syllable']) ? $this->OTLdata[$ptr]['syllable'] : 0); + + // BACKTRACK + $checkpos = $ptr; + for ($i = 0; $i < count($Backtrack); $i++) { + $checkpos--; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos--; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } elseif (!isset($this->OTLdata[$checkpos]) || $this->OTLdata[$checkpos]['uni'] != $Backtrack[$i]) { + return false; + } + } + + // INPUT + $matched = [0 => $ptr]; + $checkpos = $ptr; + for ($i = 1; $i < count($Input); $i++) { + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } elseif (isset($this->OTLdata[$checkpos]) && $this->OTLdata[$checkpos]['uni'] == $Input[$i]) { + $matched[] = $checkpos; + } else { + return false; + } + } + + // LOOKAHEAD + for ($i = 0; $i < count($Lookahead); $i++) { + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } elseif (!isset($this->OTLdata[$checkpos]) || $this->OTLdata[$checkpos]['uni'] != $Lookahead[$i]) { + return false; + } + } + + return $matched; + } + + private function checkContextMatchMultiple($Input, $Backtrack, $Lookahead, $ignore, $ptr, $class0excl = '', $bclass0excl = '', $lclass0excl = '') + { + // Input etc are string/array of glyph strings - GSUB Format 5.2, 5.3, 6.2, 6.3, GPOS Format 7.2, 7.3, 8.2, 8.3 + // Input starts with (1=>xxx) + // return false if no match, else an array of ptr for matches (0=>0, 1=>3,...) + // $class0excl is the string of glyphs in all classes except Class 0 (GSUB 5.2, 6.2, GPOS 7.2, 8.2) + // $bclass0excl & $lclass0excl are the same for lookahead and backtrack (GSUB 6.2, GPOS 8.2) + + $current_syllable = (isset($this->OTLdata[$ptr]['syllable']) ? $this->OTLdata[$ptr]['syllable'] : 0); + + // BACKTRACK + $checkpos = $ptr; + for ($i = 0; $i < count($Backtrack); $i++) { + $checkpos--; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos--; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } // If Class 0 specified, matches anything NOT in $bclass0excl + elseif (!$Backtrack[$i] && isset($this->OTLdata[$checkpos]) && strpos($bclass0excl, $this->OTLdata[$checkpos]['hex']) !== false) { + return false; + } elseif (!isset($this->OTLdata[$checkpos]) || strpos($Backtrack[$i], $this->OTLdata[$checkpos]['hex']) === false) { + return false; + } + } + + // INPUT + $matched = [0 => $ptr]; + $checkpos = $ptr; + for ($i = 1; $i < count($Input); $i++) { // Start at 1 - already matched the first InputGlyph + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } // If Input Class 0 specified, matches anything NOT in $class0excl + elseif (!$Input[$i] && isset($this->OTLdata[$checkpos]) && strpos($class0excl, $this->OTLdata[$checkpos]['hex']) === false) { + $matched[] = $checkpos; + } elseif (isset($this->OTLdata[$checkpos]) && strpos($Input[$i], $this->OTLdata[$checkpos]['hex']) !== false) { + $matched[] = $checkpos; + } else { + return false; + } + } + + // LOOKAHEAD + for ($i = 0; $i < count($Lookahead); $i++) { + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } // If Class 0 specified, matches anything NOT in $lclass0excl + elseif (!$Lookahead[$i] && isset($this->OTLdata[$checkpos]) && strpos($lclass0excl, $this->OTLdata[$checkpos]['hex']) !== false) { + return false; + } elseif (!isset($this->OTLdata[$checkpos]) || strpos($Lookahead[$i], $this->OTLdata[$checkpos]['hex']) === false) { + return false; + } + } + return $matched; + } + + private function checkContextMatchMultipleUni($Input, $Backtrack, $Lookahead, $ignore, $ptr, $class0excl = [], $bclass0excl = [], $lclass0excl = []) + { + // Input etc are array of glyphs - GSUB Format 5.2, 5.3, 6.2, 6.3, GPOS Format 7.2, 7.3, 8.2, 8.3 + // Input starts with (1=>xxx) + // return false if no match, else an array of ptr for matches (0=>0, 1=>3,...) + // $class0excl is array of glyphs in all classes except Class 0 (GSUB 5.2, 6.2, GPOS 7.2, 8.2) + // $bclass0excl & $lclass0excl are the same for lookahead and backtrack (GSUB 6.2, GPOS 8.2) + + $current_syllable = (isset($this->OTLdata[$ptr]['syllable']) ? $this->OTLdata[$ptr]['syllable'] : 0); + + // BACKTRACK + $checkpos = $ptr; + for ($i = 0; $i < count($Backtrack); $i++) { + $checkpos--; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos--; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } // If Class 0 specified, matches anything NOT in $bclass0excl + elseif (!$Backtrack[$i] && isset($this->OTLdata[$checkpos]) && isset($bclass0excl[$this->OTLdata[$checkpos]['uni']])) { + return false; + } elseif (!isset($this->OTLdata[$checkpos]) || !isset($Backtrack[$i][$this->OTLdata[$checkpos]['uni']])) { + return false; + } + } + + // INPUT + $matched = [0 => $ptr]; + $checkpos = $ptr; + for ($i = 1; $i < count($Input); $i++) { // Start at 1 - already matched the first InputGlyph + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } // If Input Class 0 specified, matches anything NOT in $class0excl + elseif (!$Input[$i] && isset($this->OTLdata[$checkpos]) && !isset($class0excl[$this->OTLdata[$checkpos]['uni']])) { + $matched[] = $checkpos; + } elseif (isset($this->OTLdata[$checkpos]) && isset($Input[$i][$this->OTLdata[$checkpos]['uni']])) { + $matched[] = $checkpos; + } else { + return false; + } + } + + // LOOKAHEAD + for ($i = 0; $i < count($Lookahead); $i++) { + $checkpos++; + while (isset($this->OTLdata[$checkpos]) && strpos($ignore, $this->OTLdata[$checkpos]['hex']) !== false) { + $checkpos++; + } + // If outside scope of current syllable - return no match + if ($this->restrictToSyllable && isset($this->OTLdata[$checkpos]['syllable']) && $this->OTLdata[$checkpos]['syllable'] != $current_syllable) { + return false; + } // If Class 0 specified, matches anything NOT in $lclass0excl + elseif (!$Lookahead[$i] && isset($this->OTLdata[$checkpos]) && isset($lclass0excl[$this->OTLdata[$checkpos]['uni']])) { + return false; + } elseif (!isset($this->OTLdata[$checkpos]) || !isset($Lookahead[$i][$this->OTLdata[$checkpos]['uni']])) { + return false; + } + } + return $matched; + } + + private function _getClassDefinitionTable($offset) + { + if (isset($this->LuDataCache[$this->fontkey][$offset])) { + $GlyphByClass = $this->LuDataCache[$this->fontkey][$offset]; + } else { + $this->seek($offset); + $ClassFormat = $this->read_ushort(); + $GlyphClass = []; + // $GlyphByClass = array(0=>array()); // NB This forces an index[0] + if ($ClassFormat == 1) { + $StartGlyph = $this->read_ushort(); + $GlyphCount = $this->read_ushort(); + for ($i = 0; $i < $GlyphCount; $i++) { + $GlyphClass[$i]['startGlyphID'] = $StartGlyph + $i; + $GlyphClass[$i]['endGlyphID'] = $StartGlyph + $i; + $GlyphClass[$i]['class'] = $this->read_ushort(); + for ($g = $GlyphClass[$i]['startGlyphID']; $g <= $GlyphClass[$i]['endGlyphID']; $g++) { + $GlyphByClass[$GlyphClass[$i]['class']][] = $this->glyphToChar($g); + } + } + } elseif ($ClassFormat == 2) { + $tableCount = $this->read_ushort(); + for ($i = 0; $i < $tableCount; $i++) { + $GlyphClass[$i]['startGlyphID'] = $this->read_ushort(); + $GlyphClass[$i]['endGlyphID'] = $this->read_ushort(); + $GlyphClass[$i]['class'] = $this->read_ushort(); + for ($g = $GlyphClass[$i]['startGlyphID']; $g <= $GlyphClass[$i]['endGlyphID']; $g++) { + $GlyphByClass[$GlyphClass[$i]['class']][] = $this->glyphToChar($g); + } + } + } + ksort($GlyphByClass); + $this->LuDataCache[$this->fontkey][$offset] = $GlyphByClass; + } + return $GlyphByClass; + } + + private function count_bits($n) + { + for ($c = 0; $n; $c++) { + $n &= $n - 1; // clear the least significant bit set + } + return $c; + } + + private function _getValueRecord($ValueFormat) + { + // Common ValueRecord for GPOS + // Only returns 3 possible: $vra['XPlacement'] $vra['YPlacement'] $vra['XAdvance'] + $vra = []; + // Horizontal adjustment for placement - in design units + if (($ValueFormat & 0x0001) == 0x0001) { + $vra['XPlacement'] = $this->read_short(); + } + // Vertical adjustment for placement - in design units + if (($ValueFormat & 0x0002) == 0x0002) { + $vra['YPlacement'] = $this->read_short(); + } + // Horizontal adjustment for advance - in design units (only used for horizontal writing) + if (($ValueFormat & 0x0004) == 0x0004) { + $vra['XAdvance'] = $this->read_short(); + } + // Vertical adjustment for advance - in design units (only used for vertical writing) + if (($ValueFormat & 0x0008) == 0x0008) { + $this->read_short(); + } + // Offset to Device table for horizontal placement-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0010) == 0x0010) { + $this->read_ushort(); + } + // Offset to Device table for vertical placement-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0020) == 0x0020) { + $this->read_ushort(); + } + // Offset to Device table for horizontal advance-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0040) == 0x0040) { + $this->read_ushort(); + } + // Offset to Device table for vertical advance-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0080) == 0x0080) { + $this->read_ushort(); + } + return $vra; + } + + private function _getAnchorTable($offset = 0) + { + if ($offset) { + $this->seek($offset); + } + $AnchorFormat = $this->read_ushort(); + $XCoordinate = $this->read_short(); + $YCoordinate = $this->read_short(); + // Format 2 specifies additional link to contour point; Format 3 additional Device table + return [$XCoordinate, $YCoordinate]; + } + + private function _getMarkRecord($offset, $MarkPos) + { + $this->seek($offset); + $MarkCount = $this->read_ushort(); + $this->skip($MarkPos * 4); + $Class = $this->read_ushort(); + $MarkAnchor = $offset + $this->read_ushort(); // = Offset to anchor table + list($x, $y) = $this->_getAnchorTable($MarkAnchor); + $MarkRecord = ['Class' => $Class, 'AnchorX' => $x, 'AnchorY' => $y]; + return $MarkRecord; + } + + private function _getGCOMignoreString($flag, $MarkFilteringSet) + { + // If ignoreFlag set, combine all ignore glyphs into -> "(?:( 0FBA1| 0FBA2| 0FBA3)*)" + // else "()" + // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup + $str = ""; + $ignoreflag = 0; + + // Flag & 0xFF?? = MarkAttachmentType + if ($flag & 0xFF00) { + // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class" + // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table + $MarkAttachmentType = $flag >> 8; + $ignoreflag = $flag; + $str = $this->MarkAttachmentType[$MarkAttachmentType]; + } + + // Flag & 0x0010 = UseMarkFilteringSet + if ($flag & 0x0010) { + throw new \Mpdf\MpdfException("This font [" . $this->fontkey . "] contains MarkGlyphSets - Not tested yet"); + // Change also in ttfontsuni.php + if ($MarkFilteringSet == '') { + throw new \Mpdf\MpdfException("This font [" . $this->fontkey . "] contains MarkGlyphSets - but MarkFilteringSet not set"); + } + $str = $this->MarkGlyphSets[$MarkFilteringSet]; + } + + // If Ignore Marks set, supercedes any above + // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType) + if (($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) { + $ignoreflag = 8; + $str = $this->GlyphClassMarks; + } + + // Flag & 0x0004 = Ignore Ligatures + if (($flag & 0x0004) == 0x0004) { + $ignoreflag += 4; + if ($str) { + $str .= "|"; + } + $str .= $this->GlyphClassLigatures; + } + // Flag & 0x0002 = Ignore BaseGlyphs + if (($flag & 0x0002) == 0x0002) { + $ignoreflag += 2; + if ($str) { + $str .= "|"; + } + $str .= $this->GlyphClassBases; + } + if ($str) { + return "((?:(?:" . $str . "))*)"; + } else { + return "()"; + } + } + + private function _checkGCOMignore($flag, $glyph, $MarkFilteringSet) + { + $ignore = false; + // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType) + if (($flag & 0x0008 && ($flag & 0xFF00) == 0) && strpos($this->GlyphClassMarks, $glyph)) { + $ignore = true; + } + if (($flag & 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) { + $ignore = true; + } + if (($flag & 0x0002) && strpos($this->GlyphClassBases, $glyph)) { + $ignore = true; + } + // Flag & 0xFF?? = MarkAttachmentType + if ($flag & 0xFF00) { + // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class" + // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table + if (strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) { + $ignore = true; + } + } + // Flag & 0x0010 = UseMarkFilteringSet + if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) { + $ignore = true; + } + return $ignore; + } + + /** + * Bidi algorithm + * + * These functions are called from mpdf after GSUB/GPOS has taken place + * At this stage the bidi-type is in string form + * + * Bidirectional Character Types + * ============================= + * Type Description General Scope + * Strong + * L Left-to-Right LRM, most alphabetic, syllabic, Han ideographs, non-European or non-Arabic digits, ... + * LRE Left-to-Right Embedding LRE + * LRO Left-to-Right Override LRO + * R Right-to-Left RLM, Hebrew alphabet, and related punctuation + * AL Right-to-Left Arabic Arabic, Thaana, and Syriac alphabets, most punctuation specific to those scripts, ... + * RLE Right-to-Left Embedding RLE + * RLO Right-to-Left Override RLO + * Weak + * PDF Pop Directional Format PDF + * EN European Number European digits, Eastern Arabic-Indic digits, ... + * ES European Number Separator Plus sign, minus sign + * ET European Number Terminator Degree sign, currency symbols, ... + * AN Arabic Number Arabic-Indic digits, Arabic decimal and thousands separators, ... + * CS Common Number Separator Colon, comma, full stop (period), No-break space, ... + * NSM Nonspacing Mark Characters marked Mn (Nonspacing_Mark) and Me (Enclosing_Mark) in the Unicode Character Database + * BN Boundary Neutral Default ignorables, non-characters, and control characters, other than those explicitly given other types. + * Neutral + * B Paragraph Separator Paragraph separator, appropriate Newline Functions, higher-level protocol paragraph determination + * S Segment Separator Tab + * WS Whitespace Space, figure space, line separator, form feed, General Punctuation spaces, ... + * ON Other Neutrals All other characters, including OBJECT REPLACEMENT CHARACTER + */ + public function bidiSort($ta, $str, $dir, &$chunkOTLdata, $useGPOS) + { + + $pel = 0; // paragraph embedding level + $maxlevel = 0; + $numchars = count($chunkOTLdata['char_data']); + + // Set the initial paragraph embedding level + if ($dir == 'rtl') { + $pel = 1; + } else { + $pel = 0; + } + + // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. + // Current Embedding Level + $cel = $pel; + // directional override status (-1 is Neutral) + $dos = -1; + $remember = []; + + // Array of characters data + $chardata = []; + + // 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 ($chunkOTLdata['char_data'][$i]['uni'] == 8235) { // 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[] = ['num' => 8235, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = -1; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8234) { // 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[] = ['num' => 8234, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = -1; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8238) { // 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[] = ['num' => 8238, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = Ucdn::BIDI_CLASS_R; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8237) { // 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[] = ['num' => 8237, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = Ucdn::BIDI_CLASS_L; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8236) { // 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'] == 8235) || ($remember[$last]['num'] == 8234) || ($remember[$last]['num'] == 8238) || + ($remember[$last]['num'] == 8237)) { + $match = array_pop($remember); + $cel = $match['cel']; + $dos = $match['dos']; + } + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 10) { // NEW LINE + // Reset to start values + $cel = $pel; + $dos = -1; + $remember = []; + } else { + // 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. When the directional override status is not neutral, reset the current character type to directional override status. + if ($dos != -1) { + $chardir = $dos; + } else { + $chardir = $chunkOTLdata['char_data'][$i]['bidi_class']; + } + // stores string characters and other information + if (isset($chunkOTLdata['GPOSinfo'][$i])) { + $gpos = $chunkOTLdata['GPOSinfo'][$i]; + } else { + $gpos = ''; + } + $chardata[] = ['char' => $chunkOTLdata['char_data'][$i]['uni'], 'level' => $cel, 'type' => $chardir, 'group' => $chunkOTLdata['group'][$i], 'GPOSinfo' => $gpos]; + } + } + + $numchars = count($chardata); + + // 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, and PDF codes. + // This is effectively done by only saving other codes to chardata + // X10. Determine the start-of-sequence (sor) and end-of-sequence (eor) types, either L or R, for each isolating run sequence. These depend on the higher of the two levels on either side of the sequence boundary: + // For sor, compare the level of the first character in the sequence with the level of the character preceding it in the paragraph or if there is none, with the paragraph embedding level. + // For eor, compare the level of the last character in the sequence with the level of the character following it in the paragraph or if there is none, with the paragraph embedding level. + // If the higher level is odd, the sor or eor is R; otherwise, it is L. + + $prelevel = $pel; + $postlevel = $pel; + $cel = $prelevel; // current embedding level + for ($i = 0; $i < $numchars; ++$i) { + $level = $chardata[$i]['level']; + if ($i == 0) { + $left = $prelevel; + } else { + $left = $chardata[$i - 1]['level']; + } + if ($i == ($numchars - 1)) { + $right = $postlevel; + } else { + $right = $chardata[$i + 1]['level']; + } + $chardata[$i]['sor'] = max($left, $level) % 2 ? Ucdn::BIDI_CLASS_R : Ucdn::BIDI_CLASS_L; + $chardata[$i]['eor'] = max($right, $level) % 2 ? Ucdn::BIDI_CLASS_R : Ucdn::BIDI_CLASS_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. + // 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. + for ($i = 0; $i < $numchars; ++$i) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_NSM) { + if ($i == 0 || $chardata[$i]['level'] != $chardata[$i - 1]['level']) { + $chardata[$i]['type'] = $chardata[$i]['sor']; + } else { + $chardata[$i]['type'] = $chardata[($i - 1)]['type']; + } + } + } + + // 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]['type'] == Ucdn::BIDI_CLASS_EN) { + $found = false; + for ($j = $levcount; $j >= 0; $j--) { + if ($chardata[$j]['type'] == Ucdn::BIDI_CLASS_AL) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_AN; + $found = true; + break; + } elseif (($chardata[$j]['type'] == Ucdn::BIDI_CLASS_L) || ($chardata[$j]['type'] == Ucdn::BIDI_CLASS_R)) { + $found = true; + 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'] == Ucdn::BIDI_CLASS_AL) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_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. + for ($i = 1; $i < $numchars; ++$i) { + if (($i + 1) < $numchars && $chardata[($i)]['level'] == $chardata[($i + 1)]['level'] && $chardata[($i)]['level'] == $chardata[($i - 1)]['level']) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ES && $chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_EN && $chardata[($i + 1)]['type'] == Ucdn::BIDI_CLASS_EN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + } elseif ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_CS && $chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_EN && $chardata[($i + 1)]['type'] == Ucdn::BIDI_CLASS_EN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + } elseif ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_CS && $chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_AN && $chardata[($i + 1)]['type'] == Ucdn::BIDI_CLASS_AN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_AN; + } + } + } + + // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers. + for ($i = 0; $i < $numchars; ++$i) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ET) { + if ($i > 0 && $chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_EN && $chardata[($i)]['level'] == $chardata[($i - 1)]['level']) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + } else { + $j = $i + 1; + while ($j < $numchars && $chardata[$j]['level'] == $chardata[$i]['level']) { + if ($chardata[$j]['type'] == Ucdn::BIDI_CLASS_EN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + break; + } elseif ($chardata[$j]['type'] != Ucdn::BIDI_CLASS_ET) { + break; + } + ++$j; + } + } + } + } + + // W6. Otherwise, separators and terminators change to Other Neutral. + for ($i = 0; $i < $numchars; ++$i) { + if (($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ET) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ES) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_CS)) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_ON; + } + } + + //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. + for ($i = 0; $i < $numchars; ++$i) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN) { + if ($i == 0) { // Start of Level run + if ($chardata[$i]['sor'] == Ucdn::BIDI_CLASS_L) { + $chardata[$i]['type'] = $chardata[$i]['sor']; + } + } else { + for ($j = $i - 1; $j >= 0; $j--) { + if ($chardata[$j]['level'] != $chardata[$i]['level']) { // Level run boundary + if ($chardata[$j + 1]['sor'] == Ucdn::BIDI_CLASS_L) { + $chardata[$i]['type'] = $chardata[$j + 1]['sor']; + } + break; + } elseif ($chardata[$j]['type'] == Ucdn::BIDI_CLASS_L) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_L; + break; + } elseif ($chardata[$j]['type'] == Ucdn::BIDI_CLASS_R) { + break; + } + } + } + } + } + + // 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. + for ($i = 0; $i < $numchars; ++$i) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ON || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_WS) { + $left = -1; + // LEFT + if ($i == 0) { // first char + $left = $chardata[($i)]['sor']; + } elseif ($chardata[($i - 1)]['level'] != $chardata[($i)]['level']) { // run boundary + $left = $chardata[($i)]['sor']; + } elseif ($chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_L) { + $left = Ucdn::BIDI_CLASS_L; + } elseif ($chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_R || $chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_EN || $chardata[($i - 1)]['type'] == Ucdn::BIDI_CLASS_AN) { + $left = Ucdn::BIDI_CLASS_R; + } + // RIGHT + $right = -1; + $j = $i; + // move to the right of any following neutrals OR hit a run boundary + while (($chardata[$j]['type'] == Ucdn::BIDI_CLASS_ON || $chardata[$j]['type'] == Ucdn::BIDI_CLASS_WS) && $j <= ($numchars - 1)) { + if ($j == ($numchars - 1)) { // last char + $right = $chardata[($j)]['eor']; + break; + } elseif ($chardata[($j + 1)]['level'] != $chardata[($j)]['level']) { // run boundary + $right = $chardata[($j)]['eor']; + break; + } elseif ($chardata[($j + 1)]['type'] == Ucdn::BIDI_CLASS_L) { + $right = Ucdn::BIDI_CLASS_L; + break; + } elseif ($chardata[($j + 1)]['type'] == Ucdn::BIDI_CLASS_R || $chardata[($j + 1)]['type'] == Ucdn::BIDI_CLASS_EN || $chardata[($j + 1)]['type'] == Ucdn::BIDI_CLASS_AN) { + $right = Ucdn::BIDI_CLASS_R; + break; + } + $j++; + } + if ($left > -1 && $left == $right) { + $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below + $chardata[$i]['type'] = $left; + } + } + } + + // N2. Any remaining neutrals take the embedding direction + for ($i = 0; $i < $numchars; ++$i) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ON || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_WS) { + $chardata[$i]['type'] = ($chardata[$i]['level'] % 2) ? Ucdn::BIDI_CLASS_R : Ucdn::BIDI_CLASS_L; + $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below + } + } + + // 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'] == Ucdn::BIDI_CLASS_L) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_AN) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN)) { + $chardata[$i]['level'] += 1; + } + } else { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_R) { + $chardata[$i]['level'] += 1; + } elseif (($chardata[$i]['type'] == Ucdn::BIDI_CLASS_AN) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN)) { + $chardata[$i]['level'] += 2; + } + } + $maxlevel = max($chardata[$i]['level'], $maxlevel); + } + + // NB + // Separate into lines at this point************ + // + // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level: + // 1. Segment separators (Tab) 'S', + // 2. Paragraph separators 'B', + // 3. Any sequence of whitespace characters 'WS' preceding a segment separator or paragraph separator, and + // 4. Any sequence of whitespace characters 'WS' at the end of the line. + // The types of characters used here are the original types, not those modified by the previous phase cf N1 and N2******* + // Because a Paragraph Separator breaks lines, there will be at most one per line, at the end of that line. + + for ($i = ($numchars - 1); $i > 0; $i--) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_WS || (isset($chardata[$i]['orig_type']) && $chardata[$i]['orig_type'] == Ucdn::BIDI_CLASS_WS)) { + $chardata[$i]['level'] = $pel; + } else { + break; + } + } + + + // 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 = []; + $revarr = []; + $onlevel = false; + for ($i = 0; $i < $numchars; ++$i) { + if ($chardata[$i]['level'] >= $j) { + $onlevel = true; + + // 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. + if (isset(Ucdn::$mirror_pairs[$chardata[$i]['char']]) && $chardata[$i]['type'] == Ucdn::BIDI_CLASS_R) { + $chardata[$i]['char'] = Ucdn::$mirror_pairs[$chardata[$i]['char']]; + } + + $revarr[] = $chardata[$i]; + } else { + if ($onlevel) { + $revarr = array_reverse($revarr); + $ordarray = array_merge($ordarray, $revarr); + $revarr = []; + $onlevel = false; + } + $ordarray[] = $chardata[$i]; + } + } + if ($onlevel) { + $revarr = array_reverse($revarr); + $ordarray = array_merge($ordarray, $revarr); + } + $chardata = $ordarray; + } + + $group = ''; + $e = ''; + $GPOS = []; + $cctr = 0; + $rtl_content = 0x0; + foreach ($chardata as $cd) { + $e .= UtfString::code2utf($cd['char']); + $group .= $cd['group']; + if ($useGPOS && is_array($cd['GPOSinfo'])) { + $GPOS[$cctr] = $cd['GPOSinfo']; + $GPOS[$cctr]['wDir'] = ($cd['level'] % 2) ? 'RTL' : 'LTR'; + } + if ($cd['type'] == Ucdn::BIDI_CLASS_L) { + $rtl_content |= 1; + } elseif ($cd['type'] == Ucdn::BIDI_CLASS_R) { + $rtl_content |= 2; + } + $cctr++; + } + + + $chunkOTLdata['group'] = $group; + if ($useGPOS) { + $chunkOTLdata['GPOSinfo'] = $GPOS; + } + + return [$e, $rtl_content]; + } + + /** + * The following versions for BidiSort work on amalgamated chunks to process the whole paragraph + * + * Firstly set the level in the OTLdata - called from fn printbuffer() [_bidiPrepare] + * Secondly re-order - called from fn writeFlowingBlock and FinishFlowingBlock, when already divided into lines. [_bidiReorder] + */ + public function bidiPrepare(&$para, $dir) + { + + // Set the initial paragraph embedding level + $pel = 0; // paragraph embedding level + if ($dir == 'rtl') { + $pel = 1; + } + + // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. + // Current Embedding Level + $cel = $pel; + // directional override status (-1 is Neutral) + $dos = -1; + $remember = []; + $controlchars = false; + $strongrtl = false; + $diid = 0; // direction isolate ID + $dictr = 0; // direction isolate counter + // 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. + $numchunks = count($para); + for ($nc = 0; $nc < $numchunks; $nc++) { + $chunkOTLdata = & $para[$nc][18]; + + $numchars = count($chunkOTLdata['char_data']); + for ($i = 0; $i < $numchars; ++$i) { + if ($chunkOTLdata['char_data'][$i]['uni'] == 8235) { // 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[] = ['num' => 8235, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = -1; + $controlchars = true; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8234) { // 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[] = ['num' => 8234, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = -1; + $controlchars = true; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8238) { // 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[] = ['num' => 8238, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = Ucdn::BIDI_CLASS_R; + $controlchars = true; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8237) { // 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[] = ['num' => 8237, 'cel' => $cel, 'dos' => $dos]; + $cel = $next_level; + $dos = Ucdn::BIDI_CLASS_L; + $controlchars = true; + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8236) { // 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'] == 8235) || ($remember[$last]['num'] == 8234) || ($remember[$last]['num'] == 8238) || + ($remember[$last]['num'] == 8237)) { + $match = array_pop($remember); + $cel = $match['cel']; + $dos = $match['dos']; + } + } + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8294 || $chunkOTLdata['char_data'][$i]['uni'] == 8295 || + $chunkOTLdata['char_data'][$i]['uni'] == 8296) { // LRI // RLI // FSI + // X5a. With each RLI: + // X5b. With each LRI: + // X5c. With each FSI, apply rules P2 and P3 for First Strong character + // Set the RLI/LRI/FSI embedding level to the embedding level of the last entry on the directional status stack. + if ($dos != -1) { + $chardir = $dos; + } else { + $chardir = $chunkOTLdata['char_data'][$i]['bidi_class']; + } + $chunkOTLdata['char_data'][$i]['level'] = $cel; + $chunkOTLdata['char_data'][$i]['type'] = $chardir; + $chunkOTLdata['char_data'][$i]['diid'] = $diid; + + $fsi = ''; + // X5c. With each FSI, apply rules P2 and P3 within the isolate run for First Strong character + if ($chunkOTLdata['char_data'][$i]['uni'] == 8296) { // FSI + $lvl = 0; + $nc2 = $nc; + $i2 = $i; + while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk + $i2++; + if ($i2 >= count($para[$nc2][18]['char_data'])) { + $nc2++; + $i2 = 0; + } + if ($lvl > 0) { + continue; + } + if ($para[$nc2][18]['char_data'][$i2]['uni'] == 8294 || $para[$nc2][18]['char_data'][$i2]['uni'] == 8295 || $para[$nc2][18]['char_data'][$i2]['uni'] == 8296) { + $lvl++; + continue; + } + if ($para[$nc2][18]['char_data'][$i2]['uni'] == 8297) { + $lvl--; + if ($lvl < 0) { + break; + } + } + if ($para[$nc2][18]['char_data'][$i2]['bidi_class'] === Ucdn::BIDI_CLASS_L || $para[$nc2][18]['char_data'][$i2]['bidi_class'] == Ucdn::BIDI_CLASS_AL || $para[$nc2][18]['char_data'][$i2]['bidi_class'] === Ucdn::BIDI_CLASS_R) { + $fsi = $para[$nc2][18]['char_data'][$i2]['bidi_class']; + break; + } + } + // if fsi not found, fsi is same as paragraph embedding level + if (!$fsi && $fsi !== 0) { + if ($pel == 1) { + $fsi = Ucdn::BIDI_CLASS_R; + } else { + $fsi = Ucdn::BIDI_CLASS_L; + } + } + } + + if ($chunkOTLdata['char_data'][$i]['uni'] == 8294 || $fsi === Ucdn::BIDI_CLASS_L) { // LRI or FSI-L + // Compute the least even embedding level greater than the embedding level of the last entry on the directional status stack. + $next_level = $cel + 2 - ($cel % 2); + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8295 || $fsi == Ucdn::BIDI_CLASS_R || $fsi == Ucdn::BIDI_CLASS_AL) { // RLI or FSI-R + // Compute the least odd embedding level greater than the embedding level of the last entry on the directional status stack. + $next_level = $cel + ($cel % 2) + 1; + } + + + // Increment the isolate count by one, and push an entry consisting of the new embedding level, + // neutral directional override status, and true directional isolate status onto the directional status stack. + $remember[] = ['num' => $chunkOTLdata['char_data'][$i]['uni'], 'cel' => $cel, 'dos' => $dos, 'diid' => $diid]; + $cel = $next_level; + $dos = -1; + $diid = ++$dictr; // Set new direction isolate ID after incrementing direction isolate counter + + $controlchars = true; + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 8297) { // PDI + // X6a. With each PDI, perform the following steps: + // Pop the last entry from the directional status stack and decrement the isolate count by one. + while (count($remember)) { + $last = count($remember) - 1; + if (($remember[$last]['num'] == 8294) || ($remember[$last]['num'] == 8295) || ($remember[$last]['num'] == 8296)) { + $match = array_pop($remember); + $cel = $match['cel']; + $dos = $match['dos']; + $diid = $match['diid']; + break; + } // End/close any open embedding states not explicitly closed during the isolate + elseif (($remember[$last]['num'] == 8235) || ($remember[$last]['num'] == 8234) || ($remember[$last]['num'] == 8238) || + ($remember[$last]['num'] == 8237)) { + $match = array_pop($remember); + } + } + // In all cases, set the PDI’s level to the embedding level of the last entry on the directional status stack left after the steps above. + // NB The level assigned to an isolate initiator is always the same as that assigned to the matching PDI. + if ($dos != -1) { + $chardir = $dos; + } else { + $chardir = $chunkOTLdata['char_data'][$i]['bidi_class']; + } + $chunkOTLdata['char_data'][$i]['level'] = $cel; + $chunkOTLdata['char_data'][$i]['type'] = $chardir; + $chunkOTLdata['char_data'][$i]['diid'] = $diid; + $controlchars = true; + } elseif ($chunkOTLdata['char_data'][$i]['uni'] == 10) { // NEW LINE + // Reset to start values + $cel = $pel; + $dos = -1; + $remember = []; + } else { + // 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. When the directional override status is not neutral, reset the current character type to directional override status. + if ($dos != -1) { + $chardir = $dos; + } else { + $chardir = $chunkOTLdata['char_data'][$i]['bidi_class']; + if ($chardir == Ucdn::BIDI_CLASS_R || $chardir == Ucdn::BIDI_CLASS_AL) { + $strongrtl = true; + } + } + $chunkOTLdata['char_data'][$i]['level'] = $cel; + $chunkOTLdata['char_data'][$i]['type'] = $chardir; + $chunkOTLdata['char_data'][$i]['diid'] = $diid; + } + } + // 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, and PDF codes. + if ($controlchars) { + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xaa"); + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xab"); + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xac"); + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xad"); + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xae"); + preg_replace("/\x{202a}-\x{202e}/u", '', $para[$nc][0]); + } + } + + // Remove any blank chunks made by removing directional codes + $numchunks = count($para); + for ($nc = ($numchunks - 1); $nc >= 0; $nc--) { + if (count($para[$nc][18]['char_data']) == 0) { + array_splice($para, $nc, 1); + } + } + if ($dir != 'rtl' && !$strongrtl && !$controlchars) { + return; + } + + $numchunks = count($para); + + // X10. Determine the start-of-sequence (sor) and end-of-sequence (eor) types, either L or R, for each isolating run sequence. These depend on the higher of the two levels on either side of the sequence boundary: + // For sor, compare the level of the first character in the sequence with the level of the character preceding it in the paragraph or if there is none, with the paragraph embedding level. + // For eor, compare the level of the last character in the sequence with the level of the character following it in the paragraph or if there is none, with the paragraph embedding level. + // If the higher level is odd, the sor or eor is R; otherwise, it is L. + + for ($ir = 0; $ir <= $dictr; $ir++) { + $prelevel = $pel; + $postlevel = $pel; + $firstchar = true; + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) { + continue; + } // Ignore characters in a different isolate run + $right = $postlevel; + $nc2 = $nc; + $i2 = $i; + while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk + $i2++; + if ($i2 >= count($para[$nc2][18]['char_data'])) { + $nc2++; + $i2 = 0; + } + + if (isset($para[$nc2][18]['char_data'][$i2]['diid']) && $para[$nc2][18]['char_data'][$i2]['diid'] == $ir) { + $right = $para[$nc2][18]['char_data'][$i2]['level']; + break; + } + } + + $level = $chardata[$i]['level']; + if ($firstchar || $level != $prelevel) { + $chardata[$i]['sor'] = max($prelevel, $level) % 2 ? Ucdn::BIDI_CLASS_R : Ucdn::BIDI_CLASS_L; + } + if (($nc == ($numchunks - 1) && $i == ($numchars - 1)) || $level != $right) { + $chardata[$i]['eor'] = max($right, $level) % 2 ? Ucdn::BIDI_CLASS_R : Ucdn::BIDI_CLASS_L; + } + $prelevel = $level; + $firstchar = false; + } + } + } + + + // 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. + // 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. + for ($ir = 0; $ir <= $dictr; $ir++) { + $prevtype = 0; + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) { + continue; + } // Ignore characters in a different isolate run + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_NSM) { + if (isset($chardata[$i]['sor'])) { + $chardata[$i]['type'] = $chardata[$i]['sor']; + } else { + $chardata[$i]['type'] = $prevtype; + } + } + $prevtype = $chardata[$i]['type']; + } + } + } + + // 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. + for ($ir = 0; $ir <= $dictr; $ir++) { + $laststrongtype = -1; + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) { + continue; + } // Ignore characters in a different isolate run + if (isset($chardata[$i]['sor'])) { + $laststrongtype = $chardata[$i]['sor']; + } + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN && $laststrongtype == Ucdn::BIDI_CLASS_AL) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_AN; + } + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_L || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_R || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_AL) { + $laststrongtype = $chardata[$i]['type']; + } + } + } + } + + + // W3. Change all ALs to R. + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (isset($chardata[$i]['type']) && $chardata[$i]['type'] == Ucdn::BIDI_CLASS_AL) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_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. + for ($ir = 0; $ir <= $dictr; $ir++) { + $prevtype = -1; + $nexttype = -1; + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) { + continue; + } // Ignore characters in a different isolate run + // Get next type + $nexttype = -1; + $nc2 = $nc; + $i2 = $i; + while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk + $i2++; + if ($i2 >= count($para[$nc2][18]['char_data'])) { + $nc2++; + $i2 = 0; + } + + if (isset($para[$nc2][18]['char_data'][$i2]['diid']) && $para[$nc2][18]['char_data'][$i2]['diid'] == $ir) { + $nexttype = $para[$nc2][18]['char_data'][$i2]['type']; + break; + } + } + + if (!isset($chardata[$i]['sor']) && !isset($chardata[$i]['eor'])) { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ES && $prevtype == Ucdn::BIDI_CLASS_EN && $nexttype == Ucdn::BIDI_CLASS_EN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + } elseif ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_CS && $prevtype == Ucdn::BIDI_CLASS_EN && $nexttype == Ucdn::BIDI_CLASS_EN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + } elseif ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_CS && $prevtype == Ucdn::BIDI_CLASS_AN && $nexttype == Ucdn::BIDI_CLASS_AN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_AN; + } + } + $prevtype = $chardata[$i]['type']; + } + } + } + + // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers. + for ($ir = 0; $ir <= $dictr; $ir++) { + $prevtype = -1; + $nexttype = -1; + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) { + continue; + } // Ignore characters in a different isolate run + if (isset($chardata[$i]['sor'])) { + $prevtype = $chardata[$i]['sor']; + } + + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ET) { + if ($prevtype == Ucdn::BIDI_CLASS_EN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + } elseif (!isset($chardata[$i]['eor'])) { + $nexttype = -1; + $nc2 = $nc; + $i2 = $i; + while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk + $i2++; + if ($i2 >= count($para[$nc2][18]['char_data'])) { + $nc2++; + $i2 = 0; + } + if (!isset($para[$nc2][18]['char_data'][$i2]['diid']) || $para[$nc2][18]['char_data'][$i2]['diid'] != $ir) { + continue; + } + $nexttype = $para[$nc2][18]['char_data'][$i2]['type']; + if (isset($para[$nc2][18]['char_data'][$i2]['sor'])) { + break; + } + if ($nexttype == Ucdn::BIDI_CLASS_EN) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_EN; + break; + } elseif ($nexttype != Ucdn::BIDI_CLASS_ET) { + break; + } + } + } + } + $prevtype = $chardata[$i]['type']; + } + } + } + + // W6. Otherwise, separators and terminators change to Other Neutral. + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (isset($chardata[$i]['type']) && (($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ET) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ES) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_CS))) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_ON; + } + } + } + + //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. + for ($ir = 0; $ir <= $dictr; $ir++) { + $laststrongtype = -1; + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) { + continue; + } // Ignore characters in a different isolate run + if (isset($chardata[$i]['sor'])) { + $laststrongtype = $chardata[$i]['sor']; + } + if (isset($chardata[$i]['type']) && $chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN && $laststrongtype == Ucdn::BIDI_CLASS_L) { + $chardata[$i]['type'] = Ucdn::BIDI_CLASS_L; + } + if (isset($chardata[$i]['type']) && ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_L || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_R || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_AL)) { + $laststrongtype = $chardata[$i]['type']; + } + } + } + } + + // 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. + for ($ir = 0; $ir <= $dictr; $ir++) { + $laststrongtype = -1; + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (!isset($chardata[$i]['diid']) || $chardata[$i]['diid'] != $ir) { + continue; + } // Ignore characters in a different isolate run + if (isset($chardata[$i]['sor'])) { + $laststrongtype = $chardata[$i]['sor']; + } + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ON || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_WS) { + $left = -1; + // LEFT + if ($laststrongtype == Ucdn::BIDI_CLASS_R || $laststrongtype == Ucdn::BIDI_CLASS_EN || $laststrongtype == Ucdn::BIDI_CLASS_AN) { + $left = Ucdn::BIDI_CLASS_R; + } elseif ($laststrongtype == Ucdn::BIDI_CLASS_L) { + $left = Ucdn::BIDI_CLASS_L; + } + // RIGHT + $right = -1; + // move to the right of any following neutrals OR hit a run boundary + + if (isset($chardata[$i]['eor'])) { + $right = $chardata[$i]['eor']; + } else { + $nexttype = -1; + $nc2 = $nc; + $i2 = $i; + while (!($nc2 == ($numchunks - 1) && $i2 == ((count($para[$nc2][18]['char_data'])) - 1))) { // while not at end of last chunk + $i2++; + if ($i2 >= count($para[$nc2][18]['char_data'])) { + $nc2++; + $i2 = 0; + } + if (!isset($para[$nc2][18]['char_data'][$i2]['diid']) || $para[$nc2][18]['char_data'][$i2]['diid'] != $ir) { + continue; + } + $nexttype = $para[$nc2][18]['char_data'][$i2]['type']; + if ($nexttype == Ucdn::BIDI_CLASS_R || $nexttype == Ucdn::BIDI_CLASS_EN || $nexttype == Ucdn::BIDI_CLASS_AN) { + $right = Ucdn::BIDI_CLASS_R; + break; + } elseif ($nexttype == Ucdn::BIDI_CLASS_L) { + $right = Ucdn::BIDI_CLASS_L; + break; + } elseif (isset($para[$nc2][18]['char_data'][$i2]['eor'])) { + $right = $para[$nc2][18]['char_data'][$i2]['eor']; + break; + } + } + } + + if ($left > -1 && $left == $right) { + $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below + $chardata[$i]['type'] = $left; + } + } elseif ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_L || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_R || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_AN) { + $laststrongtype = $chardata[$i]['type']; + } + } + } + } + + // N2. Any remaining neutrals take the embedding direction + for ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (isset($chardata[$i]['type']) && ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_ON || $chardata[$i]['type'] == Ucdn::BIDI_CLASS_WS)) { + $chardata[$i]['orig_type'] = $chardata[$i]['type']; // Need to store the original 'WS' for reference in L1 below + $chardata[$i]['type'] = ($chardata[$i]['level'] % 2) ? Ucdn::BIDI_CLASS_R : Ucdn::BIDI_CLASS_L; + } + } + } + + // 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 ($nc = 0; $nc < $numchunks; $nc++) { + $chardata = & $para[$nc][18]['char_data']; + $numchars = count($chardata); + for ($i = 0; $i < $numchars; ++$i) { + if (isset($chardata[$i]['level'])) { + $odd = $chardata[$i]['level'] % 2; + if ($odd) { + if (($chardata[$i]['type'] == Ucdn::BIDI_CLASS_L) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_AN) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN)) { + $chardata[$i]['level'] += 1; + } + } else { + if ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_R) { + $chardata[$i]['level'] += 1; + } elseif (($chardata[$i]['type'] == Ucdn::BIDI_CLASS_AN) || ($chardata[$i]['type'] == Ucdn::BIDI_CLASS_EN)) { + $chardata[$i]['level'] += 2; + } + } + } + } + } + + // Remove Isolate formatters + $numchunks = count($para); + if ($controlchars) { + for ($nc = 0; $nc < $numchunks; $nc++) { + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa6"); + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa7"); + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa8"); + $this->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa9"); + preg_replace("/\x{2066}-\x{2069}/u", '', $para[$nc][0]); + } + // Remove any blank chunks made by removing directional codes + for ($nc = ($numchunks - 1); $nc >= 0; $nc--) { + if (count($para[$nc][18]['char_data']) == 0) { + array_splice($para, $nc, 1); + } + } + } + } + + /** + * Reorder, once divided into lines + */ + public function bidiReorder(&$chunkorder, &$content, &$cOTLdata, $blockdir) + { + + $bidiData = []; + + // First combine into one array (and get the highest level in use) + $numchunks = count($content); + $maxlevel = 0; + for ($nc = 0; $nc < $numchunks; $nc++) { + $numchars = isset($cOTLdata[$nc]['char_data']) ? count($cOTLdata[$nc]['char_data']) : 0; + for ($i = 0; $i < $numchars; ++$i) { + $carac = []; + if (isset($cOTLdata[$nc]['GPOSinfo'][$i])) { + $carac['GPOSinfo'] = $cOTLdata[$nc]['GPOSinfo'][$i]; + } + $carac['uni'] = $cOTLdata[$nc]['char_data'][$i]['uni']; + if (isset($cOTLdata[$nc]['char_data'][$i]['type'])) { + $carac['type'] = $cOTLdata[$nc]['char_data'][$i]['type']; + } + if (isset($cOTLdata[$nc]['char_data'][$i]['level'])) { + $carac['level'] = $cOTLdata[$nc]['char_data'][$i]['level']; + } + if (isset($cOTLdata[$nc]['char_data'][$i]['orig_type'])) { + $carac['orig_type'] = $cOTLdata[$nc]['char_data'][$i]['orig_type']; + } + $carac['group'] = $cOTLdata[$nc]['group'][$i]; + $carac['chunkid'] = $chunkorder[$nc]; // gives font id and/or object ID + + $maxlevel = max((isset($carac['level']) ? $carac['level'] : 0), $maxlevel); + $bidiData[] = $carac; + } + } + if ($maxlevel == 0) { + return; + } + + $numchars = count($bidiData); + + // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level: + // 1. Segment separators (Tab) 'S', + // 2. Paragraph separators 'B', + // 3. Any sequence of whitespace characters 'WS' preceding a segment separator or paragraph separator, and + // 4. Any sequence of whitespace characters 'WS' at the end of the line. + // The types of characters used here are the original types, not those modified by the previous phase cf N1 and N2******* + // Because a Paragraph Separator breaks lines, there will be at most one per line, at the end of that line. + // Set the initial paragraph embedding level + if ($blockdir == 'rtl') { + $pel = 1; + } else { + $pel = 0; + } + + for ($i = ($numchars - 1); $i > 0; $i--) { + if ($bidiData[$i]['type'] == Ucdn::BIDI_CLASS_WS || (isset($bidiData[$i]['orig_type']) && $bidiData[$i]['orig_type'] == Ucdn::BIDI_CLASS_WS)) { + $bidiData[$i]['level'] = $pel; + } else { + break; + } + } + + // 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 = []; + $revarr = []; + $onlevel = false; + for ($i = 0; $i < $numchars; ++$i) { + if ($bidiData[$i]['level'] >= $j) { + $onlevel = true; + // 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. + if (isset(Ucdn::$mirror_pairs[$bidiData[$i]['uni']]) && $bidiData[$i]['type'] == Ucdn::BIDI_CLASS_R) { + $bidiData[$i]['uni'] = Ucdn::$mirror_pairs[$bidiData[$i]['uni']]; + } + + $revarr[] = $bidiData[$i]; + } else { + if ($onlevel) { + $revarr = array_reverse($revarr); + $ordarray = array_merge($ordarray, $revarr); + $revarr = []; + $onlevel = false; + } + $ordarray[] = $bidiData[$i]; + } + } + if ($onlevel) { + $revarr = array_reverse($revarr); + $ordarray = array_merge($ordarray, $revarr); + } + $bidiData = $ordarray; + } + + $content = []; + $cOTLdata = []; + $chunkorder = []; + + $nc = -1; // New chunk order ID + $chunkid = -1; + + foreach ($bidiData as $carac) { + if ($carac['chunkid'] != $chunkid) { + $nc++; + $chunkorder[$nc] = $carac['chunkid']; + $cctr = 0; + $content[$nc] = ''; + $cOTLdata[$nc]['group'] = ''; + } + if ($carac['uni'] != 0xFFFC) { // Object replacement character (65532) + $content[$nc] .= UtfString::code2utf($carac['uni']); + $cOTLdata[$nc]['group'] .= $carac['group']; + if (!empty($carac['GPOSinfo'])) { + if (isset($carac['GPOSinfo'])) { + $cOTLdata[$nc]['GPOSinfo'][$cctr] = $carac['GPOSinfo']; + } + $cOTLdata[$nc]['GPOSinfo'][$cctr]['wDir'] = ($carac['level'] % 2) ? 'RTL' : 'LTR'; + } + } + $chunkid = $carac['chunkid']; + $cctr++; + } + } + + public function splitOTLdata(&$cOTLdata, $OTLcutoffpos, $OTLrestartpos = '') + { + if (!$OTLrestartpos) { + $OTLrestartpos = $OTLcutoffpos; + } + $newOTLdata = ['GPOSinfo' => [], 'char_data' => []]; + $newOTLdata['group'] = substr($cOTLdata['group'], $OTLrestartpos); + $cOTLdata['group'] = substr($cOTLdata['group'], 0, $OTLcutoffpos); + + if (isset($cOTLdata['GPOSinfo']) && $cOTLdata['GPOSinfo']) { + foreach ($cOTLdata['GPOSinfo'] as $k => $val) { + if ($k >= $OTLrestartpos) { + $newOTLdata['GPOSinfo'][($k - $OTLrestartpos)] = $val; + } + if ($k >= $OTLcutoffpos) { + unset($cOTLdata['GPOSinfo'][$k]); + //$cOTLdata['GPOSinfo'][$k] = array(); + } + } + } + if (isset($cOTLdata['char_data'])) { + $newOTLdata['char_data'] = array_slice($cOTLdata['char_data'], $OTLrestartpos); + array_splice($cOTLdata['char_data'], $OTLcutoffpos); + } + + // Not necessary - easier to debug + if (isset($cOTLdata['GPOSinfo'])) { + ksort($cOTLdata['GPOSinfo']); + } + if (isset($newOTLdata['GPOSinfo'])) { + ksort($newOTLdata['GPOSinfo']); + } + + return $newOTLdata; + } + + public function sliceOTLdata($OTLdata, $pos, $len) + { + $newOTLdata = ['GPOSinfo' => [], 'char_data' => []]; + $newOTLdata['group'] = substr($OTLdata['group'], $pos, $len); + + if ($OTLdata['GPOSinfo']) { + foreach ($OTLdata['GPOSinfo'] as $k => $val) { + if ($k >= $pos && $k < ($pos + $len)) { + $newOTLdata['GPOSinfo'][($k - $pos)] = $val; + } + } + } + + if (isset($OTLdata['char_data'])) { + $newOTLdata['char_data'] = array_slice($OTLdata['char_data'], $pos, $len); + } + + // Not necessary - easier to debug + if ($newOTLdata['GPOSinfo']) { + ksort($newOTLdata['GPOSinfo']); + } + + return $newOTLdata; + } + + /** + * Remove one or more occurrences of $char (single character) from $txt and adjust OTLdata + */ + public function removeChar(&$txt, &$cOTLdata, $char) + { + while (mb_strpos($txt, $char, 0, $this->mpdf->mb_enc) !== false) { + $pos = mb_strpos($txt, $char, 0, $this->mpdf->mb_enc); + $newGPOSinfo = []; + $cOTLdata['group'] = substr_replace($cOTLdata['group'], '', $pos, 1); + if ($cOTLdata['GPOSinfo']) { + foreach ($cOTLdata['GPOSinfo'] as $k => $val) { + if ($k > $pos) { + $newGPOSinfo[($k - 1)] = $val; + } elseif ($k != $pos) { + $newGPOSinfo[$k] = $val; + } + } + $cOTLdata['GPOSinfo'] = $newGPOSinfo; + } + if (isset($cOTLdata['char_data'])) { + array_splice($cOTLdata['char_data'], $pos, 1); + } + + $txt = preg_replace("/" . $char . "/", '', $txt, 1); + } + } + + /** + * Remove one or more occurrences of $char (single character) from $txt and adjust OTLdata + */ + public function replaceSpace(&$txt, &$cOTLdata) + { + $char = chr(194) . chr(160); // NBSP + while (mb_strpos($txt, $char, 0, $this->mpdf->mb_enc) !== false) { + $pos = mb_strpos($txt, $char, 0, $this->mpdf->mb_enc); + if ($cOTLdata['char_data'][$pos]['uni'] == 160) { + $cOTLdata['char_data'][$pos]['uni'] = 32; + } + $txt = preg_replace("/" . $char . "/", ' ', $txt, 1); + } + } + + public function trimOTLdata(&$cOTLdata, $Left = true, $Right = true) + { + $len = (!is_array($cOTLdata) || $cOTLdata['char_data'] === null) ? 0 : count($cOTLdata['char_data']); + $nLeft = 0; + $nRight = 0; + for ($i = 0; $i < $len; $i++) { + if ($cOTLdata['char_data'][$i]['uni'] == 32 || $cOTLdata['char_data'][$i]['uni'] == 12288) { + $nLeft++; + } // 12288 = 0x3000 = CJK space + else { + break; + } + } + for ($i = ($len - 1); $i >= 0; $i--) { + if ($cOTLdata['char_data'][$i]['uni'] == 32 || $cOTLdata['char_data'][$i]['uni'] == 12288) { + $nRight++; + } // 12288 = 0x3000 = CJK space + else { + break; + } + } + + // Trim Right + if ($Right && $nRight) { + $cOTLdata['group'] = substr($cOTLdata['group'], 0, strlen($cOTLdata['group']) - $nRight); + if ($cOTLdata['GPOSinfo']) { + foreach ($cOTLdata['GPOSinfo'] as $k => $val) { + if ($k >= $len - $nRight) { + unset($cOTLdata['GPOSinfo'][$k]); + } + } + } + if (isset($cOTLdata['char_data'])) { + for ($i = 0; $i < $nRight; $i++) { + array_pop($cOTLdata['char_data']); + } + } + } + // Trim Left + if ($Left && $nLeft) { + $cOTLdata['group'] = substr($cOTLdata['group'], $nLeft); + if ($cOTLdata['GPOSinfo']) { + $newPOSinfo = []; + foreach ($cOTLdata['GPOSinfo'] as $k => $val) { + if ($k >= $nLeft) { + $newPOSinfo[$k - $nLeft] = $cOTLdata['GPOSinfo'][$k]; + } + } + $cOTLdata['GPOSinfo'] = $newPOSinfo; + } + if (isset($cOTLdata['char_data'])) { + for ($i = 0; $i < $nLeft; $i++) { + array_shift($cOTLdata['char_data']); + } + } + } + } + + //////////////////////////////////////////////////////////////// + ////////// GENERAL OTL FUNCTIONS ///////////////// + //////////////////////////////////////////////////////////////// + + private function glyphToChar($gid) + { + return (ord($this->glyphIDtoUni[$gid * 3]) << 16) + (ord($this->glyphIDtoUni[$gid * 3 + 1]) << 8) + ord($this->glyphIDtoUni[$gid * 3 + 2]); + } + + private function unicode_hex($unicode_dec) + { + return (str_pad(strtoupper(dechex($unicode_dec)), 5, '0', STR_PAD_LEFT)); + } + + private function seek($pos) + { + $this->_pos = $pos; + } + + private function skip($delta) + { + $this->_pos += $delta; + } + + private function read_short() + { + $a = (ord($this->ttfOTLdata[$this->_pos]) << 8) + ord($this->ttfOTLdata[$this->_pos + 1]); + if ($a & (1 << 15)) { + $a = ($a - (1 << 16)); + } + $this->_pos += 2; + return $a; + } + + private function read_ushort() + { + $a = (ord($this->ttfOTLdata[$this->_pos]) << 8) + ord($this->ttfOTLdata[$this->_pos + 1]); + $this->_pos += 2; + return $a; + } + + private function _getCoverageGID() + { + // Called from Lookup Type 1, Format 1 - returns glyphIDs rather than hexstrings + // Need to do this separately to cache separately + // Otherwise the same as fn below _getCoverage + $offset = $this->_pos; + if (isset($this->LuDataCache[$this->fontkey]['GID'][$offset])) { + $g = $this->LuDataCache[$this->fontkey]['GID'][$offset]; + } else { + $g = []; + $CoverageFormat = $this->read_ushort(); + if ($CoverageFormat == 1) { + $CoverageGlyphCount = $this->read_ushort(); + for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) { + $glyphID = $this->read_ushort(); + $g[] = $glyphID; + } + } + if ($CoverageFormat == 2) { + $RangeCount = $this->read_ushort(); + for ($r = 0; $r < $RangeCount; $r++) { + $start = $this->read_ushort(); + $end = $this->read_ushort(); + $StartCoverageIndex = $this->read_ushort(); // n/a + for ($glyphID = $start; $glyphID <= $end; $glyphID++) { + $g[] = $glyphID; + } + } + } + $this->LuDataCache[$this->fontkey]['GID'][$offset] = $g; + } + return $g; + } + + private function _getCoverage() + { + $offset = $this->_pos; + if (isset($this->LuDataCache[$this->fontkey][$offset])) { + $g = $this->LuDataCache[$this->fontkey][$offset]; + } else { + $g = []; + $CoverageFormat = $this->read_ushort(); + if ($CoverageFormat == 1) { + $CoverageGlyphCount = $this->read_ushort(); + for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) { + $glyphID = $this->read_ushort(); + $g[] = $this->unicode_hex($this->glyphToChar($glyphID)); + } + } + if ($CoverageFormat == 2) { + $RangeCount = $this->read_ushort(); + for ($r = 0; $r < $RangeCount; $r++) { + $start = $this->read_ushort(); + $end = $this->read_ushort(); + $StartCoverageIndex = $this->read_ushort(); // n/a + for ($glyphID = $start; $glyphID <= $end; $glyphID++) { + $g[] = $this->unicode_hex($this->glyphToChar($glyphID)); + } + } + } + $this->LuDataCache[$this->fontkey][$offset] = $g; + } + return $g; + } + + private function _getClasses($offset) + { + if (isset($this->LuDataCache[$this->fontkey][$offset])) { + $GlyphByClass = $this->LuDataCache[$this->fontkey][$offset]; + } else { + $this->seek($offset); + $ClassFormat = $this->read_ushort(); + $GlyphByClass = []; + if ($ClassFormat == 1) { + $StartGlyph = $this->read_ushort(); + $GlyphCount = $this->read_ushort(); + for ($i = 0; $i < $GlyphCount; $i++) { + $startGlyphID = $StartGlyph + $i; + $endGlyphID = $StartGlyph + $i; + $class = $this->read_ushort(); + // Note: Font FreeSerif , tag "blws" + // $BacktrackClasses[0] is defined ? a mistake in the font ??? + // Let's ignore for now + if ($class > 0) { + for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { + if ($this->glyphToChar($g)) { + $GlyphByClass[$class][$this->glyphToChar($g)] = 1; + } + } + } + } + } elseif ($ClassFormat == 2) { + $tableCount = $this->read_ushort(); + for ($i = 0; $i < $tableCount; $i++) { + $startGlyphID = $this->read_ushort(); + $endGlyphID = $this->read_ushort(); + $class = $this->read_ushort(); + // Note: Font FreeSerif , tag "blws" + // $BacktrackClasses[0] is defined ? a mistake in the font ??? + // Let's ignore for now + if ($class > 0) { + for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { + if ($this->glyphToChar($g)) { + $GlyphByClass[$class][$this->glyphToChar($g)] = 1; + } + } + } + } + } + $this->LuDataCache[$this->fontkey][$offset] = $GlyphByClass; + } + return $GlyphByClass; + } + + private function _getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $shaper, $useOTL, $mode) + { + // ScriptLang is the array of available script/lang tags supported by the font + // $scriptblock is the (number/code) for the script of the actual text string based on Unicode properties (Ucdn::$uni_scriptblock) + // $scripttag is the default tag derived from $scriptblock + /* + http://www.microsoft.com/typography/otspec/ttoreg.htm + http://www.microsoft.com/typography/otspec/scripttags.htm + + Values for useOTL + + Bit dn hn Value + 1 1 0x0001 GSUB/GPOS - Latin scripts + 2 2 0x0002 GSUB/GPOS - Cyrillic scripts + 3 4 0x0004 GSUB/GPOS - Greek scripts + 4 8 0x0008 GSUB/GPOS - CJK scripts (excluding Hangul-Jamo) + 5 16 0x0010 (Reserved) + 6 32 0x0020 (Reserved) + 7 64 0x0040 (Reserved) + 8 128 0x0080 GSUB/GPOS - All other scripts (including all RTL scripts, complex scripts with shapers etc) + + NB If change for RTL - cf. function magic_reverse_dir in mpdf.php to update + + */ + + + if ($scriptblock == Ucdn::SCRIPT_LATIN) { + if (!($useOTL & 0x01)) { + return ['', false]; + } + } elseif ($scriptblock == Ucdn::SCRIPT_CYRILLIC) { + if (!($useOTL & 0x02)) { + return ['', false]; + } + } elseif ($scriptblock == Ucdn::SCRIPT_GREEK) { + if (!($useOTL & 0x04)) { + return ['', false]; + } + } elseif ($scriptblock >= Ucdn::SCRIPT_HIRAGANA && $scriptblock <= Ucdn::SCRIPT_YI) { + if (!($useOTL & 0x08)) { + return ['', false]; + } + } else { + if (!($useOTL & 0x80)) { + return ['', false]; + } + } + + // If availabletags includes scripttag - choose + if (isset($ScriptLang[$scripttag])) { + return [$scripttag, false]; + } + + // If INDIC (or Myanmar) and available tag not includes new version, check if includes old version & choose old version + if ($shaper) { + switch ($scripttag) { + case 'bng2': + if (isset($ScriptLang['beng'])) { + return ['beng', true]; + } + // fallthrough + case 'dev2': + if (isset($ScriptLang['deva'])) { + return ['deva', true]; + } + // fallthrough + case 'gjr2': + if (isset($ScriptLang['gujr'])) { + return ['gujr', true]; + } + // fallthrough + case 'gur2': + if (isset($ScriptLang['guru'])) { + return ['guru', true]; + } + // fallthrough + case 'knd2': + if (isset($ScriptLang['knda'])) { + return ['knda', true]; + } + // fallthrough + case 'mlm2': + if (isset($ScriptLang['mlym'])) { + return ['mlym', true]; + } + // fallthrough + case 'ory2': + if (isset($ScriptLang['orya'])) { + return ['orya', true]; + } + // fallthrough + case 'tml2': + if (isset($ScriptLang['taml'])) { + return ['taml', true]; + } + // fallthrough + case 'tel2': + if (isset($ScriptLang['telu'])) { + return ['telu', true]; + } + // fallthrough + case 'mym2': + if (isset($ScriptLang['mymr'])) { + return ['mymr', true]; + } + } + } + + // choose DFLT if present + if (isset($ScriptLang['DFLT'])) { + return ['DFLT', false]; + } + // else choose dflt if present + if (isset($ScriptLang['dflt'])) { + return ['dflt', false]; + } + // else return no scriptTag + if (isset($ScriptLang['latn'])) { + return ['latn', false]; + } + // else return no scriptTag + return ['', false]; + } + + // LangSys tags + private function _getOTLLangTag($ietf, $available) + { + // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + // http://www.microsoft.com/typography/otspec/languagetags.htm + // IETF tag = e.g. en-US, und-Arab, sr-Cyrl cf. class LangToFont + if ($available == '') { + return ''; + } + $tags = preg_split('/-/', $ietf); + $lang = ''; + $country = ''; + $script = ''; + $lang = strtolower($tags[0]); + if (isset($tags[1]) && $tags[1]) { + if (strlen($tags[1]) == 2) { + $country = strtolower($tags[1]); + } + } + if (isset($tags[2]) && $tags[2]) { + $country = strtolower($tags[2]); + } + + if ($lang != '' && isset(Ucdn::$ot_languages[$lang])) { + $langsys = Ucdn::$ot_languages[$lang]; + } elseif ($lang != '' && $country != '' && isset(Ucdn::$ot_languages[$lang . '' . $country])) { + $langsys = Ucdn::$ot_languages[$lang . '' . $country]; + } else { + $langsys = "DFLT"; + } + if (strpos($available, $langsys) === false) { + if (strpos($available, "DFLT") !== false) { + return "DFLT"; + } else { + return ''; + } + } + return $langsys; + } + + private function _dumpproc($GPOSSUB, $lookupID, $subtable, $Type, $Format, $ptr, $currGlyph, $level) + { + echo '<div style="padding-left: ' . ($level * 2) . 'em;">'; + echo $GPOSSUB . ' LookupID #' . $lookupID . ' Subtable#' . $subtable . ' Type: ' . $Type . ' Format: ' . $Format . '<br />'; + echo '<div style="font-family:monospace">'; + echo 'Glyph position: ' . $ptr . ' Current Glyph: ' . $currGlyph . '<br />'; + + for ($i = 0; $i < count($this->OTLdata); $i++) { + if ($i == $ptr) { + echo '<b>'; + } + echo $this->OTLdata[$i]['hex'] . ' '; + if ($i == $ptr) { + echo '</b>'; + } + } + echo '<br />'; + + for ($i = 0; $i < count($this->OTLdata); $i++) { + if ($i == $ptr) { + echo '<b>'; + } + echo str_pad($this->OTLdata[$i]['uni'], 5) . ' '; + if ($i == $ptr) { + echo '</b>'; + } + } + echo '<br />'; + + if ($GPOSSUB == 'GPOS') { + for ($i = 0; $i < count($this->OTLdata); $i++) { + if (!empty($this->OTLdata[$i]['GPOSinfo'])) { + echo $this->OTLdata[$i]['hex'] . ' &#x' . $this->OTLdata[$i]['hex'] . '; '; + print_r($this->OTLdata[$i]['GPOSinfo']); + echo ' '; + } + } + } + + echo '</div>'; + echo '</div>'; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/OtlDump.php b/lib/MPDF/vendor/mpdf/mpdf/src/OtlDump.php new file mode 100644 index 0000000..024c54d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/OtlDump.php @@ -0,0 +1,4369 @@ +<?php + +namespace Mpdf; + +// Define the value used in the "head" table of a created TTF file +// 0x74727565 "true" for Mac +// 0x00010000 for Windows +// Either seems to work for a font embedded in a PDF file +// when read by Adobe Reader on a Windows PC(!) +use Mpdf\Fonts\GlyphOperator; + +if (!defined('_TTF_MAC_HEADER')) { + define("_TTF_MAC_HEADER", false); +} + +// Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP) +// e.g. xMin, xMax, maxNContours +if (!defined('_RECALC_PROFILE')) { + define("_RECALC_PROFILE", false); +} + +// mPDF 5.7.1 +if (!function_exists('Mpdf\unicode_hex')) { + function unicode_hex($unicode_dec) + { + return (sprintf("%05s", strtoupper(dechex($unicode_dec)))); + } +} + +class OtlDump +{ + + var $GPOSFeatures; // mPDF 5.7.1 + + var $GPOSLookups; // mPDF 5.7.1 + + var $GPOSScriptLang; // mPDF 5.7.1 + + var $ignoreStrings; // mPDF 5.7.1 + + var $MarkAttachmentType; // mPDF 5.7.1 + + var $MarkGlyphSets; // mPDF 7.5.1 + + var $GlyphClassMarks; // mPDF 5.7.1 + + var $GlyphClassLigatures; // mPDF 5.7.1 + + var $GlyphClassBases; // mPDF 5.7.1 + + var $GlyphClassComponents; // mPDF 5.7.1 + + var $GSUBScriptLang; // mPDF 5.7.1 + + var $rtlPUAstr; // mPDF 5.7.1 + + var $rtlPUAarr; // mPDF 5.7.1 + + var $fontkey; // mPDF 5.7.1 + + var $useOTL; // mPDF 5.7.1 + + var $panose; + + var $maxUni; + + var $sFamilyClass; + + var $sFamilySubClass; + + var $sipset; + + var $smpset; + + var $_pos; + + var $numTables; + + var $searchRange; + + var $entrySelector; + + var $rangeShift; + + var $tables; + + var $otables; + + var $filename; + + var $fh; + + var $glyphPos; + + var $charToGlyph; + + var $ascent; + + var $descent; + + var $name; + + var $familyName; + + var $styleName; + + var $fullName; + + var $uniqueFontID; + + var $unitsPerEm; + + var $bbox; + + var $capHeight; + + var $stemV; + + var $italicAngle; + + var $flags; + + var $underlinePosition; + + var $underlineThickness; + + var $charWidths; + + var $defaultWidth; + + var $maxStrLenRead; + + var $numTTCFonts; + + var $TTCFonts; + + var $maxUniChar; + + var $kerninfo; + + var $mode; + + var $glyphToChar; + + var $fontRevision; + + var $glyphdata; + + var $glyphIDtoUn; + + var $restrictedUse; + + var $GSUBFeatures; + + var $GSUBLookups; + + var $glyphIDtoUni; + + var $GSLuCoverage; + + var $version; + + private $mpdf; + + public function __construct(Mpdf $mpdf) + { + $this->mpdf = $mpdf; + $this->maxStrLenRead = 200000; // Maximum size of glyf table to read in as string (otherwise reads each glyph from file) + } + + function getMetrics($file, $fontkey, $TTCfontID = 0, $debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0, $mode = null) + { + // mPDF 5.7.1 + $this->mode = $mode; + $this->useOTL = $useOTL; // mPDF 5.7.1 + $this->fontkey = $fontkey; // mPDF 5.7.1 + $this->filename = $file; + $this->fh = fopen($file, 'rb'); + + if (!$this->fh) { + throw new \Mpdf\MpdfException(sprintf('Unable to open file "%s"', $file)); + } + + $this->_pos = 0; + $this->charWidths = ''; + $this->glyphPos = []; + $this->charToGlyph = []; + $this->tables = []; + $this->otables = []; + $this->kerninfo = []; + $this->ascent = 0; + $this->descent = 0; + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->version = $version = $this->read_ulong(); + $this->panose = []; + + if ($version == 0x4F54544F) { + throw new \Mpdf\MpdfException("Postscript outlines are not supported"); + } + + if ($version == 0x74746366 && !$TTCfontID) { + throw new \Mpdf\MpdfException("TTCfontID for a TrueType Collection has to be defined in ttfontdata configuration key (" . $file . ")"); + } + + if (!in_array($version, [0x00010000, 0x74727565]) && !$TTCfontID) { + throw new \Mpdf\MpdfException("Not a TrueType font: version=" . $version); + } + + if ($TTCfontID > 0) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000])) { + throw new \Mpdf\MpdfException("Error parsing TrueType Collection: version=" . $version . " - " . $file); + } + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + $this->seek($this->TTCFonts[$TTCfontID]['offset']); + $this->version = $version = $this->read_ulong(); // TTFont version again now + } + $this->readTableDirectory($debug); + $this->extractInfo($debug, $BMPonly, $kerninfo, $useOTL); + fclose($this->fh); + } + + function readTableDirectory($debug = false) + { + $this->numTables = $this->read_ushort(); + $this->searchRange = $this->read_ushort(); + $this->entrySelector = $this->read_ushort(); + $this->rangeShift = $this->read_ushort(); + $this->tables = []; + for ($i = 0; $i < $this->numTables; $i++) { + $record = []; + $record['tag'] = $this->read_tag(); + $record['checksum'] = [$this->read_ushort(), $this->read_ushort()]; + $record['offset'] = $this->read_ulong(); + $record['length'] = $this->read_ulong(); + $this->tables[$record['tag']] = $record; + } + if ($debug) { + $this->checksumTables(); + } + } + + function checksumTables() + { + // Check the checksums for all tables + foreach ($this->tables as $t) { + if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02 + $table = $this->get_chunk($t['offset'], $t['length']); + $checksum = $this->calcChecksum($table); + if ($t['tag'] == 'head') { + $up = unpack('n*', substr($table, 8, 4)); + $adjustment[0] = $up[1]; + $adjustment[1] = $up[2]; + $checksum = $this->sub32($checksum, $adjustment); + } + $xchecksum = $t['checksum']; + if ($xchecksum != $checksum) { + throw new \Mpdf\MpdfException(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename, dechex($checksum[0]) . dechex($checksum[1]), $t['tag'], dechex($xchecksum[0]) . dechex($xchecksum[1]))); + } + } + } + } + + function sub32($x, $y) + { + $xlo = $x[1]; + $xhi = $x[0]; + $ylo = $y[1]; + $yhi = $y[0]; + if ($ylo > $xlo) { + $xlo += 1 << 16; + $yhi += 1; + } + $reslo = $xlo - $ylo; + if ($yhi > $xhi) { + $xhi += 1 << 16; + } + $reshi = $xhi - $yhi; + $reshi = $reshi & 0xFFFF; + + return [$reshi, $reslo]; + } + + function calcChecksum($data) + { + if (strlen($data) % 4) { + $data .= str_repeat("\0", (4 - (strlen($data) % 4))); + } + $len = strlen($data); + $hi = 0x0000; + $lo = 0x0000; + for ($i = 0; $i < $len; $i += 4) { + $hi += (ord($data[$i]) << 8) + ord($data[$i + 1]); + $lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]); + $hi += ($lo >> 16) & 0xFFFF; + $lo = $lo & 0xFFFF; + } + + return [$hi, $lo]; + } + + function get_table_pos($tag) + { + $offset = isset($this->tables[$tag]['offset']) ? $this->tables[$tag]['offset'] : null; + $length = isset($this->tables[$tag]['length']) ? $this->tables[$tag]['length'] : null; + + return [$offset, $length]; + } + + function seek($pos) + { + $this->_pos = $pos; + fseek($this->fh, $this->_pos); + } + + function skip($delta) + { + $this->_pos = $this->_pos + $delta; + fseek($this->fh, $delta, SEEK_CUR); + } + + function seek_table($tag, $offset_in_table = 0) + { + $tpos = $this->get_table_pos($tag); + $this->_pos = $tpos[0] + $offset_in_table; + fseek($this->fh, $this->_pos); + + return $this->_pos; + } + + function read_tag() + { + $this->_pos += 4; + + return fread($this->fh, 4); + } + + function read_short() + { + $this->_pos += 2; + $s = fread($this->fh, 2); + $a = (ord($s[0]) << 8) + ord($s[1]); + if ($a & (1 << 15)) { + $a = ($a - (1 << 16)); + } + + return $a; + } + + function unpack_short($s) + { + $a = (ord($s[0]) << 8) + ord($s[1]); + if ($a & (1 << 15)) { + $a = ($a - (1 << 16)); + } + + return $a; + } + + function read_ushort() + { + $this->_pos += 2; + $s = fread($this->fh, 2); + + return (ord($s[0]) << 8) + ord($s[1]); + } + + function read_ulong() + { + $this->_pos += 4; + $s = fread($this->fh, 4); + + // if large uInt32 as an integer, PHP converts it to -ve + return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24 + } + + function get_ushort($pos) + { + fseek($this->fh, $pos); + $s = fread($this->fh, 2); + + return (ord($s[0]) << 8) + ord($s[1]); + } + + function get_ulong($pos) + { + fseek($this->fh, $pos); + $s = fread($this->fh, 4); + + // iF large uInt32 as an integer, PHP converts it to -ve + return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24 + } + + function pack_short($val) + { + if ($val < 0) { + $val = abs($val); + $val = ~$val; + $val += 1; + } + + return pack("n", $val); + } + + function splice($stream, $offset, $value) + { + return substr($stream, 0, $offset) . $value . substr($stream, $offset + strlen($value)); + } + + function _set_ushort($stream, $offset, $value) + { + $up = pack("n", $value); + + return $this->splice($stream, $offset, $up); + } + + function _set_short($stream, $offset, $val) + { + if ($val < 0) { + $val = abs($val); + $val = ~$val; + $val += 1; + } + $up = pack("n", $val); + + return $this->splice($stream, $offset, $up); + } + + function get_chunk($pos, $length) + { + fseek($this->fh, $pos); + if ($length < 1) { + return ''; + } + + return (fread($this->fh, $length)); + } + + function get_table($tag) + { + list($pos, $length) = $this->get_table_pos($tag); + if ($length == 0) { + return ''; + } + fseek($this->fh, $pos); + + return (fread($this->fh, $length)); + } + + function add($tag, $data) + { + if ($tag == 'head') { + $data = $this->splice($data, 8, "\0\0\0\0"); + } + $this->otables[$tag] = $data; + } + + ///////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////// + + function extractInfo($debug = false, $BMPonly = false, $kerninfo = false, $useOTL = 0) + { + $this->panose = []; + $this->sFamilyClass = 0; + $this->sFamilySubClass = 0; + /////////////////////////////////// + // name - Naming table + /////////////////////////////////// + $name_offset = $this->seek_table("name"); + $format = $this->read_ushort(); + if ($format != 0 && $format != 1) { + throw new \Mpdf\MpdfException("Unknown name table format " . $format); + } + $numRecords = $this->read_ushort(); + $string_data_offset = $name_offset + $this->read_ushort(); + $names = [1 => '', 2 => '', 3 => '', 4 => '', 6 => '']; + $K = array_keys($names); + $nameCount = count($names); + for ($i = 0; $i < $numRecords; $i++) { + $platformId = $this->read_ushort(); + $encodingId = $this->read_ushort(); + $languageId = $this->read_ushort(); + $nameId = $this->read_ushort(); + $length = $this->read_ushort(); + $offset = $this->read_ushort(); + if (!in_array($nameId, $K)) { + continue; + } + $N = ''; + if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name + $opos = $this->_pos; + $this->seek($string_data_offset + $offset); + if ($length % 2 != 0) { + throw new \Mpdf\MpdfException("PostScript name is UTF-16BE string of odd length"); + } + $length /= 2; + $N = ''; + while ($length > 0) { + $char = $this->read_ushort(); + $N .= (chr($char)); + $length -= 1; + } + $this->_pos = $opos; + $this->seek($opos); + } else { + if ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name + $opos = $this->_pos; + $N = $this->get_chunk($string_data_offset + $offset, $length); + $this->_pos = $opos; + $this->seek($opos); + } + } + if ($N && $names[$nameId] == '') { + $names[$nameId] = $N; + $nameCount -= 1; + if ($nameCount == 0) { + break; + } + } + } + if ($names[6]) { + $psName = $names[6]; + } else { + if ($names[4]) { + $psName = preg_replace('/ /', '-', $names[4]); + } else { + if ($names[1]) { + $psName = preg_replace('/ /', '-', $names[1]); + } else { + $psName = ''; + } + } + } + if (!$psName) { + throw new \Mpdf\MpdfException("Could not find PostScript font name: " . $this->filename); + } + if ($debug) { + for ($i = 0; $i < count($psName); $i++) { + $c = $psName[$i]; + $oc = ord($c); + if ($oc > 126 || strpos(' [](){}<>/%', $c) !== false) { + throw new \Mpdf\MpdfException("psName=" . $psName . " contains invalid character " . $c . " ie U+" . ord(c)); + } + } + } + $this->name = $psName; + if ($names[1]) { + $this->familyName = $names[1]; + } else { + $this->familyName = $psName; + } + if ($names[2]) { + $this->styleName = $names[2]; + } else { + $this->styleName = 'Regular'; + } + if ($names[4]) { + $this->fullName = $names[4]; + } else { + $this->fullName = $psName; + } + if ($names[3]) { + $this->uniqueFontID = $names[3]; + } else { + $this->uniqueFontID = $psName; + } + + if ($names[6]) { + $this->fullName = $names[6]; + } + + /////////////////////////////////// + // head - Font header table + /////////////////////////////////// + $this->seek_table("head"); + if ($debug) { + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + if ($ver_maj != 1) { + throw new \Mpdf\MpdfException('Unknown head table version ' . $ver_maj . '.' . $ver_min); + } + $this->fontRevision = $this->read_ushort() . $this->read_ushort(); + + $this->skip(4); + $magic = $this->read_ulong(); + if ($magic != 0x5F0F3CF5) { + throw new \Mpdf\MpdfException('Invalid head table magic ' . $magic); + } + $this->skip(2); + } else { + $this->skip(18); + } + $this->unitsPerEm = $unitsPerEm = $this->read_ushort(); + $scale = 1000 / $unitsPerEm; + $this->skip(16); + $xMin = $this->read_short(); + $yMin = $this->read_short(); + $xMax = $this->read_short(); + $yMax = $this->read_short(); + $this->bbox = [($xMin * $scale), ($yMin * $scale), ($xMax * $scale), ($yMax * $scale)]; + $this->skip(3 * 2); + $indexToLocFormat = $this->read_ushort(); + $glyphDataFormat = $this->read_ushort(); + if ($glyphDataFormat != 0) { + throw new \Mpdf\MpdfException('Unknown glyph data format ' . $glyphDataFormat); + } + + /////////////////////////////////// + // hhea metrics table + /////////////////////////////////// + // ttf2t1 seems to use this value rather than the one in OS/2 - so put in for compatibility + if (isset($this->tables["hhea"])) { + $this->seek_table("hhea"); + $this->skip(4); + $hheaAscender = $this->read_short(); + $hheaDescender = $this->read_short(); + $this->ascent = ($hheaAscender * $scale); + $this->descent = ($hheaDescender * $scale); + } + + /////////////////////////////////// + // OS/2 - OS/2 and Windows metrics table + /////////////////////////////////// + if (isset($this->tables["OS/2"])) { + $this->seek_table("OS/2"); + $version = $this->read_ushort(); + $this->skip(2); + $usWeightClass = $this->read_ushort(); + $this->skip(2); + $fsType = $this->read_ushort(); + if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) { + global $overrideTTFFontRestriction; + if (!$overrideTTFFontRestriction) { + throw new \Mpdf\MpdfException('ERROR - Font file ' . $this->filename . ' cannot be embedded due to copyright restrictions.'); + } + $this->restrictedUse = true; + } + $this->skip(20); + $sF = $this->read_short(); + $this->sFamilyClass = ($sF >> 8); + $this->sFamilySubClass = ($sF & 0xFF); + $this->_pos += 10; //PANOSE = 10 byte length + $panose = fread($this->fh, 10); + $this->panose = []; + for ($p = 0; $p < strlen($panose); $p++) { + $this->panose[] = ord($panose[$p]); + } + $this->skip(26); + $sTypoAscender = $this->read_short(); + $sTypoDescender = $this->read_short(); + if (!$this->ascent) { + $this->ascent = ($sTypoAscender * $scale); + } + if (!$this->descent) { + $this->descent = ($sTypoDescender * $scale); + } + if ($version > 1) { + $this->skip(16); + $sCapHeight = $this->read_short(); + $this->capHeight = ($sCapHeight * $scale); + } else { + $this->capHeight = $this->ascent; + } + } else { + $usWeightClass = 500; + if (!$this->ascent) { + $this->ascent = ($yMax * $scale); + } + if (!$this->descent) { + $this->descent = ($yMin * $scale); + } + $this->capHeight = $this->ascent; + } + $this->stemV = 50 + intval(pow(($usWeightClass / 65.0), 2)); + + /////////////////////////////////// + // post - PostScript table + /////////////////////////////////// + $this->seek_table("post"); + if ($debug) { + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + if ($ver_maj < 1 || $ver_maj > 4) { + throw new \Mpdf\MpdfException('Unknown post table version ' . $ver_maj); + } + } else { + $this->skip(4); + } + $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0; + $this->underlinePosition = $this->read_short() * $scale; + $this->underlineThickness = $this->read_short() * $scale; + $isFixedPitch = $this->read_ulong(); + + $this->flags = 4; + + if ($this->italicAngle != 0) { + $this->flags = $this->flags | 64; + } + if ($usWeightClass >= 600) { + $this->flags = $this->flags | 262144; + } + if ($isFixedPitch) { + $this->flags = $this->flags | 1; + } + + /////////////////////////////////// + // hhea - Horizontal header table + /////////////////////////////////// + $this->seek_table("hhea"); + if ($debug) { + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + if ($ver_maj != 1) { + throw new \Mpdf\MpdfException('Unknown hhea table version ' . $ver_maj); + } + $this->skip(28); + } else { + $this->skip(32); + } + $metricDataFormat = $this->read_ushort(); + if ($metricDataFormat != 0) { + throw new \Mpdf\MpdfException('Unknown horizontal metric data format ' . $metricDataFormat); + } + $numberOfHMetrics = $this->read_ushort(); + if ($numberOfHMetrics == 0) { + throw new \Mpdf\MpdfException('Number of horizontal metrics is 0'); + } + + /////////////////////////////////// + // maxp - Maximum profile table + /////////////////////////////////// + $this->seek_table("maxp"); + if ($debug) { + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + if ($ver_maj != 1) { + throw new \Mpdf\MpdfException('Unknown maxp table version ' . $ver_maj); + } + } else { + $this->skip(4); + } + $numGlyphs = $this->read_ushort(); + + /////////////////////////////////// + // cmap - Character to glyph index mapping table + /////////////////////////////////// + $cmap_offset = $this->seek_table("cmap"); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i = 0; $i < $cmapTableCount; $i++) { + $platformID = $this->read_ushort(); + $encodingID = $this->read_ushort(); + $offset = $this->read_ulong(); + $save_pos = $this->_pos; + if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + if (!$unicode_cmap_offset) { + $unicode_cmap_offset = $cmap_offset + $offset; + } + if ($BMPonly) { + break; + } + } + } // Microsoft, Unicode Format 12 table HKCS + else { + if ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) { + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 12) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } + } + $this->seek($save_pos); + } + + if (!$unicode_cmap_offset) { + throw new \Mpdf\MpdfException('Font (' . $this->filename . ') does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)'); + } + + $sipset = false; + $smpset = false; + + // mPDF 5.7.1 + $this->GSUBScriptLang = []; + $this->rtlPUAstr = ''; + $this->rtlPUAarr = []; + $this->GSUBFeatures = []; + $this->GSUBLookups = []; + $this->GPOSScriptLang = []; + $this->GPOSFeatures = []; + $this->GPOSLookups = []; + $this->glyphIDtoUni = ''; + + // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above + if ($format == 12 && !$BMPonly) { + $this->maxUniChar = 0; + $this->seek($unicode_cmap_offset + 4); + $length = $this->read_ulong(); + $limit = $unicode_cmap_offset + $length; + $this->skip(4); + + $nGroups = $this->read_ulong(); + + $glyphToChar = []; + $charToGlyph = []; + for ($i = 0; $i < $nGroups; $i++) { + $startCharCode = $this->read_ulong(); + $endCharCode = $this->read_ulong(); + $startGlyphCode = $this->read_ulong(); + if ($endCharCode > 0x20000 && $endCharCode < 0x2FFFF) { + $sipset = true; + } else { + if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) { + $smpset = true; + } + } + $offset = 0; + for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) { + $glyph = $startGlyphCode + $offset; + $offset++; + if ($unichar < 0x30000) { + $charToGlyph[$unichar] = $glyph; + $this->maxUniChar = max($unichar, $this->maxUniChar); + $glyphToChar[$glyph][] = $unichar; + } + } + } + } else { + $glyphToChar = []; + $charToGlyph = []; + $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph); + } + $this->sipset = $sipset; + $this->smpset = $smpset; + + /////////////////////////////////// + // mPDF 5.7.1 + // Map Unmapped glyphs - from $numGlyphs + if ($this->useOTL) { + $bctr = 0xE000; + for ($gid = 1; $gid < $numGlyphs; $gid++) { + if (!isset($glyphToChar[$gid])) { + while (isset($charToGlyph[$bctr])) { + $bctr++; + } // Avoid overwriting a glyph already mapped in PUA + if (($bctr > 0xF8FF) && ($bctr < 0x2CEB0)) { + if (!$BMPonly) { + $bctr = 0x2CEB0; // Use unassigned area 0x2CEB0 to 0x2F7FF (space for 10,000 characters) + $this->sipset = $sipset = true; // forces subsetting; also ensure charwidths are saved + while (isset($charToGlyph[$bctr])) { + $bctr++; + } + } else { + throw new \Mpdf\MpdfException($names[1] . " : WARNING - The font does not have enough space to map all (unmapped) included glyphs into Private Use Area U+E000 - U+F8FF"); + } + } + $glyphToChar[$gid][] = $bctr; + $charToGlyph[$bctr] = $gid; + $this->maxUniChar = max($bctr, $this->maxUniChar); + $bctr++; + } + } + } + $this->glyphToChar = $glyphToChar; + $this->charToGlyph = $charToGlyph; + /////////////////////////////////// + // mPDF 5.7.1 OpenType Layout tables + $this->GSUBScriptLang = []; + $this->rtlPUAstr = ''; + $this->rtlPUAarr = []; + if ($useOTL) { + $this->_getGDEFtables(); + list($this->GSUBScriptLang, $this->GSUBFeatures, $this->GSUBLookups, $this->rtlPUAstr, $this->rtlPUAarr) = $this->_getGSUBtables(); + list($this->GPOSScriptLang, $this->GPOSFeatures, $this->GPOSLookups) = $this->_getGPOStables(); + $this->glyphIDtoUni = str_pad('', 256 * 256 * 3, "\x00"); + foreach ($glyphToChar as $gid => $arr) { + if (isset($glyphToChar[$gid][0])) { + $char = $glyphToChar[$gid][0]; + if ($char != 0 && $char != 65535) { + $this->glyphIDtoUni[$gid * 3] = chr($char >> 16); + $this->glyphIDtoUni[$gid * 3 + 1] = chr(($char >> 8) & 0xFF); + $this->glyphIDtoUni[$gid * 3 + 2] = chr($char & 0xFF); + } + } + } + } + /////////////////////////////////// + /////////////////////////////////// + // hmtx - Horizontal metrics table + /////////////////////////////////// + $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale); + + /////////////////////////////////// + // kern - Kerning pair table + /////////////////////////////////// + if ($kerninfo) { + // Recognises old form of Kerning table - as required by Windows - Format 0 only + $kern_offset = $this->seek_table("kern"); + $version = $this->read_ushort(); + $nTables = $this->read_ushort(); + // subtable header + $sversion = $this->read_ushort(); + $slength = $this->read_ushort(); + $scoverage = $this->read_ushort(); + $format = $scoverage >> 8; + if ($kern_offset && $version == 0 && $format == 0) { + // Format 0 + $nPairs = $this->read_ushort(); + $this->skip(6); + for ($i = 0; $i < $nPairs; $i++) { + $left = $this->read_ushort(); + $right = $this->read_ushort(); + $val = $this->read_short(); + if (count($glyphToChar[$left]) == 1 && count($glyphToChar[$right]) == 1) { + if ($left != 32 && $right != 32) { + $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val * $scale); + } + } + } + } + } + } + + ///////////////////////////////////////////////////////////////////////////////////////// + function _getGDEFtables() + { + /////////////////////////////////// + // GDEF - Glyph Definition + /////////////////////////////////// + // http://www.microsoft.com/typography/otspec/gdef.htm + if (isset($this->tables["GDEF"])) { + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h1>GDEF table</h1>'); + } + $gdef_offset = $this->seek_table("GDEF"); + // ULONG Version of the GDEF table-currently 0x00010000 + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + // Version 0x00010002 of GDEF header contains additional Offset to a list defining mark glyph set definitions (MarkGlyphSetDef) + $GlyphClassDef_offset = $this->read_ushort(); + $AttachList_offset = $this->read_ushort(); + $LigCaretList_offset = $this->read_ushort(); + $MarkAttachClassDef_offset = $this->read_ushort(); + if ($ver_min == 2) { + $MarkGlyphSetsDef_offset = $this->read_ushort(); + } + + // GlyphClassDef + $this->seek($gdef_offset + $GlyphClassDef_offset); + /* + 1 Base glyph (single character, spacing glyph) + 2 Ligature glyph (multiple character, spacing glyph) + 3 Mark glyph (non-spacing combining glyph) + 4 Component glyph (part of single character, spacing glyph) + */ + $GlyphByClass = $this->_getClassDefinitionTable(); + + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h2>Glyph classes</h2>'); + } + + if (isset($GlyphByClass[1]) && count($GlyphByClass[1]) > 0) { + $this->GlyphClassBases = $this->formatClassArr($GlyphByClass[1]); + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>Glyph class 1</h3>'); + $this->mpdf->WriteHTML('<h5>Base glyph (single character, spacing glyph)</h5>'); + $html = ''; + $html .= '<div class="glyphs">'; + foreach ($GlyphByClass[1] as $g) { + $html .= '&#x' . $g . '; '; + } + $html .= '</div>'; + $this->mpdf->WriteHTML($html); + } + } else { + $this->GlyphClassBases = ''; + } + if (isset($GlyphByClass[2]) && count($GlyphByClass[2]) > 0) { + $this->GlyphClassLigatures = $this->formatClassArr($GlyphByClass[2]); + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>Glyph class 2</h3>'); + $this->mpdf->WriteHTML('<h5>Ligature glyph (multiple character, spacing glyph)</h5>'); + $html = ''; + $html .= '<div class="glyphs">'; + foreach ($GlyphByClass[2] as $g) { + $html .= '&#x' . $g . '; '; + } + $html .= '</div>'; + $this->mpdf->WriteHTML($html); + } + } else { + $this->GlyphClassLigatures = ''; + } + if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) { + $this->GlyphClassMarks = $this->formatClassArr($GlyphByClass[3]); + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>Glyph class 3</h3>'); + $this->mpdf->WriteHTML('<h5>Mark glyph (non-spacing combining glyph)</h5>'); + $html = ''; + $html .= '<div class="glyphs">'; + foreach ($GlyphByClass[3] as $g) { + $html .= '◌&#x' . $g . '; '; + } + $html .= '</div>'; + $this->mpdf->WriteHTML($html); + } + } else { + $this->GlyphClassMarks = ''; + } + if (isset($GlyphByClass[4]) && count($GlyphByClass[4]) > 0) { + $this->GlyphClassComponents = $this->formatClassArr($GlyphByClass[4]); + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>Glyph class 4</h3>'); + $this->mpdf->WriteHTML('<h5>Component glyph (part of single character, spacing glyph)</h5>'); + $html = ''; + $html .= '<div class="glyphs">'; + foreach ($GlyphByClass[4] as $g) { + $html .= '&#x' . $g . '; '; + } + $html .= '</div>'; + $this->mpdf->WriteHTML($html); + } + } else { + $this->GlyphClassComponents = ''; + } + + $Marks = $GlyphByClass[3]; // to use for MarkAttachmentType + + /* Required for GPOS + // Attachment List + if ($AttachList_offset) { + $this->seek($gdef_offset+$AttachList_offset ); + } + The Attachment Point List table (AttachmentList) identifies all the attachment points defined in the GPOS table and their associated glyphs so a client can quickly access coordinates for each glyph's attachment points. As a result, the client can cache coordinates for attachment points along with glyph bitmaps and avoid recalculating the attachment points each time it displays a glyph. Without this table, processing speed would be slower because the client would have to decode the GPOS lookups that define attachment points and compile the points in a list. + + The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps. + + The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table, a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint). The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index. + AttachList table + Type Name Description + Offset Coverage Offset to Coverage table - from beginning of AttachList table + uint16 GlyphCount Number of glyphs with attachment points + Offset AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order + + An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and an array of contour indices of those points (PointIndex), listed in increasing numerical order. + + AttachPoint table + Type Name Description + uint16 PointCount Number of attachment points on this glyph + uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order + + See Example 3 - http://www.microsoft.com/typography/otspec/gdef.htm + */ + + // Ligature Caret List + // The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font. + // Not required for mDPF + // MarkAttachmentType + if ($MarkAttachClassDef_offset) { + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h1>Mark Attachment Types</h1>'); + } + $this->seek($gdef_offset + $MarkAttachClassDef_offset); + $MarkAttachmentTypes = $this->_getClassDefinitionTable(); + foreach ($MarkAttachmentTypes as $class => $glyphs) { + if (is_array($Marks) && count($Marks)) { + $mat = array_diff($Marks, $MarkAttachmentTypes[$class]); + sort($mat, SORT_STRING); + } else { + $mat = []; + } + + $this->MarkAttachmentType[$class] = $this->formatClassArr($mat); + + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>Mark Attachment Type: ' . $class . '</h3>'); + $html = ''; + $html .= '<div class="glyphs">'; + foreach ($glyphs as $g) { + $html .= '◌&#x' . $g . '; '; + } + $html .= '</div>'; + $this->mpdf->WriteHTML($html); + } + } + } else { + $this->MarkAttachmentType = []; + } + + // MarkGlyphSets only in Version 0x00010002 of GDEF + if ($ver_min == 2 && $MarkGlyphSetsDef_offset) { + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h1>Mark Glyph Sets</h1>'); + } + $this->seek($gdef_offset + $MarkGlyphSetsDef_offset); + $MarkSetTableFormat = $this->read_ushort(); + $MarkSetCount = $this->read_ushort(); + $MarkSetOffset = []; + for ($i = 0; $i < $MarkSetCount; $i++) { + $MarkSetOffset[] = $this->read_ulong(); + } + for ($i = 0; $i < $MarkSetCount; $i++) { + $this->seek($MarkSetOffset[$i]); + $glyphs = $this->_getCoverage(); + $this->MarkGlyphSets[$i] = $this->formatClassArr($glyphs); + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>Mark Glyph Set class: ' . $i . '</h3>'); + $html = ''; + $html .= '<div class="glyphs">'; + foreach ($glyphs as $g) { + $html .= '◌&#x' . $g . '; '; + } + $html .= '</div>'; + $this->mpdf->WriteHTML($html); + } + } + } else { + $this->MarkGlyphSets = []; + } + } else { + $this->mpdf->WriteHTML('<div>GDEF table not defined</div>'); + } + +//echo $this->GlyphClassMarks ; exit; +//print_r($GlyphClass); exit; +//print_r($GlyphByClass); exit; + } + + function _getClassDefinitionTable($offset = 0) + { + + if ($offset > 0) { + $this->seek($offset); + } + + // NB Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. This is not returned by this function + $ClassFormat = $this->read_ushort(); + $GlyphByClass = []; + if ($ClassFormat == 1) { + $StartGlyph = $this->read_ushort(); + $GlyphCount = $this->read_ushort(); + for ($i = 0; $i < $GlyphCount; $i++) { + $gid = $StartGlyph + $i; + $class = $this->read_ushort(); + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]); + } + } else { + if ($ClassFormat == 2) { + $tableCount = $this->read_ushort(); + for ($i = 0; $i < $tableCount; $i++) { + $startGlyphID = $this->read_ushort(); + $endGlyphID = $this->read_ushort(); + $class = $this->read_ushort(); + for ($gid = $startGlyphID; $gid <= $endGlyphID; $gid++) { + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]); + } + } + } + } + ksort($GlyphByClass); + + return $GlyphByClass; + } + + function _getGSUBtables() + { + /////////////////////////////////// + // GSUB - Glyph Substitution + /////////////////////////////////// + if (isset($this->tables["GSUB"])) { + $this->mpdf->WriteHTML('<h1>GSUB Tables</h1>'); + $ffeats = []; + $gsub_offset = $this->seek_table("GSUB"); + $this->skip(4); + $ScriptList_offset = $gsub_offset + $this->read_ushort(); + $FeatureList_offset = $gsub_offset + $this->read_ushort(); + $LookupList_offset = $gsub_offset + $this->read_ushort(); + + // ScriptList + $this->seek($ScriptList_offset); + $ScriptCount = $this->read_ushort(); + for ($i = 0; $i < $ScriptCount; $i++) { + $ScriptTag = $this->read_tag(); // = "beng", "deva" etc. + $ScriptTableOffset = $this->read_ushort(); + $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset; + } + + // Script Table + foreach ($ffeats as $t => $o) { + $ls = []; + $this->seek($o); + $DefLangSys_offset = $this->read_ushort(); + if ($DefLangSys_offset > 0) { + $ls['DFLT'] = $DefLangSys_offset + $o; + } + $LangSysCount = $this->read_ushort(); + for ($i = 0; $i < $LangSysCount; $i++) { + $LangTag = $this->read_tag(); // = + $LangTableOffset = $this->read_ushort(); + $ls[$LangTag] = $o + $LangTableOffset; + } + $ffeats[$t] = $ls; + } +//print_r($ffeats); exit; + // Get FeatureIndexList + // LangSys Table - from first listed langsys + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = []; + $langsystable_offset = $o; + $this->seek($langsystable_offset); + $LookUpOrder = $this->read_ushort(); //==NULL + $ReqFeatureIndex = $this->read_ushort(); + if ($ReqFeatureIndex != 0xFFFF) { + $FeatureIndex[] = $ReqFeatureIndex; + } + $FeatureCount = $this->read_ushort(); + for ($i = 0; $i < $FeatureCount; $i++) { + $FeatureIndex[] = $this->read_ushort(); // = index of feature + } + $ffeats[$st][$t] = $FeatureIndex; + } + } +//print_r($ffeats); exit; + // Feauture List => LookupListIndex es + $this->seek($FeatureList_offset); + $FeatureCount = $this->read_ushort(); + $Feature = []; + for ($i = 0; $i < $FeatureCount; $i++) { + $Feature[$i] = ['tag' => $this->read_tag()]; + $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort(); + } + for ($i = 0; $i < $FeatureCount; $i++) { + $this->seek($Feature[$i]['offset']); + $this->read_ushort(); // null + $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort(); + $Feature[$i]['LookupListIndex'] = []; + for ($c = 0; $c < $Lookupcount; $c++) { + $Feature[$i]['LookupListIndex'][] = $this->read_ushort(); + } + } + + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = $ffeats[$st][$t]; + foreach ($FeatureIndex as $k => $fi) { + $ffeats[$st][$t][$k] = $Feature[$fi]; + } + } + } + //===================================================================================== + $gsub = []; + $GSUBScriptLang = []; + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $langsys) { + $lg = []; + foreach ($langsys as $ft) { + $lg[$ft['LookupListIndex'][0]] = $ft; + } + // list of Lookups in order they need to be run i.e. order listed in Lookup table + ksort($lg); + foreach ($lg as $ft) { + $gsub[$st][$t][$ft['tag']] = $ft['LookupListIndex']; + } + if (!isset($GSUBScriptLang[$st])) { + $GSUBScriptLang[$st] = ''; + } + $GSUBScriptLang[$st] .= $t . ' '; + } + } + +//print_r($gsub); exit; + + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>GSUB Scripts & Languages</h3>'); + $this->mpdf->WriteHTML('<div class="glyphs">'); + $html = ''; + if (count($gsub)) { + foreach ($gsub as $st => $g) { + $html .= '<h5>' . $st . '</h5>'; + foreach ($g as $l => $t) { + $html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: '; + foreach ($t as $tag => $o) { + $html .= $tag . ' '; + } + $html .= '</div>'; + } + } + } else { + $html .= '<div>No entries in GSUB table.</div>'; + } + $this->mpdf->WriteHTML($html); + $this->mpdf->WriteHTML('</div>'); + + return 0; + } + + //===================================================================================== + // Get metadata and offsets for whole Lookup List table + $this->seek($LookupList_offset); + $LookupCount = $this->read_ushort(); + $GSLookup = []; + $Offsets = []; + $SubtableCount = []; + for ($i = 0; $i < $LookupCount; $i++) { + $Offsets[$i] = $LookupList_offset + $this->read_ushort(); + } + for ($i = 0; $i < $LookupCount; $i++) { + $this->seek($Offsets[$i]); + $GSLookup[$i]['Type'] = $this->read_ushort(); + $GSLookup[$i]['Flag'] = $flag = $this->read_ushort(); + $GSLookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort(); + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $GSLookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort(); + } + // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure + if (($flag & 0x0010) == 0x0010) { + $GSLookup[$i]['MarkFilteringSet'] = $this->read_ushort(); + } + // else { $GSLookup[$i]['MarkFilteringSet'] = ''; } + // Lookup Type 7: Extension + if ($GSLookup[$i]['Type'] == 7) { + // Overwrites new offset (32-bit) for each subtable, and a new lookup Type + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $this->seek($GSLookup[$i]['Subtables'][$c]); + $ExtensionPosFormat = $this->read_ushort(); + $type = $this->read_ushort(); + $GSLookup[$i]['Subtables'][$c] = $GSLookup[$i]['Subtables'][$c] + $this->read_ulong(); + } + $GSLookup[$i]['Type'] = $type; + } + } + +//print_r($GSLookup); exit; + //===================================================================================== + // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph + $this->GSLuCoverage = []; + for ($i = 0; $i < $LookupCount; $i++) { + for ($c = 0; $c < $GSLookup[$i]['SubtableCount']; $c++) { + $this->seek($GSLookup[$i]['Subtables'][$c]); + $PosFormat = $this->read_ushort(); + + if ($GSLookup[$i]['Type'] == 5 && $PosFormat == 3) { + $this->skip(4); + } else { + if ($GSLookup[$i]['Type'] == 6 && $PosFormat == 3) { + $BacktrackGlyphCount = $this->read_ushort(); + $this->skip(2 * $BacktrackGlyphCount + 2); + } + } + // NB Coverage only looks at glyphs for position 1 (i.e. 5.3 and 6.3) // NEEDS TO READ ALL ******************** + $Coverage = $GSLookup[$i]['Subtables'][$c] + $this->read_ushort(); + $this->seek($Coverage); + $glyphs = $this->_getCoverage(); + $this->GSLuCoverage[$i][$c] = implode('|', $glyphs); + } + } + +// $this->GSLuCoverage and $GSLookup + //===================================================================================== + $s = '<?php +$GSLuCoverage = ' . var_export($this->GSLuCoverage, true) . '; +?>'; + + //===================================================================================== + $s = '<?php +$GlyphClassBases = \'' . $this->GlyphClassBases . '\'; +$GlyphClassMarks = \'' . $this->GlyphClassMarks . '\'; +$GlyphClassLigatures = \'' . $this->GlyphClassLigatures . '\'; +$GlyphClassComponents = \'' . $this->GlyphClassComponents . '\'; +$MarkGlyphSets = ' . var_export($this->MarkGlyphSets, true) . '; +$MarkAttachmentType = ' . var_export($this->MarkAttachmentType, true) . '; +?>'; + + //===================================================================================== + //===================================================================================== + //===================================================================================== +// Now repeats as original to get Substitution rules + //===================================================================================== + //===================================================================================== + //===================================================================================== + // Get metadata and offsets for whole Lookup List table + $this->seek($LookupList_offset); + $LookupCount = $this->read_ushort(); + $Lookup = []; + for ($i = 0; $i < $LookupCount; $i++) { + $Lookup[$i]['offset'] = $LookupList_offset + $this->read_ushort(); + } + for ($i = 0; $i < $LookupCount; $i++) { + $this->seek($Lookup[$i]['offset']); + $Lookup[$i]['Type'] = $this->read_ushort(); + $Lookup[$i]['Flag'] = $flag = $this->read_ushort(); + $Lookup[$i]['SubtableCount'] = $this->read_ushort(); + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['offset'] + $this->read_ushort(); + } + // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure + if (($flag & 0x0010) == 0x0010) { + $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort(); + } else { + $Lookup[$i]['MarkFilteringSet'] = ''; + } + + // Lookup Type 7: Extension + if ($Lookup[$i]['Type'] == 7) { + // Overwrites new offset (32-bit) for each subtable, and a new lookup Type + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $this->seek($Lookup[$i]['Subtable'][$c]['Offset']); + $ExtensionPosFormat = $this->read_ushort(); + $type = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ulong(); + } + $Lookup[$i]['Type'] = $type; + } + } + +//print_r($Lookup); exit; + //===================================================================================== + // Process (1) Whole LookupList + for ($i = 0; $i < $LookupCount; $i++) { + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $this->seek($Lookup[$i]['Subtable'][$c]['Offset']); + $SubstFormat = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['Format'] = $SubstFormat; + + /* + Lookup['Type'] Enumeration table for glyph substitution + Value Type Description + 1 Single Replace one glyph with one glyph + 2 Multiple Replace one glyph with more than one glyph + 3 Alternate Replace one glyph with one of many glyphs + 4 Ligature Replace multiple glyphs with one glyph + 5 Context Replace one or more glyphs in context + 6 Chaining Context Replace one or more glyphs in chained context + 7 Extension Substitution Extension mechanism for other substitutions (i.e. this excludes the Extension type substitution itself) + 8 Reverse chaining context single Applied in reverse order, replace single glyph in chaining context + */ + + // LookupType 1: Single Substitution Subtable + if ($Lookup[$i]['Type'] == 1) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + if ($SubstFormat == 1) { // Calculated output glyph indices + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'] = $this->read_short(); + } else { + if ($SubstFormat == 2) { // Specified output glyph indices + $GlyphCount = $this->read_ushort(); + for ($g = 0; $g < $GlyphCount; $g++) { + $Lookup[$i]['Subtable'][$c]['Glyphs'][] = $this->read_ushort(); + } + } + } + } // LookupType 2: Multiple Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 2) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SequenceCount'] = $SequenceCount = $this->read_short(); + for ($s = 0; $s < $SequenceCount; $s++) { + $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + for ($s = 0; $s < $SequenceCount; $s++) { + // Sequence Tables + $this->seek($Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['SubstituteGlyphID'][] = $this->read_ushort(); + } + } + } // LookupType 3: Alternate Forms + else { + if ($Lookup[$i]['Type'] == 3) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['AlternateSetCount'] = $AlternateSetCount = $this->read_short(); + for ($s = 0; $s < $AlternateSetCount; $s++) { + $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + + for ($s = 0; $s < $AlternateSetCount; $s++) { + // AlternateSet Tables + $this->seek($Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['SubstituteGlyphID'][] = $this->read_ushort(); + } + } + } // LookupType 4: Ligature Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 4) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['LigSetCount'] = $LigSetCount = $this->read_short(); + for ($s = 0; $s < $LigSetCount; $s++) { + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + for ($s = 0; $s < $LigSetCount; $s++) { + // LigatureSet Tables + $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g] = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] + $this->read_ushort(); + } + } + for ($s = 0; $s < $LigSetCount; $s++) { + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { + // Ligature tables + $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g]); + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount'] = $this->read_ushort(); + for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) { + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l] = $this->read_ushort(); + } + } + } + } // LookupType 5: Contextual Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 5) { + // Format 1: Context Substitution + if ($SubstFormat == 1) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubRuleSetCount'] = $SubRuleSetCount = $this->read_short(); + for ($s = 0; $s < $SubRuleSetCount; $s++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + for ($s = 0; $s < $SubRuleSetCount; $s++) { + // SubRuleSet Tables + $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] + $this->read_ushort(); + } + } + for ($s = 0; $s < $SubRuleSetCount; $s++) { + // SubRule Tables + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) { + // Ligature tables + $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g]); + + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount'] = $this->read_ushort(); + // "Input"::[GlyphCount - 1]::Array of input GlyphIDs-start with second glyph + for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount']; $l++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['Input'][$l] = $this->read_ushort(); + } + // "SubstLookupRecord"::[SubstCount]::Array of SubstLookupRecords-in design order + for ($l = 0; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount']; $l++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['SequenceIndex'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['LookupListIndex'] = $this->read_ushort(); + } + } + } + } // Format 2: Class-based Context Glyph Substitution + else { + if ($SubstFormat == 2) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubClassSetCnt'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = 0; + } else { + $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset; + } + } + } else { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Lookup[$i]['Type'] . ", Format " . $SubstFormat . " not supported (ttfontsuni.php)."); + } + } + } // LookupType 6: Chaining Contextual Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 6) { + // Format 1: Simple Chaining Context Glyph Substitution p255 + if ($SubstFormat == 1) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + } // Format 2: Class-based Chaining Context Glyph Substitution p257 + else { + if ($SubstFormat == 2) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['InputClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $offset; + } else { + $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset; + } + } + } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 + else { + if ($SubstFormat == 3) { + $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + $Lookup[$i]['Subtable'][$c]['InputGlyphCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['CoverageInput'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['CoverageLookahead'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + $Lookup[$i]['Subtable'][$c]['SubstCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'] = $this->read_ushort(); + /* + Substitution Lookup Record + All contextual substitution subtables specify the substitution data in a Substitution Lookup Record (SubstLookupRecord). Each record contains a SequenceIndex, which indicates the position where the substitution will occur in the glyph sequence. In addition, a LookupListIndex identifies the lookup to be applied at the glyph position specified by the SequenceIndex. + */ + } + } + } + } + } else { + throw new \Mpdf\MpdfException("Lookup Type " . $Lookup[$i]['Type'] . " not supported."); + } + } + } + } + } + } + } + } +//print_r($Lookup); exit; + //===================================================================================== + // Process (2) Whole LookupList + // Get Coverage tables and prepare preg_replace + for ($i = 0; $i < $LookupCount; $i++) { + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format']; + + // LookupType 1: Single Substitution Subtable 1 => 1 + if ($Lookup[$i]['Type'] == 1) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(false); + for ($g = 0; $g < count($glyphs); $g++) { + $replace = []; + $substitute = []; + $replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]); + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1 + $substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]); + } else { // Format 2 + $substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]); + } + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; + } + } // LookupType 2: Multiple Substitution Subtable 1 => n + else { + if ($Lookup[$i]['Type'] == 2) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(); + for ($g = 0; $g < count($glyphs); $g++) { + $replace = []; + $substitute = []; + $replace[] = $glyphs[$g]; + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + if (!isset($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) || count($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) == 0) { + continue; + } // Illegal for GlyphCount to be 0; either error in font, or something has gone wrong - lets carry on for now! + foreach ($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID'] as $sub) { + $substitute[] = unicode_hex($this->glyphToChar[$sub][0]); + } + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; + } + } // LookupType 3: Alternate Forms 1 => 1 (only first alternate form is used) + else { + if ($Lookup[$i]['Type'] == 3) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(); + for ($g = 0; $g < count($glyphs); $g++) { + $replace = []; + $substitute = []; + $replace[] = $glyphs[$g]; + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + + for ($gl = 0; $gl < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['GlyphCount']; $gl++) { + $gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][$gl]; + $substitute[] = unicode_hex($this->glyphToChar[$gid][0]); + } + + //$gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][0]; + //$substitute[] = unicode_hex($this->glyphToChar[$gid][0]); + + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; + } + if ($i == 166) { + print_r($Lookup[$i]['Subtable']); + exit; + } + } // LookupType 4: Ligature Substitution Subtable n => 1 + else { + if ($Lookup[$i]['Type'] == 4) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(); + $LigSetCount = $Lookup[$i]['Subtable'][$c]['LigSetCount']; + for ($s = 0; $s < $LigSetCount; $s++) { + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { + $replace = []; + $substitute = []; + $replace[] = $glyphs[$s]; + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) { + $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l]; + $rpl = unicode_hex($this->glyphToChar[$gid][0]); + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $rpl, $Lookup[$i]['MarkFilteringSet'])) { + continue 2; + } + $replace[] = $rpl; + } + $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph']; + $substitute[] = unicode_hex($this->glyphToChar[$gid][0]); + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']]; + } + } + } // LookupType 5: Contextual Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 5) { + // Format 1: Context Substitution + if ($SubstFormat == 1) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) { + $SubRuleSet = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]; + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'] = $CoverageGlyphs[$s]; + for ($r = 0; $r < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $r++) { + $GlyphCount = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['GlyphCount']; + for ($g = 1; $g < $GlyphCount; $g++) { + $glyphID = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['Input'][$g]; + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + } + } + } // Format 2: Class-based Context Glyph Substitution + else { + if ($SubstFormat == 2) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['ClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses; + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) { + if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) { + $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s]); + $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'] = $SubClassRuleCnt = $this->read_ushort(); + $SubClassRule = []; + for ($b = 0; $b < $SubClassRuleCnt; $b++) { + $SubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $SubClassRule[$b]; + } + } + } + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) { + $SubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt']; + for ($b = 0; $b < $SubClassRuleCnt; $b++) { + if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) { + $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b]); + $Rule = []; + $Rule['InputGlyphCount'] = $this->read_ushort(); + $Rule['SubstCount'] = $this->read_ushort(); + for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) { + $Rule['Input'][$r] = $this->read_ushort(); + } + for ($r = 0; $r < $Rule['SubstCount']; $r++) { + $Rule['SequenceIndex'][$r] = $this->read_ushort(); + $Rule['LookupListIndex'][$r] = $this->read_ushort(); + } + + $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $Rule; + } + } + } + } // Format 3: Coverage-based Context Glyph Substitution + else { + if ($SubstFormat == 3) { + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs); + } + throw new \Mpdf\MpdfException("Lookup Type 5, SubstFormat 3 not tested. Please report this with the name of font used - " . $this->fontkey); + } + } + } + } // LookupType 6: Chaining Contextual Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 6) { + // Format 1: Simple Chaining Context Glyph Substitution p255 + if ($SubstFormat == 1) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + $ChainSubRuleSetCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; + + for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) { + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s]); + $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'] = $this->read_ushort(); + for ($r = 0; $r < $ChainSubRuleCnt; $r++) { + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r] = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s] + $this->read_ushort(); + } + } + for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) { + $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount']; + for ($r = 0; $r < $ChainSubRuleCnt; $r++) { + // ChainSubRule + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r]); + + $BacktrackGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphCount'] = $this->read_ushort(); + for ($g = 0; $g < $BacktrackGlyphCount; $g++) { + $glyphID = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + + $InputGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphCount'] = $this->read_ushort(); + for ($g = 1; $g < $InputGlyphCount; $g++) { + $glyphID = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + + $LookaheadGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphCount'] = $this->read_ushort(); + for ($g = 0; $g < $LookaheadGlyphCount; $g++) { + $glyphID = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + + $SubstCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SubstCount'] = $this->read_ushort(); + for ($lu = 0; $lu < $SubstCount; $lu++) { + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SequenceIndex'][$lu] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookupListIndex'][$lu] = $this->read_ushort(); + } + } + } + } // Format 2: Class-based Chaining Context Glyph Substitution p257 + else { + if ($SubstFormat == 2) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + $BacktrackClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['BacktrackClasses'] = $BacktrackClasses; + + $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['InputClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses; + + $LookaheadClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['LookaheadClasses'] = $LookaheadClasses; + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) { + if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) { + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s]); + $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'] = $ChainSubClassRuleCnt = $this->read_ushort(); + $ChainSubClassRule = []; + for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { + $ChainSubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $ChainSubClassRule[$b]; + } + } + } + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) { + $ChainSubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt']; + for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { + if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) { + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b]); + $Rule = []; + $Rule['BacktrackGlyphCount'] = $this->read_ushort(); + for ($r = 0; $r < $Rule['BacktrackGlyphCount']; $r++) { + $Rule['Backtrack'][$r] = $this->read_ushort(); + } + $Rule['InputGlyphCount'] = $this->read_ushort(); + for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) { + $Rule['Input'][$r] = $this->read_ushort(); + } + $Rule['LookaheadGlyphCount'] = $this->read_ushort(); + for ($r = 0; $r < $Rule['LookaheadGlyphCount']; $r++) { + $Rule['Lookahead'][$r] = $this->read_ushort(); + } + $Rule['SubstCount'] = $this->read_ushort(); + for ($r = 0; $r < $Rule['SubstCount']; $r++) { + $Rule['SequenceIndex'][$r] = $this->read_ushort(); + $Rule['LookupListIndex'][$r] = $this->read_ushort(); + } + + $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $Rule; + } + } + } + } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 + else { + if ($SubstFormat == 3) { + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'][] = implode("|", $glyphs); + } + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs); + // Don't use above value as these are ordered numerically not as need to process + } + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageLookahead'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'][] = implode("|", $glyphs); + } + } + } + } + } + } + } + } + } + } + } + } + + //===================================================================================== + //===================================================================================== + //===================================================================================== + + $st = $this->mpdf->OTLscript; + $t = $this->mpdf->OTLlang; + $langsys = $gsub[$st][$t]; + + $lul = []; // array of LookupListIndexes + $tags = []; // corresponding array of feature tags e.g. 'ccmp' + foreach ($langsys as $tag => $ft) { + foreach ($ft as $ll) { + $lul[$ll] = $tag; + } + } + ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order + $this->_getGSUBarray($Lookup, $lul, $st); +//print_r($lul); exit; + } + +//print_r($Lookup); exit; + + return [$GSUBScriptLang, $gsub, $GSLookup, $rtlPUAstr, $rtlPUAarr]; + } + +///////////////////////////////////////////////////////////////////////////////////////// + // GSUB functions + function _getGSUBarray(&$Lookup, &$lul, $scripttag, $level = 1, $coverage = '', $exB = '', $exL = '') + { + // Process (3) LookupList for specific Script-LangSys + // Generate preg_replace + $html = ''; + if ($level == 1) { + $html .= '<bookmark level="0" content="GSUB features">'; + } + foreach ($lul as $i => $tag) { + $html .= '<div class="level' . $level . '">'; + $html .= '<h5 class="level' . $level . '">'; + if ($level == 1) { + $html .= '<bookmark level="1" content="' . $tag . ' [#' . $i . ']">'; + } + $html .= 'Lookup #' . $i . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>'; + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + if ($ignore) { + $html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> '; + } + + $Type = $Lookup[$i]['Type']; + $Flag = $Lookup[$i]['Flag']; + if (($Flag & 0x0001) == 1) { + $dir = 'RTL'; + } else { + $dir = 'LTR'; + } + + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $html .= '<div class="subtable">Subtable #' . $c; + if ($level == 1) { + $html .= '<bookmark level="2" content="Subtable #' . $c . '">'; + } + $html .= '</div>'; + + $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format']; + + // LookupType 1: Single Substitution Subtable + if ($Lookup[$i]['Type'] == 1) { + $html .= '<div class="lookuptype">LookupType 1: Single Substitution Subtable</div>'; + for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; + if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { + continue; + } + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . ' </span> '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="unchanged"> ' . $this->formatEntity($inputGlyphs[0]) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  » »  '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  <span class="unicode">' . $this->formatUni($substitute) . '</span> '; + $html .= '</div>'; + } + } // LookupType 2: Multiple Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 2) { + $html .= '<div class="lookuptype">LookupType 2: Multiple Substitution Subtable</div>'; + for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']; + if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { + continue; + } + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . ' </span> '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="unchanged"> ' . $this->formatEntity($inputGlyphs[0]) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  » »  '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="changed"> ' . $this->formatEntityArr($substitute) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  <span class="unicode">' . $this->formatUniArr($substitute) . '</span> '; + $html .= '</div>'; + } + } // LookupType 3: Alternate Forms + else { + if ($Lookup[$i]['Type'] == 3) { + $html .= '<div class="lookuptype">LookupType 3: Alternate Forms</div>'; + for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; + if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { + continue; + } + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">' . $this->formatUni($inputGlyphs[0]) . ' </span> '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="unchanged"> ' . $this->formatEntity($inputGlyphs[0]) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  » »  '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  <span class="unicode">' . $this->formatUni($substitute) . '</span> '; + if (count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']) > 1) { + for ($alt = 1; $alt < count($Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']); $alt++) { + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][$alt]; + $html .= '  |   ALT #' . $alt . '   '; + $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; + $html .= '  <span class="unicode">' . $this->formatUni($substitute) . '</span> '; + } + } + $html .= '</div>'; + } + } // LookupType 4: Ligature Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 4) { + $html .= '<div class="lookuptype">LookupType 4: Ligature Substitution Subtable</div>'; + for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; + if ($level == 2 && strpos($coverage, $inputGlyphs[0]) === false) { + continue; + } + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">' . $this->formatUniArr($inputGlyphs) . ' </span> '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="unchanged"> ' . $this->formatEntityArr($inputGlyphs) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  » »  '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="changed"> ' . $this->formatEntity($substitute) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  <span class="unicode">' . $this->formatUni($substitute) . '</span> '; + $html .= '</div>'; + } + } // LookupType 5: Contextual Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 5) { + $html .= '<div class="lookuptype">LookupType 5: Contextual Substitution Subtable</div>'; + // Format 1: Context Substitution + if ($SubstFormat == 1) { + $html .= '<div class="lookuptypesub">Format 1: Context Substitution</div>'; + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) { + // SubRuleSet + $subRule = []; + $html .= '<div class="rule">Subrule Set: ' . $s . '</div>'; + foreach ($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'] as $rctr => $rule) { + // SubRule + $html .= '<div class="rule">SubRule: ' . $rctr . '</div>'; + $inputGlyphs = []; + if ($rule['GlyphCount'] > 1) { + $inputGlyphs = $rule['InputGlyphs']; + } + $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph']; + ksort($inputGlyphs); + $nInput = count($inputGlyphs); + + $exampleI = []; + $html .= '<div class="context">CONTEXT: '; + for ($ff = 0; $ff < count($inputGlyphs); $ff++) { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; + $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); + } + $html .= '</div>'; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['SubstLookupRecord'][$b]['LookupListIndex']; + $seqIndex = $rule['SubstLookupRecord'][$b]['SequenceIndex']; + + // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex] + $exB = ''; + $exL = ''; + if ($seqIndex > 0) { + $exB .= '<span class="inputother">'; + for ($ip = 0; $ip < $seqIndex; $ip++) { + $exB .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; + } + $exB .= '</span>'; + } + if (count($inputGlyphs) > ($seqIndex + 1)) { + $exL .= '<span class="inputother">'; + for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { + $exL .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; + } + $exL .= '</span>'; + } + $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; + + $lul2 = [$lup => $tag]; + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 + // to level 2 and only apply if first Replace glyph is in this list + $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); + } + + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + } // Format 2: Class-based Context Glyph Substitution + else { + if ($SubstFormat == 2) { + $html .= '<div class="lookuptypesub">Format 2: Class-based Context Glyph Substitution</div>'; + foreach ($Lookup[$i]['Subtable'][$c]['SubClassSet'] as $inputClass => $cscs) { + $html .= '<div class="rule">Input Class: ' . $inputClass . '</div>'; + for ($cscrule = 0; $cscrule < $cscs['SubClassRuleCnt']; $cscrule++) { + $html .= '<div class="rule">Rule: ' . $cscrule . '</div>'; + $rule = $cscs['SubClassRule'][$cscrule]; + + $inputGlyphs = []; + + $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass]; + + if ($rule['InputGlyphCount'] > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) { + $classindex = $rule['Input'][$gcl]; + $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex]; + } + } + + // Class 0 contains all the glyphs NOT in the other classes + $class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']); + + $exampleI = []; + $html .= '<div class="context">CONTEXT: '; + for ($ff = 0; $ff < count($inputGlyphs); $ff++) { + if (!$inputGlyphs[$ff]) { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; + $exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; + } else { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; + $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); + } + } + $html .= '</div>'; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['LookupListIndex'][$b]; + $seqIndex = $rule['SequenceIndex'][$b]; + + // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex] + $exB = ''; + $exL = ''; + + if ($seqIndex > 0) { + $exB .= '<span class="inputother">'; + for ($ip = 0; $ip < $seqIndex; $ip++) { + if (!$inputGlyphs[$ip]) { + $exB .= '[*]'; + } else { + $exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; + } + } + $exB .= '</span>'; + } + + if (count($inputGlyphs) > ($seqIndex + 1)) { + $exL .= '<span class="inputother">'; + for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { + if (!$inputGlyphs[$ip]) { + $exL .= '[*]'; + } else { + $exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; + } + } + $exL .= '</span>'; + } + + $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; + + $lul2 = [$lup => $tag]; + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 + // to level 2 and only apply if first Replace glyph is in this list + $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); + } + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + } // Format 3: Coverage-based Context Glyph Substitution p259 + else { + if ($SubstFormat == 3) { + $html .= '<div class="lookuptypesub">Format 3: Coverage-based Context Glyph Substitution </div>'; + // IgnoreMarks flag set on main Lookup table + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs']; + $CoverageInputGlyphs = implode('|', $inputGlyphs); + $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; + + $exampleI = []; + $html .= '<div class="context">CONTEXT: '; + for ($ff = 0; $ff < count($inputGlyphs); $ff++) { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; + $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); + } + $html .= '</div>'; + + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { + $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex']; + $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex']; + // GENERATE exampleI[<seqIndex] .... exampleI[>seqIndex] + $exB = ''; + $exL = ''; + if ($seqIndex > 0) { + $exB .= '<span class="inputother">'; + for ($ip = 0; $ip < $seqIndex; $ip++) { + $exB .= $exampleI[$ip] . '‍'; + } + $exB .= '</span>'; + } + + if (count($inputGlyphs) > ($seqIndex + 1)) { + $exL .= '<span class="inputother">'; + for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { + $exL .= $exampleI[$ip] . '‍'; + } + $exL .= '</span>'; + } + + $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; + + $lul2 = [$lup => $tag]; + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 + // to level 2 and only apply if first Replace glyph is in this list + $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); + } + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + } + +//print_r($Lookup[$i]); +//print_r($volt[(count($volt)-1)]); exit; + } // LookupType 6: Chaining Contextual Substitution Subtable + else { + if ($Lookup[$i]['Type'] == 6) { + $html .= '<div class="lookuptype">LookupType 6: Chaining Contextual Substitution Subtable</div>'; + // Format 1: Simple Chaining Context Glyph Substitution p255 + if ($SubstFormat == 1) { + $html .= '<div class="lookuptypesub">Format 1: Simple Chaining Context Glyph Substitution </div>'; + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $s++) { + // ChainSubRuleSet + $subRule = []; + $html .= '<div class="rule">Subrule Set: ' . $s . '</div>'; + $firstInputGlyph = $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'][$s]; // First input gyyph + foreach ($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'] as $rctr => $rule) { + $html .= '<div class="rule">SubRule: ' . $rctr . '</div>'; + // ChainSubRule + $inputGlyphs = []; + if ($rule['InputGlyphCount'] > 1) { + $inputGlyphs = $rule['InputGlyphs']; + } + $inputGlyphs[0] = $firstInputGlyph; + ksort($inputGlyphs); + $nInput = count($inputGlyphs); + + if ($rule['BacktrackGlyphCount']) { + $backtrackGlyphs = $rule['BacktrackGlyphs']; + } else { + $backtrackGlyphs = []; + } + + if ($rule['LookaheadGlyphCount']) { + $lookaheadGlyphs = $rule['LookaheadGlyphs']; + } else { + $lookaheadGlyphs = []; + } + + $exampleB = []; + $exampleI = []; + $exampleL = []; + $html .= '<div class="context">CONTEXT: '; + for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { + $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; + $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); + } + for ($ff = 0; $ff < count($inputGlyphs); $ff++) { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; + $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); + } + for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { + $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; + $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); + } + $html .= '</div>'; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['LookupListIndex'][$b]; + $seqIndex = $rule['SequenceIndex'][$b]; + + // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] + $exB = ''; + $exL = ''; + if (count($exampleB)) { + $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; + } + + if ($seqIndex > 0) { + $exB .= '<span class="inputother">'; + for ($ip = 0; $ip < $seqIndex; $ip++) { + $exB .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; + } + $exB .= '</span>'; + } + + if (count($inputGlyphs) > ($seqIndex + 1)) { + $exL .= '<span class="inputother">'; + for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { + $exL .= $this->formatEntity($inputGlyphs[$ip]) . '‍'; + } + $exL .= '</span>'; + } + + if (count($exampleL)) { + $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; + } + + $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; + + $lul2 = [$lup => $tag]; + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 + // to level 2 and only apply if first Replace glyph is in this list + $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); + } + + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + } // Format 2: Class-based Chaining Context Glyph Substitution p257 + else { + if ($SubstFormat == 2) { + $html .= '<div class="lookuptypesub">Format 2: Class-based Chaining Context Glyph Substitution </div>'; + foreach ($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'] as $inputClass => $cscs) { + $html .= '<div class="rule">Input Class: ' . $inputClass . '</div>'; + for ($cscrule = 0; $cscrule < $cscs['ChainSubClassRuleCnt']; $cscrule++) { + $html .= '<div class="rule">Rule: ' . $cscrule . '</div>'; + $rule = $cscs['ChainSubClassRule'][$cscrule]; + + // These contain classes of glyphs as strings + // $Lookup[$i]['Subtable'][$c]['InputClasses'][(class)] e.g. 02E6|02E7|02E8 + // $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][(class)] + // $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][(class)] + // These contain arrays of classIndexes + // [Backtrack] [Lookahead] and [Input] (Input is from the second position only) + + $inputGlyphs = []; + + $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass]; + if ($rule['InputGlyphCount'] > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) { + $classindex = $rule['Input'][$gcl]; + $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex]; + } + } + // Class 0 contains all the glyphs NOT in the other classes + $class0excl = implode('|', $Lookup[$i]['Subtable'][$c]['InputClasses']); + + $nInput = $rule['InputGlyphCount']; + + if ($rule['BacktrackGlyphCount']) { + for ($gcl = 0; $gcl < $rule['BacktrackGlyphCount']; $gcl++) { + $classindex = $rule['Backtrack'][$gcl]; + $backtrackGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex]; + } + } else { + $backtrackGlyphs = []; + } + + if ($rule['LookaheadGlyphCount']) { + for ($gcl = 0; $gcl < $rule['LookaheadGlyphCount']; $gcl++) { + $classindex = $rule['Lookahead'][$gcl]; + $lookaheadGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex]; + } + } else { + $lookaheadGlyphs = []; + } + + $exampleB = []; + $exampleI = []; + $exampleL = []; + $html .= '<div class="context">CONTEXT: '; + for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { + if (!$backtrackGlyphs[$ff]) { + $html .= '<div>Backtrack #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; + $exampleB[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; + } else { + $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; + $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); + } + } + for ($ff = 0; $ff < count($inputGlyphs); $ff++) { + if (!$inputGlyphs[$ff]) { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; + $exampleI[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; + } else { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; + $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); + } + } + for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { + if (!$lookaheadGlyphs[$ff]) { + $html .= '<div>Lookahead #' . $ff . ': <span class="unchanged"> [NOT ' . $this->formatEntityStr($class0excl) . '] </span></div>'; + $exampleL[] = '[NOT ' . $this->formatEntityFirst($class0excl) . ']'; + } else { + $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; + $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); + } + } + $html .= '</div>'; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['LookupListIndex'][$b]; + $seqIndex = $rule['SequenceIndex'][$b]; + + // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] + $exB = ''; + $exL = ''; + if (count($exampleB)) { + $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; + } + + if ($seqIndex > 0) { + $exB .= '<span class="inputother">'; + for ($ip = 0; $ip < $seqIndex; $ip++) { + if (!$inputGlyphs[$ip]) { + $exB .= '[*]'; + } else { + $exB .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; + } + } + $exB .= '</span>'; + } + + if (count($inputGlyphs) > ($seqIndex + 1)) { + $exL .= '<span class="inputother">'; + for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { + if (!$inputGlyphs[$ip]) { + $exL .= '[*]'; + } else { + $exL .= $this->formatEntityFirst($inputGlyphs[$ip]) . '‍'; + } + } + $exL .= '</span>'; + } + + if (count($exampleL)) { + $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; + } + + $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; + + $lul2 = [$lup => $tag]; + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 + // to level 2 and only apply if first Replace glyph is in this list + $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); + } + } + } + +//print_r($Lookup[$i]['Subtable'][$c]); exit; + } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 + else { + if ($SubstFormat == 3) { + $html .= '<div class="lookuptypesub">Format 3: Coverage-based Chaining Context Glyph Substitution </div>'; + // IgnoreMarks flag set on main Lookup table + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs']; + $CoverageInputGlyphs = implode('|', $inputGlyphs); + $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; + + if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) { + $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs']; + } else { + $backtrackGlyphs = []; + } + + if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) { + $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs']; + } else { + $lookaheadGlyphs = []; + } + + $exampleB = []; + $exampleI = []; + $exampleL = []; + $html .= '<div class="context">CONTEXT: '; + for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { + $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; + $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); + } + for ($ff = 0; $ff < count($inputGlyphs); $ff++) { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; + $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); + } + for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { + $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; + $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); + } + $html .= '</div>'; + + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { + $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex']; + $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex']; + + // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] + $exB = ''; + $exL = ''; + if (count($exampleB)) { + $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; + } + + if ($seqIndex > 0) { + $exB .= '<span class="inputother">'; + for ($ip = 0; $ip < $seqIndex; $ip++) { + $exB .= $exampleI[$ip] . '‍'; + } + $exB .= '</span>'; + } + + if (count($inputGlyphs) > ($seqIndex + 1)) { + $exL .= '<span class="inputother">'; + for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { + $exL .= $exampleI[$ip] . '‍'; + } + $exL .= '</span>'; + } + + if (count($exampleL)) { + $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; + } + + $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; + + $lul2 = [$lup => $tag]; + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 + // to level 2 and only apply if first Replace glyph is in this list + $html .= $this->_getGSUBarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); + } + } + } + } + } + } + } + } + } + } + } + $html .= '</div>'; + } + if ($level == 1) { + $this->mpdf->WriteHTML($html); + } else { + return $html; + } +//print_r($Lookup); exit; + } + + //===================================================================================== + //===================================================================================== + // mPDF 5.7.1 + function _checkGSUBignore($flag, $glyph, $MarkFilteringSet) + { + $ignore = false; + // Flag & 0x0008 = Ignore Marks + if ((($flag & 0x0008) == 0x0008) && strpos($this->GlyphClassMarks, $glyph)) { + $ignore = true; + } + if ((($flag & 0x0004) == 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) { + $ignore = true; + } + if ((($flag & 0x0002) == 0x0002) && strpos($this->GlyphClassBases, $glyph)) { + $ignore = true; + } + // Flag & 0xFF?? = MarkAttachmentType + if (($flag & 0xFF00) && strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) { + $ignore = true; + } + // Flag & 0x0010 = UseMarkFilteringSet + if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) { + $ignore = true; + } + + return $ignore; + } + + function _getGSUBignoreString($flag, $MarkFilteringSet) + { + // If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)" + // else "()" + // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup + $str = ""; + $ignoreflag = 0; + + // Flag & 0xFF?? = MarkAttachmentType + if ($flag & 0xFF00) { + $MarkAttachmentType = $flag >> 8; + $ignoreflag = $flag; + //$str = $this->MarkAttachmentType[$MarkAttachmentType]; + $str = "MarkAttachmentType[" . $MarkAttachmentType . "] "; + } + + // Flag & 0x0010 = UseMarkFilteringSet + if ($flag & 0x0010) { + throw new \Mpdf\MpdfException("This font " . $this->fontkey . " contains MarkGlyphSets"); + $str = "Mark Glyph Set: "; + $str .= $this->MarkGlyphSets[$MarkFilteringSet]; + } + + // If Ignore Marks set, supercedes any above + // Flag & 0x0008 = Ignore Marks + if (($flag & 0x0008) == 0x0008) { + $ignoreflag = 8; + //$str = $this->GlyphClassMarks; + $str = "Mark Glyphs "; + } + + // Flag & 0x0004 = Ignore Ligatures + if (($flag & 0x0004) == 0x0004) { + $ignoreflag += 4; + if ($str) { + $str .= "|"; + } + //$str .= $this->GlyphClassLigatures; + $str .= "Ligature Glyphs "; + } + // Flag & 0x0002 = Ignore BaseGlyphs + if (($flag & 0x0002) == 0x0002) { + $ignoreflag += 2; + if ($str) { + $str .= "|"; + } + //$str .= $this->GlyphClassBases; + $str .= "Base Glyphs "; + } + if ($str) { + return $str; + } else { + return ""; + } + } + + // GSUB Patterns + + /* + BACKTRACK INPUT LOOKAHEAD + ================================== ================== ================================== + (FEEB|FEEC)(ign) ¦(FD12|FD13)(ign) ¦(0612)¦(ign) (0613)¦(ign) (FD12|FD13)¦(ign) (FEEB|FEEC) + ---------------- ---------------- ----- ------------ --------------- --------------- + Backtrack 1 Backtrack 2 Input 1 Input 2 Lookahead 1 Lookahead 2 + -------- --- --------- --- ---- --- ---- --- --------- --- ------- + \${1} \${2} \${3} \${4} \${5+} \${6+} \${7+} \${8+} + + nBacktrack = 2 nInput = 2 nLookahead = 2 + + nBsubs = 2xnBack nIsubs = (nBsubs+) nLsubs = (nBsubs+nIsubs+) 2xnLookahead + "\${1}\${2} " (nInput*2)-1 "\${5+} \${6+}" + "REPL" + + ¦\${1}\${2} ¦\${3}\${4} ¦REPL¦\${5+} \${6+}¦\${7+} \${8+}¦ + + + INPUT nInput = 5 + ============================================================ + ¦(0612)¦(ign) (0613)¦(ign) (0614)¦(ign) (0615)¦(ign) (0615)¦ + \${1} \${2} \${3} \${4} \${5} \${6} \${7} \${8} \${9} (All backreference numbers are + nBsubs) + ----- ------------ ------------ ------------ ------------ + Input 1 Input 2 Input 3 Input 4 Input 5 + + A====== SequenceIndex=1 ; Lookup match nGlyphs=1 + B=================== SequenceIndex=1 ; Lookup match nGlyphs=2 + C=============================== SequenceIndex=1 ; Lookup match nGlyphs=3 + D======================= SequenceIndex=2 ; Lookup match nGlyphs=2 + E===================================== SequenceIndex=2 ; Lookup match nGlyphs=3 + F====================== SequenceIndex=4 ; Lookup match nGlyphs=2 + + All backreference numbers are + nBsubs + A - "REPL\${2} \${3}\${4} \${5}\${6} \${7}\${8} \${9}" + B - "REPL\${2}\${4} \${5}\${6} \${7}\${8} \${9}" + C - "REPL\${2}\${4}\${6} \${7}\${8} \${9}" + D - "\${1} REPL\${2}\${4}\${6} \${7}\${8} \${9}" + E - "\${1} REPL\${2}\${4}\${6}\${8} \${9}" + F - "\${1}\${2} \${3}\${4} \${5} REPL\${6}\${8}" + */ + + function _makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context + // $lookupGlyphs = array of glyphs (single Glyphs) making up Lookup Input sequence + $mLen = count($lookupGlyphs); // nGlyphs in the secondary Lookup match + $nInput = count($inputGlyphs); // nGlyphs in the Primary Input sequence + $str = ""; + for ($i = 0; $i < $nInput; $i++) { + if ($i > 0) { + $str .= $ignore . " "; + } + if ($i >= $seqIndex && $i < ($seqIndex + $mLen)) { + $str .= "" . $lookupGlyphs[($i - $seqIndex)] . ""; + } else { + $str .= "" . $inputGlyphs[($i)] . ""; + } + } + + return $str; + } + + function _makeGSUBinputMatch($inputGlyphs, $ignore) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context + // $lookupGlyphs = array of glyphs making up Lookup Input sequence - if applicable + $str = ""; + for ($i = 1; $i <= count($inputGlyphs); $i++) { + if ($i > 1) { + $str .= $ignore . " "; + } + $str .= "" . $inputGlyphs[($i - 1)] . ""; + } + + return $str; + } + + function _makeGSUBbacktrackMatch($backtrackGlyphs, $ignore) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦ + // $backtrackGlyphs = array of glyphstrings making up Backtrack sequence + // 3 2 1 0 + // each item being e.g. E0AD|E0AF|F1FD + $str = ""; + for ($i = (count($backtrackGlyphs) - 1); $i >= 0; $i--) { + $str .= "" . $backtrackGlyphs[$i] . " " . $ignore . " "; + } + + return $str; + } + + function _makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦ + // $lookaheadGlyphs = array of glyphstrings making up Lookahead sequence + // 0 1 2 3 + // each item being e.g. E0AD|E0AF|F1FD + $str = ""; + for ($i = 0; $i < count($lookaheadGlyphs); $i++) { + $str .= $ignore . " " . $lookaheadGlyphs[$i] . ""; + } + + return $str; + } + + function _makeGSUBinputReplacement($nInput, $REPL, $ignore, $nBsubs, $mLen, $seqIndex) + { + // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}" + // $nInput nGlyphs in the Primary Input sequence + // $REPL replacement glyphs from secondary lookup + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // $nBsubs Number of Backtrack substitutions (= 2x Number of Backtrack glyphs) + // $mLen nGlyphs in the secondary Lookup match - if no secondary lookup, should=$nInput + // $seqIndex Sequence Index to apply the secondary match + if ($ignore == "()") { + $ign = false; + } else { + $ign = true; + } + $str = ""; + if ($nInput == 1) { + $str = $REPL; + } else { + if ($nInput > 1) { + if ($mLen == $nInput) { // whole string replaced + $str = $REPL; + if ($ign) { + // for every nInput over 1, add another replacement backreference, to move IGNORES after replacement + for ($x = 2; $x <= $nInput; $x++) { + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + } + } else { // if only part of string replaced: + for ($x = 1; $x < ($seqIndex + 1); $x++) { + if ($x == 1) { + $str .= '\\' . ($nBsubs + 1); + } else { + if ($ign) { + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1))); + } + } + if ($seqIndex > 0) { + $str .= " "; + } + $str .= $REPL; + if ($ign) { + for ($x = (max(($seqIndex + 1), 2)); $x < ($seqIndex + 1 + $mLen); $x++) { // move IGNORES after replacement + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + } + for ($x = ($seqIndex + 1 + $mLen); $x <= $nInput; $x++) { + if ($ign) { + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1))); + } + } + } + } + + return $str; + } + + ////////////////////////////////////////////////////////////////////////////////// + function _getCoverage($convert2hex = true) + { + $g = []; + $CoverageFormat = $this->read_ushort(); + if ($CoverageFormat == 1) { + $CoverageGlyphCount = $this->read_ushort(); + for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) { + $glyphID = $this->read_ushort(); + if ($convert2hex) { + $g[] = unicode_hex($this->glyphToChar[$glyphID][0]); + } else { + $g[] = $glyphID; + } + } + } + if ($CoverageFormat == 2) { + $RangeCount = $this->read_ushort(); + for ($r = 0; $r < $RangeCount; $r++) { + $start = $this->read_ushort(); + $end = $this->read_ushort(); + $StartCoverageIndex = $this->read_ushort(); // n/a + for ($gid = $start; $gid <= $end; $gid++) { + $glyphID = $gid; + if ($convert2hex) { + $g[] = unicode_hex($this->glyphToChar[$glyphID][0]); + } else { + $g[] = $glyphID; + } + } + } + } + + return $g; + } + + ////////////////////////////////////////////////////////////////////////////////// + function _getClasses($offset) + { + $this->seek($offset); + $ClassFormat = $this->read_ushort(); + $GlyphByClass = []; + if ($ClassFormat == 1) { + $StartGlyph = $this->read_ushort(); + $GlyphCount = $this->read_ushort(); + for ($i = 0; $i < $GlyphCount; $i++) { + $startGlyphID = $StartGlyph + $i; + $endGlyphID = $StartGlyph + $i; + $class = $this->read_ushort(); + for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { + if ($this->glyphToChar[$g][0]) { + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]); + } + } + } + } else { + if ($ClassFormat == 2) { + $tableCount = $this->read_ushort(); + for ($i = 0; $i < $tableCount; $i++) { + $startGlyphID = $this->read_ushort(); + $endGlyphID = $this->read_ushort(); + $class = $this->read_ushort(); + for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { + if ($this->glyphToChar[$g][0]) { + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]); + } + } + } + } + } + $gbc = []; + foreach ($GlyphByClass as $class => $garr) { + $gbc[$class] = implode('|', $garr); + } + + return $gbc; + } + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + function _getGPOStables() + { + /////////////////////////////////// + // GPOS - Glyph Positioning + /////////////////////////////////// + if (isset($this->tables["GPOS"])) { + $this->mpdf->WriteHTML('<h1>GPOS Tables</h1>'); + $ffeats = []; + $gpos_offset = $this->seek_table("GPOS"); + $this->skip(4); + $ScriptList_offset = $gpos_offset + $this->read_ushort(); + $FeatureList_offset = $gpos_offset + $this->read_ushort(); + $LookupList_offset = $gpos_offset + $this->read_ushort(); + + // ScriptList + $this->seek($ScriptList_offset); + $ScriptCount = $this->read_ushort(); + for ($i = 0; $i < $ScriptCount; $i++) { + $ScriptTag = $this->read_tag(); // = "beng", "deva" etc. + $ScriptTableOffset = $this->read_ushort(); + $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset; + } + + // Script Table + foreach ($ffeats as $t => $o) { + $ls = []; + $this->seek($o); + $DefLangSys_offset = $this->read_ushort(); + if ($DefLangSys_offset > 0) { + $ls['DFLT'] = $DefLangSys_offset + $o; + } + $LangSysCount = $this->read_ushort(); + for ($i = 0; $i < $LangSysCount; $i++) { + $LangTag = $this->read_tag(); // = + $LangTableOffset = $this->read_ushort(); + $ls[$LangTag] = $o + $LangTableOffset; + } + $ffeats[$t] = $ls; + } + + // Get FeatureIndexList + // LangSys Table - from first listed langsys + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = []; + $langsystable_offset = $o; + $this->seek($langsystable_offset); + $LookUpOrder = $this->read_ushort(); //==NULL + $ReqFeatureIndex = $this->read_ushort(); + if ($ReqFeatureIndex != 0xFFFF) { + $FeatureIndex[] = $ReqFeatureIndex; + } + $FeatureCount = $this->read_ushort(); + for ($i = 0; $i < $FeatureCount; $i++) { + $FeatureIndex[] = $this->read_ushort(); // = index of feature + } + $ffeats[$st][$t] = $FeatureIndex; + } + } +//print_r($ffeats); exit; + // Feauture List => LookupListIndex es + $this->seek($FeatureList_offset); + $FeatureCount = $this->read_ushort(); + $Feature = []; + for ($i = 0; $i < $FeatureCount; $i++) { + $Feature[$i] = ['tag' => $this->read_tag()]; + $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort(); + } + for ($i = 0; $i < $FeatureCount; $i++) { + $this->seek($Feature[$i]['offset']); + $this->read_ushort(); // null + $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort(); + $Feature[$i]['LookupListIndex'] = []; + for ($c = 0; $c < $Lookupcount; $c++) { + $Feature[$i]['LookupListIndex'][] = $this->read_ushort(); + } + } + + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = $ffeats[$st][$t]; + foreach ($FeatureIndex as $k => $fi) { + $ffeats[$st][$t][$k] = $Feature[$fi]; + } + } + } +//print_r($ffeats); exit; + //===================================================================================== + $gpos = []; + $GPOSScriptLang = []; + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $langsys) { + $lg = []; + foreach ($langsys as $ft) { + $lg[$ft['LookupListIndex'][0]] = $ft; + } + // list of Lookups in order they need to be run i.e. order listed in Lookup table + ksort($lg); + foreach ($lg as $ft) { + $gpos[$st][$t][$ft['tag']] = $ft['LookupListIndex']; + } + if (!isset($GPOSScriptLang[$st])) { + $GPOSScriptLang[$st] = ''; + } + $GPOSScriptLang[$st] .= $t . ' '; + } + } + if ($this->mode == 'summary') { + $this->mpdf->WriteHTML('<h3>GPOS Scripts & Languages</h3>'); + $html = ''; + if (count($gpos)) { + foreach ($gpos as $st => $g) { + $html .= '<h5>' . $st . '</h5>'; + foreach ($g as $l => $t) { + $html .= '<div><a href="font_dump_OTL.php?script=' . $st . '&lang=' . $l . '">' . $l . '</a></b>: '; + foreach ($t as $tag => $o) { + $html .= $tag . ' '; + } + $html .= '</div>'; + } + } + } else { + $html .= '<div>No entries in GPOS table.</div>'; + } + $this->mpdf->WriteHTML($html); + $this->mpdf->WriteHTML('</div>'); + + return 0; + } + + //===================================================================================== + // Get metadata and offsets for whole Lookup List table + $this->seek($LookupList_offset); + $LookupCount = $this->read_ushort(); + $Lookup = []; + $Offsets = []; + $SubtableCount = []; + for ($i = 0; $i < $LookupCount; $i++) { + $Offsets[$i] = $LookupList_offset + $this->read_ushort(); + } + for ($i = 0; $i < $LookupCount; $i++) { + $this->seek($Offsets[$i]); + $Lookup[$i]['Type'] = $this->read_ushort(); + $Lookup[$i]['Flag'] = $flag = $this->read_ushort(); + $Lookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort(); + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $Lookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort(); + } + // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure + if (($flag & 0x0010) == 0x0010) { + $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort(); + } + // else { $Lookup[$i]['MarkFilteringSet'] = ''; } + // Lookup Type 9: Extension + if ($Lookup[$i]['Type'] == 9) { + // Overwrites new offset (32-bit) for each subtable, and a new lookup Type + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $this->seek($Lookup[$i]['Subtables'][$c]); + $ExtensionPosFormat = $this->read_ushort(); + $type = $this->read_ushort(); + $Lookup[$i]['Subtables'][$c] = $Lookup[$i]['Subtables'][$c] + $this->read_ulong(); + } + $Lookup[$i]['Type'] = $type; + } + } + + //===================================================================================== + + $st = $this->mpdf->OTLscript; + $t = $this->mpdf->OTLlang; + $langsys = $gpos[$st][$t]; + + $lul = []; // array of LookupListIndexes + $tags = []; // corresponding array of feature tags e.g. 'ccmp' + if (count($langsys)) { + foreach ($langsys as $tag => $ft) { + foreach ($ft as $ll) { + $lul[$ll] = $tag; + } + } + } + ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order + $this->_getGPOSarray($Lookup, $lul, $st); + +//print_r($lul); exit; + + return [$GPOSScriptLang, $gpos, $Lookup]; + } // end if GPOS + } + + ////////////////////////////////////////////////////////////////////////////////// + //===================================================================================== + //===================================================================================== + //===================================================================================== +///////////////////////////////////////////////////////////////////////////////////////// + // GPOS functions + function _getGPOSarray(&$Lookup, $lul, $scripttag, $level = 1, $lcoverage = '', $exB = '', $exL = '') + { + // Process (3) LookupList for specific Script-LangSys + $html = ''; + if ($level == 1) { + $html .= '<bookmark level="0" content="GPOS features">'; + } + foreach ($lul as $luli => $tag) { + $html .= '<div class="level' . $level . '">'; + $html .= '<h5 class="level' . $level . '">'; + if ($level == 1) { + $html .= '<bookmark level="1" content="' . $tag . ' [#' . $luli . ']">'; + } + $html .= 'Lookup #' . $luli . ' [tag: <span style="color:#000066;">' . $tag . '</span>]</h5>'; + $ignore = $this->_getGSUBignoreString($Lookup[$luli]['Flag'], $Lookup[$luli]['MarkFilteringSet']); + if ($ignore) { + $html .= '<div class="ignore">Ignoring: ' . $ignore . '</div> '; + } + + $Type = $Lookup[$luli]['Type']; + $Flag = $Lookup[$luli]['Flag']; + if (($Flag & 0x0001) == 1) { + $dir = 'RTL'; + } else { + $dir = 'LTR'; + } + + for ($c = 0; $c < $Lookup[$luli]['SubtableCount']; $c++) { + $html .= '<div class="subtable">Subtable #' . $c; + if ($level == 1) { + $html .= '<bookmark level="2" content="Subtable #' . $c . '">'; + } + $html .= '</div>'; + + // Lets start + $subtable_offset = $Lookup[$luli]['Subtables'][$c]; + $this->seek($subtable_offset); + $PosFormat = $this->read_ushort(); + + //////////////////////////////////////////////////////////////////////////////// + // LookupType 1: Single adjustment Adjust position of a single glyph (e.g. SmallCaps/Sups/Subs) + //////////////////////////////////////////////////////////////////////////////// + if ($Lookup[$luli]['Type'] == 1) { + $html .= '<div class="lookuptype">LookupType 1: Single adjustment [Format ' . $PosFormat . ']</div>'; + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + $Coverage = $subtable_offset + $this->read_ushort(); + $ValueFormat = $this->read_ushort(); + $Value = $this->_getValueRecord($ValueFormat); + + $this->seek($Coverage); + $glyphs = $this->_getCoverage(); // Array of Hex Glyphs + for ($g = 0; $g < count($glyphs); $g++) { + if ($level == 2 && strpos($lcoverage, $glyphs[$g]) === false) { + continue; + } + + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">' . $this->formatUni($glyphs[$g]) . ' </span> '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="unchanged"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  » »  '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= ' <span class="unicode">'; + if ($Value['XPlacement']) { + $html .= ' Xpl: ' . $Value['XPlacement'] . ';'; + } + if ($Value['YPlacement']) { + $html .= ' YPl: ' . $Value['YPlacement'] . ';'; + } + if ($Value['XAdvance']) { + $html .= ' Xadv: ' . $Value['XAdvance']; + } + $html .= '</span>'; + $html .= '</div>'; + } + } //=========== + // Format 2: + //=========== + else { + if ($PosFormat == 2) { + $Coverage = $subtable_offset + $this->read_ushort(); + $ValueFormat = $this->read_ushort(); + $ValueCount = $this->read_ushort(); + $Values = []; + for ($v = 0; $v < $ValueCount; $v++) { + $Values[] = $this->_getValueRecord($ValueFormat); + } + + $this->seek($Coverage); + $glyphs = $this->_getCoverage(); // Array of Hex Glyphs + + for ($g = 0; $g < count($glyphs); $g++) { + if ($level == 2 && strpos($lcoverage, $glyphs[$g]) === false) { + continue; + } + $Value = $Values[$g]; + + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">' . $this->formatUni($glyphs[$g]) . ' </span> '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="unchanged"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  » »  '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;"> ' . $this->formatEntity($glyphs[$g]) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= ' <span class="unicode">'; + if ($Value['XPlacement']) { + $html .= ' Xpl: ' . $Value['XPlacement'] . ';'; + } + if ($Value['YPlacement']) { + $html .= ' YPl: ' . $Value['YPlacement'] . ';'; + } + if ($Value['XAdvance']) { + $html .= ' Xadv: ' . $Value['XAdvance']; + } + $html .= '</span>'; + $html .= '</div>'; + } + } + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 2: Pair adjustment Adjust position of a pair of glyphs (Kerning) + //////////////////////////////////////////////////////////////////////////////// + else { + if ($Lookup[$luli]['Type'] == 2) { + $html .= '<div class="lookuptype">LookupType 2: Pair adjustment e.g. Kerning [Format ' . $PosFormat . ']</div>'; + $Coverage = $subtable_offset + $this->read_ushort(); + $ValueFormat1 = $this->read_ushort(); + $ValueFormat2 = $this->read_ushort(); + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + $PairSetCount = $this->read_ushort(); + $PairSetOffset = []; + for ($p = 0; $p < $PairSetCount; $p++) { + $PairSetOffset[] = $subtable_offset + $this->read_ushort(); + } + $this->seek($Coverage); + $glyphs = $this->_getCoverage(); // Array of Hex Glyphs + for ($p = 0; $p < $PairSetCount; $p++) { + if ($level == 2 && strpos($lcoverage, $glyphs[$p]) === false) { + continue; + } + $this->seek($PairSetOffset[$p]); + // First Glyph = $glyphs[$p] +// Takes too long e.g. Calibri font - just list kerning pairs with this: + $html .= '<div class="glyphs">'; + $html .= '<span class="unchanged"> ' . $this->formatEntity($glyphs[$p]) . ' </span>'; + + //PairSet table + $PairValueCount = $this->read_ushort(); + for ($pv = 0; $pv < $PairValueCount; $pv++) { + //PairValueRecord + $gid = $this->read_ushort(); + $SecondGlyph = unicode_hex($this->glyphToChar[$gid][0]); + $Value1 = $this->_getValueRecord($ValueFormat1); + $Value2 = $this->_getValueRecord($ValueFormat2); + + // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 to take + // account of direction. mPDF does not need the XPlacement adjustment + if ($dir == 'RTL' && $Value1['XPlacement']) { + $Value1['XPlacement'] -= $Value1['XAdvance']; + } + + if ($ValueFormat2) { + // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 to take + // account of direction. mPDF does not need the XPlacement adjustment + if ($dir == 'RTL' && $Value2['XPlacement'] && $Value2['XAdvance']) { + $Value2['XPlacement'] -= $Value2['XAdvance']; + } + } + + $html .= ' ' . $this->formatEntity($SecondGlyph) . ' '; + + /* + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">'.$this->formatUni($glyphs[$p]).' </span> '; + if ($level==2 && $exB) { $html .= $exB; } + $html .= '<span class="unchanged"> '.$this->formatEntity($glyphs[$p]).$this->formatEntity($SecondGlyph).'</span>'; + if ($level==2 && $exL) { $html .= $exL; } + $html .= '  » »  '; + if ($level==2 && $exB) { $html .= $exB; } + $html .= '<span class="changed" style="font-feature-settings:\''.$tag.'\' 1;"> '.$this->formatEntity($glyphs[$p]).$this->formatEntity($SecondGlyph).'</span>'; + if ($level==2 && $exL) { $html .= $exL; } + $html .= ' <span class="unicode">'; + if ($Value1['XPlacement']) { $html .= ' Xpl[1]: '.$Value1['XPlacement'].';'; } + if ($Value1['YPlacement']) { $html .= ' YPl[1]: '.$Value1['YPlacement'].';'; } + if ($Value1['XAdvance']) { $html .= ' Xadv[1]: '.$Value1['XAdvance']; } + if ($Value2['XPlacement']) { $html .= ' Xpl[2]: '.$Value2['XPlacement'].';'; } + if ($Value2['YPlacement']) { $html .= ' YPl[2]: '.$Value2['YPlacement'].';'; } + if ($Value2['XAdvance']) { $html .= ' Xadv[2]: '.$Value2['XAdvance']; } + $html .= '</span>'; + $html .= '</div>'; + */ + } + $html .= '</div>'; + } + } //=========== + // Format 2: + //=========== + else { + if ($PosFormat == 2) { + $ClassDef1 = $subtable_offset + $this->read_ushort(); + $ClassDef2 = $subtable_offset + $this->read_ushort(); + $Class1Count = $this->read_ushort(); + $Class2Count = $this->read_ushort(); + + $sizeOfPair = (2 * $this->count_bits($ValueFormat1)) + (2 * $this->count_bits($ValueFormat2)); + $sizeOfValueRecords = $Class1Count * $Class2Count * $sizeOfPair; + + // NB Class1Count includes Class 0 even though it is not defined by $ClassDef1 + // i.e. Class1Count = 5; Class1 will contain array(indices 1-4); + $Class1 = $this->_getClassDefinitionTable($ClassDef1); + $Class2 = $this->_getClassDefinitionTable($ClassDef2); + + $this->seek($subtable_offset + 16); + + for ($i = 0; $i < $Class1Count; $i++) { + for ($j = 0; $j < $Class2Count; $j++) { + $Value1 = $this->_getValueRecord($ValueFormat1); + $Value2 = $this->_getValueRecord($ValueFormat2); + + // If RTL pairs, GPOS declares a XPlacement e.g. -180 for an XAdvance of -180 + // of direction. mPDF does not need the XPlacement adjustment + if ($dir == 'RTL' && $Value1['XPlacement'] && $Value1['XAdvance']) { + $Value1['XPlacement'] -= $Value1['XAdvance']; + } + if ($ValueFormat2) { + if ($dir == 'RTL' && $Value2['XPlacement'] && $Value2['XAdvance']) { + $Value2['XPlacement'] -= $Value2['XAdvance']; + } + } + + for ($c1 = 0; $c1 < count($Class1[$i]); $c1++) { + $FirstGlyph = $Class1[$i][$c1]; + if ($level == 2 && strpos($lcoverage, $FirstGlyph) === false) { + continue; + } + + for ($c2 = 0; $c2 < count($Class2[$j]); $c2++) { + $SecondGlyph = $Class2[$j][$c2]; + + if (!$Value1['XPlacement'] && !$Value1['YPlacement'] && !$Value1['XAdvance'] && !$Value2['XPlacement'] && !$Value2['YPlacement'] && !$Value2['XAdvance']) { + continue; + } + + $html .= '<div class="substitution">'; + $html .= '<span class="unicode">' . $this->formatUni($FirstGlyph) . ' </span> '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="unchanged"> ' . $this->formatEntity($FirstGlyph) . $this->formatEntity($SecondGlyph) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= '  » »  '; + if ($level == 2 && $exB) { + $html .= $exB; + } + $html .= '<span class="changed" style="font-feature-settings:\'' . $tag . '\' 1;"> ' . $this->formatEntity($FirstGlyph) . $this->formatEntity($SecondGlyph) . '</span>'; + if ($level == 2 && $exL) { + $html .= $exL; + } + $html .= ' <span class="unicode">'; + if ($Value1['XPlacement']) { + $html .= ' Xpl[1]: ' . $Value1['XPlacement'] . ';'; + } + if ($Value1['YPlacement']) { + $html .= ' YPl[1]: ' . $Value1['YPlacement'] . ';'; + } + if ($Value1['XAdvance']) { + $html .= ' Xadv[1]: ' . $Value1['XAdvance']; + } + if ($Value2['XPlacement']) { + $html .= ' Xpl[2]: ' . $Value2['XPlacement'] . ';'; + } + if ($Value2['YPlacement']) { + $html .= ' YPl[2]: ' . $Value2['YPlacement'] . ';'; + } + if ($Value2['XAdvance']) { + $html .= ' Xadv[2]: ' . $Value2['XAdvance']; + } + $html .= '</span>'; + $html .= '</div>'; + } + } + } + } + } + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 3: Cursive attachment Attach cursive glyphs + //////////////////////////////////////////////////////////////////////////////// + else { + if ($Lookup[$luli]['Type'] == 3) { + $html .= '<div class="lookuptype">LookupType 3: Cursive attachment </div>'; + $Coverage = $subtable_offset + $this->read_ushort(); + $EntryExitCount = $this->read_ushort(); + $EntryAnchors = []; + $ExitAnchors = []; + for ($i = 0; $i < $EntryExitCount; $i++) { + $EntryAnchors[$i] = $this->read_ushort(); + $ExitAnchors[$i] = $this->read_ushort(); + } + + $this->seek($Coverage); + $Glyphs = $this->_getCoverage(); + for ($i = 0; $i < $EntryExitCount; $i++) { + // Need default XAdvance for glyph + $pdfWidth = $this->mpdf->_getCharWidth($this->mpdf->fonts[$this->fontkey]['cw'], hexdec($Glyphs[$i])); + $EntryAnchor = $EntryAnchors[$i]; + $ExitAnchor = $ExitAnchors[$i]; + $html .= '<div class="glyphs">'; + $html .= '<span class="unchanged">' . $this->formatEntity($Glyphs[$i]) . ' </span> '; + $html .= '<span class="unicode"> ' . $this->formatUni($Glyphs[$i]) . ' => '; + + if ($EntryAnchor != 0) { + $EntryAnchor += $subtable_offset; + list($x, $y) = $this->_getAnchorTable($EntryAnchor); + if ($dir == 'RTL') { + if (round($pdfWidth) == round($x * 1000 / $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'])) { + $x = 0; + } else { + $x = $x - ($pdfWidth * $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'] / 1000); + } + } + $html .= " Entry X: " . $x . " Y: " . $y . "; "; + } + if ($ExitAnchor != 0) { + $ExitAnchor += $subtable_offset; + list($x, $y) = $this->_getAnchorTable($ExitAnchor); + if ($dir == 'LTR') { + if (round($pdfWidth) == round($x * 1000 / $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'])) { + $x = 0; + } else { + $x = $x - ($pdfWidth * $this->mpdf->fonts[$this->fontkey]['desc']['unitsPerEm'] / 1000); + } + } + $html .= " Exit X: " . $x . " Y: " . $y . "; "; + } + + $html .= '</span></div>'; + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 4: MarkToBase attachment Attach a combining mark to a base glyph + //////////////////////////////////////////////////////////////////////////////// + else { + if ($Lookup[$luli]['Type'] == 4) { + $html .= '<div class="lookuptype">LookupType 4: MarkToBase attachment </div>'; + $MarkCoverage = $subtable_offset + $this->read_ushort(); + $BaseCoverage = $subtable_offset + $this->read_ushort(); + + $this->seek($MarkCoverage); + $MarkGlyphs = $this->_getCoverage(); + + $this->seek($BaseCoverage); + $BaseGlyphs = $this->_getCoverage(); + + $firstMark = ''; + $html .= '<div class="glyphs">Marks: '; + for ($i = 0; $i < count($MarkGlyphs); $i++) { + if ($level == 2 && strpos($lcoverage, $MarkGlyphs[$i]) === false) { + continue; + } else { + if (!$firstMark) { + $firstMark = $MarkGlyphs[$i]; + } + } + $html .= ' ' . $this->formatEntity($MarkGlyphs[$i]) . ' '; + } + $html .= '</div>'; + if (!$firstMark) { + return; + } + + $html .= '<div class="glyphs">Bases: '; + for ($j = 0; $j < count($BaseGlyphs); $j++) { + $html .= ' ' . $this->formatEntity($BaseGlyphs[$j]) . ' '; + } + $html .= '</div>'; + + // Example + $html .= '<div class="glyphs" style="font-feature-settings:\'' . $tag . '\' 1;">Example(s): '; + for ($j = 0; $j < min(count($BaseGlyphs), 20); $j++) { + $html .= ' ' . $this->formatEntity($BaseGlyphs[$j]) . $this->formatEntity($firstMark, true) . '   '; + } + $html .= '</div>'; + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 5: MarkToLigature attachment Attach a combining mark to a ligature + //////////////////////////////////////////////////////////////////////////////// + else { + if ($Lookup[$luli]['Type'] == 5) { + $html .= '<div class="lookuptype">LookupType 5: MarkToLigature attachment </div>'; + $MarkCoverage = $subtable_offset + $this->read_ushort(); + //$MarkCoverage is already set in $lcoverage 00065|00073 etc + $LigatureCoverage = $subtable_offset + $this->read_ushort(); + $ClassCount = $this->read_ushort(); // Number of classes defined for marks = Number of mark glyphs in the MarkCoverage table + $MarkArray = $subtable_offset + $this->read_ushort(); // Offset to MarkArray table + $LigatureArray = $subtable_offset + $this->read_ushort(); // Offset to LigatureArray table + + $this->seek($MarkCoverage); + $MarkGlyphs = $this->_getCoverage(); + $this->seek($LigatureCoverage); + $LigatureGlyphs = $this->_getCoverage(); + + $firstMark = ''; + $html .= '<div class="glyphs">Marks: <span class="unchanged">'; + $MarkRecord = []; + for ($i = 0; $i < count($MarkGlyphs); $i++) { + if ($level == 2 && strpos($lcoverage, $MarkGlyphs[$i]) === false) { + continue; + } else { + if (!$firstMark) { + $firstMark = $MarkGlyphs[$i]; + } + } + // Get the relevant MarkRecord + $MarkRecord[$i] = $this->_getMarkRecord($MarkArray, $i); + //Mark Class is = $MarkRecord[$i]['Class'] + $html .= ' ' . $this->formatEntity($MarkGlyphs[$i]) . ' '; + } + $html .= '</span></div>'; + if (!$firstMark) { + return; + } + + $this->seek($LigatureArray); + $LigatureCount = $this->read_ushort(); + $LigatureAttach = []; + $html .= '<div class="glyphs">Ligatures: <span class="unchanged">'; + for ($j = 0; $j < count($LigatureGlyphs); $j++) { + // Get the relevant LigatureRecord + $LigatureAttach[$j] = $LigatureArray + $this->read_ushort(); + $html .= ' ' . $this->formatEntity($LigatureGlyphs[$j]) . ' '; + } + $html .= '</span></div>'; + + /* + for ($i=0;$i<count($MarkGlyphs);$i++) { + $html .= '<div class="glyphs">'; + $html .= '<span class="unchanged">'.$this->formatEntity($MarkGlyphs[$i]).'</span>'; + + for ($j=0;$j<count($LigatureGlyphs);$j++) { + $this->seek($LigatureAttach[$j]); + $ComponentCount = $this->read_ushort(); + $html .= '<span class="unchanged">'.$this->formatEntity($LigatureGlyphs[$j]).'</span>'; + $offsets = array(); + for ($comp=0;$comp<$ComponentCount;$comp++) { + // ComponentRecords + for ($class=0;$class<$ClassCount;$class++) { + $offset = $this->read_ushort(); + if ($offset!= 0 && $class == $MarkRecord[$i]['Class']) { + + $html .= ' ['.$comp.'] '; + + } + } + } + } + $html .= '</span></div>'; + } + */ + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 6: MarkToMark attachment Attach a combining mark to another mark + //////////////////////////////////////////////////////////////////////////////// + else { + if ($Lookup[$luli]['Type'] == 6) { + $html .= '<div class="lookuptype">LookupType 6: MarkToMark attachment </div>'; + $Mark1Coverage = $subtable_offset + $this->read_ushort(); // Combining Mark + //$Mark1Coverage is already set in $LuCoverage 0065|0073 etc + $Mark2Coverage = $subtable_offset + $this->read_ushort(); // Base Mark + $ClassCount = $this->read_ushort(); // Number of classes defined for marks = No. of Combining mark1 glyphs in the MarkCoverage table + $this->seek($Mark1Coverage); + $Mark1Glyphs = $this->_getCoverage(); + $this->seek($Mark2Coverage); + $Mark2Glyphs = $this->_getCoverage(); + + $firstMark = ''; + $html .= '<div class="glyphs">Marks: <span class="unchanged">'; + for ($i = 0; $i < count($Mark1Glyphs); $i++) { + if ($level == 2 && strpos($lcoverage, $Mark1Glyphs[$i]) === false) { + continue; + } else { + if (!$firstMark) { + $firstMark = $Mark1Glyphs[$i]; + } + } + $html .= ' ' . $this->formatEntity($Mark1Glyphs[$i]) . ' '; + } + $html .= '</span></div>'; + + if ($firstMark) { + $html .= '<div class="glyphs">Bases: <span class="unchanged">'; + for ($j = 0; $j < count($Mark2Glyphs); $j++) { + $html .= ' ' . $this->formatEntity($Mark2Glyphs[$j]) . ' '; + } + $html .= '</span></div>'; + + // Example + $html .= '<div class="glyphs" style="font-feature-settings:\'' . $tag . '\' 1;">Example(s): <span class="changed">'; + for ($j = 0; $j < min(count($Mark2Glyphs), 20); $j++) { + $html .= ' ' . $this->formatEntity($Mark2Glyphs[$j]) . $this->formatEntity($firstMark, true) . '   '; + } + $html .= '</span></div>'; + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 7: Context positioning Position one or more glyphs in context + //////////////////////////////////////////////////////////////////////////////// + else { + if ($Lookup[$luli]['Type'] == 7) { + $html .= '<div class="lookuptype">LookupType 7: Context positioning [Format ' . $PosFormat . ']</div>'; + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED."); + } //=========== + // Format 2: + //=========== + else { + if ($PosFormat == 2) { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED."); + } //=========== + // Format 3: + //=========== + else { + if ($PosFormat == 3) { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not YET TESTED."); + } else { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . ", Format " . $PosFormat . " not supported."); + } + } + } + } //////////////////////////////////////////////////////////////////////////////// + // LookupType 8: Chained Context positioning Position one or more glyphs in chained context + //////////////////////////////////////////////////////////////////////////////// + else { + if ($Lookup[$luli]['Type'] == 8) { + $html .= '<div class="lookuptype">LookupType 8: Chained Context positioning [Format ' . $PosFormat . ']</div>'; + //=========== + // Format 1: + //=========== + if ($PosFormat == 1) { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET."); + } //=========== + // Format 2: + //=========== + else { + if ($PosFormat == 2) { + $html .= '<div>GPOS Lookup Type 8: Format 2 not yet supported in OTL dump</div>'; + continue; + /* NB When developing - cf. GSUB 6.2 */ + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Type . " Format " . $PosFormat . " not TESTED YET."); + } //=========== + // Format 3: + //=========== + else { + if ($PosFormat == 3) { + $BacktrackGlyphCount = $this->read_ushort(); + $CoverageBacktrackOffset = []; + for ($b = 0; $b < $BacktrackGlyphCount; $b++) { + $CoverageBacktrackOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $InputGlyphCount = $this->read_ushort(); + $CoverageInputOffset = []; + for ($b = 0; $b < $InputGlyphCount; $b++) { + $CoverageInputOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $LookaheadGlyphCount = $this->read_ushort(); + $CoverageLookaheadOffset = []; + for ($b = 0; $b < $LookaheadGlyphCount; $b++) { + $CoverageLookaheadOffset[] = $subtable_offset + $this->read_ushort(); // in glyph sequence order + } + $PosCount = $this->read_ushort(); + + $PosLookupRecord = []; + for ($p = 0; $p < $PosCount; $p++) { + // PosLookupRecord + $PosLookupRecord[$p]['SequenceIndex'] = $this->read_ushort(); + $PosLookupRecord[$p]['LookupListIndex'] = $this->read_ushort(); + } + + $backtrackGlyphs = []; + for ($b = 0; $b < $BacktrackGlyphCount; $b++) { + $this->seek($CoverageBacktrackOffset[$b]); + $backtrackGlyphs[$b] = implode('|', $this->_getCoverage()); + } + $inputGlyphs = []; + for ($b = 0; $b < $InputGlyphCount; $b++) { + $this->seek($CoverageInputOffset[$b]); + $inputGlyphs[$b] = implode('|', $this->_getCoverage()); + } + $lookaheadGlyphs = []; + for ($b = 0; $b < $LookaheadGlyphCount; $b++) { + $this->seek($CoverageLookaheadOffset[$b]); + $lookaheadGlyphs[$b] = implode('|', $this->_getCoverage()); + } + + $exampleB = []; + $exampleI = []; + $exampleL = []; + $html .= '<div class="context">CONTEXT: '; + for ($ff = count($backtrackGlyphs) - 1; $ff >= 0; $ff--) { + $html .= '<div>Backtrack #' . $ff . ': <span class="unicode">' . $this->formatUniStr($backtrackGlyphs[$ff]) . '</span></div>'; + $exampleB[] = $this->formatEntityFirst($backtrackGlyphs[$ff]); + } + for ($ff = 0; $ff < count($inputGlyphs); $ff++) { + $html .= '<div>Input #' . $ff . ': <span class="unchanged"> ' . $this->formatEntityStr($inputGlyphs[$ff]) . ' </span></div>'; + $exampleI[] = $this->formatEntityFirst($inputGlyphs[$ff]); + } + for ($ff = 0; $ff < count($lookaheadGlyphs); $ff++) { + $html .= '<div>Lookahead #' . $ff . ': <span class="unicode">' . $this->formatUniStr($lookaheadGlyphs[$ff]) . '</span></div>'; + $exampleL[] = $this->formatEntityFirst($lookaheadGlyphs[$ff]); + } + $html .= '</div>'; + + for ($p = 0; $p < $PosCount; $p++) { + $lup = $PosLookupRecord[$p]['LookupListIndex']; + $seqIndex = $PosLookupRecord[$p]['SequenceIndex']; + + // GENERATE exampleB[n] exampleI[<seqIndex] .... exampleI[>seqIndex] exampleL[n] + $exB = ''; + $exL = ''; + if (count($exampleB)) { + $exB .= '<span class="backtrack">' . implode('‍', $exampleB) . '</span>'; + } + + if ($seqIndex > 0) { + $exB .= '<span class="inputother">'; + for ($ip = 0; $ip < $seqIndex; $ip++) { + $exB .= $exampleI[$ip] . '‍'; + } + $exB .= '</span>'; + } + + if (count($inputGlyphs) > ($seqIndex + 1)) { + $exL .= '<span class="inputother">'; + for ($ip = $seqIndex + 1; $ip < count($inputGlyphs); $ip++) { + $exL .= '‍' . $exampleI[$ip]; + } + $exL .= '</span>'; + } + + if (count($exampleL)) { + $exL .= '<span class="lookahead">' . implode('‍', $exampleL) . '</span>'; + } + + $html .= '<div class="sequenceIndex">Substitution Position: ' . $seqIndex . '</div>'; + + $lul2 = [$lup => $tag]; + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // Pass $inputGlyphs[$seqIndex] e.g. 00636|00645|00656 + // to level 2 and only apply if first Replace glyph is in this list + $html .= $this->_getGPOSarray($Lookup, $lul2, $scripttag, 2, $inputGlyphs[$seqIndex], $exB, $exL); + } + } + } + } + } + } + } + } + } + } + } + } + } + $html .= '</div>'; + } + if ($level == 1) { + $this->mpdf->WriteHTML($html); + } else { + return $html; + } +//print_r($Lookup); exit; + } + + //===================================================================================== + //===================================================================================== + // GPOS FUNCTIONS + //===================================================================================== + + function count_bits($n) + { + for ($c = 0; $n; $c++) { + $n &= $n - 1; // clear the least significant bit set + } + + return $c; + } + + function _getValueRecord($ValueFormat) + { + // Common ValueRecord for GPOS + // Only returns 3 possible: $vra['XPlacement'] $vra['YPlacement'] $vra['XAdvance'] + $vra = []; + // Horizontal adjustment for placement-in design units + if (($ValueFormat & 0x0001) == 0x0001) { + $vra['XPlacement'] = $this->read_short(); + } + // Vertical adjustment for placement-in design units + if (($ValueFormat & 0x0002) == 0x0002) { + $vra['YPlacement'] = $this->read_short(); + } + // Horizontal adjustment for advance-in design units (only used for horizontal writing) + if (($ValueFormat & 0x0004) == 0x0004) { + $vra['XAdvance'] = $this->read_short(); + } + // Vertical adjustment for advance-in design units (only used for vertical writing) + if (($ValueFormat & 0x0008) == 0x0008) { + $this->read_short(); + } + // Offset to Device table for horizontal placement-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0010) == 0x0010) { + $this->read_ushort(); + } + // Offset to Device table for vertical placement-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0020) == 0x0020) { + $this->read_ushort(); + } + // Offset to Device table for horizontal advance-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0040) == 0x0040) { + $this->read_ushort(); + } + // Offset to Device table for vertical advance-measured from beginning of PosTable (may be NULL) + if (($ValueFormat & 0x0080) == 0x0080) { + $this->read_ushort(); + } + + return $vra; + } + + function _getAnchorTable($offset = 0) + { + if ($offset) { + $this->seek($offset); + } + $AnchorFormat = $this->read_ushort(); + $XCoordinate = $this->read_short(); + $YCoordinate = $this->read_short(); + + // Format 2 specifies additional link to contour point; Format 3 additional Device table + return [$XCoordinate, $YCoordinate]; + } + + function _getMarkRecord($offset, $MarkPos) + { + $this->seek($offset); + $MarkCount = $this->read_ushort(); + $this->skip($MarkPos * 4); + $Class = $this->read_ushort(); + $MarkAnchor = $offset + $this->read_ushort(); // = Offset to anchor table + list($x, $y) = $this->_getAnchorTable($MarkAnchor); + $MarkRecord = ['Class' => $Class, 'AnchorX' => $x, 'AnchorY' => $y]; + + return $MarkRecord; + } + + ////////////////////////////////////////////////////////////////////////////////// + // Recursively get composite glyph data + function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours) + { + $depth++; + $maxdepth = max($maxdepth, $depth); + if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) { + foreach ($this->glyphdata[$originalGlyphIdx]['compGlyphs'] as $glyphIdx) { + $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours); + } + } else { + if (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple + $contours += $this->glyphdata[$originalGlyphIdx]['nContours']; + $points += $this->glyphdata[$originalGlyphIdx]['nPoints']; + } + } + $depth--; + } + + ////////////////////////////////////////////////////////////////////////////////// + // Recursively get composite glyphs + function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs) + { + $glyphPos = $this->glyphPos[$originalGlyphIdx]; + $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos; + if (!$glyphLen) { + return; + } + $this->seek($start + $glyphPos); + $numberOfContours = $this->read_short(); + if ($numberOfContours < 0) { + $this->skip(8); + $flags = GlyphOperator::MORE; + while ($flags & GlyphOperator::MORE) { + $flags = $this->read_ushort(); + } + $glyphIdx = $this->read_ushort(); + if (!isset($glyphSet[$glyphIdx])) { + $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID + $subsetglyphs[$glyphIdx] = true; + } + $savepos = ftell($this->fh); + $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs); + $this->seek($savepos); + if ($flags & GlyphOperator::WORDS) { + $this->skip(4); + } else { + $this->skip(2); + } + if ($flags & GlyphOperator::SCALE) { + $this->skip(2); + } else { + if ($flags & GlyphOperator::XYSCALE) { + $this->skip(4); + } else { + if ($flags & GlyphOperator::TWOBYTWO) { + $this->skip(8); + } + } + } + } + } + + + ////////////////////////////////////////////////////////////////////////////////// + + function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale) + { + $start = $this->seek_table("hmtx"); + $aw = 0; + $this->charWidths = str_pad('', 256 * 256 * 2, "\x00"); + if ($this->maxUniChar > 65536) { + $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00"); + } // Plane 1 SMP + if ($this->maxUniChar > 131072) { + $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00"); + } // Plane 2 SMP + $nCharWidths = 0; + if (($numberOfHMetrics * 4) < $this->maxStrLenRead) { + $data = $this->get_chunk($start, ($numberOfHMetrics * 4)); + $arr = unpack("n*", $data); + } else { + $this->seek($start); + } + for ($glyph = 0; $glyph < $numberOfHMetrics; $glyph++) { + if (($numberOfHMetrics * 4) < $this->maxStrLenRead) { + $aw = $arr[($glyph * 2) + 1]; + } else { + $aw = $this->read_ushort(); + $lsb = $this->read_ushort(); + } + if (isset($glyphToChar[$glyph]) || $glyph == 0) { + if ($aw >= (1 << 15)) { + $aw = 0; + } // 1.03 Some (arabic) fonts have -ve values for width + // although should be unsigned value - comes out as e.g. 65108 (intended -50) + if ($glyph == 0) { + $this->defaultWidth = $scale * $aw; + continue; + } + foreach ($glyphToChar[$glyph] as $char) { + //$this->charWidths[$char] = intval(round($scale*$aw)); + if ($char != 0 && $char != 65535) { + $w = intval(round($scale * $aw)); + if ($w == 0) { + $w = 65535; + } + if ($char < 196608) { + $this->charWidths[$char * 2] = chr($w >> 8); + $this->charWidths[$char * 2 + 1] = chr($w & 0xFF); + $nCharWidths++; + } + } + } + } + } + $data = $this->get_chunk(($start + $numberOfHMetrics * 4), ($numGlyphs * 2)); + $arr = unpack("n*", $data); + $diff = $numGlyphs - $numberOfHMetrics; + $w = intval(round($scale * $aw)); + if ($w == 0) { + $w = 65535; + } + for ($pos = 0; $pos < $diff; $pos++) { + $glyph = $pos + $numberOfHMetrics; + if (isset($glyphToChar[$glyph])) { + foreach ($glyphToChar[$glyph] as $char) { + if ($char != 0 && $char != 65535) { + if ($char < 196608) { + $this->charWidths[$char * 2] = chr($w >> 8); + $this->charWidths[$char * 2 + 1] = chr($w & 0xFF); + $nCharWidths++; + } + } + } + } + } + // NB 65535 is a set width of 0 + // First bytes define number of chars in font + $this->charWidths[0] = chr($nCharWidths >> 8); + $this->charWidths[1] = chr($nCharWidths & 0xFF); + } + + function getHMetric($numberOfHMetrics, $gid) + { + $start = $this->seek_table("hmtx"); + if ($gid < $numberOfHMetrics) { + $this->seek($start + ($gid * 4)); + $hm = fread($this->fh, 4); + } else { + $this->seek($start + (($numberOfHMetrics - 1) * 4)); + $hm = fread($this->fh, 2); + $this->seek($start + ($numberOfHMetrics * 2) + ($gid * 2)); + $hm .= fread($this->fh, 2); + } + + return $hm; + } + + function getLOCA($indexToLocFormat, $numGlyphs) + { + $start = $this->seek_table('loca'); + $this->glyphPos = []; + if ($indexToLocFormat == 0) { + $data = $this->get_chunk($start, ($numGlyphs * 2) + 2); + $arr = unpack("n*", $data); + for ($n = 0; $n <= $numGlyphs; $n++) { + $this->glyphPos[] = ($arr[$n + 1] * 2); + } + } else { + if ($indexToLocFormat == 1) { + $data = $this->get_chunk($start, ($numGlyphs * 4) + 4); + $arr = unpack("N*", $data); + for ($n = 0; $n <= $numGlyphs; $n++) { + $this->glyphPos[] = ($arr[$n + 1]); + } + } else { + throw new \Mpdf\MpdfException('Unknown location table format ' . $indexToLocFormat); + } + } + } + + // CMAP Format 4 + function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph) + { + $this->maxUniChar = 0; + $this->seek($unicode_cmap_offset + 2); + $length = $this->read_ushort(); + $limit = $unicode_cmap_offset + $length; + $this->skip(2); + + $segCount = $this->read_ushort() / 2; + $this->skip(6); + $endCount = []; + for ($i = 0; $i < $segCount; $i++) { + $endCount[] = $this->read_ushort(); + } + $this->skip(2); + $startCount = []; + for ($i = 0; $i < $segCount; $i++) { + $startCount[] = $this->read_ushort(); + } + $idDelta = []; + for ($i = 0; $i < $segCount; $i++) { + $idDelta[] = $this->read_short(); + } // ???? was unsigned short + $idRangeOffset_start = $this->_pos; + $idRangeOffset = []; + for ($i = 0; $i < $segCount; $i++) { + $idRangeOffset[] = $this->read_ushort(); + } + + for ($n = 0; $n < $segCount; $n++) { + $endpoint = ($endCount[$n] + 1); + for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) { + if ($idRangeOffset[$n] == 0) { + $glyph = ($unichar + $idDelta[$n]) & 0xFFFF; + } else { + $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n]; + $offset = $idRangeOffset_start + 2 * $n + $offset; + if ($offset >= $limit) { + $glyph = 0; + } else { + $glyph = $this->get_ushort($offset); + if ($glyph != 0) { + $glyph = ($glyph + $idDelta[$n]) & 0xFFFF; + } + } + } + $charToGlyph[$unichar] = $glyph; + if ($unichar < 196608) { + $this->maxUniChar = max($unichar, $this->maxUniChar); + } + $glyphToChar[$glyph][] = $unichar; + } + } + } + + function formatUni($char) + { + $x = preg_replace('/^[0]*/', '', $char); + $x = str_pad($x, 4, '0', STR_PAD_LEFT); + $d = hexdec($x); + if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { + $id = 'M'; + } // E000 - F8FF, 1E000-1F000 + else { + $id = 'U'; + } + + return $id . '+' . $x; + } + + function formatEntity($char, $allowjoining = false) + { + $char = preg_replace('/^[0]/', '', $char); + $x = '&#x' . $char . ';'; + if (strpos($this->GlyphClassMarks, $char) !== false) { + if (!$allowjoining) { + $x = '◌' . $x; + } + } + + return $x; + } + + function formatUniArr($arr) + { + $s = []; + foreach ($arr as $c) { + $x = preg_replace('/^[0]*/', '', $c); + $d = hexdec($x); + if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { + $id = 'M'; + } // E000 - F8FF, 1E000-1F000 + else { + $id = 'U'; + } + $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT); + } + + return implode(', ', $s); + } + + function formatEntityArr($arr) + { + $s = []; + foreach ($arr as $c) { + $c = preg_replace('/^[0]/', '', $c); + $x = '&#x' . $c . ';'; + if (strpos($this->GlyphClassMarks, $c) !== false) { + $x = '◌' . $x; + } + $s[] = $x; + } + + return implode(' ', $s); // ZWNJ? ‍ + } + + function formatClassArr($arr) + { + $s = []; + foreach ($arr as $c) { + $x = preg_replace('/^[0]*/', '', $c); + $d = hexdec($x); + if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { + $id = 'M'; + } // E000 - F8FF, 1E000-1F000 + else { + $id = 'U'; + } + $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT); + } + + return implode(', ', $s); + } + + function formatUniStr($str) + { + $s = []; + $arr = explode('|', $str); + foreach ($arr as $c) { + $x = preg_replace('/^[0]*/', '', $c); + $d = hexdec($x); + if (($d > 57343 && $d < 63744) || ($d > 122879 && $d < 126977)) { + $id = 'M'; + } // E000 - F8FF, 1E000-1F000 + else { + $id = 'U'; + } + $s[] = $id . '+' . str_pad($x, 4, '0', STR_PAD_LEFT); + } + + return implode(', ', $s); + } + + function formatEntityStr($str) + { + $s = []; + $arr = explode('|', $str); + foreach ($arr as $c) { + $c = preg_replace('/^[0]/', '', $c); + $x = '&#x' . $c . ';'; + if (strpos($this->GlyphClassMarks, $c) !== false) { + $x = '◌' . $x; + } + $s[] = $x; + } + + return implode(' ', $s); // ZWNJ? ‍ + } + + function formatEntityFirst($str) + { + $arr = explode('|', $str); + $char = preg_replace('/^[0]/', '', $arr[0]); + $x = '&#x' . $char . ';'; + if (strpos($this->GlyphClassMarks, $char) !== false) { + $x = '◌' . $x; + } + + return $x; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Output/Destination.php b/lib/MPDF/vendor/mpdf/mpdf/src/Output/Destination.php new file mode 100644 index 0000000..25b46d6 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Output/Destination.php @@ -0,0 +1,15 @@ +<?php + +namespace Mpdf\Output; + +class Destination +{ + + const FILE = 'F'; + + const DOWNLOAD = 'D'; + + const STRING_RETURN = 'S'; + + const INLINE = 'I'; +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/PageFormat.php b/lib/MPDF/vendor/mpdf/mpdf/src/PageFormat.php new file mode 100644 index 0000000..57a534e --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/PageFormat.php @@ -0,0 +1,83 @@ +<?php + +namespace Mpdf; + +class PageFormat +{ + + /** + * This method returns an array of width and height of given named format. + * + * Returned values are milimeters multiplied by scale factor of 72 / 25.4 + * + * @param string $name + * @return float[] Width and height of given format + */ + public static function getSizeFromName($name) + { + $format = strtoupper($name); + $formats = [ + '4A0' => [4767.87, 6740.79], + '2A0' => [3370.39, 4767.87], + 'A0' => [2383.94, 3370.39], + 'A1' => [1683.78, 2383.94], + 'A2' => [1190.55, 1683.78], + 'A3' => [841.89, 1190.55], + 'A4' => [595.28, 841.89], + 'A5' => [419.53, 595.28], + 'A6' => [297.64, 419.53], + 'A7' => [209.76, 297.64], + 'A8' => [147.40, 209.76], + 'A9' => [104.88, 147.40], + 'A10' => [73.70, 104.88], + 'B0' => [2834.65, 4008.19], + 'B1' => [2004.09, 2834.65], + 'B2' => [1417.32, 2004.09], + 'B3' => [1000.63, 1417.32], + 'B4' => [708.66, 1000.63], + 'B5' => [498.90, 708.66], + 'B6' => [354.33, 498.90], + 'B7' => [249.45, 354.33], + 'B8' => [175.75, 249.45], + 'B9' => [124.72, 175.75], + 'B10' => [87.87, 124.72], + 'C0' => [2599.37, 3676.54], + 'C1' => [1836.85, 2599.37], + 'C2' => [1298.27, 1836.85], + 'C3' => [918.43, 1298.27], + 'C4' => [649.13, 918.43], + 'C5' => [459.21, 649.13], + 'C6' => [323.15, 459.21], + 'C7' => [229.61, 323.15], + 'C8' => [161.57, 229.61], + 'C9' => [113.39, 161.57], + 'C10' => [79.37, 113.39], + 'RA0' => [2437.80, 3458.27], + 'RA1' => [1729.13, 2437.80], + 'RA2' => [1218.90, 1729.13], + 'RA3' => [864.57, 1218.90], + 'RA4' => [609.45, 864.57], + 'SRA0' => [2551.18, 3628.35], + 'SRA1' => [1814.17, 2551.18], + 'SRA2' => [1275.59, 1814.17], + 'SRA3' => [907.09, 1275.59], + 'SRA4' => [637.80, 907.09], + 'LETTER' => [612.00, 792.00], + 'LEGAL' => [612.00, 1008.00], + 'LEDGER' => [1224.00, 792.00], + 'TABLOID' => [792.00, 1224.00], + 'EXECUTIVE' => [521.86, 756.00], + 'FOLIO' => [612.00, 936.00], + 'B' => [362.83, 561.26], // 'B' format paperback size 128x198mm + 'A' => [314.65, 504.57], // 'A' format paperback size 111x178mm + 'DEMY' => [382.68, 612.28], // 'Demy' format paperback size 135x216mm + 'ROYAL' => [433.70, 663.30], // 'Royal' format paperback size 153x234mm + ]; + + if (!isset($formats[$format])) { + throw new \Mpdf\MpdfException(sprintf('Unknown page format %s', $format)); + } + + return $formats[$format]; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Pdf/Protection.php b/lib/MPDF/vendor/mpdf/mpdf/src/Pdf/Protection.php new file mode 100644 index 0000000..8a63da9 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Pdf/Protection.php @@ -0,0 +1,360 @@ +<?php + +namespace Mpdf\Pdf; + +use Mpdf\Pdf\Protection\UniqidGenerator; + +class Protection +{ + + /** + * @var string + */ + private $lastRc4Key; + + /** + * @var string + */ + private $lastRc4KeyC; + + /** + * @var bool + */ + private $useRC128Encryption; + + /** + * @var string + */ + private $encryptionKey; + + /** + * @var string + */ + private $padding; + + /** + * @var string + */ + private $uniqid; + + /** + * @var string + */ + private $oValue; + + /** + * @var string + */ + private $uValue; + + /** + * @var string + */ + private $pValue; + + /** + * @var int[] Array of permission => byte representation + */ + private $options; + + /** + * @var \Mpdf\Pdf\Protection\UniqidGenerator + */ + private $uniqidGenerator; + + public function __construct(UniqidGenerator $uniqidGenerator) + { + if (!function_exists('random_int') || !function_exists('random_bytes')) { + throw new \Mpdf\MpdfException( + 'Unable to set PDF file protection, CSPRNG Functions are not available. ' + . 'Use paragonie/random_compat polyfill or upgrade to PHP 7.' + ); + } + + $this->uniqidGenerator = $uniqidGenerator; + + $this->lastRc4Key = ''; + + $this->padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" . + "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A"; + + $this->useRC128Encryption = false; + + $this->options = [ + 'print' => 4, // bit 3 + 'modify' => 8, // bit 4 + 'copy' => 16, // bit 5 + 'annot-forms' => 32, // bit 6 + 'fill-forms' => 256, // bit 9 + 'extract' => 512, // bit 10 + 'assemble' => 1024, // bit 11 + 'print-highres' => 2048 // bit 12 + ]; + } + + /** + * @param array $permissions + * @param string $user_pass + * @param string $owner_pass + * @param int $length + * + * @return bool + */ + public function setProtection($permissions = [], $user_pass = '', $owner_pass = null, $length = 40) + { + if (is_string($permissions) && strlen($permissions) > 0) { + $permissions = [$permissions]; + } elseif (!is_array($permissions)) { + return false; + } + + $protection = $this->getProtectionBitsFromOptions($permissions); + + if ($length === 128) { + $this->useRC128Encryption = true; + } elseif ($length !== 40) { + throw new \Mpdf\MpdfException('PDF protection only allows lenghts of 40 or 128'); + } + + if ($owner_pass === null) { + $owner_pass = bin2hex(random_bytes(23)); + } + + $this->generateEncryptionKey($user_pass, $owner_pass, $protection); + + return true; + } + + /** + * Compute key depending on object number where the encrypted data is stored + * + * @param int $n + * + * @return string + */ + public function objectKey($n) + { + if ($this->useRC128Encryption) { + $len = 16; + } else { + $len = 10; + } + + return substr($this->md5toBinary($this->encryptionKey . pack('VXxx', $n)), 0, $len); + } + + /** + * RC4 is the standard encryption algorithm used in PDF format + * + * @param string $key + * @param string $text + * + * @return string + */ + public function rc4($key, $text) + { + if ($this->lastRc4Key != $key) { + $k = str_repeat($key, 256 / strlen($key) + 1); + $rc4 = range(0, 255); + $j = 0; + for ($i = 0; $i < 256; $i++) { + $t = $rc4[$i]; + $j = ($j + $t + ord($k[$i])) % 256; + $rc4[$i] = $rc4[$j]; + $rc4[$j] = $t; + } + $this->lastRc4Key = $key; + $this->lastRc4KeyC = $rc4; + } else { + $rc4 = $this->lastRc4KeyC; + } + + $len = strlen($text); + $a = 0; + $b = 0; + $out = ''; + for ($i = 0; $i < $len; $i++) { + $a = ($a + 1) % 256; + $t = $rc4[$a]; + $b = ($b + $t) % 256; + $rc4[$a] = $rc4[$b]; + $rc4[$b] = $t; + $k = $rc4[($rc4[$a] + $rc4[$b]) % 256]; + $out .= chr(ord($text[$i]) ^ $k); + } + + return $out; + } + + /** + * @return mixed + */ + public function getUseRC128Encryption() + { + return $this->useRC128Encryption; + } + + /** + * @return mixed + */ + public function getUniqid() + { + return $this->uniqid; + } + + /** + * @return mixed + */ + public function getOValue() + { + return $this->oValue; + } + + /** + * @return mixed + */ + public function getUValue() + { + return $this->uValue; + } + + /** + * @return mixed + */ + public function getPValue() + { + return $this->pValue; + } + + private function getProtectionBitsFromOptions($permissions) + { + // bit 31 = 1073741824 + // bit 32 = 2147483648 + // bits 13-31 = 2147479552 + // bits 13-32 = 4294963200 + 192 = 4294963392 + + $protection = 4294963392; // bits 7, 8, 13-32 + + foreach ($permissions as $permission) { + if (!isset($this->options[$permission])) { + throw new \Mpdf\MpdfException(sprintf('Invalid permission type "%s"', $permission)); + } + if ($this->options[$permission] > 32) { + $this->useRC128Encryption = true; + } + if (isset($this->options[$permission])) { + $protection += $this->options[$permission]; + } + } + + return $protection; + } + + private function oValue($user_pass, $owner_pass) + { + $tmp = $this->md5toBinary($owner_pass); + if ($this->useRC128Encryption) { + for ($i = 0; $i < 50; ++$i) { + $tmp = $this->md5toBinary($tmp); + } + } + if ($this->useRC128Encryption) { + $keybytelen = (128 / 8); + } else { + $keybytelen = (40 / 8); + } + $owner_rc4_key = substr($tmp, 0, $keybytelen); + $enc = $this->rc4($owner_rc4_key, $user_pass); + if ($this->useRC128Encryption) { + $len = strlen($owner_rc4_key); + for ($i = 1; $i <= 19; ++$i) { + $key = ''; + for ($j = 0; $j < $len; ++$j) { + $key .= chr(ord($owner_rc4_key[$j]) ^ $i); + } + $enc = $this->rc4($key, $enc); + } + } + + return $enc; + } + + private function uValue() + { + if ($this->useRC128Encryption) { + $tmp = $this->md5toBinary($this->padding . $this->hexToString($this->uniqid)); + $enc = $this->rc4($this->encryptionKey, $tmp); + $len = strlen($tmp); + for ($i = 1; $i <= 19; ++$i) { + $key = ''; + for ($j = 0; $j < $len; ++$j) { + $key .= chr(ord($this->encryptionKey[$j]) ^ $i); + } + $enc = $this->rc4($key, $enc); + } + $enc .= str_repeat("\x00", 16); + + return substr($enc, 0, 32); + } else { + return $this->rc4($this->encryptionKey, $this->padding); + } + } + + private function generateEncryptionKey($user_pass, $owner_pass, $protection) + { + // Pad passwords + $user_pass = substr($user_pass . $this->padding, 0, 32); + $owner_pass = substr($owner_pass . $this->padding, 0, 32); + + $this->oValue = $this->oValue($user_pass, $owner_pass); + + $this->uniqid = $this->uniqidGenerator->generate(); + + // Compute encyption key + if ($this->useRC128Encryption) { + $keybytelen = (128 / 8); + } else { + $keybytelen = (40 / 8); + } + + $prot = sprintf('%032b', $protection); + + $perms = chr(bindec(substr($prot, 24, 8))); + $perms .= chr(bindec(substr($prot, 16, 8))); + $perms .= chr(bindec(substr($prot, 8, 8))); + $perms .= chr(bindec(substr($prot, 0, 8))); + + $tmp = $this->md5toBinary($user_pass . $this->oValue . $perms . $this->hexToString($this->uniqid)); + + if ($this->useRC128Encryption) { + for ($i = 0; $i < 50; ++$i) { + $tmp = $this->md5toBinary(substr($tmp, 0, $keybytelen)); + } + } + + $this->encryptionKey = substr($tmp, 0, $keybytelen); + + $this->uValue = $this->uValue(); + $this->pValue = $protection; + } + + private function md5toBinary($string) + { + return pack('H*', md5($string)); + } + + private function hexToString($hs) + { + $s = ''; + $len = strlen($hs); + if (($len % 2) != 0) { + $hs .= '0'; + ++$len; + } + for ($i = 0; $i < $len; $i += 2) { + $s .= chr(hexdec($hs[$i] . $hs[($i + 1)])); + } + + return $s; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Pdf/Protection/UniqidGenerator.php b/lib/MPDF/vendor/mpdf/mpdf/src/Pdf/Protection/UniqidGenerator.php new file mode 100644 index 0000000..54753d4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Pdf/Protection/UniqidGenerator.php @@ -0,0 +1,32 @@ +<?php + +namespace Mpdf\Pdf\Protection; + +class UniqidGenerator +{ + + public function __construct() + { + if (!function_exists('random_int') || !function_exists('random_bytes')) { + throw new \Mpdf\MpdfException( + 'Unable to set PDF file protection, CSPRNG Functions are not available. ' + . 'Use paragonie/random_compat polyfill or upgrade to PHP 7.' + ); + } + } + + /** + * @return string + */ + public function generate() + { + $chars = 'ABCDEF1234567890'; + $id = ''; + + for ($i = 0; $i < 32; $i++) { + $id .= $chars[random_int(0, 15)]; + } + + return md5($id); + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/RemoteContentFetcher.php b/lib/MPDF/vendor/mpdf/mpdf/src/RemoteContentFetcher.php new file mode 100644 index 0000000..f1547ec --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/RemoteContentFetcher.php @@ -0,0 +1,148 @@ +<?php + +namespace Mpdf; + +use Mpdf\Utils\Arrays; +use Psr\Log\LoggerInterface; +use Mpdf\Log\Context as LogContext; + +class RemoteContentFetcher implements \Psr\Log\LoggerAwareInterface +{ + + /** + * @var \Mpdf\Mpdf + */ + private $mpdf; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + public function __construct(Mpdf $mpdf, LoggerInterface $logger) + { + $this->mpdf = $mpdf; + $this->logger = $logger; + } + + public function getFileContentsByCurl($url) + { + $this->logger->debug(sprintf('Fetching (cURL) content of remote URL "%s"', $url), ['context' => LogContext::REMOTE_CONTENT]); + + $ch = curl_init($url); + + curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:13.0) Gecko/20100101 Firefox/13.0.1'); // mPDF 5.7.4 + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_NOBODY, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->mpdf->curlTimeout); + + if ($this->mpdf->curlFollowLocation) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + } + + if ($this->mpdf->curlAllowUnsafeSslRequests) { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + } + + if (is_file($this->mpdf->curlCaCertificate)) { + curl_setopt($ch, CURLOPT_CAINFO, $this->mpdf->curlCaCertificate); + } + + if ($this->mpdf->curlProxy) { + curl_setopt($ch, CURLOPT_PROXY, $this->mpdf->curlProxy); + if ($this->mpdf->curlProxyAuth) { + curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->mpdf->curlProxyAuth); + } + } + + $data = curl_exec($ch); + + if (curl_error($ch)) { + $message = sprintf('cURL error: "%s"', curl_error($ch)); + $this->logger->error($message, ['context' => LogContext::REMOTE_CONTENT]); + + if ($this->mpdf->debug) { + throw new \Mpdf\MpdfException($message); + } + } + + $info = curl_getinfo($ch); + if (isset($info['http_code']) && $info['http_code'] !== 200) { + $message = sprintf('HTTP error: %d', $info['http_code']); + $this->logger->error($message, ['context' => LogContext::REMOTE_CONTENT]); + + if ($this->mpdf->debug) { + throw new \Mpdf\MpdfException($message); + } + } + + curl_close($ch); + + return $data; + } + + public function getFileContentsBySocket($url) + { + $this->logger->debug(sprintf('Fetching (socket) content of remote URL "%s"', $url), ['context' => LogContext::REMOTE_CONTENT]); + // mPDF 5.7.3 + + $timeout = 1; + $p = parse_url($url); + + $file = Arrays::get($p, 'path', ''); + $scheme = Arrays::get($p, 'scheme', ''); + $port = Arrays::get($p, 'port', 80); + $prefix = ''; + + if ($scheme === 'https') { + $prefix = 'ssl://'; + $port = Arrays::get($p, 'port', 443); + } + + $query = Arrays::get($p, 'query', null); + if ($query) { + $file .= '?' . $query; + } + + if (!($fh = @fsockopen($prefix . $p['host'], $port, $errno, $errstr, $timeout))) { + $this->logger->error(sprintf('Socket error "%s": "%s"', $errno, $errstr), ['context' => LogContext::REMOTE_CONTENT]); + return false; + } + + $getstring = 'GET ' . $file . " HTTP/1.0 \r\n" . + 'Host: ' . $p['host'] . " \r\n" . + "Connection: close\r\n\r\n"; + + fwrite($fh, $getstring); + + // Get rid of HTTP header + $s = fgets($fh, 1024); + if (!$s) { + return false; + } + + while (!feof($fh)) { + $s = fgets($fh, 1024); + if ($s === "\r\n") { + break; + } + } + + $data = ''; + + while (!feof($fh)) { + $data .= fgets($fh, 1024); + } + + fclose($fh); + + return $data; + } + + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/ServiceFactory.php b/lib/MPDF/vendor/mpdf/mpdf/src/ServiceFactory.php new file mode 100644 index 0000000..f81e6f7 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/ServiceFactory.php @@ -0,0 +1,173 @@ +<?php + +namespace Mpdf; + +use Mpdf\Color\ColorConverter; +use Mpdf\Color\ColorModeConverter; +use Mpdf\Color\ColorSpaceRestrictor; + +use Mpdf\Fonts\FontCache; +use Mpdf\Fonts\FontFileFinder; + +use Mpdf\Image\ImageProcessor; + +use Mpdf\Pdf\Protection; +use Mpdf\Pdf\Protection\UniqidGenerator; + +use Mpdf\Writer\BaseWriter; +use Mpdf\Writer\BackgroundWriter; +use Mpdf\Writer\ColorWriter; +use Mpdf\Writer\BookmarkWriter; +use Mpdf\Writer\FontWriter; +use Mpdf\Writer\FormWriter; +use Mpdf\Writer\ImageWriter; +use Mpdf\Writer\JavaScriptWriter; +use Mpdf\Writer\MetadataWriter; +use Mpdf\Writer\OptionalContentWriter; +use Mpdf\Writer\PageWriter; + +use Mpdf\Writer\ResourceWriter; +use Psr\Log\LoggerInterface; + +class ServiceFactory +{ + + public function getServices( + Mpdf $mpdf, + LoggerInterface $logger, + $config, + $restrictColorSpace, + $languageToFont, + $scriptToLanguage, + $fontDescriptor, + $bmp, + $directWrite, + $wmf + ) { + $sizeConverter = new SizeConverter($mpdf->dpi, $mpdf->default_font_size, $mpdf, $logger); + + $colorModeConverter = new ColorModeConverter(); + $colorSpaceRestrictor = new ColorSpaceRestrictor( + $mpdf, + $colorModeConverter, + $restrictColorSpace + ); + $colorConverter = new ColorConverter($mpdf, $colorModeConverter, $colorSpaceRestrictor); + + $tableOfContents = new TableOfContents($mpdf, $sizeConverter); + + $cache = new Cache($config['tempDir']); + $fontCache = new FontCache(new Cache($config['tempDir'] . '/ttfontdata')); + + $fontFileFinder = new FontFileFinder($config['fontDir']); + + $cssManager = new CssManager($mpdf, $cache, $sizeConverter, $colorConverter); + + $otl = new Otl($mpdf, $fontCache); + + $protection = new Protection(new UniqidGenerator()); + + $writer = new BaseWriter($mpdf, $protection); + + $gradient = new Gradient($mpdf, $sizeConverter, $colorConverter, $writer); + + $formWriter = new FormWriter($mpdf, $writer); + + $form = new Form($mpdf, $otl, $colorConverter, $writer, $formWriter); + + $hyphenator = new Hyphenator($mpdf); + + $remoteContentFetcher = new RemoteContentFetcher($mpdf, $logger); + + $imageProcessor = new ImageProcessor( + $mpdf, + $otl, + $cssManager, + $sizeConverter, + $colorConverter, + $colorModeConverter, + $cache, + $languageToFont, + $scriptToLanguage, + $remoteContentFetcher, + $logger + ); + + $tag = new Tag( + $mpdf, + $cache, + $cssManager, + $form, + $otl, + $tableOfContents, + $sizeConverter, + $colorConverter, + $imageProcessor, + $languageToFont + ); + + $fontWriter = new FontWriter($mpdf, $writer, $fontCache, $fontDescriptor); + $metadataWriter = new MetadataWriter($mpdf, $writer, $form, $protection, $logger); + $imageWriter = new ImageWriter($mpdf, $writer); + $pageWriter = new PageWriter($mpdf, $form, $writer, $metadataWriter); + $bookmarkWriter = new BookmarkWriter($mpdf, $writer); + $optionalContentWriter = new OptionalContentWriter($mpdf, $writer); + $colorWriter = new ColorWriter($mpdf, $writer); + $backgroundWriter = new BackgroundWriter($mpdf, $writer); + $javaScriptWriter = new JavaScriptWriter($mpdf, $writer); + + $resourceWriter = new ResourceWriter( + $mpdf, + $writer, + $colorWriter, + $fontWriter, + $imageWriter, + $formWriter, + $optionalContentWriter, + $backgroundWriter, + $bookmarkWriter, + $metadataWriter, + $javaScriptWriter, + $logger + ); + + return [ + 'otl' => $otl, + 'bmp' => $bmp, + 'cache' => $cache, + 'cssManager' => $cssManager, + 'directWrite' => $directWrite, + 'fontCache' => $fontCache, + 'fontFileFinder' => $fontFileFinder, + 'form' => $form, + 'gradient' => $gradient, + 'tableOfContents' => $tableOfContents, + 'tag' => $tag, + 'wmf' => $wmf, + 'sizeConverter' => $sizeConverter, + 'colorConverter' => $colorConverter, + 'hyphenator' => $hyphenator, + 'remoteContentFetcher' => $remoteContentFetcher, + 'imageProcessor' => $imageProcessor, + 'protection' => $protection, + + 'languageToFont' => $languageToFont, + 'scriptToLanguage' => $scriptToLanguage, + + 'writer' => $writer, + 'fontWriter' => $fontWriter, + 'metadataWriter' => $metadataWriter, + 'imageWriter' => $imageWriter, + 'formWriter' => $formWriter, + 'pageWriter' => $pageWriter, + 'bookmarkWriter' => $bookmarkWriter, + 'optionalContentWriter' => $optionalContentWriter, + 'colorWriter' => $colorWriter, + 'backgroundWriter' => $backgroundWriter, + 'javaScriptWriter' => $javaScriptWriter, + + 'resourceWriter' => $resourceWriter + ]; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Indic.php b/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Indic.php new file mode 100644 index 0000000..28e3001 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Indic.php @@ -0,0 +1,1945 @@ +<?php + +namespace Mpdf\Shaper; + +use Mpdf\Ucdn; + +class Indic +{ + /* FROM hb-ot-shape-complex-indic-private.hh */ + + // indic_category + const OT_X = 0; + const OT_C = 1; + const OT_V = 2; + const OT_N = 3; + const OT_H = 4; + const OT_ZWNJ = 5; + const OT_ZWJ = 6; + const OT_M = 7; /* Matra or Dependent Vowel */ + const OT_SM = 8; + const OT_VD = 9; + const OT_A = 10; + const OT_NBSP = 11; + const OT_DOTTEDCIRCLE = 12; /* Not in the spec, but special in Uniscribe. /Very very/ special! */ + const OT_RS = 13; /* Register Shifter, used in Khmer OT spec */ + const OT_COENG = 14; + const OT_REPHA = 15; + + const OT_RA = 16; /* Not explicitly listed in the OT spec, but used in the grammar. */ + const OT_CM = 17; + + /* Visual positions in a syllable from left to right. */ + /* FROM hb-ot-shape-complex-indic-private.hh */ + + // indic_position + const POS_START = 0; + + const POS_RA_TO_BECOME_REPH = 1; + const POS_PRE_M = 2; + const POS_PRE_C = 3; + + const POS_BASE_C = 4; + const POS_AFTER_MAIN = 5; + + const POS_ABOVE_C = 6; + + const POS_BEFORE_SUB = 7; + const POS_BELOW_C = 8; + const POS_AFTER_SUB = 9; + + const POS_BEFORE_POST = 10; + const POS_POST_C = 11; + const POS_AFTER_POST = 12; + + const POS_FINAL_C = 13; + const POS_SMVD = 14; + + const POS_END = 15; + + /* + * Basic features. + * These features are applied in order, one at a time, after initial_reordering. + */ + + /* + * Must be in the same order as the indic_features array. Ones starting with _ are F_GLOBAL + * Ones without the _ are only applied where the mask says! + */ + + const _NUKT = 0; + const _AKHN = 1; + const RPHF = 2; + const _RKRF = 3; + const PREF = 4; + const BLWF = 5; + const HALF = 6; + const ABVF = 7; + const PSTF = 8; + const CFAR = 9; // Khmer only + const _VATU = 10; + const _CJCT = 11; + const INIT = 12; + + // Based on indic_category used to make string to find syllables + // OT_ to string character (using e.g. OT_C from INDIC) hb-ot-shape-complex-indic-private.hh + public static $indic_category_char = [ + 'x', + 'C', + 'V', + 'N', + 'H', + 'Z', + 'J', + 'M', + 'S', + 'v', + 'A', /* Spec gives Andutta U+0952 as OT_A. However, testing shows that Uniscribe + * treats U+0951..U+0952 all as OT_VD - see set_indic_properties */ + 's', + 'D', + 'F', /* Register shift Khmer only */ + 'G', /* Khmer only */ + 'r', /* 0D4E (dot reph) only one in Malayalam */ + 'R', + 'm', /* Consonant medial only used in Indic 0A75 in Gurmukhi (0A00..0A7F) : also in Lao, Myanmar, Tai Tham, Javanese & Cham */ + ]; + + public static function set_indic_properties(&$info, $scriptblock) + { + $u = $info['uni']; + $type = self::indic_get_categories($u); + $cat = ($type & 0x7F); + $pos = ($type >> 8); + + /* + * Re-assign category + */ + + if ($u == 0x17D1) { + $cat = self::OT_X; + } + + if ($cat == self::OT_X && self::in_range($u, 0x17CB, 0x17D3)) { /* Khmer Various signs */ + /* These are like Top Matras. */ + $cat = self::OT_M; + $pos = self::POS_ABOVE_C; + } + + if ($u == 0x17C6) { + $cat = self::OT_N; + } /* Khmer Bindu doesn't like to be repositioned. */ + + if ($u == 0x17D2) { + $cat = self::OT_COENG; + } /* Khmer coeng */ + + /* The spec says U+0952 is OT_A. However, testing shows that Uniscribe + * treats U+0951..U+0952 all as OT_VD. + * TESTS: + * U+092E,U+0947,U+0952 + * U+092E,U+0952,U+0947 + * U+092E,U+0947,U+0951 + * U+092E,U+0951,U+0947 + * */ + //if ($u == 0x0952) $cat = self::OT_A; + if (self::in_range($u, 0x0951, 0x0954)) { + $cat = self::OT_VD; + } + + if ($u == 0x200C) { + $cat = self::OT_ZWNJ; + } elseif ($u == 0x200D) { + $cat = self::OT_ZWJ; + } elseif ($u == 0x25CC) { + $cat = self::OT_DOTTEDCIRCLE; + } elseif ($u == 0x0A71) { + $cat = self::OT_SM; + } /* GURMUKHI ADDAK. More like consonant medial. like 0A75. */ + + if ($cat == self::OT_REPHA) { + /* There are two kinds of characters marked as Repha: + * - The ones that are GenCat=Mn are already positioned visually, ie. after base. (eg. Khmer) + * - The ones that are GenCat=Lo is encoded logically, ie. beginning of syllable. (eg. Malayalam) + * + * We recategorize the first kind to look like a Nukta and attached to the base directly. + */ + if ($info['general_category'] == Ucdn::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) { + $cat = self::OT_N; + } + } + + /* + * Re-assign position. + */ + + if ((self::FLAG($cat) & (self::FLAG(self::OT_C) | self::FLAG(self::OT_CM) | self::FLAG(self::OT_RA) | self::FLAG(self::OT_V) | self::FLAG(self::OT_NBSP) | self::FLAG(self::OT_DOTTEDCIRCLE)))) { // = CONSONANT_FLAGS like is_consonant + if ($scriptblock == Ucdn::SCRIPT_KHMER) { + $pos = self::POS_BELOW_C; + } /* Khmer differs from Indic here. */ + else { + $pos = self::POS_BASE_C; + } /* Will recategorize later based on font lookups. */ + + if (self::is_ra($u)) { + $cat = self::OT_RA; + } + } elseif ($cat == self::OT_M) { + $pos = self::matra_position($u, $pos); + } elseif ($cat == self::OT_SM || $cat == self::OT_VD) { + $pos = self::POS_SMVD; + } + + if ($u == 0x0B01) { + $pos = self::POS_BEFORE_SUB; + } /* Oriya Bindu is BeforeSub in the spec. */ + + $info['indic_category'] = $cat; + $info['indic_position'] = $pos; + } + + // syllable_type + const CONSONANT_SYLLABLE = 0; + const VOWEL_SYLLABLE = 1; + const STANDALONE_CLUSTER = 2; + const BROKEN_CLUSTER = 3; + const NON_INDIC_CLUSTER = 4; + + public static function set_syllables(&$o, $s, &$broken_syllables) + { + $ptr = 0; + $syllable_serial = 1; + $broken_syllables = false; + + while ($ptr < strlen($s)) { + $match = ''; + $syllable_length = 1; + $syllable_type = self::NON_INDIC_CLUSTER; + // CONSONANT_SYLLABLE Consonant syllable + // From OT spec: + if (preg_match('/^([CR]m*[N]?(H[ZJ]?|[ZJ]H))*[CR]m*[N]?[A]?(H[ZJ]?|[M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s, $ptr), $ma)) { + // From HarfBuzz: + //if (preg_match('/^r?([CR]J?(Z?[N]{0,2})?[ZJ]?H(J[N]?)?){0,4}[CR]J?(Z?[N]{0,2})?A?((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})?(S[Z]?)?[v]{0,2}/', substr($s,$ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::CONSONANT_SYLLABLE; + } // VOWEL_SYLLABLE Vowel-based syllable + // From OT spec: + elseif (preg_match('/^(RH|r)?V[N]?([ZJ]?H[CR]m*|J[CR]m*)?([M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s, $ptr), $ma)) { + // From HarfBuzz: + //else if (preg_match('/^(RH|r)?V(Z?[N]{0,2})?(J|([ZJ]?H(J[N]?)?[CR]J?(Z?[N]{0,2})?){0,4}((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})?(S[Z]?)?[v]{0,2})/', substr($s,$ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::VOWEL_SYLLABLE; + } /* Apply only if it's a word start. */ + // STANDALONE_CLUSTER Stand Alone syllable at start of word + // From OT spec: + elseif (($ptr == 0 || + $o[$ptr - 1]['general_category'] < Ucdn::UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER || + $o[$ptr - 1]['general_category'] > Ucdn::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK + ) && (preg_match('/^(RH|r)?[sD][N]?([ZJ]?H[CR]m*)?([M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s, $ptr), $ma))) { + // From HarfBuzz: + // && (preg_match('/^(RH|r)?[sD](Z?[N]{0,2})?(([ZJ]?H(J[N]?)?)[CR]J?(Z?[N]{0,2})?){0,4}((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})?(S[Z]?)?[v]{0,2}/', substr($s,$ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::STANDALONE_CLUSTER; + } // BROKEN_CLUSTER syllable + elseif (preg_match('/^(RH|r)?[N]?([ZJ]?H[CR])?([M]*[N]?[H]?)?[S]?[v]{0,2}/', substr($s, $ptr), $ma)) { + // From HarfBuzz: + //else if (preg_match('/^(RH|r)?(Z?[N]{0,2})?(([ZJ]?H(J[N]?)?)[CR]J?(Z?[N]{0,2})?){0,4}((([ZJ]?H(J[N]?)?)|HZ)|(HJ)?([ZJ]{0,3}M[N]?(H|JHJR)?){0,4})(S[Z]?)?[v]{0,2}/', substr($s,$ptr), $ma)) { + if (strlen($ma[0])) { // May match blank + $syllable_length = strlen($ma[0]); + $syllable_type = self::BROKEN_CLUSTER; + $broken_syllables = true; + } + } + + for ($i = $ptr; $i < $ptr + $syllable_length; $i++) { + $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; + } + $ptr += $syllable_length; + $syllable_serial++; + if ($syllable_serial == 16) { + $syllable_serial = 1; + } + } + } + + public static function set_syllables_sinhala(&$o, $s, &$broken_syllables) + { + $ptr = 0; + $syllable_serial = 1; + $broken_syllables = false; + + while ($ptr < strlen($s)) { + $match = ''; + $syllable_length = 1; + $syllable_type = self::NON_INDIC_CLUSTER; + // CONSONANT_SYLLABLE Consonant syllable + // From OT spec: + if (preg_match('/^([CR]HJ|[CR]JH){0,8}[CR][HM]{0,3}[S]{0,1}/', substr($s, $ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::CONSONANT_SYLLABLE; + } // VOWEL_SYLLABLE Vowel-based syllable + // From OT spec: + elseif (preg_match('/^V[S]{0,1}/', substr($s, $ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::VOWEL_SYLLABLE; + } + + for ($i = $ptr; $i < $ptr + $syllable_length; $i++) { + $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; + } + $ptr += $syllable_length; + $syllable_serial++; + if ($syllable_serial == 16) { + $syllable_serial = 1; + } + } + } + + public static function set_syllables_khmer(&$o, $s, &$broken_syllables) + { + $ptr = 0; + $syllable_serial = 1; + $broken_syllables = false; + + while ($ptr < strlen($s)) { + $match = ''; + $syllable_length = 1; + $syllable_type = self::NON_INDIC_CLUSTER; + // CONSONANT_SYLLABLE Consonant syllable + if (preg_match('/^r?([CR]J?((Z?F)?[N]{0,2})?[ZJ]?G(JN?)?){0,4}[CR]J?((Z?F)?[N]{0,2})?A?((([ZJ]?G(JN?)?)|GZ)|(GJ)?([ZJ]{0,3}MN?(H|JHJR)?){0,4})?(G([CR]J?((Z?F)?[N]{0,2})?|V))?(SZ?)?[v]{0,2}/', substr($s, $ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::CONSONANT_SYLLABLE; + } // VOWEL_SYLLABLE Vowel-based syllable + elseif (preg_match('/^(RH|r)?V((Z?F)?[N]{0,2})?(J|([ZJ]?G(JN?)?[CR]J?((Z?F)?[N]{0,2})?){0,4}((([ZJ]?G(JN?)?)|GZ)|(GJ)?([ZJ]{0,3}MN?(H|JHJR)?){0,4})?(G([CR]J?((Z?F)?[N]{0,2})?|V))?(SZ?)?[v]{0,2})/', substr($s, $ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::VOWEL_SYLLABLE; + } // BROKEN_CLUSTER syllable + elseif (preg_match('/^(RH|r)?((Z?F)?[N]{0,2})?(([ZJ]?G(JN?)?)[CR]J?((Z?F)?[N]{0,2})?){0,4}((([ZJ]?G(JN?)?)|GZ)|(GJ)?([ZJ]{0,3}MN?(H|JHJR)?){0,4})(G([CR]J?((Z?F)?[N]{0,2})?|V))?(SZ?)?[v]{0,2}/', substr($s, $ptr), $ma)) { + if (strlen($ma[0])) { // May match blank + $syllable_length = strlen($ma[0]); + $syllable_type = self::BROKEN_CLUSTER; + $broken_syllables = true; + } + } + + for ($i = $ptr; $i < $ptr + $syllable_length; $i++) { + $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; + } + $ptr += $syllable_length; + $syllable_serial++; + if ($syllable_serial == 16) { + $syllable_serial = 1; + } + } + } + + public static function initial_reordering(&$info, $GSUBdata, $broken_syllables, $indic_config, $scriptblock, $is_old_spec, $dottedcircle) + { + + self::update_consonant_positions($info, $GSUBdata); + + if ($broken_syllables && $dottedcircle) { + self::insert_dotted_circles($info, $dottedcircle); + } + + $count = count($info); + if (!$count) { + return; + } + $last = 0; + $last_syllable = $info[0]['syllable']; + for ($i = 1; $i < $count; $i++) { + if ($last_syllable != $info[$i]['syllable']) { + self::initial_reordering_syllable($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $i); + $last = $i; + $last_syllable = $info[$last]['syllable']; + } + } + self::initial_reordering_syllable($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $count); + } + + public static function update_consonant_positions(&$info, $GSUBdata) + { + $count = count($info); + for ($i = 0; $i < $count; $i++) { + if ($info[$i]['indic_position'] == self::POS_BASE_C) { + $c = $info[$i]['uni']; + // If would substitute... + if (isset($GSUBdata['pref'][$c])) { + $info[$i]['indic_position'] = self::POS_POST_C; + } elseif (isset($GSUBdata['blwf'][$c])) { + $info[$i]['indic_position'] = self::POS_BELOW_C; + } elseif (isset($GSUBdata['pstf'][$c])) { + $info[$i]['indic_position'] = self::POS_POST_C; + } + } + } + } + + public static function insert_dotted_circles(&$info, $dottedcircle) + { + $idx = 0; + $last_syllable = 0; + while ($idx < count($info)) { + $syllable = $info[$idx]['syllable']; + $syllable_type = ($syllable & 0x0F); + if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) { + $last_syllable = $syllable; + + $dottedcircle[0]['syllable'] = $info[$idx]['syllable']; + + /* Insert dottedcircle after possible Repha. */ + while ($idx < count($info) && $last_syllable == $info[$idx]['syllable'] && $info[$idx]['indic_category'] == self::OT_REPHA) { + $idx++; + } + array_splice($info, $idx, 0, $dottedcircle); + } else { + $idx++; + } + } + + // I am not sue how this code below got in here, since $idx should now be > count($info) and thus invalid. + // In case I am missing something(!) I'll leave a warning here for now: + if (isset($info[$idx])) { + throw new \Mpdf\MpdfException('Unexpected error occured in Indic processing'); + } + // In case of final bloken cluster... + //$syllable = $info[$idx]['syllable']; + //$syllable_type = ($syllable & 0x0F); + //if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) { + // $dottedcircle[0]['syllable'] = $info[$idx]['syllable']; + // array_splice($info, $idx, 0, $dottedcircle); + //} + } + + /* Rules from: + * https://www.microsoft.com/typography/otfntdev/devanot/shaping.aspx */ + + public static function initial_reordering_syllable(&$info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $start, $end) + { + /* vowel_syllable: We made the vowels look like consonants. So uses the consonant logic! */ + /* broken_cluster: We already inserted dotted-circles, so just call the standalone_cluster. */ + /* standalone_cluster: We treat NBSP/dotted-circle as if they are consonants, so we should just chain. */ + + $syllable_type = ($info[$start]['syllable'] & 0x0F); + if ($syllable_type == self::NON_INDIC_CLUSTER) { + return; + } + if ($syllable_type == self::BROKEN_CLUSTER || $syllable_type == self::STANDALONE_CLUSTER) { + //if ($uniscribe_bug_compatible) { + /* For dotted-circle, this is what Uniscribe does: + * If dotted-circle is the last glyph, it just does nothing. + * i.e. It doesn't form Reph. */ + if ($info[$end - 1]['indic_category'] == self::OT_DOTTEDCIRCLE) { + return; + } + } + + /* 1. Find base consonant: + * + * The shaping engine finds the base consonant of the syllable, using the + * following algorithm: starting from the end of the syllable, move backwards + * until a consonant is found that does not have a below-base or post-base + * form (post-base forms have to follow below-base forms), or that is not a + * pre-base reordering Ra, or arrive at the first consonant. The consonant + * stopped at will be the base. + * + * o If the syllable starts with Ra + Halant (in a script that has Reph) + * and has more than one consonant, Ra is excluded from candidates for + * base consonants. + */ + + $base = $end; + $has_reph = false; + $limit = $start; + + if ($scriptblock != Ucdn::SCRIPT_KHMER) { + /* -> If the syllable starts with Ra + Halant (in a script that has Reph) + * and has more than one consonant, Ra is excluded from candidates for + * base consonants. */ + if (count($GSUBdata['rphf']) /* ?? $indic_plan->mask_array[RPHF] */ && $start + 3 <= $end && + ( + ($indic_config[4] == self::REPH_MODE_IMPLICIT && !self::is_joiner($info[$start + 2])) || + ($indic_config[4] == self::REPH_MODE_EXPLICIT && $info[$start + 2]['indic_category'] == self::OT_ZWJ) + )) { + /* See if it matches the 'rphf' feature. */ + //$glyphs = array($info[$start]['uni'], $info[$start + 1]['uni']); + //if ($indic_plan->rphf->would_substitute ($glyphs, count($glyphs), true, face)) { + if (isset($GSUBdata['rphf'][$info[$start]['uni']]) && self::is_halant_or_coeng($info[$start + 1])) { + $limit += 2; + while ($limit < $end && self::is_joiner($info[$limit])) { + $limit++; + } + $base = $start; + $has_reph = true; + } + } elseif ($indic_config[4] == self::REPH_MODE_LOG_REPHA && $info[$start]['indic_category'] == self::OT_REPHA) { + $limit += 1; + while ($limit < $end && self::is_joiner($info[$limit])) { + $limit++; + } + $base = $start; + $has_reph = true; + } + } + + switch ($indic_config[2]) { // base_pos + case self::BASE_POS_LAST: + /* -> starting from the end of the syllable, move backwards */ + $i = $end; + $seen_below = false; + do { + $i--; + /* -> until a consonant is found */ + if (self::is_consonant($info[$i])) { + /* -> that does not have a below-base or post-base form + * (post-base forms have to follow below-base forms), */ + if ($info[$i]['indic_position'] != self::POS_BELOW_C && ($info[$i]['indic_position'] != self::POS_POST_C || $seen_below)) { + $base = $i; + break; + } + if ($info[$i]['indic_position'] == self::POS_BELOW_C) { + $seen_below = true; + } + + /* -> or that is not a pre-base reordering Ra, + * + * IMPLEMENTATION NOTES: + * + * Our pre-base reordering Ra's are marked POS_POST_C, so will be skipped + * by the logic above already. + */ + + /* -> or arrive at the first consonant. The consonant stopped at will + * be the base. */ + $base = $i; + } else { + /* A ZWJ after a Halant stops the base search, and requests an explicit + * half form. + * [A ZWJ before a Halant, requests a subjoined form instead, and hence + * search continues. This is particularly important for Bengali + * sequence Ra,H,Ya that should form Ya-Phalaa by subjoining Ya] */ + if ($start < $i && $info[$i]['indic_category'] == self::OT_ZWJ && $info[$i - 1]['indic_category'] == self::OT_H) { + if (!defined("OMIT_INDIC_FIX_1") || OMIT_INDIC_FIX_1 != 1) { + $base = $i; + } // INDIC_FIX_1 + break; + } + // ZKI8 + if ($start < $i && $info[$i]['indic_category'] == self::OT_ZWNJ) { + break; + } + } + } while ($i > $limit); + break; + + case self::BASE_POS_FIRST: + /* In scripts without half forms (eg. Khmer), the first consonant is always the base. */ + + if (!$has_reph) { + $base = $limit; + } + + /* Find the last base consonant that is not blocked by ZWJ. If there is + * a ZWJ right before a base consonant, that would request a subjoined form. */ + for ($i = $limit; $i < $end; $i++) { + if (self::is_consonant($info[$i]) && $info[$i]['indic_position'] == self::POS_BASE_C) { + if ($limit < $i && $info[$i - 1]['indic_category'] == self::OT_ZWJ) { + break; + } else { + $base = $i; + } + } + } + + /* Mark all subsequent consonants as below. */ + for ($i = $base + 1; $i < $end; $i++) { + if (self::is_consonant($info[$i]) && $info[$i]['indic_position'] == self::POS_BASE_C) { + $info[$i]['indic_position'] = self::POS_BELOW_C; + } + } + break; + //default: + //assert (false); + /* fallthrough */ + } + + /* -> If the syllable starts with Ra + Halant (in a script that has Reph) + * and has more than one consonant, Ra is excluded from candidates for + * base consonants. + * + * Only do this for unforced Reph. (ie. not for Ra,H,ZWJ. */ + if ($scriptblock != Ucdn::SCRIPT_KHMER) { + if ($has_reph && $base == $start && $limit - $base <= 2) { + /* Have no other consonant, so Reph is not formed and Ra becomes base. */ + $has_reph = false; + } + } + + /* 2. Decompose and reorder Matras: + * + * Each matra and any syllable modifier sign in the cluster are moved to the + * appropriate position relative to the consonant(s) in the cluster. The + * shaping engine decomposes two- or three-part matras into their constituent + * parts before any repositioning. Matra characters are classified by which + * consonant in a conjunct they have affinity for and are reordered to the + * following positions: + * + * o Before first half form in the syllable + * o After subjoined consonants + * o After post-form consonant + * o After main consonant (for above marks) + * + * IMPLEMENTATION NOTES: + * + * The normalize() routine has already decomposed matras for us, so we don't + * need to worry about that. + */ + + + /* 3. Reorder marks to canonical order: + * + * Adjacent nukta and halant or nukta and vedic sign are always repositioned + * if necessary, so that the nukta is first. + * + * IMPLEMENTATION NOTES: + * + * Use the combining Class from Unicode categories? to bubble_sort. + */ + + /* Reorder characters */ + + for ($i = $start; $i < $base; $i++) { + $info[$i]['indic_position'] = min(self::POS_PRE_C, $info[$i]['indic_position']); + } + + if ($base < $end) { + $info[$base]['indic_position'] = self::POS_BASE_C; + } + + /* Mark final consonants. A final consonant is one appearing after a matra, + * ? only in Khmer. */ + for ($i = $base + 1; $i < $end; $i++) { + if ($info[$i]['indic_category'] == self::OT_M) { + for ($j = $i + 1; $j < $end; $j++) { + if (self::is_consonant($info[$j])) { + $info[$j]['indic_position'] = self::POS_FINAL_C; + break; + } + } + break; + } + } + + /* Handle beginning Ra */ + if ($scriptblock != Ucdn::SCRIPT_KHMER) { + if ($has_reph) { + $info[$start]['indic_position'] = self::POS_RA_TO_BECOME_REPH; + } + } + + + /* For old-style Indic script tags, move the first post-base Halant after + * last consonant. Only do this if there is *not* a Halant after last + * consonant. Otherwise it becomes messy. */ + if ($is_old_spec) { + for ($i = $base + 1; $i < $end; $i++) { + if ($info[$i]['indic_category'] == self::OT_H) { + for ($j = $end - 1; $j > $i; $j--) { + if (self::is_consonant($info[$j]) || $info[$j]['indic_category'] == self::OT_H) { + break; + } + } + if ($info[$j]['indic_category'] != self::OT_H && $j > $i) { + /* Move Halant to after last consonant. */ + self::_move_info_pos($info, $i, $j + 1); + } + break; + } + } + } + + /* Attach misc marks to previous char to move with them. */ + $last_pos = self::POS_START; + for ($i = $start; $i < $end; $i++) { + if ((self::FLAG($info[$i]['indic_category']) & (self::FLAG(self::OT_ZWJ) | self::FLAG(self::OT_ZWNJ) | self::FLAG(self::OT_N) | self::FLAG(self::OT_RS) | self::FLAG(self::OT_H) | self::FLAG(self::OT_COENG) ))) { + $info[$i]['indic_position'] = $last_pos; + if ($info[$i]['indic_category'] == self::OT_H && $info[$i]['indic_position'] == self::POS_PRE_M) { + /* + * Uniscribe doesn't move the Halant with Left Matra. + * TEST: U+092B,U+093F,U+094DE + * We follow. This is important for the Sinhala + * U+0DDA split matra since it decomposes to U+0DD9,U+0DCA + * where U+0DD9 is a left matra and U+0DCA is the virama. + * We don't want to move the virama with the left matra. + * TEST: U+0D9A,U+0DDA + */ + for ($j = $i; $j > $start; $j--) { + if ($info[$j - 1]['indic_position'] != self::POS_PRE_M) { + $info[$i]['indic_position'] = $info[$j - 1]['indic_position']; + break; + } + } + } + } elseif ($info[$i]['indic_position'] != self::POS_SMVD) { + $last_pos = $info[$i]['indic_position']; + } + } + + /* Re-attach ZWJ, ZWNJ, and halant to next char, for after-base consonants. */ + $last_halant = $end; + for ($i = $base + 1; $i < $end; $i++) { + if (self::is_halant_or_coeng($info[$i])) { + $last_halant = $i; + } elseif (self::is_consonant($info[$i])) { + for ($j = $last_halant; $j < $i; $j++) { + if ($info[$j]['indic_position'] != self::POS_SMVD) { + $info[$j]['indic_position'] = $info[$i]['indic_position']; + } + } + } + } + + + if ($scriptblock == Ucdn::SCRIPT_KHMER) { + /* KHMER_FIX_2 */ + /* Move Coeng+RO (Halant,Ra) sequence before base consonant. */ + for ($i = $base + 1; $i < $end; $i++) { + if (self::is_halant_or_coeng($info[$i]) && self::is_ra($info[$i + 1]['uni'])) { + $info[$i]['indic_position'] = self::POS_PRE_C; + $info[$i + 1]['indic_position'] = self::POS_PRE_C; + break; + } + } + } + + + /* + if (!defined("OMIT_INDIC_FIX_2") || OMIT_INDIC_FIX_2 != 1) { + // INDIC_FIX_2 + $ZWNJ_found = false; + $POST_ZWNJ_c_found = false; + for ($i = $base + 1; $i < $end; $i++) { + if ($info[$i]['indic_category'] == self::OT_ZWNJ) { $ZWNJ_found = true; } + else if ($ZWNJ_found && $info[$i]['indic_category'] == self::OT_C) { $POST_ZWNJ_c_found = true; } + else if ($POST_ZWNJ_c_found && $info[$i]['indic_position'] == self::POS_BEFORE_SUB) { $info[$i]['indic_position'] = self::POS_AFTER_SUB; } + } + } + */ + + /* Setup masks now */ + for ($i = $start; $i < $end; $i++) { + $info[$i]['mask'] = 0; + } + + + if ($scriptblock == Ucdn::SCRIPT_KHMER) { + /* Find a Coeng+RO (Halant,Ra) sequence and mark it for pre-base processing. */ + $mask = self::FLAG(self::PREF); + for ($i = $base; $i < $end - 1; $i++) { /* KHMER_FIX_1 From $start (not base) */ + if (self::is_halant_or_coeng($info[$i]) && self::is_ra($info[$i + 1]['uni'])) { + $info[$i]['mask'] |= self::FLAG(self::PREF); + $info[$i + 1]['mask'] |= self::FLAG(self::PREF); + + /* Mark the subsequent stuff with 'cfar'. Used in Khmer. + * Read the feature spec. + * This allows distinguishing the following cases with MS Khmer fonts: + * U+1784,U+17D2,U+179A,U+17D2,U+1782 [C+Coeng+RO+Coeng+C] => Should activate CFAR + * U+1784,U+17D2,U+1782,U+17D2,U+179A [C+Coeng+C+Coeng+RO] => Should NOT activate CFAR + */ + for ($j = ($i + 2); $j < $end; $j++) { + $info[$j]['mask'] |= self::FLAG(self::CFAR); + } + + break; + } + } + } + + + + /* Sit tight, rock 'n roll! */ + self::bubble_sort($info, $start, $end - $start); + + /* Find base again */ + $base = $end; + for ($i = $start; $i < $end; $i++) { + if ($info[$i]['indic_position'] == self::POS_BASE_C) { + $base = $i; + break; + } + } + + if ($scriptblock != Ucdn::SCRIPT_KHMER) { + /* Reph */ + for ($i = $start; $i < $end; $i++) { + if ($info[$i]['indic_position'] == self::POS_RA_TO_BECOME_REPH) { + $info[$i]['mask'] |= self::FLAG(self::RPHF); + } + } + + /* Pre-base */ + $mask = self::FLAG(self::HALF); + for ($i = $start; $i < $base; $i++) { + $info[$i]['mask'] |= $mask; + } + } + + /* Post-base */ + $mask = (self::FLAG(self::BLWF) | self::FLAG(self::ABVF) | self::FLAG(self::PSTF)); + for ($i = $base + 1; $i < $end; $i++) { + $info[$i]['mask'] |= $mask; + } + + + if ($scriptblock != Ucdn::SCRIPT_KHMER) { + if (!defined("OMIT_INDIC_FIX_3") || OMIT_INDIC_FIX_3 != 1) { + /* INDIC_FIX_3 */ + /* Find a (pre-base) Consonant, Halant,Ra sequence and mark Halant|Ra for below-base BLWF processing. */ + // TEST CASE ক্র্ক in FreeSans versus Vrinda + if (($base - $start) >= 3) { + for ($i = $start; $i < ($base - 2); $i++) { + if (self::is_consonant($info[$i])) { + if (self::is_halant_or_coeng($info[$i + 1]) && self::is_ra($info[$i + 2]['uni'])) { + // If would substitute Halant+Ra...BLWF + if (isset($GSUBdata['blwf'][$info[$i + 2]['uni']])) { + $info[$i + 1]['mask'] |= self::FLAG(self::BLWF); + $info[$i + 2]['mask'] |= self::FLAG(self::BLWF); + } /* If would not substitute as blwf, mark Ra+Halant for RPHF using following Halant (if present) */ elseif (self::is_halant_or_coeng($info[$i + 3])) { + $info[$i + 2]['mask'] |= self::FLAG(self::RPHF); + $info[$i + 3]['mask'] |= self::FLAG(self::RPHF); + } + break; + } + } + } + } + } + } + + + + if ($is_old_spec && $scriptblock == Ucdn::SCRIPT_DEVANAGARI) { + /* Old-spec eye-lash Ra needs special handling. From the spec: + * "The feature 'below-base form' is applied to consonants + * having below-base forms and following the base consonant. + * The exception is vattu, which may appear below half forms + * as well as below the base glyph. The feature 'below-base + * form' will be applied to all such occurrences of Ra as well." + * + * Test case: U+0924,U+094D,U+0930,U+094d,U+0915 + * with Sanskrit 2003 font. + * + * However, note that Ra,Halant,ZWJ is the correct way to + * request eyelash form of Ra, so we wouldbn't inhibit it + * in that sequence. + * + * Test case: U+0924,U+094D,U+0930,U+094d,U+200D,U+0915 + */ + for ($i = $start; ($i + 1) < $base; $i++) { + if ($info[$i]['indic_category'] == self::OT_RA && $info[$i + 1]['indic_category'] == self::OT_H && + ($i + 2 == $base || $info[$i + 2]['indic_category'] != self::OT_ZWJ)) { + $info[$i]['mask'] |= self::FLAG(self::BLWF); + $info[$i + 1]['mask'] |= self::FLAG(self::BLWF); + } + } + } + + if ($scriptblock != Ucdn::SCRIPT_KHMER) { + if (count($GSUBdata['pref']) && $base + 2 < $end) { + /* Find a Halant,Ra sequence and mark it for pre-base processing. */ + for ($i = $base + 1; $i + 1 < $end; $i++) { + // If old_spec find Ra-Halant... + if ((isset($GSUBdata['pref'][$info[$i + 1]['uni']]) && self::is_halant_or_coeng($info[$i]) && self::is_ra($info[$i + 1]['uni']) ) || + ($is_old_spec && isset($GSUBdata['pref'][$info[$i]['uni']]) && self::is_halant_or_coeng($info[$i + 1]) && self::is_ra($info[$i]['uni']) ) + ) { + $info[$i++]['mask'] |= self::FLAG(self::PREF); + $info[$i++]['mask'] |= self::FLAG(self::PREF); + break; + } + } + } + } + + + /* Apply ZWJ/ZWNJ effects */ + for ($i = $start + 1; $i < $end; $i++) { + if (self::is_joiner($info[$i])) { + $non_joiner = ($info[$i]['indic_category'] == self::OT_ZWNJ); + $j = $i; + while ($j > $start) { + if (defined("OMIT_INDIC_FIX_4") && OMIT_INDIC_FIX_4 == 1) { + // INDIC_FIX_4 = do nothing - carry on // + // ZWNJ should block H C from forming blwf post-base - need to unmask backwards beyond first consonant arrived at // + if (!self::is_consonant($info[$j])) { + break; + } + } + $j--; + + /* ZWJ/ZWNJ should disable CJCT. They do that by simply + * being there, since we don't skip them for the CJCT + * feature (ie. F_MANUAL_ZWJ) */ + + /* A ZWNJ disables HALF. */ + if ($non_joiner) { + $info[$j]['mask'] &= ~(self::FLAG(self::HALF) | self::FLAG(self::BLWF)); + } + } + } + } + } + + public static function final_reordering(&$info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec) + { + $count = count($info); + if (!$count) { + return; + } + $last = 0; + $last_syllable = $info[0]['syllable']; + for ($i = 1; $i < $count; $i++) { + if ($last_syllable != $info[$i]['syllable']) { + self::final_reordering_syllable($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $i); + $last = $i; + $last_syllable = $info[$last]['syllable']; + } + } + self::final_reordering_syllable($info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $last, $count); + } + + public static function final_reordering_syllable(&$info, $GSUBdata, $indic_config, $scriptblock, $is_old_spec, $start, $end) + { + + /* 4. Final reordering: + * + * After the localized forms and basic shaping forms GSUB features have been + * applied (see below), the shaping engine performs some final glyph + * reordering before applying all the remaining font features to the entire + * cluster. + */ + + /* Find base again */ + for ($base = $start; $base < $end; $base++) { + if ($info[$base]['indic_position'] >= self::POS_BASE_C) { + if ($start < $base && $info[$base]['indic_position'] > self::POS_BASE_C) { + $base--; + } + break; + } + } + if ($base == $end && $start < $base && $info[$base - 1]['indic_category'] != self::OT_ZWJ) { + $base--; + } + while ($start < $base && isset($info[$base]) && ($info[$base]['indic_category'] == self::OT_H || $info[$base]['indic_category'] == self::OT_N)) { + $base--; + } + + + /* o Reorder matras: + * + * If a pre-base matra character had been reordered before applying basic + * features, the glyph can be moved closer to the main consonant based on + * whether half-forms had been formed. Actual position for the matra is + * defined as "after last standalone halant glyph, after initial matra + * position and before the main consonant". If ZWJ or ZWNJ follow this + * halant, position is moved after it. + */ + + + if ($start + 1 < $end && $start < $base) { /* Otherwise there can't be any pre-base matra characters. */ + /* If we lost track of base, alas, position before last thingy. */ + $new_pos = ($base == $end) ? $base - 2 : $base - 1; + + /* Malayalam / Tamil do not have "half" forms or explicit virama forms. + * The glyphs formed by 'half' are Chillus or ligated explicit viramas. + * We want to position matra after them. + */ + if ($scriptblock != Ucdn::SCRIPT_MALAYALAM && $scriptblock != Ucdn::SCRIPT_TAMIL) { + while ($new_pos > $start && !(self::is_one_of($info[$new_pos], (self::FLAG(self::OT_M) | self::FLAG(self::OT_H) | self::FLAG(self::OT_COENG))))) { + $new_pos--; + } + + /* If we found no Halant we are done. + * Otherwise only proceed if the Halant does + * not belong to the Matra itself! */ + if (self::is_halant_or_coeng($info[$new_pos]) && $info[$new_pos]['indic_position'] != self::POS_PRE_M) { + /* -> If ZWJ or ZWNJ follow this halant, position is moved after it. */ + if ($new_pos + 1 < $end && self::is_joiner($info[$new_pos + 1])) { + $new_pos++; + } + } else { + $new_pos = $start; + } /* No move. */ + } + + if ($start < $new_pos && $info[$new_pos]['indic_position'] != self::POS_PRE_M) { + /* Now go see if there's actually any matras... */ + for ($i = $new_pos; $i > $start; $i--) { + if ($info[$i - 1]['indic_position'] == self::POS_PRE_M) { + $old_pos = $i - 1; + //memmove (&info[$old_pos], &info[$old_pos + 1], ($new_pos - $old_pos) * sizeof ($info[0])); + self::_move_info_pos($info, $old_pos, $new_pos + 1); + + if ($old_pos < $base && $base <= $new_pos) { /* Shouldn't actually happen. */ + $base--; + } + $new_pos--; + } + } + } + } + + + /* o Reorder reph: + * + * Reph's original position is always at the beginning of the syllable, + * (i.e. it is not reordered at the character reordering stage). However, + * it will be reordered according to the basic-forms shaping results. + * Possible positions for reph, depending on the script, are; after main, + * before post-base consonant forms, and after post-base consonant forms. + */ + + /* If there's anything after the Ra that has the REPH pos, it ought to be halant. + * Which means that the font has failed to ligate the Reph. In which case, we + * shouldn't move. */ + if ($start + 1 < $end && + $info[$start]['indic_position'] == self::POS_RA_TO_BECOME_REPH && $info[$start + 1]['indic_position'] != self::POS_RA_TO_BECOME_REPH) { + $reph_pos = $indic_config[3]; + $skip_to_reph_step_5 = false; + $skip_to_reph_move = false; + + /* 1. If reph should be positioned after post-base consonant forms, + * proceed to step 5. + */ + if ($reph_pos == self::REPH_POS_AFTER_POST) { + $skip_to_reph_step_5 = true; + } + + /* 2. If the reph repositioning class is not after post-base: target + * position is after the first explicit halant glyph between the + * first post-reph consonant and last main consonant. If ZWJ or ZWNJ + * are following this halant, position is moved after it. If such + * position is found, this is the target position. Otherwise, + * proceed to the next step. + * + * Note: in old-implementation fonts, where classifications were + * fixed in shaping engine, there was no case where reph position + * will be found on this step. + */ + + if (!$skip_to_reph_step_5) { + $new_reph_pos = $start + 1; + + while ($new_reph_pos < $base && !self::is_halant_or_coeng($info[$new_reph_pos])) { + $new_reph_pos++; + } + + if ($new_reph_pos < $base && self::is_halant_or_coeng($info[$new_reph_pos])) { + /* ->If ZWJ or ZWNJ are following this halant, position is moved after it. */ + if ($new_reph_pos + 1 < $base && self::is_joiner($info[$new_reph_pos + 1])) { + $new_reph_pos++; + } + $skip_to_reph_move = true; + } + } + + /* 3. If reph should be repositioned after the main consonant: find the + * first consonant not ligated with main, or find the first + * consonant that is not a potential pre-base reordering Ra. + */ + if ($reph_pos == self::REPH_POS_AFTER_MAIN && !$skip_to_reph_move && !$skip_to_reph_step_5) { + $new_reph_pos = $base; + /* XXX Skip potential pre-base reordering Ra. */ + while ($new_reph_pos + 1 < $end && $info[$new_reph_pos + 1]['indic_position'] <= self::POS_AFTER_MAIN) { + $new_reph_pos++; + } + if ($new_reph_pos < $end) { + $skip_to_reph_move = true; + } + } + + /* 4. If reph should be positioned before post-base consonant, find + * first post-base classified consonant not ligated with main. If no + * consonant is found, the target position should be before the + * first matra, syllable modifier sign or vedic sign. + */ + /* This is our take on what step 4 is trying to say (and failing, BADLY). */ + if ($reph_pos == self::REPH_POS_AFTER_SUB && !$skip_to_reph_move && !$skip_to_reph_step_5) { + $new_reph_pos = $base; + while ($new_reph_pos < $end && isset($info[$new_reph_pos + 1]['indic_position']) && + !( self::FLAG($info[$new_reph_pos + 1]['indic_position']) & (self::FLAG(self::POS_POST_C) | self::FLAG(self::POS_AFTER_POST) | self::FLAG(self::POS_SMVD)))) { + $new_reph_pos++; + } + if ($new_reph_pos < $end) { + $skip_to_reph_move = true; + } + } + + /* 5. If no consonant is found in steps 3 or 4, move reph to a position + * immediately before the first post-base matra, syllable modifier + * sign or vedic sign that has a reordering class after the intended + * reph position. For example, if the reordering position for reph + * is post-main, it will skip above-base matras that also have a + * post-main position. + */ + if (!$skip_to_reph_move) { + /* Copied from step 2. */ + $new_reph_pos = $start + 1; + while ($new_reph_pos < $base && !self::is_halant_or_coeng($info[$new_reph_pos])) { + $new_reph_pos++; + } + + if ($new_reph_pos < $base && self::is_halant_or_coeng($info[$new_reph_pos])) { + /* ->If ZWJ or ZWNJ are following this halant, position is moved after it. */ + if ($new_reph_pos + 1 < $base && self::is_joiner($info[$new_reph_pos + 1])) { + $new_reph_pos++; + } + $skip_to_reph_move = true; + } + } + + + /* 6. Otherwise, reorder reph to the end of the syllable. + */ + if (!$skip_to_reph_move) { + $new_reph_pos = $end - 1; + while ($new_reph_pos > $start && $info[$new_reph_pos]['indic_position'] == self::POS_SMVD) { + $new_reph_pos--; + } + + /* + * If the Reph is to be ending up after a Matra,Halant sequence, + * position it before that Halant so it can interact with the Matra. + * However, if it's a plain Consonant,Halant we shouldn't do that. + * Uniscribe doesn't do this. + * TEST: U+0930,U+094D,U+0915,U+094B,U+094D + */ + //if (!$hb_options.uniscribe_bug_compatible && self::is_halant_or_coeng($info[$new_reph_pos])) { + if (self::is_halant_or_coeng($info[$new_reph_pos])) { + for ($i = $base + 1; $i < $new_reph_pos; $i++) { + if ($info[$i]['indic_category'] == self::OT_M) { + /* Ok, got it. */ + $new_reph_pos--; + } + } + } + } + + + /* Move */ + self::_move_info_pos($info, $start, $new_reph_pos + 1); + + if ($start < $base && $base <= $new_reph_pos) { + $base--; + } + } + + + /* o Reorder pre-base reordering consonants: + * + * If a pre-base reordering consonant is found, reorder it according to + * the following rules: + */ + + + if (count($GSUBdata['pref']) && $base + 1 < $end) { /* Otherwise there can't be any pre-base reordering Ra. */ + for ($i = $base + 1; $i < $end; $i++) { + if ($info[$i]['mask'] & self::FLAG(self::PREF)) { + /* 1. Only reorder a glyph produced by substitution during application + * of the <pref> feature. (Note that a font may shape a Ra consonant with + * the feature generally but block it in certain contexts.) + */ + // ??? Need to TEST if actual substitution has occurred + if ($i + 1 == $end || ($info[$i + 1]['mask'] & self::FLAG(self::PREF)) == 0) { + /* + * 2. Try to find a target position the same way as for pre-base matra. + * If it is found, reorder pre-base consonant glyph. + * + * 3. If position is not found, reorder immediately before main + * consonant. + */ + $new_pos = $base; + /* Malayalam / Tamil do not have "half" forms or explicit virama forms. + * The glyphs formed by 'half' are Chillus or ligated explicit viramas. + * We want to position matra after them. + */ + if ($scriptblock != Ucdn::SCRIPT_MALAYALAM && $scriptblock != Ucdn::SCRIPT_TAMIL) { + while ($new_pos > $start && + !(self::is_one_of($info[$new_pos - 1], self::FLAG(self::OT_M) | self::FLAG(self::OT_H) | self::FLAG(self::OT_COENG)))) { + $new_pos--; + } + + /* In Khmer coeng model, a V,Ra can go *after* matras. If it goes after a + * split matra, it should be reordered to *before* the left part of such matra. */ + if ($new_pos > $start && $info[$new_pos - 1]['indic_category'] == self::OT_M) { + $old_pos = $i; + for ($i = $base + 1; $i < $old_pos; $i++) { + if ($info[$i]['indic_category'] == self::OT_M) { + $new_pos--; + break; + } + } + } + } + + if ($new_pos > $start && self::is_halant_or_coeng($info[$new_pos - 1])) { + /* -> If ZWJ or ZWNJ follow this halant, position is moved after it. */ + if ($new_pos < $end && self::is_joiner($info[$new_pos])) { + $new_pos++; + } + } + + $old_pos = $i; + self::_move_info_pos($info, $old_pos, $new_pos); + + if ($new_pos <= $base && $base < $old_pos) { + $base++; + } + } + + break; + } + } + } + + + /* Apply 'init' to the Left Matra if it's a word start. */ + if ($info[$start]['indic_position'] == self::POS_PRE_M && + ($start == 0 || + ($info[$start - 1]['general_category'] < Ucdn::UNICODE_GENERAL_CATEGORY_FORMAT || $info[$start - 1]['general_category'] > Ucdn::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) + )) { + $info[$start]['mask'] |= self::FLAG(self::INIT); + } + + + /* + * Finish off and go home! + */ + } + + public static function _move_info_pos(&$info, $from, $to) + { + $t = []; + $t[0] = $info[$from]; + if ($from > $to) { + array_splice($info, $from, 1); + array_splice($info, $to, 0, $t); + } else { + array_splice($info, $to, 0, $t); + array_splice($info, $from, 1); + } + } + + public static $ra_chars = [ + 0x0930 => 1, /* Devanagari */ + 0x09B0 => 1, /* Bengali */ + 0x09F0 => 1, /* Bengali (Assamese) */ + 0x0A30 => 1, /* Gurmukhi */ /* No Reph */ + 0x0AB0 => 1, /* Gujarati */ + 0x0B30 => 1, /* Oriya */ + 0x0BB0 => 1, /* Tamil */ /* No Reph */ + 0x0C30 => 1, /* Telugu */ /* Reph formed only with ZWJ */ + 0x0CB0 => 1, /* Kannada */ + 0x0D30 => 1, /* Malayalam */ /* No Reph, Logical Repha */ + 0x0DBB => 1, /* Sinhala */ /* Reph formed only with ZWJ */ + 0x179A => 1, /* Khmer */ /* No Reph, Visual Repha */ + ]; + + public static function is_ra($u) + { + return isset(self::$ra_chars[$u]); + } + + public static function is_one_of($info, $flags) + { + if (isset($info['is_ligature']) && $info['is_ligature']) { + return false; + } /* If it ligated, all bets are off. */ + return !!(self::FLAG($info['indic_category']) & $flags); + } + + public static function is_joiner($info) + { + return self::is_one_of($info, (self::FLAG(self::OT_ZWJ) | self::FLAG(self::OT_ZWNJ))); + } + + /* Vowels and placeholders treated as if they were consonants. */ + + public static function is_consonant($info) + { + return self::is_one_of($info, (self::FLAG(self::OT_C) | self::FLAG(self::OT_CM) | self::FLAG(self::OT_RA) | self::FLAG(self::OT_V) | self::FLAG(self::OT_NBSP) | self::FLAG(self::OT_DOTTEDCIRCLE))); + } + + public static function is_halant_or_coeng($info) + { + return self::is_one_of($info, (self::FLAG(self::OT_H) | self::FLAG(self::OT_COENG))); + } + + // From hb-private.hh + public static function in_range($u, $lo, $hi) + { + if ((($lo ^ $hi) & $lo) == 0 && (($lo ^ $hi) & $hi) == ($lo ^ $hi) && (($lo ^ $hi) & (($lo ^ $hi) + 1)) == 0) { + return ($u & ~($lo ^ $hi)) == $lo; + } else { + return $lo <= $u && $u <= $hi; + } + } + + // From hb-private.hh + public static function FLAG($x) + { + return (1 << ($x)); + } + + // BELOW from hb-ot-shape-complex-indic.cc + + /* + * Indic configurations. + */ + + // base_position + const BASE_POS_FIRST = 0; + const BASE_POS_LAST = 1; + + // reph_position + const REPH_POS_DEFAULT = 10; // POS_BEFORE_POST, + + const REPH_POS_AFTER_MAIN = 5; // POS_AFTER_MAIN, + + const REPH_POS_BEFORE_SUB = 7; // POS_BEFORE_SUB, + const REPH_POS_AFTER_SUB = 9; // POS_AFTER_SUB, + const REPH_POS_BEFORE_POST = 10; // POS_BEFORE_POST, + const REPH_POS_AFTER_POST = 12; // POS_AFTER_POST + + // reph_mode + const REPH_MODE_IMPLICIT = 0; /* Reph formed out of initial Ra,H sequence. */ + const REPH_MODE_EXPLICIT = 1; /* Reph formed out of initial Ra,H,ZWJ sequence. */ + const REPH_MODE_VIS_REPHA = 2; /* Encoded Repha character, no reordering needed. */ + const REPH_MODE_LOG_REPHA = 3; /* Encoded Repha character, needs reordering. */ + + /* + struct of indic_configs{ + KEY - script; + 0 - has_old_spec; + 1 - virama; + 2 - base_pos; + 3 - reph_pos; + 4 - reph_mode; + }; + */ + + public static $indic_configs = [/* index is SCRIPT_number from UCDN */ + 9 => [true, 0x094D, 1, 10, 0], + 10 => [true, 0x09CD, 1, 9, 0], + 11 => [true, 0x0A4D, 1, 7, 0], + 12 => [true, 0x0ACD, 1, 10, 0], + 13 => [true, 0x0B4D, 1, 5, 0], + 14 => [true, 0x0BCD, 1, 12, 0], + 15 => [true, 0x0C4D, 1, 12, 1], + 16 => [true, 0x0CCD, 1, 12, 0], + 17 => [true, 0x0D4D, 1, 5, 3], + 18 => [false, 0x0DCA, 0, 5, 1], /* Sinhala */ + 30 => [false, 0x17D2, 0, 10, 2], /* Khmer */ + 84 => [false, 0xA9C0, 1, 10, 0], /* Javanese */ + ]; + + + + /* + + // from "hb-ot-shape-complex-indic-table.cc" + + + const ISC_A = 0; // INDIC_SYLLABIC_CATEGORY_AVAGRAHA Avagraha + const ISC_Bi = 8; // INDIC_SYLLABIC_CATEGORY_BINDU Bindu + const ISC_C = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT Consonant + const ISC_CD = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT_DEAD Consonant_Dead + const ISC_CF = 17; // INDIC_SYLLABIC_CATEGORY_CONSONANT_FINAL Consonant_Final + const ISC_CHL = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT_HEAD_LETTER Consonant_Head_Letter + const ISC_CM = 17; // INDIC_SYLLABIC_CATEGORY_CONSONANT_MEDIAL Consonant_Medial + const ISC_CP = 11; // INDIC_SYLLABIC_CATEGORY_CONSONANT_PLACEHOLDER Consonant_Placeholder + const ISC_CR = 15; // INDIC_SYLLABIC_CATEGORY_CONSONANT_REPHA Consonant_Repha + const ISC_CS = 1; // INDIC_SYLLABIC_CATEGORY_CONSONANT_SUBJOINED Consonant_Subjoined + const ISC_ML = 0; // INDIC_SYLLABIC_CATEGORY_MODIFYING_LETTER Modifying_Letter + const ISC_N = 3; // INDIC_SYLLABIC_CATEGORY_NUKTA Nukta + const ISC_x = 0; // INDIC_SYLLABIC_CATEGORY_OTHER Other + const ISC_RS = 13; // INDIC_SYLLABIC_CATEGORY_REGISTER_SHIFTER Register_Shifter + const ISC_TL = 0; // INDIC_SYLLABIC_CATEGORY_TONE_LETTER Tone_Letter + const ISC_TM = 3; // INDIC_SYLLABIC_CATEGORY_TONE_MARK Tone_Mark + const ISC_V = 4; // INDIC_SYLLABIC_CATEGORY_VIRAMA Virama + const ISC_Vs = 8; // INDIC_SYLLABIC_CATEGORY_VISARGA Visarga + const ISC_Vo = 2; // INDIC_SYLLABIC_CATEGORY_VOWEL Vowel + const ISC_M = 7; // INDIC_SYLLABIC_CATEGORY_VOWEL_DEPENDENT Vowel_Dependent + const ISC_VI = 2; // INDIC_SYLLABIC_CATEGORY_VOWEL_INDEPENDENT Vowel_Independent + + const IMC_B = 8; // INDIC_MATRA_CATEGORY_BOTTOM Bottom + const IMC_BR = 11; // INDIC_MATRA_CATEGORY_BOTTOM_AND_RIGHT Bottom_And_Right + const IMC_I = 15; // INDIC_MATRA_CATEGORY_INVISIBLE Invisible + const IMC_L = 3; // INDIC_MATRA_CATEGORY_LEFT Left + const IMC_LR = 11; // INDIC_MATRA_CATEGORY_LEFT_AND_RIGHT Left_And_Right + const IMC_x = 15; // INDIC_MATRA_CATEGORY_NOT_APPLICABLE Not_Applicable + const IMC_O = 5; // INDIC_MATRA_CATEGORY_OVERSTRUCK Overstruck + const IMC_R = 11; // INDIC_MATRA_CATEGORY_RIGHT Right + const IMC_T = 6; // INDIC_MATRA_CATEGORY_TOP Top + const IMC_TB = 8; // INDIC_MATRA_CATEGORY_TOP_AND_BOTTOM Top_And_Bottom + const IMC_TBR = 11; // INDIC_MATRA_CATEGORY_TOP_AND_BOTTOM_AND_RIGHT Top_And_Bottom_And_Right + const IMC_TL = 6; // INDIC_MATRA_CATEGORY_TOP_AND_LEFT Top_And_Left + const IMC_TLR = 11; // INDIC_MATRA_CATEGORY_TOP_AND_LEFT_AND_RIGHT Top_And_Left_And_Right + const IMC_TR = 11; // INDIC_MATRA_CATEGORY_TOP_AND_RIGHT Top_And_Right + const IMC_VOL = 2; // INDIC_MATRA_CATEGORY_VISUAL_ORDER_LEFT Visual_Order_Left + + If in original table = _(C,x), that = ISC_C,IMC_x + Value is IMC_x << 8 (or IMC_x * 256) = 3840 + plus ISC_C = 1, so = 3841 + + */ + + public static $indic_table = [ + /* Devanagari (0900..097F) */ + + /* 0900 */ 3848, 3848, 3848, 3848, 3842, 3842, 3842, 3842, + /* 0908 */ 3842, 3842, 3842, 3842, 3842, 3842, 3842, 3842, + /* 0910 */ 3842, 3842, 3842, 3842, 3842, 3841, 3841, 3841, + /* 0918 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0920 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0928 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0930 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0938 */ 3841, 3841, 1543, 2823, 3843, 3840, 2823, 775, + /* 0940 */ 2823, 2055, 2055, 2055, 2055, 1543, 1543, 1543, + /* 0948 */ 1543, 2823, 2823, 2823, 2823, 2052, 775, 2823, + /* 0950 */ 3840, 3840, 3840, 3840, 3840, 1543, 2055, 2055, + /* 0958 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0960 */ 3842, 3842, 2055, 2055, 3840, 3840, 3840, 3840, + /* 0968 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0970 */ 3840, 3840, 3842, 3842, 3842, 3842, 3842, 3842, + /* 0978 */ 3840, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* Bengali (0980..09FF) */ + + /* 0980 */ 3840, 3848, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0988 */ 3842, 3842, 3842, 3842, 3842, 3840, 3840, 3842, + /* 0990 */ 3842, 3840, 3840, 3842, 3842, 3841, 3841, 3841, + /* 0998 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 09A0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 09A8 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* 09B0 */ 3841, 3840, 3841, 3840, 3840, 3840, 3841, 3841, + /* 09B8 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 775, + /* 09C0 */ 2823, 2055, 2055, 2055, 2055, 3840, 3840, 775, + /* 09C8 */ 775, 3840, 3840, 2823, 2823, 2052, 3841, 3840, + /* 09D0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 2823, + /* 09D8 */ 3840, 3840, 3840, 3840, 3841, 3841, 3840, 3841, + /* 09E0 */ 3842, 3842, 2055, 2055, 3840, 3840, 3840, 3840, + /* 09E8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 09F0 */ 3841, 3841, 3840, 3840, 3840, 3840, 3840, 3840, + /* 09F8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Gurmukhi (0A00..0A7F) */ + + /* 0A00 */ 3840, 3848, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0A08 */ 3842, 3842, 3842, 3840, 3840, 3840, 3840, 3842, + /* 0A10 */ 3842, 3840, 3840, 3842, 3842, 3841, 3841, 3841, + /* 0A18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0A20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0A28 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0A30 */ 3841, 3840, 3841, 3841, 3840, 3841, 3841, 3840, + /* 0A38 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 775, + /* 0A40 */ 2823, 2055, 2055, 3840, 3840, 3840, 3840, 1543, + /* 0A48 */ 1543, 3840, 3840, 1543, 1543, 2052, 3840, 3840, + /* 0A50 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0A58 */ 3840, 3841, 3841, 3841, 3841, 3840, 3841, 3840, + /* 0A60 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0A68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0A70 */ 3848, 3840, 13841, 13841, 3840, 3857, 3840, 3840, + /* 0A78 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Gujarati (0A80..0AFF) */ + + /* 0A80 */ 3840, 3848, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0A88 */ 3842, 3842, 3842, 3842, 3842, 3842, 3840, 3842, + /* 0A90 */ 3842, 3842, 3840, 3842, 3842, 3841, 3841, 3841, + /* 0A98 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0AA0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0AA8 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0AB0 */ 3841, 3840, 3841, 3841, 3840, 3841, 3841, 3841, + /* 0AB8 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 775, + /* 0AC0 */ 2823, 2055, 2055, 2055, 2055, 1543, 3840, 1543, + /* 0AC8 */ 1543, 2823, 3840, 2823, 2823, 2052, 3840, 3840, + /* 0AD0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0AD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0AE0 */ 3842, 3842, 2055, 2055, 3840, 3840, 3840, 3840, + /* 0AE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0AF0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0AF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Oriya (0B00..0B7F) */ + + /* 0B00 */ 3840, 3848, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0B08 */ 3842, 3842, 3842, 3842, 3842, 3840, 3840, 3842, + /* 0B10 */ 3842, 3840, 3840, 3842, 3842, 3841, 3841, 3841, + /* 0B18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0B20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0B28 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0B30 */ 3841, 3840, 3841, 3841, 3840, 3841, 3841, 3841, + /* 0B38 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 1543, + /* 0B40 */ 2823, 2055, 2055, 2055, 2055, 3840, 3840, 775, + /* 0B48 */ 1543, 3840, 3840, 2823, 2823, 2052, 3840, 3840, + /* 0B50 */ 3840, 3840, 3840, 3840, 3840, 3840, 1543, 2823, + /* 0B58 */ 3840, 3840, 3840, 3840, 3841, 3841, 3840, 3841, + /* 0B60 */ 3842, 3842, 2055, 2055, 3840, 3840, 3840, 3840, + /* 0B68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0B70 */ 3840, 3841, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0B78 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Tamil (0B80..0BFF) */ + + /* 0B80 */ 3840, 3840, 3848, 3840, 3840, 3842, 3842, 3842, + /* 0B88 */ 3842, 3842, 3842, 3840, 3840, 3840, 3842, 3842, + /* 0B90 */ 3842, 3840, 3842, 3842, 3842, 3841, 3840, 3840, + /* 0B98 */ 3840, 3841, 3841, 3840, 3841, 3840, 3841, 3841, + /* 0BA0 */ 3840, 3840, 3840, 3841, 3841, 3840, 3840, 3840, + /* 0BA8 */ 3841, 3841, 3841, 3840, 3840, 3840, 3841, 3841, + /* 0BB0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0BB8 */ 3841, 3841, 3840, 3840, 3840, 3840, 2823, 2823, + /* 0BC0 */ 1543, 2055, 2055, 3840, 3840, 3840, 775, 775, + /* 0BC8 */ 775, 3840, 2823, 2823, 2823, 1540, 3840, 3840, + /* 0BD0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 2823, + /* 0BD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0BE0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0BE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0BF0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0BF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Telugu (0C00..0C7F) */ + + /* 0C00 */ 3840, 3848, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0C08 */ 3842, 3842, 3842, 3842, 3842, 3840, 3842, 3842, + /* 0C10 */ 3842, 3840, 3842, 3842, 3842, 3841, 3841, 3841, + /* 0C18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0C20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0C28 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0C30 */ 3841, 3841, 3841, 3841, 3840, 3841, 3841, 3841, + /* 0C38 */ 3841, 3841, 3840, 3840, 3840, 3840, 1543, 1543, + /* 0C40 */ 1543, 2823, 2823, 2823, 2823, 3840, 1543, 1543, + /* 0C48 */ 2055, 3840, 1543, 1543, 1543, 1540, 3840, 3840, + /* 0C50 */ 3840, 3840, 3840, 3840, 3840, 1543, 2055, 3840, + /* 0C58 */ 3841, 3841, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0C60 */ 3842, 3842, 2055, 2055, 3840, 3840, 3840, 3840, + /* 0C68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0C70 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0C78 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Kannada (0C80..0CFF) */ + + /* 0C80 */ 3840, 3840, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0C88 */ 3842, 3842, 3842, 3842, 3842, 3840, 3842, 3842, + /* 0C90 */ 3842, 3840, 3842, 3842, 3842, 3841, 3841, 3841, + /* 0C98 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0CA0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0CA8 */ 3841, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0CB0 */ 3841, 3841, 3841, 3841, 3840, 3841, 3841, 3841, + /* 0CB8 */ 3841, 3841, 3840, 3840, 3843, 3840, 2823, 1543, + /* 0CC0 */ 2823, 2823, 2823, 2823, 2823, 3840, 1543, 2823, + /* 0CC8 */ 2823, 3840, 2823, 2823, 1543, 1540, 3840, 3840, + /* 0CD0 */ 3840, 3840, 3840, 3840, 3840, 2823, 2823, 3840, + /* 0CD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3841, 3840, + /* 0CE0 */ 3842, 3842, 2055, 2055, 3840, 3840, 3840, 3840, + /* 0CE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0CF0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0CF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Malayalam (0D00..0D7F) */ + + /* 0D00 */ 3840, 3840, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0D08 */ 3842, 3842, 3842, 3842, 3842, 3840, 3842, 3842, + /* 0D10 */ 3842, 3840, 3842, 3842, 3842, 3841, 3841, 3841, + /* 0D18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0D20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0D28 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0D30 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0D38 */ 3841, 3841, 3841, 3840, 3840, 3840, 2823, 2823, + /* 0D40 */ 2823, 2823, 2823, 2055, 2055, 3840, 775, 775, + /* 0D48 */ 775, 3840, 2823, 2823, 2823, 1540, 3855, 3840, + /* 0D50 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 2823, + /* 0D58 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0D60 */ 3842, 3842, 2055, 2055, 3840, 3840, 3840, 3840, + /* 0D68 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0D70 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0D78 */ 3840, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* Sinhala (0D80..0DFF) */ + + /* 0D80 */ 3840, 3840, 3848, 3848, 3840, 3842, 3842, 3842, + /* 0D88 */ 3842, 3842, 3842, 3842, 3842, 3842, 3842, 3842, + /* 0D90 */ 3842, 3842, 3842, 3842, 3842, 3842, 3842, 3840, + /* 0D98 */ 3840, 3840, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0DA0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0DA8 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 0DB0 */ 3841, 3841, 3840, 3841, 3841, 3841, 3841, 3841, + /* 0DB8 */ 3841, 3841, 3841, 3841, 3840, 3841, 3840, 3840, + /* 0DC0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3840, + /* 0DC8 */ 3840, 3840, 1540, 3840, 3840, 3840, 3840, 2823, + /* 0DD0 */ 2823, 2823, 1543, 1543, 2055, 3840, 2055, 3840, + /* 0DD8 */ 2823, 775, 1543, 775, 2823, 2823, 2823, 2823, + /* 0DE0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0DE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 0DF0 */ 3840, 3840, 2823, 2823, 3840, 3840, 3840, 3840, + /* 0DF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Vedic Extensions (1CD0..1CFF) */ + + /* 1CD0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1CD8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1CE0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1CE8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1CF0 */ 3840, 3840, 3848, 3848, 3840, 3840, 3840, 3840, + /* 1CF8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + ]; + + public static $khmer_table = [ + /* Khmer (1780..17FF) */ + + /* 1780 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1788 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1790 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1798 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 17A0 */ 3841, 3841, 3841, 3842, 3842, 3842, 3842, 3842, + /* 17A8 */ 3842, 3842, 3842, 3842, 3842, 3842, 3842, 3842, + /* 17B0 */ 3842, 3842, 3842, 3842, 3840, 3840, 2823, 1543, + /* 17B8 */ 1543, 1543, 1543, 2055, 2055, 2055, 1543, 2823, + /* 17C0 */ 2823, 775, 775, 775, 2823, 2823, 3848, 3848, + /* 17C8 */ 2823, 3853, 3853, 3840, 3855, 3840, 3840, 3840, + /* 17D0 */ 3840, 1540, 3844, 3840, 3840, 3840, 3840, 3840, + /* 17D8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 17E0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 17E8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 17F0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 17F8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + ]; + + // from "hb-ot-shape-complex-indic-table.cc" + public static function indic_get_categories($u) + { + if (0x0900 <= $u && $u <= 0x0DFF) { + return self::$indic_table[$u - 0x0900 + 0]; // offset 0 for Most "indic" + } + if (0x1CD0 <= $u && $u <= 0x1D00) { + return self::$indic_table[$u - 0x1CD0 + 1152]; // offset for Vedic extensions + } + if (0x1780 <= $u && $u <= 0x17FF) { + return self::$khmer_table[$u - 0x1780]; // Khmer + } + if ($u == 0x00A0) { + return 3851; // (ISC_CP | (IMC_x << 8)) + } + if ($u == 0x25CC) { + return 3851; // (ISC_CP | (IMC_x << 8)) + } + return 3840; // (ISC_x | (IMC_x << 8)) + } + + // BELOW from hb-ot-shape-complex-indic.cc + /* + * Indic shaper. + */ + + public static function IN_HALF_BLOCK($u, $Base) + { + return (($u & ~0x7F) == $Base); + } + + public static function IS_DEVA($u) + { + return self::IN_HALF_BLOCK($u, 0x0900); + } + + public static function IS_BENG($u) + { + return self::IN_HALF_BLOCK($u, 0x0980); + } + + public static function IS_GURU($u) + { + return self::IN_HALF_BLOCK($u, 0x0A00); + } + + public static function IS_GUJR($u) + { + return self::IN_HALF_BLOCK($u, 0x0A80); + } + + public static function IS_ORYA($u) + { + return self::IN_HALF_BLOCK($u, 0x0B00); + } + + public static function IS_TAML($u) + { + return self::IN_HALF_BLOCK($u, 0x0B80); + } + + public static function IS_TELU($u) + { + return self::IN_HALF_BLOCK($u, 0x0C00); + } + + public static function IS_KNDA($u) + { + return self::IN_HALF_BLOCK($u, 0x0C80); + } + + public static function IS_MLYM($u) + { + return self::IN_HALF_BLOCK($u, 0x0D00); + } + + public static function IS_SINH($u) + { + return self::IN_HALF_BLOCK($u, 0x0D80); + } + + public static function IS_KHMR($u) + { + return self::IN_HALF_BLOCK($u, 0x1780); + } + + public static function MATRA_POS_LEFT($u) + { + return self::POS_PRE_M; + } + + public static function MATRA_POS_RIGHT($u) + { + return + (self::IS_DEVA($u) ? self::POS_AFTER_SUB : + (self::IS_BENG($u) ? self::POS_AFTER_POST : + (self::IS_GURU($u) ? self::POS_AFTER_POST : + (self::IS_GUJR($u) ? self::POS_AFTER_POST : + (self::IS_ORYA($u) ? self::POS_AFTER_POST : + (self::IS_TAML($u) ? self::POS_AFTER_POST : + (self::IS_TELU($u) ? ($u <= 0x0C42 ? self::POS_BEFORE_SUB : self::POS_AFTER_SUB) : + (self::IS_KNDA($u) ? ($u < 0x0CC3 || $u > 0xCD6 ? self::POS_BEFORE_SUB : self::POS_AFTER_SUB) : + (self::IS_MLYM($u) ? self::POS_AFTER_POST : + (self::IS_SINH($u) ? self::POS_AFTER_SUB : + (self::IS_KHMR($u) ? self::POS_AFTER_POST : + self::POS_AFTER_SUB))))))))))); /* default */ + } + + public static function MATRA_POS_TOP($u) + { + return /* BENG and MLYM don't have top matras. */ + (self::IS_DEVA($u) ? self::POS_AFTER_SUB : + (self::IS_GURU($u) ? self::POS_AFTER_POST : /* Deviate from spec */ + (self::IS_GUJR($u) ? self::POS_AFTER_SUB : + (self::IS_ORYA($u) ? self::POS_AFTER_MAIN : + (self::IS_TAML($u) ? self::POS_AFTER_SUB : + (self::IS_TELU($u) ? self::POS_BEFORE_SUB : + (self::IS_KNDA($u) ? self::POS_BEFORE_SUB : + (self::IS_SINH($u) ? self::POS_AFTER_SUB : + (self::IS_KHMR($u) ? self::POS_AFTER_POST : + self::POS_AFTER_SUB))))))))); /* default */ + } + + public static function MATRA_POS_BOTTOM($u) + { + return + (self::IS_DEVA($u) ? self::POS_AFTER_SUB : + (self::IS_BENG($u) ? self::POS_AFTER_SUB : + (self::IS_GURU($u) ? self::POS_AFTER_POST : + (self::IS_GUJR($u) ? self::POS_AFTER_POST : + (self::IS_ORYA($u) ? self::POS_AFTER_SUB : + (self::IS_TAML($u) ? self::POS_AFTER_POST : + (self::IS_TELU($u) ? self::POS_BEFORE_SUB : + (self::IS_KNDA($u) ? self::POS_BEFORE_SUB : + (self::IS_MLYM($u) ? self::POS_AFTER_POST : + (self::IS_SINH($u) ? self::POS_AFTER_SUB : + (self::IS_KHMR($u) ? self::POS_AFTER_POST : + self::POS_AFTER_SUB))))))))))); /* default */ + } + + public static function matra_position($u, $side) + { + switch ($side) { + case self::POS_PRE_C: + return self::MATRA_POS_LEFT($u); + case self::POS_POST_C: + return self::MATRA_POS_RIGHT($u); + case self::POS_ABOVE_C: + return self::MATRA_POS_TOP($u); + case self::POS_BELOW_C: + return self::MATRA_POS_BOTTOM($u); + } + return $side; + } + + // vowel matras that have to be split into two parts. + // From Harfbuzz (old) + // New HarfBuzz uses /src/hb-ucdn/ucdn.c and unicodedata_db.h for full method of decomposition for all characters + // Should always fully decompose and then recompose back, but we will just do the split matras + public static function decompose_indic($ab) + { + $sub = []; + switch ($ab) { + /* + * Decompose split matras. + */ + /* bengali */ + case 0x9cb: + $sub[0] = 0x9c7; + $sub[1] = 0x9be; + return $sub; + case 0x9cc: + $sub[0] = 0x9c7; + $sub[1] = 0x9d7; + return $sub; + /* oriya */ + case 0xb48: + $sub[0] = 0xb47; + $sub[1] = 0xb56; + return $sub; + case 0xb4b: + $sub[0] = 0xb47; + $sub[1] = 0xb3e; + return $sub; + case 0xb4c: + $sub[0] = 0xb47; + $sub[1] = 0xb57; + return $sub; + /* tamil */ + case 0xbca: + $sub[0] = 0xbc6; + $sub[1] = 0xbbe; + return $sub; + case 0xbcb: + $sub[0] = 0xbc7; + $sub[1] = 0xbbe; + return $sub; + case 0xbcc: + $sub[0] = 0xbc6; + $sub[1] = 0xbd7; + return $sub; + /* telugu */ + case 0xc48: + $sub[0] = 0xc46; + $sub[1] = 0xc56; + return $sub; + /* kannada */ + case 0xcc0: + $sub[0] = 0xcbf; + $sub[1] = 0xcd5; + return $sub; + case 0xcc7: + $sub[0] = 0xcc6; + $sub[1] = 0xcd5; + return $sub; + case 0xcc8: + $sub[0] = 0xcc6; + $sub[1] = 0xcd6; + return $sub; + case 0xcca: + $sub[0] = 0xcc6; + $sub[1] = 0xcc2; + return $sub; + case 0xccb: + $sub[0] = 0xcc6; + $sub[1] = 0xcc2; + $sub[2] = 0xcd5; + return $sub; + /* malayalam */ + case 0xd4a: + $sub[0] = 0xd46; + $sub[1] = 0xd3e; + return $sub; + case 0xd4b: + $sub[0] = 0xd47; + $sub[1] = 0xd3e; + return $sub; + case 0xd4c: + $sub[0] = 0xd46; + $sub[1] = 0xd57; + return $sub; + /* sinhala */ + // NB Some fonts break with these Sinhala decomps (although this is Uniscribe spec) + // Can check if character would be substituted by pstf and only decompose if true + // e.g. if (isset($GSUBdata['pstf'][$ab])) - would need to pass $GSUBdata as parameter to this function + case 0xdda: + $sub[0] = 0xdd9; + $sub[1] = 0xdca; + return $sub; + case 0xddc: + $sub[0] = 0xdd9; + $sub[1] = 0xdcf; + return $sub; + case 0xddd: + $sub[0] = 0xdd9; + $sub[1] = 0xdcf; + $sub[2] = 0xdca; + return $sub; + case 0xdde: + $sub[0] = 0xdd9; + $sub[1] = 0xddf; + return $sub; + /* khmer */ + case 0x17be: + $sub[0] = 0x17c1; + $sub[1] = 0x17be; + return $sub; + case 0x17bf: + $sub[0] = 0x17c1; + $sub[1] = 0x17bf; + return $sub; + case 0x17c0: + $sub[0] = 0x17c1; + $sub[1] = 0x17c0; + return $sub; + + case 0x17c4: + $sub[0] = 0x17c1; + $sub[1] = 0x17c4; + return $sub; + case 0x17c5: + $sub[0] = 0x17c1; + $sub[1] = 0x17c5; + return $sub; + /* tibetan - included here although does not use Inidc shaper in other ways */ + case 0xf73: + $sub[0] = 0xf71; + $sub[1] = 0xf72; + return $sub; + case 0xf75: + $sub[0] = 0xf71; + $sub[1] = 0xf74; + return $sub; + case 0xf76: + $sub[0] = 0xfb2; + $sub[1] = 0xf80; + return $sub; + case 0xf77: + $sub[0] = 0xfb2; + $sub[1] = 0xf81; + return $sub; + case 0xf78: + $sub[0] = 0xfb3; + $sub[1] = 0xf80; + return $sub; + case 0xf79: + $sub[0] = 0xfb3; + $sub[1] = 0xf71; + $sub[2] = 0xf80; + return $sub; + case 0xf81: + $sub[0] = 0xf71; + $sub[1] = 0xf80; + return $sub; + } + return false; + } + + public static function bubble_sort(&$arr, $start, $len) + { + if ($len < 2) { + return; + } + $k = $start + $len - 2; + while ($k >= $start) { + for ($j = $start; $j <= $k; $j++) { + if ($arr[$j]['indic_position'] > $arr[$j + 1]['indic_position']) { + $t = $arr[$j]; + $arr[$j] = $arr[$j + 1]; + $arr[$j + 1] = $t; + } + } + $k--; + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Myanmar.php b/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Myanmar.php new file mode 100644 index 0000000..9a68eda --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Myanmar.php @@ -0,0 +1,543 @@ +<?php + +namespace Mpdf\Shaper; + +class Myanmar +{ + /* FROM hb-ot-shape-complex-indic-private.hh */ + + // indic_category + const OT_X = 0; + const OT_C = 1; + const OT_V = 2; + const OT_N = 3; + const OT_H = 4; + const OT_ZWNJ = 5; + const OT_ZWJ = 6; + const OT_M = 7; /* Matra or Dependent Vowel */ + const OT_SM = 8; + const OT_VD = 9; + const OT_A = 10; + const OT_NBSP = 11; + const OT_DOTTEDCIRCLE = 12; /* Not in the spec, but special in Uniscribe. /Very very/ special! */ + const OT_RS = 13; /* Register Shifter, used in Khmer OT spec */ + const OT_COENG = 14; + const OT_REPHA = 15; + const OT_RA = 16; /* Not explicitly listed in the OT spec, but used in the grammar. */ + const OT_CM = 17; + + /* FROM hb-ot-shape-complex-myanmar.hh */ + + // myanmar_category + const OT_DB = 3; // same as Indic::OT_N; /* Dot below */ + const OT_GB = 12; // same as Indic::OT_DOTTEDCIRCLE; + const OT_AS = 18; /* Asat */ + const OT_D = 19; /* Digits except zero */ + const OT_D0 = 20; /* Digit zero */ + const OT_MH = 21; /* Various consonant medial types */ + const OT_MR = 22; /* Various consonant medial types */ + const OT_MW = 23; /* Various consonant medial types */ + const OT_MY = 24; /* Various consonant medial types */ + const OT_PT = 25; /* Pwo and other tones */ + + const OT_VABV = 26; + const OT_VBLW = 27; + const OT_VPRE = 28; + const OT_VPST = 29; + const OT_VS = 30; /* Variation selectors */ + + /* Visual positions in a syllable from left to right. */ + /* FROM hb-ot-shape-complex-myanmar-private.hh */ + + // myanmar_position + const POS_START = 0; + + const POS_RA_TO_BECOME_REPH = 1; + const POS_PRE_M = 2; + const POS_PRE_C = 3; + + const POS_BASE_C = 4; + const POS_AFTER_MAIN = 5; + + const POS_ABOVE_C = 6; + + const POS_BEFORE_SUB = 7; + const POS_BELOW_C = 8; + const POS_AFTER_SUB = 9; + + const POS_BEFORE_POST = 10; + const POS_POST_C = 11; + const POS_AFTER_POST = 12; + + const POS_FINAL_C = 13; + const POS_SMVD = 14; + + const POS_END = 15; + + // Based on myanmar_category used to make string to find syllables + // OT_ to string character (using e.g. OT_C from MYANMAR) hb-ot-shape-complex-myanmar-private.hh + public static $myanmar_category_char = [ + 'x', + 'C', + 'V', + 'N', + 'H', + 'Z', + 'J', + 'x', + 'S', + 'x', + 'A', + 'x', + 'D', + 'x', + 'x', + 'x', + 'R', + 'x', + 'a', /* As Asat */ + 'd', /* Digits except zero */ + 'o', /* Digit zero */ + 'k', /* Medial types */ + 'l', /* Medial types */ + 'm', /* Medial types */ + 'n', /* Medial types */ + 'p', /* Pwo and other tones */ + 'v', /* Vowel aboVe */ + 'b', /* Vowel Below */ + 'e', /* Vowel prE */ + 't', /* Vowel posT */ + 's', /* variation Selector */ + ]; + + public static function set_myanmar_properties(&$info) + { + $u = $info['uni']; + $type = self::myanmar_get_categories($u); + $cat = ($type & 0x7F); + $pos = ($type >> 8); + /* + * Re-assign category + * http://www.microsoft.com/typography/OpenTypeDev/myanmar/intro.htm#analyze + */ + if (self::in_range($u, 0xFE00, 0xFE0F)) { + $cat = self::OT_VS; + } elseif ($u == 0x200C) { + $cat = self::OT_ZWNJ; + } elseif ($u == 0x200D) { + $cat = self::OT_ZWJ; + } + + switch ($u) { + case 0x002D: + case 0x00A0: + case 0x00D7: + case 0x2012: + case 0x2013: + case 0x2014: + case 0x2015: + case 0x2022: + case 0x25CC: + case 0x25FB: + case 0x25FC: + case 0x25FD: + case 0x25FE: + $cat = self::OT_GB; + break; + + case 0x1004: + case 0x101B: + case 0x105A: + $cat = self::OT_RA; + break; + + case 0x1032: + case 0x1036: + $cat = self::OT_A; + break; + + case 0x103A: + $cat = self::OT_AS; + break; + + case 0x1041: + case 0x1042: + case 0x1043: + case 0x1044: + case 0x1045: + case 0x1046: + case 0x1047: + case 0x1048: + case 0x1049: + case 0x1090: + case 0x1091: + case 0x1092: + case 0x1093: + case 0x1094: + case 0x1095: + case 0x1096: + case 0x1097: + case 0x1098: + case 0x1099: + $cat = self::OT_D; + break; + + case 0x1040: + $cat = self::OT_D; /* XXX The spec says D0, but Uniscribe doesn't seem to do. */ + break; + + case 0x103E: + case 0x1060: + $cat = self::OT_MH; + break; + + case 0x103C: + $cat = self::OT_MR; + break; + + case 0x103D: + case 0x1082: + $cat = self::OT_MW; + break; + + case 0x103B: + case 0x105E: + case 0x105F: + $cat = self::OT_MY; + break; + + case 0x1063: + case 0x1064: + case 0x1069: + case 0x106A: + case 0x106B: + case 0x106C: + case 0x106D: + case 0xAA7B: + $cat = self::OT_PT; + break; + + case 0x1038: + case 0x1087: + case 0x1088: + case 0x1089: + case 0x108A: + case 0x108B: + case 0x108C: + case 0x108D: + case 0x108F: + case 0x109A: + case 0x109B: + case 0x109C: + $cat = self::OT_SM; + break; + } + + if ($cat == self::OT_M) { + switch ($pos) { + case self::POS_PRE_C: + $cat = self::OT_VPRE; + $pos = self::POS_PRE_M; + break; + case self::POS_ABOVE_C: + $cat = self::OT_VABV; + break; + case self::POS_BELOW_C: + $cat = self::OT_VBLW; + break; + case self::POS_POST_C: + $cat = self::OT_VPST; + break; + } + } + $info['myanmar_category'] = $cat; + $info['myanmar_position'] = $pos; + } + + // syllable_type + const CONSONANT_SYLLABLE = 0; + const BROKEN_CLUSTER = 3; + const NON_MYANMAR_CLUSTER = 4; + + public static function set_syllables(&$o, $s, &$broken_syllables) + { + $ptr = 0; + $syllable_serial = 1; + $broken_syllables = false; + + while ($ptr < strlen($s)) { + $match = ''; + $syllable_length = 1; + $syllable_type = self::NON_MYANMAR_CLUSTER; + // CONSONANT_SYLLABLE Consonant syllable + // From OT spec: + if (preg_match('/^(RaH)?([C|R]|V|d|D)[s]?(H([C|R|V])[s]?)*(H|[a]*[n]?[l]?((m[k]?|k)[a]?)?[e]*[v]*[b]*[A]*(N[a]?)?(t[k]?[a]*[v]*[A]*(N[a]?)?)*(p[A]*(N[a]?)?)*S*[J|Z]?)/', substr($s, $ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::CONSONANT_SYLLABLE; + } // BROKEN_CLUSTER syllable + elseif (preg_match('/^(RaH)?s?(H|[a]*[n]?[l]?((m[k]?|k)[a]?)?[e]*[v]*[b]*[A]*(N[a]?)?(t[k]?[a]*[v]*[A]*(N[a]?)?)*(p[A]*(N[a]?)?)*S*[J|Z]?)/', substr($s, $ptr), $ma)) { + if (strlen($ma[0])) { // May match blank + $syllable_length = strlen($ma[0]); + $syllable_type = self::BROKEN_CLUSTER; + $broken_syllables = true; + } + } + for ($i = $ptr; $i < $ptr + $syllable_length; $i++) { + $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; + } + $ptr += $syllable_length; + $syllable_serial++; + if ($syllable_serial == 16) { + $syllable_serial = 1; + } + } + } + + public static function reordering(&$info, $GSUBdata, $broken_syllables, $dottedcircle) + { + if ($broken_syllables && $dottedcircle) { + self::insert_dotted_circles($info, $dottedcircle); + } + $count = count($info); + if (!$count) { + return; + } + $last = 0; + $last_syllable = $info[0]['syllable']; + for ($i = 1; $i < $count; $i++) { + if ($last_syllable != $info[$i]['syllable']) { + self::reordering_syllable($info, $GSUBdata, $last, $i); + $last = $i; + $last_syllable = $info[$last]['syllable']; + } + } + self::reordering_syllable($info, $GSUBdata, $last, $count); + } + + public static function insert_dotted_circles(&$info, $dottedcircle) + { + $idx = 0; + $last_syllable = 0; + while ($idx < count($info)) { + $syllable = $info[$idx]['syllable']; + $syllable_type = ($syllable & 0x0F); + if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) { + $last_syllable = $syllable; + $dottedcircle[0]['syllable'] = $info[$idx]['syllable']; + array_splice($info, $idx, 0, $dottedcircle); + } else { + $idx++; + } + } + // In case of final bloken cluster... + $syllable = $info[$idx]['syllable']; + $syllable_type = ($syllable & 0x0F); + if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) { + $dottedcircle[0]['syllable'] = $info[$idx]['syllable']; + array_splice($info, $idx, 0, $dottedcircle); + } + } + + /* Rules from: + * https://www.microsoft.com/typography/otfntdev/devanot/shaping.aspx */ + + public static function reordering_syllable(&$info, $GSUBdata, $start, $end) + { + /* vowel_syllable: We made the vowels look like consonants. So uses the consonant logic! */ + /* broken_cluster: We already inserted dotted-circles, so just call the standalone_cluster. */ + + $syllable_type = ($info[$start]['syllable'] & 0x0F); + if ($syllable_type == self::NON_MYANMAR_CLUSTER) { + return; + } + if ($syllable_type == self::BROKEN_CLUSTER) { + //if ($uniscribe_bug_compatible) { + /* For dotted-circle, this is what Uniscribe does: + * If dotted-circle is the last glyph, it just does nothing. + * i.e. It doesn't form Reph. */ + if ($info[$end - 1]['myanmar_category'] == self::OT_DOTTEDCIRCLE) { + return; + } + } + + $base = $end; + $has_reph = false; + $limit = $start; + + if (($start + 3 <= $end) && + $info[$start]['myanmar_category'] == self::OT_RA && + $info[$start + 1]['myanmar_category'] == self::OT_AS && + $info[$start + 2]['myanmar_category'] == self::OT_H) { + $limit += 3; + $base = $start; + $has_reph = true; + } + + if (!$has_reph) { + $base = $limit; + } + + for ($i = $limit; $i < $end; $i++) { + if (self::is_consonant($info[$i])) { + $base = $i; + break; + } + } + + + /* Reorder! */ + $i = $start; + for (; $i < $start + ($has_reph ? 3 : 0); $i++) { + $info[$i]['myanmar_position'] = self::POS_AFTER_MAIN; + } + for (; $i < $base; $i++) { + $info[$i]['myanmar_position'] = self::POS_PRE_C; + } + if ($i < $end) { + $info[$i]['myanmar_position'] = self::POS_BASE_C; + $i++; + } + $pos = self::POS_AFTER_MAIN; + /* The following loop may be ugly, but it implements all of + * Myanmar reordering! */ + for (; $i < $end; $i++) { + if ($info[$i]['myanmar_category'] == self::OT_MR) { /* Pre-base reordering */ + $info[$i]['myanmar_position'] = self::POS_PRE_C; + continue; + } + if ($info[$i]['myanmar_position'] < self::POS_BASE_C) { /* Left matra */ + continue; + } + + if ($pos == self::POS_AFTER_MAIN && $info[$i]['myanmar_category'] == self::OT_VBLW) { + $pos = self::POS_BELOW_C; + $info[$i]['myanmar_position'] = $pos; + continue; + } + + if ($pos == self::POS_BELOW_C && $info[$i]['myanmar_category'] == self::OT_A) { + $info[$i]['myanmar_position'] = self::POS_BEFORE_SUB; + continue; + } + if ($pos == self::POS_BELOW_C && $info[$i]['myanmar_category'] == self::OT_VBLW) { + $info[$i]['myanmar_position'] = $pos; + continue; + } + if ($pos == self::POS_BELOW_C && $info[$i]['myanmar_category'] != self::OT_A) { + $pos = self::POS_AFTER_SUB; + $info[$i]['myanmar_position'] = $pos; + continue; + } + $info[$i]['myanmar_position'] = $pos; + } + + + /* Sit tight, rock 'n roll! */ + self::bubble_sort($info, $start, $end - $start); + } + + public static function is_one_of($info, $flags) + { + if (isset($info['is_ligature']) && $info['is_ligature']) { + return false; + } /* If it ligated, all bets are off. */ + return !!(self::FLAG($info['myanmar_category']) & $flags); + } + + /* Vowels and placeholders treated as if they were consonants. */ + + public static function is_consonant($info) + { + return self::is_one_of($info, (self::FLAG(self::OT_C) | self::FLAG(self::OT_CM) | self::FLAG(self::OT_RA) | self::FLAG(self::OT_V) | self::FLAG(self::OT_NBSP) | self::FLAG(self::OT_GB))); + } + +// From hb-private.hh + public static function in_range($u, $lo, $hi) + { + if ((($lo ^ $hi) & $lo) == 0 && (($lo ^ $hi) & $hi) == ($lo ^ $hi) && (($lo ^ $hi) & (($lo ^ $hi) + 1)) == 0) { + return ($u & ~($lo ^ $hi)) == $lo; + } else { + return $lo <= $u && $u <= $hi; + } + } + + // From hb-private.hh + public static function FLAG($x) + { + return (1 << ($x)); + } + + public static function FLAG_RANGE($x, $y) + { + return self::FLAG($y + 1) - self::FLAG($x); + } + + // BELOW from hb-ot-shape-complex-indic.cc + // see INDIC for details + public static $myanmar_table = [ + /* Myanmar (1000..109F) */ + + /* 1000 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1008 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1010 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1018 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1020 */ 3841, 3842, 3842, 3842, 3842, 3842, 3842, 3842, + /* 1028 */ 3842, 3842, 3842, 2823, 2823, 1543, 1543, 2055, + /* 1030 */ 2055, 775, 1543, 1543, 1543, 1543, 3848, 3843, + /* 1038 */ 3848, 3844, 1540, 3857, 3857, 3857, 3857, 3841, + /* 1040 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1048 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1050 */ 3841, 3841, 3842, 3842, 3842, 3842, 2823, 2823, + /* 1058 */ 2055, 2055, 3841, 3841, 3841, 3841, 3857, 3857, + /* 1060 */ 3857, 3841, 2823, 3843, 3843, 3841, 3841, 2823, + /* 1068 */ 2823, 3843, 3843, 3843, 3843, 3843, 3841, 3841, + /* 1070 */ 3841, 1543, 1543, 1543, 1543, 3841, 3841, 3841, + /* 1078 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1080 */ 3841, 3841, 3857, 2823, 775, 1543, 1543, 3843, + /* 1088 */ 3843, 3843, 3843, 3843, 3843, 3843, 3841, 3843, + /* 1090 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1098 */ 3840, 3840, 3843, 3843, 2823, 1543, 3840, 3840, + /* Myanmar Extended-A (AA60..AA7F) */ + + /* AA60 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* AA68 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* AA70 */ 3840, 3841, 3841, 3841, 3840, 3840, 3840, 3840, + /* AA78 */ 3840, 3840, 3841, 3843, 3840, 3840, 3840, 3840, + ]; + + // from "hb-ot-shape-complex-indic-table.cc" + public static function myanmar_get_categories($u) + { + if (0x1000 <= $u && $u <= 0x109F) { + return self::$myanmar_table[$u - 0x1000 + 0]; // offset 0 for Most "myanmar" + } + if (0xAA60 <= $u && $u <= 0xAA7F) { + return self::$myanmar_table[$u - 0xAA60 + 160]; // offset for extensions + } + if ($u == 0x00A0) { + return 3851; // (ISC_CP | (IMC_x << 8)) + } + if ($u == 0x25CC) { + return 3851; // (ISC_CP | (IMC_x << 8)) + } + return 3840; // (ISC_x | (IMC_x << 8)) + } + + public static function bubble_sort(&$arr, $start, $len) + { + if ($len < 2) { + return; + } + $k = $start + $len - 2; + while ($k >= $start) { + for ($j = $start; $j <= $k; $j++) { + if ($arr[$j]['myanmar_position'] > $arr[$j + 1]['myanmar_position']) { + $t = $arr[$j]; + $arr[$j] = $arr[$j + 1]; + $arr[$j + 1] = $t; + } + } + $k--; + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Sea.php b/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Sea.php new file mode 100644 index 0000000..40adacc --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Shaper/Sea.php @@ -0,0 +1,397 @@ +<?php + +namespace Mpdf\Shaper; + +class Sea +{ + // South East Asian shaper + // sea_category + const OT_X = 0; + + const OT_C = 1; + + const OT_IV = 2; # Independent Vowel + + const OT_T = 3; # Tone Marks + + const OT_H = 4; # Halant + + const OT_A = 10; # Anusvara + + const OT_GB = 12; # Generic Base (OT_DOTTEDCIRCLE in Indic) + + const OT_CM = 17; # Consonant Medial + + const OT_MR = 22; # Medial Ra + + const OT_VABV = 26; + + const OT_VBLW = 27; + + const OT_VPRE = 28; + + const OT_VPST = 29; + + // ? From Indic categories + const OT_ZWNJ = 5; + + const OT_ZWJ = 6; + + const OT_M = 7; + + const OT_SM = 8; + + const OT_VD = 9; + + const OT_NBSP = 11; + + const OT_RS = 13; + + const OT_COENG = 14; + + const OT_REPHA = 15; + + const OT_RA = 16; + + /* Visual positions in a syllable from left to right. */ + // sea_position + const POS_START = 0; + + const POS_RA_TO_BECOME_REPH = 1; + + const POS_PRE_M = 2; + + const POS_PRE_C = 3; + + const POS_BASE_C = 4; + + const POS_AFTER_MAIN = 5; + + const POS_ABOVE_C = 6; + + const POS_BEFORE_SUB = 7; + + const POS_BELOW_C = 8; + + const POS_AFTER_SUB = 9; + + const POS_BEFORE_POST = 10; + + const POS_POST_C = 11; + + const POS_AFTER_POST = 12; + + const POS_FINAL_C = 13; + + const POS_SMVD = 14; + + const POS_END = 15; + + // Based on sea_category used to make string to find syllables + // OT_ to string character (using e.g. OT_C from INDIC) hb-ot-shape-complex-sea-private.hh + public static $sea_category_char = [ + 'x', + 'C', + 'V', + 'T', + 'H', + 'x', + 'x', + 'x', + 'x', + 'x', + 'A', + 'x', + 'G', + 'x', + 'x', + 'x', + 'x', + 'M', + 'x', + 'x', + 'x', + 'x', + 'R', + 'x', + 'x', + 'x', + 'a', + 'b', + 'p', + 't', + ]; + + public static function set_sea_properties(&$info, $scriptblock) + { + $u = $info['uni']; + $type = self::sea_get_categories($u); + $cat = ($type & 0x7F); + $pos = ($type >> 8); + + /* + * Re-assign category + */ + // Medial Ra + if ($u == 0x1A55 || $u == 0xAA34) { + $cat = self::OT_MR; + } + + /* + * Re-assign position. + */ + if ($cat == self::OT_M) { // definitely "OT_M" in HarfBuzz - although this does not seem to have been defined ? should be OT_MR + switch ($pos) { + case self::POS_PRE_C: + $cat = self::OT_VPRE; + break; + case self::POS_ABOVE_C: + $cat = self::OT_VABV; + break; + case self::POS_BELOW_C: + $cat = self::OT_VBLW; + break; + case self::POS_POST_C: + $cat = self::OT_VPST; + break; + } + } + + $info['sea_category'] = $cat; + $info['sea_position'] = $pos; + } + + // syllable_type + const CONSONANT_SYLLABLE = 0; + + const BROKEN_CLUSTER = 1; + + const NON_SEA_CLUSTER = 2; + + public static function set_syllables(&$o, $s, &$broken_syllables) + { + $ptr = 0; + $syllable_serial = 1; + $broken_syllables = false; + while ($ptr < strlen($s)) { + $match = ''; + $syllable_length = 1; + $syllable_type = self::NON_SEA_CLUSTER; + + // CONSONANT_SYLLABLE Consonant syllable + if (preg_match('/^(C|V|G)(p|a|b|t|HC|M|R|T|A)*/', substr($s, $ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::CONSONANT_SYLLABLE; + } // BROKEN_CLUSTER syllable + elseif (preg_match('/^(p|a|b|t|HC|M|R|T|A)+/', substr($s, $ptr), $ma)) { + $syllable_length = strlen($ma[0]); + $syllable_type = self::BROKEN_CLUSTER; + $broken_syllables = true; + } + + for ($i = $ptr; $i < $ptr + $syllable_length; $i++) { + $o[$i]['syllable'] = ($syllable_serial << 4) | $syllable_type; + } + $ptr += $syllable_length; + $syllable_serial++; + if ($syllable_serial == 16) { + $syllable_serial = 1; + } + } + } + + public static function initial_reordering(&$info, $GSUBdata, $broken_syllables, $scriptblock, $dottedcircle) + { + + if ($broken_syllables && $dottedcircle) { + self::insert_dotted_circles($info, $dottedcircle); + } + + $count = count($info); + if (!$count) { + return; + } + $last = 0; + $last_syllable = $info[0]['syllable']; + for ($i = 1; $i < $count; $i++) { + if ($last_syllable != $info[$i]['syllable']) { + self::initial_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $i); + $last = $i; + $last_syllable = $info[$last]['syllable']; + } + } + self::initial_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $count); + } + + public static function insert_dotted_circles(&$info, $dottedcircle) + { + $idx = 0; + $last_syllable = 0; + while ($idx < count($info)) { + $syllable = $info[$idx]['syllable']; + $syllable_type = ($syllable & 0x0F); + if ($last_syllable != $syllable && $syllable_type == self::BROKEN_CLUSTER) { + $last_syllable = $syllable; + $dottedcircle[0]['syllable'] = $info[$idx]['syllable']; + array_splice($info, $idx, 0, $dottedcircle); + } else { + $idx++; + } + } + } + + public static function initial_reordering_syllable(&$info, $GSUBdata, $scriptblock, $start, $end) + { + /* broken_cluster: We already inserted dotted-circles, so just call the standalone_cluster. */ + + $syllable_type = ($info[$start]['syllable'] & 0x0F); + if ($syllable_type == self::NON_SEA_CLUSTER) { + return; + } + if ($syllable_type == self::BROKEN_CLUSTER) { + /* For dotted-circle, this is what Uniscribe does: + * If dotted-circle is the last glyph, it just does nothing. */ + if ($info[$end - 1]['sea_category'] == self::OT_GB) { + return; + } + } + + $base = $start; + $i = $start; + for (; $i < $base; $i++) { + $info[$i]['sea_position'] = self::POS_PRE_C; + } + if ($i < $end) { + $info[$i]['sea_position'] = self::POS_BASE_C; + $i++; + } + for (; $i < $end; $i++) { + if (isset($info[$i]['sea_category']) && $info[$i]['sea_category'] == self::OT_MR) { /* Pre-base reordering */ + $info[$i]['sea_position'] = self::POS_PRE_C; + continue; + } + if (isset($info[$i]['sea_category']) && $info[$i]['sea_category'] == self::OT_VPRE) { /* Left matra */ + $info[$i]['sea_position'] = self::POS_PRE_M; + continue; + } + $info[$i]['sea_position'] = self::POS_AFTER_MAIN; + } + + /* Sit tight, rock 'n roll! */ + self::bubble_sort($info, $start, $end - $start); + } + + public static function final_reordering(&$info, $GSUBdata, $scriptblock) + { + $count = count($info); + if (!$count) { + return; + } + $last = 0; + $last_syllable = $info[0]['syllable']; + for ($i = 1; $i < $count; $i++) { + if ($last_syllable != $info[$i]['syllable']) { + self::final_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $i); + $last = $i; + $last_syllable = $info[$last]['syllable']; + } + } + self::final_reordering_syllable($info, $GSUBdata, $scriptblock, $last, $count); + } + + public static function final_reordering_syllable(&$info, $GSUBdata, $scriptblock, $start, $end) + { + /* + * Nothing to do here at present! + */ + } + + public static $sea_table = [ + /* New Tai Lue (1980..19DF) */ + + /* 1980 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1988 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1990 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1998 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 19A0 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 19A8 */ 3841, 3841, 3841, 3841, 3840, 3840, 3840, 3840, + /* 19B0 */ 2823, 2823, 2823, 2823, 2823, 775, 775, 775, + /* 19B8 */ 2823, 2823, 775, 2823, 2823, 2823, 2823, 2823, + /* 19C0 */ 2823, 3857, 3857, 3857, 3857, 3857, 3857, 3857, + /* 19C8 */ 3843, 3843, 3840, 3840, 3840, 3840, 3840, 3840, + /* 19D0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 19D8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Tai Tham (1A20..1AAF) */ + + /* 1A20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1A28 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1A30 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1A38 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1A40 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* 1A48 */ 3841, 3841, 3841, 3841, 3841, 3842, 3842, 3842, + /* 1A50 */ 3842, 3842, 3842, 3841, 3841, 3857, 3857, 3857, + /* 1A58 */ 3857, 3857, 3857, 3857, 3857, 3857, 3857, 3840, + /* 1A60 */ 3844, 2823, 1543, 2823, 2823, 1543, 1543, 1543, + /* 1A68 */ 1543, 2055, 2055, 1543, 2055, 2823, 775, 775, + /* 1A70 */ 775, 775, 775, 1543, 1543, 3843, 3843, 3843, + /* 1A78 */ 3843, 3843, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1A80 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1A88 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1A90 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1A98 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1AA0 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* 1AA8 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* Cham (AA00..AA5F) */ + + /* AA00 */ 3842, 3842, 3842, 3842, 3842, 3842, 3841, 3841, + /* AA08 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* AA10 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* AA18 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* AA20 */ 3841, 3841, 3841, 3841, 3841, 3841, 3841, 3841, + /* AA28 */ 3841, 1543, 1543, 1543, 1543, 2055, 1543, 775, + /* AA30 */ 775, 1543, 2055, 3857, 3857, 3857, 3857, 3840, + /* AA38 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* AA40 */ 3857, 3857, 3857, 3857, 3857, 3857, 3857, 3857, + /* AA48 */ 3857, 3857, 3857, 3857, 3857, 3857, 3840, 3840, + /* AA50 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + /* AA58 */ 3840, 3840, 3840, 3840, 3840, 3840, 3840, 3840, + ]; + + public static function sea_get_categories($u) + { + if (0x1980 <= $u && $u <= 0x19DF) { + return self::$sea_table[$u - 0x1980]; // offset 0 for New Tai Lue + } + if (0x1A20 <= $u && $u <= 0x1AAF) { + return self::$sea_table[$u - 0x1A20 + 96]; // offset for Tai Tham + } + if (0xAA00 <= $u && $u <= 0xAA5F) { + return self::$sea_table[$u - 0xAA00 + 96 + 144]; // Cham + } + if ($u == 0x00A0) { + return 3851; // (ISC_CP | (IMC_x << 8)) + } + if ($u == 0x25CC) { + return 3851; // (ISC_CP | (IMC_x << 8)) + } + return 3840; // (ISC_x | (IMC_x << 8)) + } + + public static function bubble_sort(&$arr, $start, $len) + { + if ($len < 2) { + return; + } + $k = $start + $len - 2; + while ($k >= $start) { + for ($j = $start; $j <= $k; $j++) { + if ($arr[$j]['sea_position'] > $arr[$j + 1]['sea_position']) { + $t = $arr[$j]; + $arr[$j] = $arr[$j + 1]; + $arr[$j + 1] = $t; + } + } + $k--; + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/SizeConverter.php b/lib/MPDF/vendor/mpdf/mpdf/src/SizeConverter.php new file mode 100644 index 0000000..9ec93fd --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/SizeConverter.php @@ -0,0 +1,161 @@ +<?php + +namespace Mpdf; + +use Psr\Log\LoggerInterface; +use Mpdf\Log\Context as LogContext; + +class SizeConverter implements \Psr\Log\LoggerAwareInterface +{ + + private $dpi; + + private $defaultFontSize; + + /** + * @var \Mpdf\Mpdf + */ + private $mpdf; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + public function __construct($dpi, $defaultFontSize, Mpdf $mpdf, LoggerInterface $logger) + { + $this->dpi = $dpi; + $this->defaultFontSize = $defaultFontSize; + $this->mpdf = $mpdf; + $this->logger = $logger; + } + + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Depends of maxsize value to make % work properly. Usually maxsize == pagewidth + * For text $maxsize = $fontsize + * Setting e.g. margin % will use maxsize (pagewidth) and em will use fontsize + * + * @param mixed $size + * @param mixed $maxsize + * @param mixed $fontsize + * @param mixed $usefontsize Set false for e.g. margins - will ignore fontsize for % values + * + * @return float Final size in mm + */ + public function convert($size = 5, $maxsize = 0, $fontsize = false, $usefontsize = true) + { + $size = trim(strtolower($size)); + $res = preg_match('/^(?P<size>[-0-9.,]+([eE]\-?[0-9]+)?)?(?P<unit>[%a-z-]+)?$/', $size, $parts); + if (!$res) { + // ignore definition + $this->logger->warning(sprintf('Invalid size representation "%s"', $size), ['context' => LogContext::CSS_SIZE_CONVERSION]); + } + + $unit = !empty($parts['unit']) ? $parts['unit'] : null; + $size = !empty($parts['size']) ? (float) $parts['size'] : 0.0; + + switch ($unit) { + case 'mm': + // do nothing + break; + + case 'cm': + $size *= 10; + break; + + case 'pt': + $size *= 1 / Mpdf::SCALE; + break; + + case 'rem': + $size *= $this->mpdf->default_font_size / Mpdf::SCALE; + break; + + case '%': + if ($fontsize && $usefontsize) { + $size *= $fontsize / 100; + } else { + $size *= $maxsize / 100; + } + break; + + case 'in': + // mm in an inch + $size *= 25.4; + break; + + case 'pc': + // PostScript picas + $size *= 38.1 / 9; + break; + + case 'ex': + // Approximates "ex" as half of font height + $size *= $this->multiplyFontSize($fontsize, $maxsize, 0.5); + break; + + case 'em': + $size *= $this->multiplyFontSize($fontsize, $maxsize, 1); + break; + + case 'thin': + $size = 1 * (25.4 / $this->dpi); + break; + + case 'medium': + $size = 3 * (25.4 / $this->dpi); + // Commented-out dead code from legacy method + // $size *= $this->multiplyFontSize($fontsize, $maxsize, 1); + break; + + case 'thick': + $size = 5 * (25.4 / $this->dpi); // 5 pixel width for table borders + break; + + case 'xx-small': + $size *= $this->multiplyFontSize($fontsize, $maxsize, 0.7); + break; + + case 'x-small': + $size *= $this->multiplyFontSize($fontsize, $maxsize, 0.77); + break; + + case 'small': + $size *= $this->multiplyFontSize($fontsize, $maxsize, 0.86); + break; + + case 'large': + $size *= $this->multiplyFontSize($fontsize, $maxsize, 1.2); + break; + + case 'x-large': + $size *= $this->multiplyFontSize($fontsize, $maxsize, 1.5); + break; + + case 'xx-large': + $size *= $this->multiplyFontSize($fontsize, $maxsize, 2); + break; + + case 'px': + default: + $size *= (25.4 / $this->dpi); + break; + } + + return $size; + } + + private function multiplyFontSize($fontsize, $maxsize, $ratio) + { + if ($fontsize) { + return $fontsize * $ratio; + } + + return $maxsize * $ratio; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Strict.php b/lib/MPDF/vendor/mpdf/mpdf/src/Strict.php new file mode 100644 index 0000000..bab863d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Strict.php @@ -0,0 +1,67 @@ +<?php + +namespace Mpdf; + +trait Strict +{ + + /** + * @param string $name method name + * @param array $args arguments + */ + public function __call($name, $args) + { + $class = method_exists($this, $name) ? 'parent' : get_class($this); + throw new \Mpdf\MpdfException("Call to undefined method $class::$name()"); + } + + /** + * @param string $name lowercase method name + * @param array $args arguments + */ + public static function __callStatic($name, $args) + { + $class = get_called_class(); + throw new \Mpdf\MpdfException("Call to undefined static function $class::$name()"); + } + + /** + * @param string $name property name + */ + public function &__get($name) + { + $class = get_class($this); + throw new \Mpdf\MpdfException("Cannot read an undeclared property $class::\$$name"); + } + + /** + * @param string $name property name + * @param mixed $value property value + */ + public function __set($name, $value) + { + $class = get_class($this); + throw new \Mpdf\MpdfException("Cannot write to an undeclared property $class::\$$name"); + } + + /** + * @param string $name property name + * @throws \Kdyby\StrictObjects\\Mpdf\MpdfException + */ + public function __isset($name) + { + $class = get_class($this); + throw new \Mpdf\MpdfException("Cannot read an undeclared property $class::\$$name"); + } + + /** + * @param string $name property name + * @throws \Kdyby\StrictObjects\\Mpdf\MpdfException + */ + public function __unset($name) + { + $class = get_class($this); + throw new \Mpdf\MpdfException("Cannot unset the property $class::\$$name."); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/TTFontFile.php b/lib/MPDF/vendor/mpdf/mpdf/src/TTFontFile.php new file mode 100644 index 0000000..0834ff0 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/TTFontFile.php @@ -0,0 +1,4947 @@ +<?php + +namespace Mpdf; + +use Mpdf\Fonts\FontCache; +use Mpdf\Fonts\GlyphOperator; + +// NOTE*** If you change the defined constants below, be sure to delete all temporary font data files in /ttfontdata/ +// to force mPDF to regenerate cached font files. +if (!defined('_OTL_OLD_SPEC_COMPAT_2')) { + define('_OTL_OLD_SPEC_COMPAT_2', true); +} + +// Define the value used in the "head" table of a created TTF file +// 0x74727565 "true" for Mac +// 0x00010000 for Windows +// Either seems to work for a font embedded in a PDF file +// when read by Adobe Reader on a Windows PC(!) +if (!defined('_TTF_MAC_HEADER')) { + define('_TTF_MAC_HEADER', false); +} + +// Recalculate correct metadata/profiles when making subset fonts (not SIP/SMP) +// e.g. xMin, xMax, maxNContours +if (!defined('_RECALC_PROFILE')) { + define('_RECALC_PROFILE', false); +} + +// mPDF 5.7.1 +if (!function_exists('\Mpdf\unicode_hex')) { + function unicode_hex($unicode_dec) + { + return sprintf("%05s", strtoupper(dechex($unicode_dec))); + } +} + +/** + * TTFontFile class + * + * This class is based on The ReportLab Open Source PDF library + * written in Python - http://www.reportlab.com/software/opensource/ + * together with ideas from the OpenOffice source code and others. + * This header must be retained in any redistribution or + * modification of the file. + * + * @author Ian Back <ianb@bpm1.com> + * @license LGPL + */ +class TTFontFile +{ + + use Strict; + + private $fontCache; + + private $fontDescriptor; + + var $GPOSFeatures; + + var $GPOSLookups; + + var $GPOSScriptLang; + + var $MarkAttachmentType; + + var $MarkGlyphSets; + + var $GlyphClassMarks; + + var $GlyphClassLigatures; + + var $GlyphClassBases; + + var $GlyphClassComponents; + + var $GSUBScriptLang; + + var $rtlPUAstr; + + var $fontkey; + + var $useOTL; + + var $maxUni; + + var $sFamilyClass; + + var $sFamilySubClass; + + var $sipset; + + var $smpset; + + var $_pos; + + var $numTables; + + var $searchRange; + + var $entrySelector; + + var $rangeShift; + + var $tables; + + var $otables; + + var $filename; + + var $fh; + + var $glyphPos; + + var $charToGlyph; + + var $ascent; + + var $descent; + + var $lineGap; + + var $hheaascent; + + var $hheadescent; + + var $hhealineGap; + + var $advanceWidthMax; + + var $typoAscender; + + var $typoDescender; + + var $typoLineGap; + + var $usWinAscent; + + var $usWinDescent; + + var $strikeoutSize; + + var $strikeoutPosition; + + var $name; + + var $familyName; + + var $styleName; + + var $fullName; + + var $uniqueFontID; + + var $unitsPerEm; + + var $bbox; + + var $capHeight; + + var $xHeight; + + var $stemV; + + var $italicAngle; + + var $flags; + + var $underlinePosition; + + var $underlineThickness; + + var $charWidths; + + var $defaultWidth; + + var $maxStrLenRead; + + var $numTTCFonts; + + var $TTCFonts; + + var $maxUniChar; + + var $kerninfo; + + var $haskernGPOS; + + var $hassmallcapsGSUB; + + var $codeToGlyph; + + var $glyphdata; + + var $LuCoverage; + + public $panose; + + public $version; + + public $fontRevision; + + public $restrictedUse; + + public $glyphIDtoUni; + + public $glyphToChar; + + public $GSUBFeatures; + + public $GSUBLookups; + + public $GSLuCoverage; + + public function __construct(FontCache $fontCache, $fontDescriptor) + { + $this->fontCache = $fontCache; + $this->fontDescriptor = $fontDescriptor; + + // Maximum size of glyf table to read in as string (otherwise reads each glyph from file) + $this->maxStrLenRead = 200000; + } + + public function getMetrics($file, $fontkey, $TTCfontID = 0, $debug = false, $BMPonly = false, $useOTL = 0) + { + $this->useOTL = $useOTL; + $this->fontkey = $fontkey; + $this->filename = $file; + $this->fh = fopen($file, 'rb'); + + if (!$this->fh) { + throw new \Mpdf\MpdfException(sprintf('Unable to open font file "%s"', $file)); + } + + $this->_pos = 0; + $this->charWidths = ''; + $this->glyphPos = []; + $this->charToGlyph = []; + $this->tables = []; + $this->otables = []; + $this->kerninfo = []; + $this->haskernGPOS = []; + $this->hassmallcapsGSUB = []; + $this->ascent = 0; + $this->descent = 0; + $this->lineGap = 0; + $this->hheaascent = 0; + $this->hheadescent = 0; + $this->hhealineGap = 0; + $this->xHeight = 0; + $this->capHeight = 0; + $this->panose = []; + $this->sFamilyClass = 0; + $this->sFamilySubClass = 0; + $this->typoAscender = 0; + $this->typoDescender = 0; + $this->typoLineGap = 0; + $this->usWinAscent = 0; + $this->usWinDescent = 0; + $this->advanceWidthMax = 0; + $this->strikeoutSize = 0; + $this->strikeoutPosition = 0; + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->version = $version = $this->read_ulong(); + $this->panose = []; + + if ($version === 0x4F54544F) { + throw new \Mpdf\MpdfException('Postscript outlines are not supported'); + } + + if ($version === 0x74746366 && !$TTCfontID) { + throw new \Mpdf\MpdfException(sprintf('TTCfontID for a TrueType Collection is not defined in mPDF "fontdata" configuration (%s)', $file)); + } + + if (!in_array($version, [0x00010000, 0x74727565], true) && !$TTCfontID) { + throw new \Mpdf\MpdfException(sprintf('Not a TrueType font: version=%s)', $version)); + } + + if ($TTCfontID > 0) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000], true)) { + throw new \Mpdf\MpdfException(sprintf('Error parsing TrueType Collection: version=%s - (%s)', $version, $file)); + } + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + $this->seek($this->TTCFonts[$TTCfontID]['offset']); + $this->version = $version = $this->read_ulong(); // TTFont version again now + } + + $this->readTableDirectory($debug); + $this->extractInfo($debug, $BMPonly, $useOTL); + + fclose($this->fh); + } + + function readTableDirectory($debug = false) + { + $this->numTables = $this->read_ushort(); + $this->searchRange = $this->read_ushort(); + $this->entrySelector = $this->read_ushort(); + $this->rangeShift = $this->read_ushort(); + $this->tables = []; + + for ($i = 0; $i < $this->numTables; $i++) { + $record = []; + $record['tag'] = $this->read_tag(); + $record['checksum'] = [$this->read_ushort(), $this->read_ushort()]; + $record['offset'] = $this->read_ulong(); + $record['length'] = $this->read_ulong(); + $this->tables[$record['tag']] = $record; + } + + if ($debug) { + $this->checksumTables(); + } + } + + function checksumTables() + { + // Check the checksums for all tables + foreach ($this->tables as $t) { + if ($t['length'] > 0 && $t['length'] < $this->maxStrLenRead) { // 1.02 + $table = $this->get_chunk($t['offset'], $t['length']); + $checksum = $this->calcChecksum($table); + if ($t['tag'] === 'head') { + $up = unpack('n*', substr($table, 8, 4)); + $adjustment[0] = $up[1]; + $adjustment[1] = $up[2]; + $checksum = $this->sub32($checksum, $adjustment); + } + $xchecksum = $t['checksum']; + if ($xchecksum != $checksum) { + throw new \Mpdf\MpdfException(sprintf('TTF file "%s": invalid checksum %s table: %s (expected %s)', $this->filename, dechex($checksum[0]) . dechex($checksum[1]), $t['tag'], dechex($xchecksum[0]) . dechex($xchecksum[1]))); + } + } + } + } + + function sub32($x, $y) + { + $xlo = $x[1]; + $xhi = $x[0]; + $ylo = $y[1]; + $yhi = $y[0]; + + if ($ylo > $xlo) { + $xlo += 1 << 16; + ++$yhi; + } + $reslo = $xlo - $ylo; + if ($yhi > $xhi) { + $xhi += 1 << 16; + } + $reshi = $xhi - $yhi; + $reshi &= 0xFFFF; + + return [$reshi, $reslo]; + } + + function calcChecksum($data) + { + if (strlen($data) % 4) { + $data .= str_repeat("\0", 4 - (strlen($data) % 4)); + } + + $len = strlen($data); + $hi = 0x0000; + $lo = 0x0000; + + for ($i = 0; $i < $len; $i += 4) { + $hi += (ord($data[$i]) << 8) + ord($data[$i + 1]); + $lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]); + $hi += ($lo >> 16) & 0xFFFF; + $lo &= 0xFFFF; + } + + $hi &= 0xFFFF; + + return [$hi, $lo]; + } + + function get_table_pos($tag) + { + if (!isset($this->tables[$tag])) { + return [0, 0]; + } + $offset = $this->tables[$tag]['offset']; + $length = $this->tables[$tag]['length']; + + return [$offset, $length]; + } + + function seek($pos) + { + $this->_pos = $pos; + fseek($this->fh, $this->_pos); + } + + function skip($delta) + { + $this->_pos = $this->_pos + $delta; + fseek($this->fh, $delta, SEEK_CUR); + } + + function seek_table($tag, $offset_in_table = 0) + { + $tpos = $this->get_table_pos($tag); + $this->_pos = $tpos[0] + $offset_in_table; + fseek($this->fh, $this->_pos); + + return $this->_pos; + } + + function read_tag() + { + $this->_pos += 4; + + return fread($this->fh, 4); + } + + function read_short() + { + $this->_pos += 2; + $s = fread($this->fh, 2); + $a = (ord($s[0]) << 8) + ord($s[1]); + if ($a & (1 << 15)) { + $a = ($a - (1 << 16)); + } + + return $a; + } + + function unpack_short($s) + { + $a = (ord($s[0]) << 8) + ord($s[1]); + if ($a & (1 << 15)) { + $a = ($a - (1 << 16)); + } + + return $a; + } + + function read_ushort() + { + $this->_pos += 2; + $s = fread($this->fh, 2); + + return (ord($s[0]) << 8) + ord($s[1]); + } + + function read_ulong() + { + $this->_pos += 4; + $s = fread($this->fh, 4); + + // if large uInt32 as an integer, PHP converts it to -ve + return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24 + } + + function get_ushort($pos) + { + fseek($this->fh, $pos); + $s = fread($this->fh, 2); + + return (ord($s[0]) << 8) + ord($s[1]); + } + + function get_ulong($pos) + { + fseek($this->fh, $pos); + $s = fread($this->fh, 4); + + // iF large uInt32 as an integer, PHP converts it to -ve + return (ord($s[0]) * 16777216) + (ord($s[1]) << 16) + (ord($s[2]) << 8) + ord($s[3]); // 16777216 = 1<<24 + } + + function pack_short($val) + { + if ($val < 0) { + $val = abs($val); + $val = ~$val; + ++$val; + } + + return pack('n', $val); + } + + function splice($stream, $offset, $value) + { + return substr($stream, 0, $offset) . $value . substr($stream, $offset + strlen($value)); + } + + function _set_ushort($stream, $offset, $value) + { + $up = pack("n", $value); + + return $this->splice($stream, $offset, $up); + } + + function _set_short($stream, $offset, $val) + { + if ($val < 0) { + $val = abs($val); + $val = ~$val; + $val += 1; + } + $up = pack("n", $val); + + return $this->splice($stream, $offset, $up); + } + + function get_chunk($pos, $length) + { + fseek($this->fh, $pos); + if ($length < 1) { + return ''; + } + + return (fread($this->fh, $length)); + } + + function get_table($tag) + { + list($pos, $length) = $this->get_table_pos($tag); + if ($length == 0) { + return ''; + } + fseek($this->fh, $pos); + + return (fread($this->fh, $length)); + } + + function add($tag, $data) + { + if ($tag === 'head') { + $data = $this->splice($data, 8, "\0\0\0\0"); + } + $this->otables[$tag] = $data; + } + + function getCTG($file, $TTCfontID = 0, $debug = false, $useOTL = false) + { + // Only called if font is not to be used as embedded subset i.e. NOT called for SIP/SMP fonts + $this->useOTL = $useOTL; // mPDF 5.7.1 + $this->filename = $file; + $this->fh = fopen($file, 'rb'); + + if (!$this->fh) { + throw new \Mpdf\MpdfException(sprintf('Unable to open file "%s"', $file)); + } + + $this->_pos = 0; + $this->charWidths = ''; + $this->glyphPos = []; + $this->charToGlyph = []; + $this->tables = []; + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->skip(4); + + if ($TTCfontID > 0) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000], true)) { + throw new \Mpdf\MpdfException(sprintf("Error parsing TrueType Collection: version=%s (%s)", $version, $file)); + } + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + $this->seek($this->TTCFonts[$TTCfontID]['offset']); + $this->version = $version = $this->read_ulong(); // TTFont version again now + } + $this->readTableDirectory($debug); + + // cmap - Character to glyph index mapping table + $cmap_offset = $this->seek_table('cmap'); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i = 0; $i < $cmapTableCount; $i++) { + $platformID = $this->read_ushort(); + $encodingID = $this->read_ushort(); + $offset = $this->read_ulong(); + $save_pos = $this->_pos; + if ($platformID == 3 && $encodingID == 1) { // Microsoft, Unicode + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } elseif ($platformID == 0) { // Unicode -- assume all encodings are compatible + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } + $this->seek($save_pos); + } + + $glyphToChar = []; + $charToGlyph = []; + $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph); + + // Map Unmapped glyphs - from $numGlyphs + if ($useOTL) { + $this->seek_table("maxp"); + $this->skip(4); + $numGlyphs = $this->read_ushort(); + $bctr = 0xE000; + for ($gid = 1; $gid < $numGlyphs; $gid++) { + if (!isset($glyphToChar[$gid])) { + while (isset($charToGlyph[$bctr])) { + $bctr++; + } // Avoid overwriting a glyph already mapped in PUA + if ($bctr > 0xF8FF) { + throw new \Mpdf\MpdfException(sprintf('Font "%s" cannot map all included glyphs into Private Use Area U+E000-U+F8FF; cannot use useOTL on this font', $file)); + } + $glyphToChar[$gid][] = $bctr; + $charToGlyph[$bctr] = $gid; + $bctr++; + } + } + } + + fclose($this->fh); + + return $charToGlyph; + } + + function getTTCFonts($file) + { + $this->filename = $file; + + $this->fh = fopen($file, 'rb'); + if (!$this->fh) { + throw new \Mpdf\MpdfException(sprintf('Unable to open file "%s"', $file)); + } + + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->version = $version = $this->read_ulong(); + if ($version === 0x74746366) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000], true)) { + throw new \Mpdf\MpdfException(sprintf("Error parsing TrueType Collection: version=%s (%s)", $version, $file)); + } + } else { + throw new \Mpdf\MpdfException(sprintf("Not a TrueType Collection: version=%s (%s)", $version, $file)); + } + + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + } + + function extractInfo($debug = false, $BMPonly = false, $useOTL = 0) + { + // Values are all set to 0 or blank at start of getMetrics + // name - Naming table + $name_offset = $this->seek_table("name"); + $format = $this->read_ushort(); + if ($format != 0 && $format != 1) { + throw new \Mpdf\MpdfException("Unknown name table format " . $format); + } + + $numRecords = $this->read_ushort(); + $string_data_offset = $name_offset + $this->read_ushort(); + $names = [1 => '', 2 => '', 3 => '', 4 => '', 6 => '']; + $K = array_keys($names); + $nameCount = count($names); + + for ($i = 0; $i < $numRecords; $i++) { + + $platformId = $this->read_ushort(); + $encodingId = $this->read_ushort(); + $languageId = $this->read_ushort(); + $nameId = $this->read_ushort(); + $length = $this->read_ushort(); + $offset = $this->read_ushort(); + + if (!in_array($nameId, $K)) { + continue; + } + + $N = ''; + if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name + $opos = $this->_pos; + $this->seek($string_data_offset + $offset); + if ($length % 2 != 0) { + throw new \Mpdf\MpdfException("PostScript name is UTF-16BE string of odd length"); + } + $length /= 2; + $N = ''; + while ($length > 0) { + $char = $this->read_ushort(); + $N .= (chr($char)); + $length -= 1; + } + $this->_pos = $opos; + $this->seek($opos); + } elseif ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name + $opos = $this->_pos; + $N = $this->get_chunk($string_data_offset + $offset, $length); + $this->_pos = $opos; + $this->seek($opos); + } + if ($N && $names[$nameId] == '') { + $names[$nameId] = $N; + $nameCount -= 1; + if ($nameCount == 0) { + break; + } + } + } + + if ($names[6]) { + $psName = $names[6]; + } elseif ($names[4]) { + $psName = preg_replace('/ /', '-', $names[4]); + } elseif ($names[1]) { + $psName = preg_replace('/ /', '-', $names[1]); + } else { + $psName = ''; + } + + if (!$psName) { + throw new \Mpdf\MpdfException("Could not find PostScript font name: " . $this->filename); + } + + // CHECK IF psName valid (PadaukBook contains illegal characters in Name ID 6 i.e. Postscript Name) + $psNameInvalid = false; + $nameLength = strlen($psName); + for ($i = 0; $i < $nameLength; $i++) { + $c = $psName[$i]; + $oc = ord($c); + if ($oc > 126 || strpos(' [](){}<>/%', $c) !== false) { + //throw new \Mpdf\MpdfException("psName=".$psName." contains invalid character ".$c." ie U+".ord(c)); + $psNameInvalid = true; + break; + } + } + + if ($psNameInvalid && $names[4]) { + $psName = preg_replace('/ /', '-', $names[4]); + } + + $this->name = $psName; + if ($names[1]) { + $this->familyName = $names[1]; + } else { + $this->familyName = $psName; + } + if ($names[2]) { + $this->styleName = $names[2]; + } else { + $this->styleName = 'Regular'; + } + if ($names[4]) { + $this->fullName = $names[4]; + } else { + $this->fullName = $psName; + } + if ($names[3]) { + $this->uniqueFontID = $names[3]; + } else { + $this->uniqueFontID = $psName; + } + + if (!$psNameInvalid && $names[6]) { + $this->fullName = $names[6]; + } + + // head - Font header table + $this->seek_table('head'); + if ($debug) { + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + if ($ver_maj != 1) { + throw new \Mpdf\MpdfException('Unknown head table version ' . $ver_maj . '.' . $ver_min); + } + $this->fontRevision = $this->read_ushort() . $this->read_ushort(); + + $this->skip(4); + $magic = $this->read_ulong(); + if ($magic !== 0x5F0F3CF5) { + throw new \Mpdf\MpdfException('Invalid head table magic ' . $magic); + } + $this->skip(2); + } else { + $this->skip(18); + } + $this->unitsPerEm = $unitsPerEm = $this->read_ushort(); + $scale = 1000 / $unitsPerEm; + $this->skip(16); + $xMin = $this->read_short(); + $yMin = $this->read_short(); + $xMax = $this->read_short(); + $yMax = $this->read_short(); + $this->bbox = [($xMin * $scale), ($yMin * $scale), ($xMax * $scale), ($yMax * $scale)]; + + $this->skip(3 * 2); + $indexToLocFormat = $this->read_ushort(); + $glyphDataFormat = $this->read_ushort(); + if ($glyphDataFormat != 0) { + throw new \Mpdf\MpdfException(sprintf('Unknown glyph data format %s', $glyphDataFormat)); + } + + // hhea metrics table + if (isset($this->tables["hhea"])) { + $this->seek_table("hhea"); + $this->skip(4); + $hheaAscender = $this->read_short(); + $hheaDescender = $this->read_short(); + $hheaLineGap = $this->read_short(); + $hheaAdvanceWidthMax = $this->read_ushort(); + $this->hheaascent = ($hheaAscender * $scale); + $this->hheadescent = ($hheaDescender * $scale); + $this->hhealineGap = ($hheaLineGap * $scale); + $this->advanceWidthMax = ($hheaAdvanceWidthMax * $scale); + } + + // OS/2 - OS/2 and Windows metrics table + $use_typo_metrics = false; + if (isset($this->tables["OS/2"])) { + $this->seek_table("OS/2"); + $version = $this->read_ushort(); + $this->skip(2); + $usWeightClass = $this->read_ushort(); + $this->skip(2); + $fsType = $this->read_ushort(); + if ($fsType == 0x0002 || ($fsType & 0x0300) != 0) { + $this->restrictedUse = true; + } + + $this->skip(16); + $yStrikeoutSize = $this->read_short(); + $yStrikeoutPosition = $this->read_short(); + $this->strikeoutSize = ($yStrikeoutSize * $scale); + $this->strikeoutPosition = ($yStrikeoutPosition * $scale); + + $sF = $this->read_short(); + $this->sFamilyClass = ($sF >> 8); + $this->sFamilySubClass = ($sF & 0xFF); + $this->_pos += 10; //PANOSE = 10 byte length + $panose = fread($this->fh, 10); + $this->panose = []; + $panoseLenght = strlen($panose); + for ($p = 0; $p < $panoseLenght; $p++) { + $this->panose[] = ord($panose[$p]); + } + + $this->skip(20); + $fsSelection = $this->read_ushort(); + $use_typo_metrics = (($fsSelection & 0x80) === 0x80); // bit#7 = USE_TYPO_METRICS + $this->skip(4); + + $sTypoAscender = $this->read_short(); + $sTypoDescender = $this->read_short(); + $sTypoLineGap = $this->read_short(); + + if ($sTypoAscender) { + $this->typoAscender = ($sTypoAscender * $scale); + } + if ($sTypoDescender) { + $this->typoDescender = ($sTypoDescender * $scale); + } + if ($sTypoLineGap) { + $this->typoLineGap = ($sTypoLineGap * $scale); + } + + $usWinAscent = $this->read_ushort(); + $usWinDescent = $this->read_ushort(); + if ($usWinAscent) { + $this->usWinAscent = ($usWinAscent * $scale); + } + if ($usWinDescent) { + $this->usWinDescent = ($usWinDescent * $scale); + } + + if ($version > 1) { + $this->skip(8); + $sxHeight = $this->read_short(); + $this->xHeight = ($sxHeight * $scale); + $sCapHeight = $this->read_short(); + $this->capHeight = ($sCapHeight * $scale); + } + } else { + $usWeightClass = 400; + } + $this->stemV = 50 + (int) (($usWeightClass / 65.0) ** 2); + + // FONT DESCRIPTOR METRICS + if ($this->fontDescriptor === 'winTypo') { + $this->ascent = $this->typoAscender; + $this->descent = $this->typoDescender; + $this->lineGap = $this->typoLineGap; + } elseif ($this->fontDescriptor === 'mac') { + $this->ascent = $this->hheaascent; + $this->descent = $this->hheadescent; + $this->lineGap = $this->hhealineGap; + } else { // $this->fontDescriptor === 'win' + $this->ascent = $this->usWinAscent; + $this->descent = -$this->usWinDescent; + $this->lineGap = 0; + + // Special case - if either the winAscent or winDescent are greater than the + // font bounding box yMin yMax, then reduce them accordingly. + // This works with Myanmar Text (Windows 8 version) to give a + // line-height normal that is equivalent to that produced in browsers. + // Also Khmer OS = compatible with MSWord, Wordpad and browser. + if ($this->ascent > $this->bbox[3]) { + $this->ascent = $this->bbox[3]; + } + + if ($this->descent < $this->bbox[1]) { + $this->descent = $this->bbox[1]; + } + + // Override case - if the USE_TYPO_METRICS bit is set on OS/2 fsSelection + // this is telling the font to use the sTypo values and not the usWinAscent values. + // This works as a fix with Cambria Math to give a normal line-height; + // at present, this is the only font I have found with this bit set; + // although note that MS WordPad and windows FF browser uses the big line-height from winAscent + // but Word 2007 get it right + if ($use_typo_metrics && $this->typoAscender) { + $this->ascent = $this->typoAscender; + $this->descent = $this->typoDescender; + $this->lineGap = $this->typoLineGap; + } + } + + // post - PostScript table + $this->seek_table('post'); + if ($debug) { + $ver_maj = $this->read_ushort(); + if ($ver_maj < 1 || $ver_maj > 4) { + throw new \Mpdf\MpdfException(sprintf('Unknown post table version %s', $ver_maj)); + } + } else { + $this->skip(4); + } + + $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0; + $this->underlinePosition = $this->read_short() * $scale; + $this->underlineThickness = $this->read_short() * $scale; + $isFixedPitch = $this->read_ulong(); + + $this->flags = 4; + + if ($this->italicAngle != 0) { + $this->flags |= 64; + } + if ($usWeightClass >= 600) { + $this->flags |= 262144; + } + if ($isFixedPitch) { + $this->flags |= 1; + } + + // hhea - Horizontal header table + $this->seek_table('hhea'); + if ($debug) { + $ver_maj = $this->read_ushort(); + if ($ver_maj != 1) { + throw new \Mpdf\MpdfException(sprintf('Unknown hhea table version %s', $ver_maj)); + } + $this->skip(28); + } else { + $this->skip(32); + } + + $metricDataFormat = $this->read_ushort(); + + if ($metricDataFormat != 0) { + throw new \Mpdf\MpdfException(sprintf('Unknown horizontal metric data format "%s"', $metricDataFormat)); + } + + $numberOfHMetrics = $this->read_ushort(); + + if ($numberOfHMetrics == 0) { + throw new \Mpdf\MpdfException('Number of horizontal metrics is 0'); + } + + // maxp - Maximum profile table + $this->seek_table('maxp'); + if ($debug) { + $ver_maj = $this->read_ushort(); + if ($ver_maj != 1) { + throw new \Mpdf\MpdfException(sprintf('Unknown maxp table version ', $ver_maj)); + } + } else { + $this->skip(4); + } + $numGlyphs = $this->read_ushort(); + + // cmap - Character to glyph index mapping table + $cmap_offset = $this->seek_table('cmap'); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i = 0; $i < $cmapTableCount; $i++) { + $platformID = $this->read_ushort(); + $encodingID = $this->read_ushort(); + $offset = $this->read_ulong(); + $save_pos = $this->_pos; + if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + if (!$unicode_cmap_offset) { + $unicode_cmap_offset = $cmap_offset + $offset; + } + if ($BMPonly) { + break; + } + } + } elseif ((($platformID == 3 && $encodingID == 10) || $platformID == 0) && !$BMPonly) { // Microsoft, Unicode Format 12 table HKCS + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 12) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } + $this->seek($save_pos); + } + + if (!$unicode_cmap_offset) { + throw new \Mpdf\MpdfException(sprintf('Font "%s" does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)', $this->filename)); + } + + $sipset = false; + $smpset = false; + + $this->rtlPUAstr = ''; + $this->GSUBScriptLang = []; + $this->GSUBFeatures = []; + $this->GSUBLookups = []; + $this->GPOSScriptLang = []; + $this->GPOSFeatures = []; + $this->GPOSLookups = []; + $this->glyphIDtoUni = ''; + + // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above + if ($format == 12 && !$BMPonly) { + $this->maxUniChar = 0; + $this->seek($unicode_cmap_offset + 4); + $length = $this->read_ulong(); + $limit = $unicode_cmap_offset + $length; + $this->skip(4); + + $nGroups = $this->read_ulong(); + + $glyphToChar = []; + $charToGlyph = []; + for ($i = 0; $i < $nGroups; $i++) { + $startCharCode = $this->read_ulong(); + $endCharCode = $this->read_ulong(); + $startGlyphCode = $this->read_ulong(); + // ZZZ98 + if ($endCharCode > 0x20000 && $endCharCode < 0x2FFFF) { + $sipset = true; + } elseif ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) { + $smpset = true; + } + $offset = 0; + for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) { + $glyph = $startGlyphCode + $offset; + $offset++; + // ZZZ98 + if ($unichar < 0x30000) { + $charToGlyph[$unichar] = $glyph; + $this->maxUniChar = max($unichar, $this->maxUniChar); + $glyphToChar[$glyph][] = $unichar; + } + } + } + } else { + $glyphToChar = []; + $charToGlyph = []; + $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph); + } + $this->sipset = $sipset; + $this->smpset = $smpset; + + // Map Unmapped glyphs (or glyphs mapped to upper PUA U+F00000 onwards i.e. > U+2FFFF) - from $numGlyphs + if ($this->useOTL) { + + $bctr = 0xE000; + + for ($gid = 1; $gid < $numGlyphs; $gid++) { + + if (!isset($glyphToChar[$gid])) { + + while (isset($charToGlyph[$bctr])) { + $bctr++; + } + + // Avoid overwriting a glyph already mapped in PUA + // ZZZ98 + if (($bctr > 0xF8FF) && ($bctr < 0x2CEB0)) { + if (!$BMPonly) { + $bctr = 0x2CEB0; // Use unassigned area 0x2CEB0 to 0x2F7FF (space for 10,000 characters) + $this->sipset = $sipset = true; // forces subsetting; also ensure charwidths are saved + while (isset($charToGlyph[$bctr])) { + $bctr++; + } + } else { + throw new \Mpdf\MpdfException(sprintf('The font "%s" does not have enough space to map all (unmapped) included glyphs into Private Use Area U+E000-U+F8FF', $names[1])); + } + } + + $glyphToChar[$gid][] = $bctr; + $charToGlyph[$bctr] = $gid; + $this->maxUniChar = max($bctr, $this->maxUniChar); + $bctr++; + } + } + } + + $this->glyphToChar = $glyphToChar; + + $this->GSUBScriptLang = []; + $this->rtlPUAstr = ''; + if ($useOTL) { + $this->_getGDEFtables(); + list($this->GSUBScriptLang, $this->GSUBFeatures, $this->GSUBLookups, $this->rtlPUAstr) = $this->_getGSUBtables(); + list($this->GPOSScriptLang, $this->GPOSFeatures, $this->GPOSLookups) = $this->_getGPOStables(); + $this->glyphIDtoUni = str_pad('', 256 * 256 * 3, "\x00"); + foreach ($glyphToChar as $gid => $arr) { + if (isset($glyphToChar[$gid][0])) { + $char = $glyphToChar[$gid][0]; + + if ($char != 0 && $char != 65535) { + $this->glyphIDtoUni[$gid * 3] = chr($char >> 16); + $this->glyphIDtoUni[$gid * 3 + 1] = chr(($char >> 8) & 0xFF); + $this->glyphIDtoUni[$gid * 3 + 2] = chr($char & 0xFF); + } + } + } + } + + // if xHeight and/or CapHeight are not available from OS/2 (e.g. eraly versions) + // Calculate from yMax of 'x' or 'H' Glyphs... + if ($this->xHeight == 0) { + if (isset($charToGlyph[0x78])) { + $gidx = $charToGlyph[0x78]; // U+0078 (LATIN SMALL LETTER X) + $start = $this->seek_table('loca'); + if ($indexToLocFormat == 0) { + $this->skip($gidx * 2); + $locax = $this->read_ushort() * 2; + } elseif ($indexToLocFormat == 1) { + $this->skip($gidx * 4); + $locax = $this->read_ulong(); + } + $start = $this->seek_table('glyf'); + $this->skip($locax); + $this->skip(8); + $yMaxx = $this->read_short(); + $this->xHeight = $yMaxx * $scale; + } + } + + if ($this->capHeight == 0) { + if (isset($charToGlyph[0x48])) { + $gidH = $charToGlyph[0x48]; // U+0048 (LATIN CAPITAL LETTER H) + $start = $this->seek_table('loca'); + if ($indexToLocFormat == 0) { + $this->skip($gidH * 2); + $locaH = $this->read_ushort() * 2; + } elseif ($indexToLocFormat == 1) { + $this->skip($gidH * 4); + $locaH = $this->read_ulong(); + } + $start = $this->seek_table('glyf'); + $this->skip($locaH); + $this->skip(8); + $yMaxH = $this->read_short(); + $this->capHeight = $yMaxH * $scale; + } else { + $this->capHeight = $this->ascent; + } + // final default is to set it = to Ascent + } + + // hmtx - Horizontal metrics table + $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale); + + // kern - Kerning pair table + // Recognises old form of Kerning table - as required by Windows - Format 0 only + $kern_offset = $this->seek_table("kern"); + $version = $this->read_ushort(); + $nTables = $this->read_ushort(); + + // subtable header + $sversion = $this->read_ushort(); + $slength = $this->read_ushort(); + $scoverage = $this->read_ushort(); + $format = $scoverage >> 8; + if ($kern_offset && $version == 0 && $format == 0) { + // Format 0 + $nPairs = $this->read_ushort(); + $this->skip(6); + for ($i = 0; $i < $nPairs; $i++) { + $left = $this->read_ushort(); + $right = $this->read_ushort(); + $val = $this->read_short(); + if (isset($glyphToChar[$left]) && count($glyphToChar[$left]) == 1 && isset($glyphToChar[$right]) && count($glyphToChar[$right]) == 1) { + if ($left != 32 && $right != 32) { + $this->kerninfo[$glyphToChar[$left][0]][$glyphToChar[$right][0]] = intval($val * $scale); + } + } + } + } + } + + function _getGDEFtables() + { + // http://www.microsoft.com/typography/otspec/gdef.htm + if (isset($this->tables["GDEF"])) { + $gdef_offset = $this->seek_table("GDEF"); + + // ULONG Version of the GDEF table-currently 0x00010000 + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + $GlyphClassDef_offset = $this->read_ushort(); + $AttachList_offset = $this->read_ushort(); + $LigCaretList_offset = $this->read_ushort(); + $MarkAttachClassDef_offset = $this->read_ushort(); + + // Version 0x00010002 of GDEF header contains additional Offset to a list defining mark glyph set definitions (MarkGlyphSetDef) + if ($ver_min == 2) { + $MarkGlyphSetsDef_offset = $this->read_ushort(); + } + + // GlyphClassDef + if ($GlyphClassDef_offset) { + + $this->seek($gdef_offset + $GlyphClassDef_offset); + // 1 Base glyph (single character, spacing glyph) + // 2 Ligature glyph (multiple character, spacing glyph) + // 3 Mark glyph (non-spacing combining glyph) + // 4 Component glyph (part of single character, spacing glyph) + $GlyphByClass = $this->_getClassDefinitionTable(); + } else { + $GlyphByClass = []; + } + + if (isset($GlyphByClass[1]) && count($GlyphByClass[1]) > 0) { + $this->GlyphClassBases = ' ' . implode('| ', $GlyphByClass[1]); + } else { + $this->GlyphClassBases = ''; + } + if (isset($GlyphByClass[2]) && count($GlyphByClass[2]) > 0) { + $this->GlyphClassLigatures = ' ' . implode('| ', $GlyphByClass[2]); + } else { + $this->GlyphClassLigatures = ''; + } + if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) { + $this->GlyphClassMarks = ' ' . implode('| ', $GlyphByClass[3]); + } else { + $this->GlyphClassMarks = ''; + } + if (isset($GlyphByClass[4]) && count($GlyphByClass[4]) > 0) { + $this->GlyphClassComponents = ' ' . implode('| ', $GlyphByClass[4]); + } else { + $this->GlyphClassComponents = ''; + } + + if (isset($GlyphByClass[3]) && count($GlyphByClass[3]) > 0) { + $Marks = $GlyphByClass[3]; + } else { // to use for MarkAttachmentType + $Marks = []; + } + + /* Required for GPOS + // Attachment List + if ($AttachList_offset) { + $this->seek($gdef_offset+$AttachList_offset ); + } + The Attachment Point List table (AttachmentList) identifies all the attachment points defined in the GPOS table and their associated glyphs so a client can quickly access coordinates for each glyph's attachment points. As a result, the client can cache coordinates for attachment points along with glyph bitmaps and avoid recalculating the attachment points each time it displays a glyph. Without this table, processing speed would be slower because the client would have to decode the GPOS lookups that define attachment points and compile the points in a list. + + The Attachment List table (AttachList) may be used to cache attachment point coordinates along with glyph bitmaps. + + The table consists of an offset to a Coverage table (Coverage) listing all glyphs that define attachment points in the GPOS table, a count of the glyphs with attachment points (GlyphCount), and an array of offsets to AttachPoint tables (AttachPoint). The array lists the AttachPoint tables, one for each glyph in the Coverage table, in the same order as the Coverage Index. + AttachList table + Type Name Description + Offset Coverage Offset to Coverage table - from beginning of AttachList table + uint16 GlyphCount Number of glyphs with attachment points + Offset AttachPoint[GlyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order + + An AttachPoint table consists of a count of the attachment points on a single glyph (PointCount) and an array of contour indices of those points (PointIndex), listed in increasing numerical order. + + AttachPoint table + Type Name Description + uint16 PointCount Number of attachment points on this glyph + uint16 PointIndex[PointCount] Array of contour point indices -in increasing numerical order + + See Example 3 - http://www.microsoft.com/typography/otspec/gdef.htm + */ + + // Ligature Caret List + // The Ligature Caret List table (LigCaretList) defines caret positions for all the ligatures in a font. + // Not required for mDPF + // MarkAttachmentType + if ($MarkAttachClassDef_offset) { + $this->seek($gdef_offset + $MarkAttachClassDef_offset); + $MarkAttachmentTypes = $this->_getClassDefinitionTable(); + foreach ($MarkAttachmentTypes as $class => $glyphs) { + if (is_array($Marks) && count($Marks)) { + $mat = array_diff($Marks, $MarkAttachmentTypes[$class]); + sort($mat, SORT_STRING); + } else { + $mat = []; + } + + $this->MarkAttachmentType[$class] = ' ' . implode('| ', $mat); + } + } else { + $this->MarkAttachmentType = []; + } + + // MarkGlyphSets only in Version 0x00010002 of GDEF + if ($ver_min == 2 && $MarkGlyphSetsDef_offset) { + $this->seek($gdef_offset + $MarkGlyphSetsDef_offset); + $MarkSetTableFormat = $this->read_ushort(); + $MarkSetCount = $this->read_ushort(); + $MarkSetOffset = []; + for ($i = 0; $i < $MarkSetCount; $i++) { + $MarkSetOffset[] = $this->read_ulong(); + } + for ($i = 0; $i < $MarkSetCount; $i++) { + $this->seek($MarkSetOffset[$i]); + $glyphs = $this->_getCoverage(); + $this->MarkGlyphSets[$i] = ' ' . implode('| ', $glyphs); + } + } else { + $this->MarkGlyphSets = []; + } + } else { + throw new \Mpdf\MpdfException(sprintf('Unable to set font "%s" to use OTL as it does not include OTL tables (or at least not a GDEF table).', $this->filename)); + } + + $GSUB_offset = 0; + $GPOS_offset = 0; + $GSUB_length = 0; + + $s = ''; + + if (isset($this->tables['GSUB'])) { + $GSUB_offset = $this->seek_table('GSUB'); + $GSUB_length = $this->tables['GSUB']['length']; + $s .= fread($this->fh, $this->tables['GSUB']['length']); + } + + if (isset($this->tables['GPOS'])) { + $GPOS_offset = $this->seek_table('GPOS'); + $s .= fread($this->fh, $this->tables['GPOS']['length']); + } + + if ($s) { + $this->fontCache->write($this->fontkey . '.GSUBGPOStables.dat', $s); + } + + $font = [ + 'GSUB_offset' => $GSUB_offset, + 'GPOS_offset' => $GPOS_offset, + 'GSUB_length' => $GSUB_length, + 'GlyphClassBases' => $this->GlyphClassBases, + 'GlyphClassMarks' => $this->GlyphClassMarks, + 'GlyphClassLigatures' => $this->GlyphClassLigatures, + 'GlyphClassComponents' => $this->GlyphClassComponents, + 'MarkGlyphSets' => $this->MarkGlyphSets, + 'MarkAttachmentType' => $this->MarkAttachmentType, + ]; + + $this->fontCache->jsonWrite($this->fontkey . '.GDEFdata.json', $font); + } + + function _getClassDefinitionTable() + { + // NB Any glyph not included in the range of covered GlyphIDs automatically belongs to Class 0. This is not returned by this function + $ClassFormat = $this->read_ushort(); + $GlyphByClass = []; + + if ($ClassFormat == 1) { + $StartGlyph = $this->read_ushort(); + $GlyphCount = $this->read_ushort(); + for ($i = 0; $i < $GlyphCount; $i++) { + $gid = $StartGlyph + $i; + $class = $this->read_ushort(); + // Several fonts (mainly dejavu.../Freeserif etc) have a MarkAttachClassDef Format 1, where StartGlyph is 0 and GlyphCount is 1 + // This doesn't seem to do anything useful? + // Freeserif does not have $this->glyphToChar[0] allocated and would throw an error, so check if isset: + if (isset($this->glyphToChar[$gid][0])) { + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]); + } + } + } elseif ($ClassFormat == 2) { + $tableCount = $this->read_ushort(); + for ($i = 0; $i < $tableCount; $i++) { + $startGlyphID = $this->read_ushort(); + $endGlyphID = $this->read_ushort(); + $class = $this->read_ushort(); + for ($gid = $startGlyphID; $gid <= $endGlyphID; $gid++) { + if (isset($this->glyphToChar[$gid][0])) { + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$gid][0]); + } + } + } + } + foreach ($GlyphByClass as $class => $glyphs) { + sort($GlyphByClass[$class], SORT_STRING); // SORT makes it easier to read in development ? order not important ??? + } + ksort($GlyphByClass); + + return $GlyphByClass; + } + + /** + * GSUB - Glyph Substitution + */ + function _getGSUBtables() + { + if (!isset($this->tables['GSUB'])) { + return [[], [], [], '']; + } + + $ffeats = []; + $gsub_offset = $this->seek_table('GSUB'); + $this->skip(4); + $ScriptList_offset = $gsub_offset + $this->read_ushort(); + $FeatureList_offset = $gsub_offset + $this->read_ushort(); + $LookupList_offset = $gsub_offset + $this->read_ushort(); + + // ScriptList + $this->seek($ScriptList_offset); + $ScriptCount = $this->read_ushort(); + for ($i = 0; $i < $ScriptCount; $i++) { + $ScriptTag = $this->read_tag(); // = "beng", "deva" etc. + $ScriptTableOffset = $this->read_ushort(); + $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset; + } + + // Script Table + foreach ($ffeats as $t => $o) { + $ls = []; + $this->seek($o); + $DefLangSys_offset = $this->read_ushort(); + if ($DefLangSys_offset > 0) { + $ls['DFLT'] = $DefLangSys_offset + $o; + } + $LangSysCount = $this->read_ushort(); + for ($i = 0; $i < $LangSysCount; $i++) { + $LangTag = $this->read_tag(); // = + $LangTableOffset = $this->read_ushort(); + $ls[$LangTag] = $o + $LangTableOffset; + } + $ffeats[$t] = $ls; + } + + // Get FeatureIndexList + // LangSys Table - from first listed langsys + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = []; + $langsystable_offset = $o; + $this->seek($langsystable_offset); + $LookUpOrder = $this->read_ushort(); //==NULL + $ReqFeatureIndex = $this->read_ushort(); + if ($ReqFeatureIndex != 0xFFFF) { + $FeatureIndex[] = $ReqFeatureIndex; + } + $FeatureCount = $this->read_ushort(); + for ($i = 0; $i < $FeatureCount; $i++) { + $FeatureIndex[] = $this->read_ushort(); // = index of feature + } + $ffeats[$st][$t] = $FeatureIndex; + } + } + + // Feauture List => LookupListIndex es + $this->seek($FeatureList_offset); + $FeatureCount = $this->read_ushort(); + $Feature = []; + + for ($i = 0; $i < $FeatureCount; $i++) { + $tag = $this->read_tag(); + if ($tag == 'smcp') { + $this->hassmallcapsGSUB = true; + } + $Feature[$i] = ['tag' => $tag]; + $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort(); + } + + for ($i = 0; $i < $FeatureCount; $i++) { + $this->seek($Feature[$i]['offset']); + $this->read_ushort(); // null [FeatureParams] + $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort(); + $Feature[$i]['LookupListIndex'] = []; + for ($c = 0; $c < $Lookupcount; $c++) { + $Feature[$i]['LookupListIndex'][] = $this->read_ushort(); + } + } + + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = $ffeats[$st][$t]; + foreach ($FeatureIndex as $k => $fi) { + $ffeats[$st][$t][$k] = $Feature[$fi]; + } + } + } + + $gsub = []; + $GSUBScriptLang = []; + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $langsys) { + $lg = []; + foreach ($langsys as $ft) { + $lg[$ft['LookupListIndex'][0]] = $ft; + } + // list of Lookups in order they need to be run i.e. order listed in Lookup table + ksort($lg); + foreach ($lg as $ft) { + $gsub[$st][$t][$ft['tag']] = $ft['LookupListIndex']; + } + if (!isset($GSUBScriptLang[$st])) { + $GSUBScriptLang[$st] = ''; + } + $GSUBScriptLang[$st] .= $t . ' '; + } + } + + // Get metadata and offsets for whole Lookup List table + $this->seek($LookupList_offset); + $LookupCount = $this->read_ushort(); + $GSLookup = []; + $Offsets = []; + $SubtableCount = []; + + for ($i = 0; $i < $LookupCount; $i++) { + $Offsets[$i] = $LookupList_offset + $this->read_ushort(); + } + + for ($i = 0; $i < $LookupCount; $i++) { + + $this->seek($Offsets[$i]); + + $GSLookup[$i]['Type'] = $this->read_ushort(); + $GSLookup[$i]['Flag'] = $flag = $this->read_ushort(); + $GSLookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort(); + + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $GSLookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort(); + } + + // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure + if (($flag & 0x0010) == 0x0010) { + $GSLookup[$i]['MarkFilteringSet'] = $this->read_ushort(); + } else { + $GSLookup[$i]['MarkFilteringSet'] = ''; + } + + // Lookup Type 7: Extension + if ($GSLookup[$i]['Type'] == 7) { + // Overwrites new offset (32-bit) for each subtable, and a new lookup Type + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $this->seek($GSLookup[$i]['Subtables'][$c]); + $ExtensionPosFormat = $this->read_ushort(); + $type = $this->read_ushort(); + $ext_offset = $this->read_ulong(); + $GSLookup[$i]['Subtables'][$c] = $GSLookup[$i]['Subtables'][$c] + $ext_offset; + } + $GSLookup[$i]['Type'] = $type; + } + } + + // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph + $this->GSLuCoverage = []; + for ($i = 0; $i < $LookupCount; $i++) { + for ($c = 0; $c < $GSLookup[$i]['SubtableCount']; $c++) { + $this->seek($GSLookup[$i]['Subtables'][$c]); + $PosFormat = $this->read_ushort(); + + if ($GSLookup[$i]['Type'] == 5 && $PosFormat == 3) { + $this->skip(4); + } elseif ($GSLookup[$i]['Type'] == 6 && $PosFormat == 3) { + $BacktrackGlyphCount = $this->read_ushort(); + $this->skip(2 * $BacktrackGlyphCount + 2); + } + + // NB Coverage only looks at glyphs for position 1 (i.e. 5.3 and 6.3) // NEEDS TO READ ALL ******************** + $Coverage = $GSLookup[$i]['Subtables'][$c] + $this->read_ushort(); + $this->seek($Coverage); + $glyphs = $this->_getCoverage(false, 2); + $this->GSLuCoverage[$i][$c] = $glyphs; + } + } + + // $this->GSLuCoverage and $GSLookup + $this->fontCache->jsonWrite($this->fontkey . '.GSUBdata.json', $this->GSLuCoverage); + + // Now repeats as original to get Substitution rules + // Get metadata and offsets for whole Lookup List table + $this->seek($LookupList_offset); + $LookupCount = $this->read_ushort(); + $Lookup = []; + + for ($i = 0; $i < $LookupCount; $i++) { + $Lookup[$i]['offset'] = $LookupList_offset + $this->read_ushort(); + } + + for ($i = 0; $i < $LookupCount; $i++) { + $this->seek($Lookup[$i]['offset']); + $Lookup[$i]['Type'] = $this->read_ushort(); + $Lookup[$i]['Flag'] = $flag = $this->read_ushort(); + $Lookup[$i]['SubtableCount'] = $this->read_ushort(); + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['offset'] + $this->read_ushort(); + } + // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure + if (($flag & 0x0010) == 0x0010) { + $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort(); + } else { + $Lookup[$i]['MarkFilteringSet'] = ''; + } + + // Lookup Type 7: Extension + if ($Lookup[$i]['Type'] == 7) { + // Overwrites new offset (32-bit) for each subtable, and a new lookup Type + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $this->seek($Lookup[$i]['Subtable'][$c]['Offset']); + $ExtensionPosFormat = $this->read_ushort(); + $type = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ulong(); + } + $Lookup[$i]['Type'] = $type; + } + } + + // Process (1) Whole LookupList + for ($i = 0; $i < $LookupCount; $i++) { + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $this->seek($Lookup[$i]['Subtable'][$c]['Offset']); + $SubstFormat = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['Format'] = $SubstFormat; + + /* + Lookup['Type'] Enumeration table for glyph substitution + Value Type Description + 1 Single Replace one glyph with one glyph + 2 Multiple Replace one glyph with more than one glyph + 3 Alternate Replace one glyph with one of many glyphs + 4 Ligature Replace multiple glyphs with one glyph + 5 Context Replace one or more glyphs in context + 6 Chaining Context Replace one or more glyphs in chained context + 7 Extension Substitution Extension mechanism for other substitutions (i.e. this excludes the Extension type substitution itself) + 8 Reverse chaining context single Applied in reverse order, replace single glyph in chaining context + */ + + // LookupType 1: Single Substitution Subtable + if ($Lookup[$i]['Type'] == 1) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + if ($SubstFormat == 1) { // Calculated output glyph indices + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'] = $this->read_short(); + } elseif ($SubstFormat == 2) { // Specified output glyph indices + $GlyphCount = $this->read_ushort(); + for ($g = 0; $g < $GlyphCount; $g++) { + $Lookup[$i]['Subtable'][$c]['Glyphs'][] = $this->read_ushort(); + } + } + } // LookupType 2: Multiple Substitution Subtable + elseif ($Lookup[$i]['Type'] == 2) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SequenceCount'] = $SequenceCount = $this->read_short(); + for ($s = 0; $s < $SequenceCount; $s++) { + $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + for ($s = 0; $s < $SequenceCount; $s++) { + // Sequence Tables + $this->seek($Lookup[$i]['Subtable'][$c]['Sequences'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['GlyphCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['Sequences'][$s]['SubstituteGlyphID'][] = $this->read_ushort(); + } + } + } // LookupType 3: Alternate Forms + elseif ($Lookup[$i]['Type'] == 3) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['AlternateSetCount'] = $AlternateSetCount = $this->read_short(); + for ($s = 0; $s < $AlternateSetCount; $s++) { + $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + + for ($s = 0; $s < $AlternateSetCount; $s++) { + // AlternateSet Tables + $this->seek($Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['GlyphCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['AlternateSets'][$s]['SubstituteGlyphID'][] = $this->read_ushort(); + } + } + } // LookupType 4: Ligature Substitution Subtable + elseif ($Lookup[$i]['Type'] == 4) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['LigSetCount'] = $LigSetCount = $this->read_short(); + for ($s = 0; $s < $LigSetCount; $s++) { + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + for ($s = 0; $s < $LigSetCount; $s++) { + // LigatureSet Tables + $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g] = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Offset'] + $this->read_ushort(); + } + } + for ($s = 0; $s < $LigSetCount; $s++) { + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { + // Ligature tables + $this->seek($Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigatureOffset'][$g]); + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount'] = $this->read_ushort(); + for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) { + $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l] = $this->read_ushort(); + } + } + } + } // LookupType 5: Contextual Substitution Subtable + elseif ($Lookup[$i]['Type'] == 5) { + // Format 1: Context Substitution + if ($SubstFormat == 1) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubRuleSetCount'] = $SubRuleSetCount = $this->read_short(); + for ($s = 0; $s < $SubRuleSetCount; $s++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_short(); + } + for ($s = 0; $s < $SubRuleSetCount; $s++) { + // SubRuleSet Tables + $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset']); + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount'] = $this->read_short(); + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['Offset'] + $this->read_ushort(); + } + } + for ($s = 0; $s < $SubRuleSetCount; $s++) { + // SubRule Tables + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $g++) { + // Ligature tables + $this->seek($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleOffset'][$g]); + + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount'] = $this->read_ushort(); + // "Input"::[GlyphCount - 1]::Array of input GlyphIDs-start with second glyph + for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['GlyphCount']; $l++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['Input'][$l] = $this->read_ushort(); + } + // "SubstLookupRecord"::[SubstCount]::Array of SubstLookupRecords-in design order + for ($l = 0; $l < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstCount']; $l++) { + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['SequenceIndex'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$g]['SubstLookupRecord'][$l]['LookupListIndex'] = $this->read_ushort(); + } + } + } + } // Format 2: Class-based Context Glyph Substitution + elseif ($SubstFormat == 2) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubClassSetCnt'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = 0; + } else { + $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset; + } + } + } else { + throw new \Mpdf\MpdfException("GPOS Lookup Type " . $Lookup[$i]['Type'] . ", Format " . $SubstFormat . " not supported (ttfontsuni.php)."); + } + } // LookupType 6: Chaining Contextual Substitution Subtable + elseif ($Lookup[$i]['Type'] == 6) { + // Format 1: Simple Chaining Context Glyph Substitution p255 + if ($SubstFormat == 1) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + } // Format 2: Class-based Chaining Context Glyph Substitution p257 + elseif ($SubstFormat == 2) { + $Lookup[$i]['Subtable'][$c]['CoverageTableOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['InputClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset'] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $b++) { + $offset = $this->read_ushort(); + if ($offset == 0x0000) { + $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $offset; + } else { + $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $offset; + } + } + } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 + elseif ($SubstFormat == 3) { + $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + $Lookup[$i]['Subtable'][$c]['InputGlyphCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['CoverageInput'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['CoverageLookahead'][] = $Lookup[$i]['Subtable'][$c]['Offset'] + $this->read_ushort(); + } + $Lookup[$i]['Subtable'][$c]['SubstCount'] = $this->read_ushort(); + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { + $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'] = $this->read_ushort(); + // Substitution Lookup Record + // All contextual substitution subtables specify the substitution data in a Substitution Lookup Record + // (SubstLookupRecord). Each record contains a SequenceIndex, which indicates the position where the substitution + // will occur in the glyph sequence. In addition, a LookupListIndex identifies the lookup to be applied at the + // glyph position specified by the SequenceIndex. + } + } + } else { + throw new \Mpdf\MpdfException(sprintf('Lookup Type "%s" not supported.', $Lookup[$i]['Type'])); + } + } + } + + // Process (2) Whole LookupList + // Get Coverage tables and prepare preg_replace + for ($i = 0; $i < $LookupCount; $i++) { + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format']; + + // LookupType 1: Single Substitution Subtable 1 => 1 + if ($Lookup[$i]['Type'] == 1) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(false); + for ($g = 0; $g < count($glyphs); $g++) { + $replace = []; + $substitute = []; + $replace[] = unicode_hex($this->glyphToChar[$glyphs[$g]][0]); + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + if (isset($Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])) { // Format 1 + $substitute[] = unicode_hex($this->glyphToChar[($glyphs[$g] + $Lookup[$i]['Subtable'][$c]['DeltaGlyphID'])][0]); + } else { // Format 2 + $substitute[] = unicode_hex($this->glyphToChar[($Lookup[$i]['Subtable'][$c]['Glyphs'][$g])][0]); + } + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; + } + } // LookupType 2: Multiple Substitution Subtable 1 => n + elseif ($Lookup[$i]['Type'] == 2) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(); + for ($g = 0; $g < count($glyphs); $g++) { + $replace = []; + $substitute = []; + $replace[] = $glyphs[$g]; + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + if (!isset($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) || count($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID']) == 0) { + continue; + } // Illegal for GlyphCount to be 0; either error in font, or something has gone wrong - lets carry on for now! + foreach ($Lookup[$i]['Subtable'][$c]['Sequences'][$g]['SubstituteGlyphID'] as $sub) { + $substitute[] = unicode_hex($this->glyphToChar[$sub][0]); + } + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; + } + } // LookupType 3: Alternate Forms 1 => 1 (only first alternate form is used) + elseif ($Lookup[$i]['Type'] == 3) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(); + for ($g = 0; $g < count($glyphs); $g++) { + $replace = []; + $substitute = []; + $replace[] = $glyphs[$g]; + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + $gid = $Lookup[$i]['Subtable'][$c]['AlternateSets'][$g]['SubstituteGlyphID'][0]; + if (!isset($this->glyphToChar[$gid][0])) { + continue; + } + $substitute[] = unicode_hex($this->glyphToChar[$gid][0]); + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute]; + } + } // LookupType 4: Ligature Substitution Subtable n => 1 + elseif ($Lookup[$i]['Type'] == 4) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $glyphs = $this->_getCoverage(); + $LigSetCount = $Lookup[$i]['Subtable'][$c]['LigSetCount']; + for ($s = 0; $s < $LigSetCount; $s++) { + for ($g = 0; $g < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['LigCount']; $g++) { + $replace = []; + $substitute = []; + $replace[] = $glyphs[$s]; + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $replace[0], $Lookup[$i]['MarkFilteringSet'])) { + continue; + } + for ($l = 1; $l < $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']; $l++) { + $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['GlyphID'][$l]; + $rpl = unicode_hex($this->glyphToChar[$gid][0]); + // Flag = Ignore + if ($this->_checkGSUBignore($Lookup[$i]['Flag'], $rpl, $Lookup[$i]['MarkFilteringSet'])) { + continue 2; + } + $replace[] = $rpl; + } + $gid = $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['LigGlyph']; + if (!isset($this->glyphToChar[$gid][0])) { + continue; + } + $substitute[] = unicode_hex($this->glyphToChar[$gid][0]); + $Lookup[$i]['Subtable'][$c]['subs'][] = ['Replace' => $replace, 'substitute' => $substitute, 'CompCount' => $Lookup[$i]['Subtable'][$c]['LigSet'][$s]['Ligature'][$g]['CompCount']]; + } + } + } // LookupType 5: Contextual Substitution Subtable + elseif ($Lookup[$i]['Type'] == 5) { + // Format 1: Context Substitution + if ($SubstFormat == 1) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) { + $SubRuleSet = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]; + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph'] = $CoverageGlyphs[$s]; + for ($r = 0; $r < $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRuleCount']; $r++) { + $GlyphCount = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['GlyphCount']; + for ($g = 1; $g < $GlyphCount; $g++) { + $glyphID = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['Input'][$g]; + $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + } + } + } // Format 2: Class-based Context Glyph Substitution + elseif ($SubstFormat == 2) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['ClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses; + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) { + if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) { + $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s]); + $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt'] = $SubClassRuleCnt = $this->read_ushort(); + $SubClassRule = []; + for ($b = 0; $b < $SubClassRuleCnt; $b++) { + $SubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $SubClassRule[$b]; + } + } + } + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubClassSetCnt']; $s++) { + if ($Lookup[$i]['Subtable'][$c]['SubClassSetOffset'][$s] > 0) { + $SubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRuleCnt']; + for ($b = 0; $b < $SubClassRuleCnt; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b]); + $Rule = []; + $Rule['InputGlyphCount'] = $this->read_ushort(); + $Rule['SubstCount'] = $this->read_ushort(); + for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) { + $Rule['Input'][$r] = $this->read_ushort(); + } + for ($r = 0; $r < $Rule['SubstCount']; $r++) { + $Rule['SequenceIndex'][$r] = $this->read_ushort(); + $Rule['LookupListIndex'][$r] = $this->read_ushort(); + } + + $Lookup[$i]['Subtable'][$c]['SubClassSet'][$s]['SubClassRule'][$b] = $Rule; + } + } + } + } // Format 3: Coverage-based Context Glyph Substitution + elseif ($SubstFormat == 3) { + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs); + } + throw new \Mpdf\MpdfException("Lookup Type 5, SubstFormat 3 not tested. Please report this with the name of font used - " . $this->fontkey); + } + } // LookupType 6: Chaining Contextual Substitution Subtable + elseif ($Lookup[$i]['Type'] == 6) { + // Format 1: Simple Chaining Context Glyph Substitution p255 + if ($SubstFormat == 1) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + $ChainSubRuleSetCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; + + for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) { + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s]); + $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount'] = $this->read_ushort(); + for ($r = 0; $r < $ChainSubRuleCnt; $r++) { + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r] = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetOffset'][$s] + $this->read_ushort(); + } + } + for ($s = 0; $s < $ChainSubRuleSetCnt; $s++) { + $ChainSubRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleCount']; + for ($r = 0; $r < $ChainSubRuleCnt; $r++) { + // ChainSubRule + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRuleOffset'][$r]); + + $BacktrackGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphCount'] = $this->read_ushort(); + for ($g = 0; $g < $BacktrackGlyphCount; $g++) { + $glyphID = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['BacktrackGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + + $InputGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphCount'] = $this->read_ushort(); + for ($g = 1; $g < $InputGlyphCount; $g++) { + $glyphID = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['InputGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + + $LookaheadGlyphCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphCount'] = $this->read_ushort(); + for ($g = 0; $g < $LookaheadGlyphCount; $g++) { + $glyphID = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookaheadGlyphs'][$g] = unicode_hex($this->glyphToChar[$glyphID][0]); + } + + $SubstCount = $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SubstCount'] = $this->read_ushort(); + for ($lu = 0; $lu < $SubstCount; $lu++) { + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['SequenceIndex'][$lu] = $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'][$r]['LookupListIndex'][$lu] = $this->read_ushort(); + } + } + } + } // Format 2: Class-based Chaining Context Glyph Substitution p257 + elseif ($SubstFormat == 2) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageTableOffset']); + $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'] = $CoverageGlyphs = $this->_getCoverage(); + + $BacktrackClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['BacktrackClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['BacktrackClasses'] = $BacktrackClasses; + + $InputClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['InputClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['InputClasses'] = $InputClasses; + + $LookaheadClasses = $this->_getClasses($Lookup[$i]['Subtable'][$c]['LookaheadClassDefOffset']); + $Lookup[$i]['Subtable'][$c]['LookaheadClasses'] = $LookaheadClasses; + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) { + if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) { + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s]); + $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'] = $ChainSubClassRuleCnt = $this->read_ushort(); + $ChainSubClassRule = []; + for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { + $ChainSubClassRule[$b] = $Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] + $this->read_ushort(); + $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $ChainSubClassRule[$b]; + } + } + } + + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubClassSetCnt']; $s++) { + if (isset($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt'])) { + $ChainSubClassRuleCnt = $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRuleCnt']; + } else { + $ChainSubClassRuleCnt = 0; + } + for ($b = 0; $b < $ChainSubClassRuleCnt; $b++) { + if ($Lookup[$i]['Subtable'][$c]['ChainSubClassSetOffset'][$s] > 0) { + $this->seek($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b]); + $Rule = []; + $Rule['BacktrackGlyphCount'] = $this->read_ushort(); + for ($r = 0; $r < $Rule['BacktrackGlyphCount']; $r++) { + $Rule['Backtrack'][$r] = $this->read_ushort(); + } + $Rule['InputGlyphCount'] = $this->read_ushort(); + for ($r = 1; $r < $Rule['InputGlyphCount']; $r++) { + $Rule['Input'][$r] = $this->read_ushort(); + } + $Rule['LookaheadGlyphCount'] = $this->read_ushort(); + for ($r = 0; $r < $Rule['LookaheadGlyphCount']; $r++) { + $Rule['Lookahead'][$r] = $this->read_ushort(); + } + $Rule['SubstCount'] = $this->read_ushort(); + for ($r = 0; $r < $Rule['SubstCount']; $r++) { + $Rule['SequenceIndex'][$r] = $this->read_ushort(); + $Rule['LookupListIndex'][$r] = $this->read_ushort(); + } + + $Lookup[$i]['Subtable'][$c]['ChainSubClassSet'][$s]['ChainSubClassRule'][$b] = $Rule; + } + } + } + } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 + elseif ($SubstFormat == 3) { + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageBacktrack'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs'][] = implode("|", $glyphs); + } + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageInput'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs'][] = implode("|", $glyphs); + // Don't use above value as these are ordered numerically not as need to process + } + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']; $b++) { + $this->seek($Lookup[$i]['Subtable'][$c]['CoverageLookahead'][$b]); + $glyphs = $this->_getCoverage(); + $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs'][] = implode("|", $glyphs); + } + } + } + } + } + + $GSUBScriptLang = []; + $rtlpua = []; // All glyphs added to PUA [for magic_reverse] + foreach ($gsub as $st => $scripts) { + foreach ($scripts as $t => $langsys) { + $lul = []; // array of LookupListIndexes + $tags = []; // corresponding array of feature tags e.g. 'ccmp' + + foreach ($langsys as $tag => $ft) { + foreach ($ft as $ll) { + $lul[$ll] = $tag; + } + } + ksort($lul); // Order the Lookups in the order they are in the GUSB table, regardless of Feature order + $volt = $this->_getGSUBarray($Lookup, $lul, $st); + + // Interrogate $volt + // isol, fin, medi, init(arab syrc) into $rtlSUB for use in ArabJoin + // but also identify all RTL chars in PUA for magic_reverse (arab syrc hebr thaa nko samr) + // identify reph, matras, vatu, half forms etc for Indic for final re-ordering + $rtl = []; + $rtlSUB = []; + $finals = ''; + + if (strpos('arab syrc hebr thaa nko samr', $st) !== false) { // all RTL scripts [any/all languages] ? Mandaic + + foreach ($volt as $v) { + // isol fina fin2 fin3 medi med2 for Syriac + // ISOLATED FORM :: FINAL :: INITIAL :: MEDIAL :: MED2 :: FIN2 :: FIN3 + if (strpos('isol fina init medi fin2 fin3 med2', $v['tag']) !== false) { + + $key = $v['match']; + $key = preg_replace('/[\(\)]*/', '', $key); + $sub = $v['replace']; + if ($v['tag'] === 'isol') { + $kk = 0; + } elseif ($v['tag'] === 'fina') { + $kk = 1; + } elseif ($v['tag'] === 'init') { + $kk = 2; + } elseif ($v['tag'] === 'medi') { + $kk = 3; + } elseif ($v['tag'] === 'med2') { + $kk = 4; + } elseif ($v['tag'] === 'fin2') { + $kk = 5; + } elseif ($v['tag'] === 'fin3') { + $kk = 6; + } + + $rtl[$key][$kk] = $sub; + if (isset($v['prel']) && count($v['prel'])) { + $rtl[$key]['prel'][$kk] = $v['prel']; + } + if (isset($v['postl']) && count($v['postl'])) { + $rtl[$key]['postl'][$kk] = $v['postl']; + } + if (isset($v['ignore']) && $v['ignore']) { + $rtl[$key]['ignore'][$kk] = $v['ignore']; + } + $rtlpua[] = $sub; + + } else { // Add any other glyphs which are in PUA + if (isset($v['context']) && $v['context']) { + foreach ($v['rules'] as $vs) { + $matchCount = count($vs['match']); + for ($i = 0; $i < $matchCount; $i++) { + if (isset($vs['replace'][$i]) && preg_match('/^0[A-F0-9]{4}$/', $vs['match'][$i])) { + if (preg_match('/^0[EF][A-F0-9]{3}$/', $vs['replace'][$i])) { + $rtlpua[] = $vs['replace'][$i]; + } + } + } + } + } else { + preg_match_all('/\((0[A-F0-9]{4})\)/', $v['match'], $m); + $matchCount = count($m[0]); + for ($i = 0; $i < $matchCount; $i++) { + $sb = explode(' ', $v['replace']); + foreach ($sb as $sbg) { + if (preg_match('/(0[EF][A-F0-9]{3})/', $sbg, $mr)) { + $rtlpua[] = $mr[1]; + } + } + } + } + } + } + + // For kashida, need to determine all final forms except ones already identified by kashida priority rules (see \Mpdf\Otl) + foreach ($rtl as $base => $variants) { + if (isset($variants[1])) { // i.e. final form + if (strpos('0FE8E 0FE94 0FEA2 0FEAA 0FEAE 0FEC2 0FEDA 0FEDE 0FB93 0FECA 0FED2 0FED6 0FEEE 0FEF0 0FEF2', $variants[1]) === false) { // not already included + // This version does not exclude RA (0631) FEAE; Ya (064A) FEF2; Alef Maqsurah (0649) FEF0 which + // are selected in priority if connected to a medial Bah + //if (strpos('0FE8E 0FE94 0FEA2 0FEAA 0FEC2 0FEDA 0FEDE 0FB93 0FECA 0FED2 0FED6 0FEEE', $variants[1])===false) { // not already included + $finals .= $variants[1] . ' '; + } + } + } + + ksort($rtl); + $rtlSUB = $rtl; + } + + // INDIC - Dynamic properties + $rphf = []; + $half = []; + $pref = []; + $blwf = []; + $pstf = []; + + if (strpos('dev2 bng2 gur2 gjr2 ory2 tml2 tel2 knd2 mlm2 deva beng guru gujr orya taml telu knda mlym', $st) !== false) { // all INDIC scripts [any/all languages] + if (strpos('deva beng guru gujr orya taml telu knda mlym', $st) !== false) { + $is_old_spec = true; + } else { + $is_old_spec = false; + } + + // First get 'locl' substitutions (reversed!) + $loclsubs = []; + foreach ($volt as $v) { + if (strpos('locl', $v['tag']) !== false) { + $key = $v['match']; + $key = preg_replace('/[\(\)]*/', '', $key); + $sub = $v['replace']; + if ($key && strlen(trim($key)) == 5 && $sub) { + $loclsubs[$sub] = $key; + } + } + } + + foreach ($volt as $v) { + // <rphf> <half> <pref> <blwf> <pstf> + // defines consonant types: + // Reph <rphf> + // Half forms <half> + // Pre-base-reordering forms of Ra/Rra <pref> + // Below-base forms <blwf> + // Post-base forms <pstf> + // applied together with <locl> feature to input sequences consisting of two characters + // This is done for each consonant + // for <rphf> and <half>, features are applied to Consonant + Halant combinations + // for <pref>, <blwf> and <pstf>, features are applied to Halant + Consonant combinations + // Old version eg 'deva' <pref>, <blwf> and <pstf>, features are applied to Consonant + Halant + // Some malformed fonts still do Consonant + Halant for these - so match both?? + // If these two glyphs form a ligature, with no additional glyphs in context + // this means the consonant has the corresponding form + // Currently set to cope with both + // See also classes/otl.php + + if (strpos('rphf half pref blwf pstf', $v['tag']) !== false) { + if (isset($v['context']) && $v['context'] && $v['nBacktrack'] == 0 && $v['nLookahead'] == 0) { + foreach ($v['rules'] as $vs) { + if (count($vs['match']) == 2 && count($vs['replace']) == 1) { + $sub = $vs['replace'][0]; + // If Halant Cons <pref>, <blwf> and <pstf> in New version only + if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $vs['match'][0]) !== false && strpos('pref blwf pstf', $v['tag']) !== false && !$is_old_spec) { + $key = $vs['match'][1]; + $tag = $v['tag']; + if (isset($loclsubs[$key])) { + ${$tag[$loclsubs[$key]]} = $sub; + } + $tmp = &$$tag; + $tmp[hexdec($key)] = hexdec($sub); + } // If Cons Halant <rphf> and <half> always + // and <pref>, <blwf> and <pstf> in Old version + elseif (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', $vs['match'][1]) !== false && (strpos('rphf half', $v['tag']) !== false || (strpos('pref blwf pstf', $v['tag']) !== false && ($is_old_spec || _OTL_OLD_SPEC_COMPAT_2)))) { + $key = $vs['match'][0]; + $tag = $v['tag']; + if (isset($loclsubs[$key])) { + ${$tag[$loclsubs[$key]]} = $sub; + } + $tmp = &$$tag; + $tmp[hexdec($key)] = hexdec($sub); + } + } + } + } elseif (!isset($v['context'])) { + $key = $v['match']; + $key = preg_replace('/[\(\)]*/', '', $key); + $sub = $v['replace']; + if ($key && strlen(trim($key)) == 11 && $sub) { + // If Cons Halant <rphf> and <half> always + // and <pref>, <blwf> and <pstf> in Old version + // If Halant Cons <pref>, <blwf> and <pstf> in New version only + if (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', substr($key, 0, 5)) !== false && strpos('pref blwf pstf', $v['tag']) !== false && !$is_old_spec) { + $key = substr($key, 6, 5); + $tag = $v['tag']; + if (isset($loclsubs[$key])) { + ${$tag[$loclsubs[$key]]} = $sub; + } + $tmp = &$$tag; + $tmp[hexdec($key)] = hexdec($sub); + } elseif (strpos('0094D 009CD 00A4D 00ACD 00B4D 00BCD 00C4D 00CCD 00D4D', substr($key, 6, 5)) !== false && (strpos('rphf half', $v['tag']) !== false || (strpos('pref blwf pstf', $v['tag']) !== false && ($is_old_spec || _OTL_OLD_SPEC_COMPAT_2)))) { + $key = substr($key, 0, 5); + $tag = $v['tag']; + if (isset($loclsubs[$key])) { + ${$tag[$loclsubs[$key]]} = $sub; + } + $tmp = &$$tag; + $tmp[hexdec($key)] = hexdec($sub); + } + } + } + } + } + } + + if (count($rtl) || count($rphf) || count($half) || count($pref) || count($blwf) || count($pstf) || $finals) { + $font = [ + 'rtlSUB' => $rtlSUB, + 'finals' => $finals, + 'rphf' => $rphf, + 'half' => $half, + 'pref' => $pref, + 'blwf' => $blwf, + 'pstf' => $pstf, + ]; + + $this->fontCache->jsonWrite($this->fontkey . '.GSUB.' . $st . '.' . $t . '.json', $font); + } + + if (!isset($GSUBScriptLang[$st])) { + $GSUBScriptLang[$st] = ''; + } + $GSUBScriptLang[$st] .= $t . ' '; + } + } + + // All RTL glyphs from font added to (or already in) PUA [reqd for magic_reverse] + $rtlPUAstr = ''; + if (count($rtlpua)) { + $rtlpua = array_unique($rtlpua); + sort($rtlpua); + $n = count($rtlpua); + for ($i = 0; $i < $n; $i++) { + if (hexdec($rtlpua[$i]) < hexdec('E000') || hexdec($rtlpua[$i]) > hexdec('F8FF')) { + unset($rtlpua[$i]); + } + } + sort($rtlpua, SORT_STRING); + + $rangeid = -1; + $range = []; + $prevgid = -2; + + // for each character + foreach ($rtlpua as $gidhex) { + $gid = hexdec($gidhex); + if ($gid == ($prevgid + 1)) { + $range[$rangeid]['end'] = $gidhex; + $range[$rangeid]['count']++; + } else { + // new range + $rangeid++; + $range[$rangeid] = []; + $range[$rangeid]['start'] = $gidhex; + $range[$rangeid]['end'] = $gidhex; + $range[$rangeid]['count'] = 1; + } + $prevgid = $gid; + } + + foreach ($range as $rg) { + if ($rg['count'] == 1) { + $rtlPUAstr .= "\x{" . $rg['start'] . "}"; + } elseif ($rg['count'] == 2) { + $rtlPUAstr .= "\x{" . $rg['start'] . "}\x{" . $rg['end'] . "}"; + } else { + $rtlPUAstr .= "\x{" . $rg['start'] . "}-\x{" . $rg['end'] . "}"; + } + } + } + + return [$GSUBScriptLang, $gsub, $GSLookup, $rtlPUAstr]; + } + + // GSUB functions + function _getGSUBarray(&$Lookup, &$lul, $scripttag) + { + // Process (3) LookupList for specific Script-LangSys + // Generate preg_replace + $volt = []; + $reph = ''; + $matraE = ''; + $vatu = ''; + + foreach ($lul as $i => $tag) { + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $SubstFormat = $Lookup[$i]['Subtable'][$c]['Format']; + + // LookupType 1: Single Substitution Subtable + if ($Lookup[$i]['Type'] == 1) { + $subCount = count($Lookup[$i]['Subtable'][$c]['subs']); + for ($s = 0; $s < $subCount; $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; + // Ignore has already been applied earlier on + $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()"); + $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0); + $volt[] = ['match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 1]; + } + } // LookupType 2: Multiple Substitution Subtable + elseif ($Lookup[$i]['Type'] == 2) { + for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = implode(" ", $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']); + // Ignore has already been applied earlier on + $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()"); + $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0); + $volt[] = ['match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 2]; + } + } // LookupType 3: Alternate Forms + elseif ($Lookup[$i]['Type'] == 3) { + for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; + // Ignore has already been applied earlier on + $repl = $this->_makeGSUBinputMatch($inputGlyphs, "()"); + $subs = $this->_makeGSUBinputReplacement(1, $substitute, "()", 0, 1, 0); + $volt[] = ['match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 3]; + } + } // LookupType 4: Ligature Substitution Subtable + elseif ($Lookup[$i]['Type'] == 4) { + for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) { + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace']; + $substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0]; + // Ignore has already been applied earlier on + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + $repl = $this->_makeGSUBinputMatch($inputGlyphs, $ignore); + $subs = $this->_makeGSUBinputReplacement(count($inputGlyphs), $substitute, $ignore, 0, count($inputGlyphs), 0); + $volt[] = ['match' => $repl, 'replace' => $subs, 'tag' => $tag, 'key' => $inputGlyphs[0], 'type' => 4, 'CompCount' => $Lookup[$i]['Subtable'][$c]['subs'][$s]['CompCount'], 'Lig' => $substitute]; + } + } // LookupType 5: Chaining Contextual Substitution Subtable + elseif ($Lookup[$i]['Type'] == 5) { + // Format 1: Context Substitution + if ($SubstFormat == 1) { + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['SubRuleSetCount']; $s++) { + // SubRuleSet + $subRule = []; + foreach ($Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['SubRule'] as $rule) { + // SubRule + $inputGlyphs = []; + if ($rule['GlyphCount'] > 1) { + $inputGlyphs = $rule['InputGlyphs']; + } + $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['SubRuleSet'][$s]['FirstGlyph']; + ksort($inputGlyphs); + $nInput = count($inputGlyphs); + + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, [], 0); + $subRule = ['context' => 1, 'tag' => $tag, 'matchback' => '', 'match' => $contextInputMatch, 'nBacktrack' => 0, 'nInput' => $nInput, 'nLookahead' => 0, 'rules' => [],]; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['SubstLookupRecord'][$b]['LookupListIndex']; + $seqIndex = $rule['SubstLookupRecord'][$b]['SequenceIndex']; + + // $Lookup[$lup] = secondary Lookup + for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) { + if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) { + foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) { + $lookupGlyphs = $luss['Replace']; + $mLen = count($lookupGlyphs); + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // then apply the substitution + if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) { + continue; + } + $REPL = implode(" ", $luss['substitute']); + if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') { + $volt[] = ['match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore]; + } else { + $subRule['rules'][] = ['type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],]; + } + } + } + } + } + + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + } // Format 2: Class-based Context Glyph Substitution + elseif ($SubstFormat == 2) { + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + foreach ($Lookup[$i]['Subtable'][$c]['SubClassSet'] as $inputClass => $cscs) { + for ($cscrule = 0; $cscrule < $cscs['SubClassRuleCnt']; $cscrule++) { + $rule = $cscs['SubClassRule'][$cscrule]; + + $inputGlyphs = []; + + $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass]; + if ($rule['InputGlyphCount'] > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) { + $classindex = $rule['Input'][$gcl]; + if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex])) { + $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex]; + } // if class[0] = all glyphs excluding those specified in all other classes + // set to blank '' for now + else { + $inputGlyphs[$gcl] = ''; + } + } + } + + $nInput = $rule['InputGlyphCount']; + $nIsubs = (2 * $nInput) - 1; + + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, [], 0); + $subRule = ['context' => 1, 'tag' => $tag, 'matchback' => '', 'match' => $contextInputMatch, 'nBacktrack' => 0, 'nInput' => $nInput, 'nLookahead' => 0, 'rules' => [],]; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['LookupListIndex'][$b]; + $seqIndex = $rule['SequenceIndex'][$b]; + + // $Lookup[$lup] = secondary Lookup + for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) { + if (isset($Lookup[$lup]['Subtable'][$lus]['subs']) && count($Lookup[$lup]['Subtable'][$lus]['subs'])) { + foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) { + $lookupGlyphs = $luss['Replace']; + $mLen = count($lookupGlyphs); + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // then apply the substitution + if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) { + continue; + } + + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex); + $REPL = implode(" ", $luss['substitute']); + // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}" + + if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') { + $volt[] = ['match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore]; + } else { + $subRule['rules'][] = ['type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],]; + } + } + } + } + } + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + + } // Format 3: Coverage-based Context Glyph Substitution p259 + elseif ($SubstFormat == 3) { + + // IgnoreMarks flag set on main Lookup table + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs']; + $CoverageInputGlyphs = implode('|', $inputGlyphs); + $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; + + if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) { + $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs']; + } else { + $backtrackGlyphs = []; + } + + // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦ + $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore); + + if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) { + $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs']; + } else { + $lookaheadGlyphs = []; + } + + // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦ + $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore); + + $nBsubs = 2 * count($backtrackGlyphs); + $nIsubs = (2 * $nInput) - 1; + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, [], 0); + $subRule = ['context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => [],]; + + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { + $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex']; + $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex']; + for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) { + if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) { + foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) { + $lookupGlyphs = $luss['Replace']; + $mLen = count($lookupGlyphs); + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // then apply the substitution + if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) { + continue; + } + + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex); + $REPL = implode(" ", $luss['substitute']); + + if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') { + $volt[] = ['match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore]; + } else { + $subRule['rules'][] = ['type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],]; + } + } + } + } + } + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + + } // LookupType 6: ing Contextual Substitution Subtable + elseif ($Lookup[$i]['Type'] == 6) { + + // Format 1: Simple Chaining Context Glyph Substitution p255 + if ($SubstFormat == 1) { + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + for ($s = 0; $s < $Lookup[$i]['Subtable'][$c]['ChainSubRuleSetCount']; $s++) { + + // ChainSubRuleSet + $subRule = []; + $firstInputGlyph = $Lookup[$i]['Subtable'][$c]['CoverageGlyphs'][$s]; // First input gyyph + + foreach ($Lookup[$i]['Subtable'][$c]['ChainSubRuleSet'][$s]['ChainSubRule'] as $rule) { + // ChainSubRule + $inputGlyphs = []; + if ($rule['InputGlyphCount'] > 1) { + $inputGlyphs = $rule['InputGlyphs']; + } + $inputGlyphs[0] = $firstInputGlyph; + ksort($inputGlyphs); + $nInput = count($inputGlyphs); + + if ($rule['BacktrackGlyphCount']) { + $backtrackGlyphs = $rule['BacktrackGlyphs']; + } else { + $backtrackGlyphs = []; + } + $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore); + + if ($rule['LookaheadGlyphCount']) { + $lookaheadGlyphs = $rule['LookaheadGlyphs']; + } else { + $lookaheadGlyphs = []; + } + + $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore); + + $nBsubs = 2 * count($backtrackGlyphs); + $nIsubs = (2 * $nInput) - 1; + + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, [], 0); + $subRule = ['context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => [],]; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['LookupListIndex'][$b]; + $seqIndex = $rule['SequenceIndex'][$b]; + + // $Lookup[$lup] = secondary Lookup + for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) { + if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) { + foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) { + $lookupGlyphs = $luss['Replace']; + $mLen = count($lookupGlyphs); + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // then apply the substitution + if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) { + continue; + } + + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex); + + $REPL = implode(" ", $luss['substitute']); + + if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') { + $volt[] = ['match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore]; + } else { + $subRule['rules'][] = ['type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],]; + } + } + } + } + } + + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + + } // Format 2: Class-based Chaining Context Glyph Substitution p257 + elseif ($SubstFormat == 2) { + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + foreach ($Lookup[$i]['Subtable'][$c]['ChainSubClassSet'] as $inputClass => $cscs) { + for ($cscrule = 0; $cscrule < $cscs['ChainSubClassRuleCnt']; $cscrule++) { + $rule = $cscs['ChainSubClassRule'][$cscrule]; + + // These contain classes of glyphs as strings + // $Lookup[$i]['Subtable'][$c]['InputClasses'][(class)] e.g. 02E6|02E7|02E8 + // $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][(class)] + // $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][(class)] + // These contain arrays of classIndexes + // [Backtrack] [Lookahead] and [Input] (Input is from the second position only) + + $inputGlyphs = []; + + if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass])) { + $inputGlyphs[0] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$inputClass]; + } else { + $inputGlyphs[0] = ''; + } + if ($rule['InputGlyphCount'] > 1) { + // NB starts at 1 + for ($gcl = 1; $gcl < $rule['InputGlyphCount']; $gcl++) { + $classindex = $rule['Input'][$gcl]; + if (isset($Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex])) { + $inputGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['InputClasses'][$classindex]; + } // if class[0] = all glyphs excluding those specified in all other classes + // set to blank '' for now + else { + $inputGlyphs[$gcl] = ''; + } + } + } + + $nInput = $rule['InputGlyphCount']; + + if ($rule['BacktrackGlyphCount']) { + for ($gcl = 0; $gcl < $rule['BacktrackGlyphCount']; $gcl++) { + $classindex = $rule['Backtrack'][$gcl]; + if (isset($Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex])) { + $backtrackGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['BacktrackClasses'][$classindex]; + } // if class[0] = all glyphs excluding those specified in all other classes + // set to blank '' for now + else { + $backtrackGlyphs[$gcl] = ''; + } + } + } else { + $backtrackGlyphs = []; + } + // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦ + $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore); + + if ($rule['LookaheadGlyphCount']) { + for ($gcl = 0; $gcl < $rule['LookaheadGlyphCount']; $gcl++) { + $classindex = $rule['Lookahead'][$gcl]; + if (isset($Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex])) { + $lookaheadGlyphs[$gcl] = $Lookup[$i]['Subtable'][$c]['LookaheadClasses'][$classindex]; + } // if class[0] = all glyphs excluding those specified in all other classes + // set to blank '' for now + else { + $lookaheadGlyphs[$gcl] = ''; + } + } + } else { + $lookaheadGlyphs = []; + } + // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦ + $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore); + + $nBsubs = 2 * count($backtrackGlyphs); + $nIsubs = (2 * $nInput) - 1; + + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, [], 0); + $subRule = ['context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => [],]; + + for ($b = 0; $b < $rule['SubstCount']; $b++) { + $lup = $rule['LookupListIndex'][$b]; + $seqIndex = $rule['SequenceIndex'][$b]; + + // $Lookup[$lup] = secondary Lookup + for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) { + if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) { + foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) { + $lookupGlyphs = $luss['Replace']; + $mLen = count($lookupGlyphs); + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // then apply the substitution + if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) { + continue; + } + + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex); + $REPL = implode(" ", $luss['substitute']); + // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}" + + if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') { + $volt[] = ['match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore]; + } else { + $subRule['rules'][] = ['type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],]; + } + } + } + } + } + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + + } // Format 3: Coverage-based Chaining Context Glyph Substitution p259 + elseif ($SubstFormat == 3) { + // IgnoreMarks flag set on main Lookup table + $ignore = $this->_getGSUBignoreString($Lookup[$i]['Flag'], $Lookup[$i]['MarkFilteringSet']); + $inputGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageInputGlyphs']; + $CoverageInputGlyphs = implode('|', $inputGlyphs); + $nInput = $Lookup[$i]['Subtable'][$c]['InputGlyphCount']; + + if ($Lookup[$i]['Subtable'][$c]['BacktrackGlyphCount']) { + $backtrackGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageBacktrackGlyphs']; + } else { + $backtrackGlyphs = []; + } + // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦ + $backtrackMatch = $this->_makeGSUBbacktrackMatch($backtrackGlyphs, $ignore); + + if ($Lookup[$i]['Subtable'][$c]['LookaheadGlyphCount']) { + $lookaheadGlyphs = $Lookup[$i]['Subtable'][$c]['CoverageLookaheadGlyphs']; + } else { + $lookaheadGlyphs = []; + } + // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦ + $lookaheadMatch = $this->_makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore); + + $nBsubs = 2 * count($backtrackGlyphs); + $nIsubs = (2 * $nInput) - 1; + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, [], 0); + $subRule = ['context' => 1, 'tag' => $tag, 'matchback' => $backtrackMatch, 'match' => ($contextInputMatch . $lookaheadMatch), 'nBacktrack' => count($backtrackGlyphs), 'nInput' => $nInput, 'nLookahead' => count($lookaheadGlyphs), 'rules' => [],]; + + for ($b = 0; $b < $Lookup[$i]['Subtable'][$c]['SubstCount']; $b++) { + $lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex']; + $seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex']; + for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) { + if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) { + foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) { + $lookupGlyphs = $luss['Replace']; + $mLen = count($lookupGlyphs); + + // Only apply if the (first) 'Replace' glyph from the + // Lookup list is in the [inputGlyphs] at ['SequenceIndex'] + // then apply the substitution + if (strpos($inputGlyphs[$seqIndex], $lookupGlyphs[0]) === false) { + continue; + } + + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + $contextInputMatch = $this->_makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex); + $REPL = implode(" ", $luss['substitute']); + + if (strpos("isol fina fin2 fin3 medi med2 init ", $tag) !== false && $scripttag == 'arab') { + $volt[] = ['match' => $lookupGlyphs[0], 'replace' => $REPL, 'tag' => $tag, 'prel' => $backtrackGlyphs, 'postl' => $lookaheadGlyphs, 'ignore' => $ignore]; + } else { + $subRule['rules'][] = ['type' => $Lookup[$lup]['Type'], 'match' => $lookupGlyphs, 'replace' => $luss['substitute'], 'seqIndex' => $seqIndex, 'key' => $lookupGlyphs[0],]; + } + } + } + } + } + if (count($subRule['rules'])) { + $volt[] = $subRule; + } + } + } + } + } + + return $volt; + } + + function _checkGSUBignore($flag, $glyph, $MarkFilteringSet) + { + $ignore = false; + // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType) + if ((($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) && strpos($this->GlyphClassMarks, $glyph)) { + $ignore = true; + } + if ((($flag & 0x0004) == 0x0004) && strpos($this->GlyphClassLigatures, $glyph)) { + $ignore = true; + } + if ((($flag & 0x0002) == 0x0002) && strpos($this->GlyphClassBases, $glyph)) { + $ignore = true; + } + // Flag & 0xFF?? = MarkAttachmentType + if ($flag & 0xFF00) { + // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class" + // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table + if (strpos($this->MarkAttachmentType[($flag >> 8)], $glyph)) { + $ignore = true; + } + } + // Flag & 0x0010 = UseMarkFilteringSet + if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) { + $ignore = true; + } + + return $ignore; + } + + function _getGSUBignoreString($flag, $MarkFilteringSet) + { + // If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)" + // else "()" + // for Input - set on secondary Lookup table if in Context, and set Backtrack and Lookahead on Context Lookup + $str = ""; + $ignoreflag = 0; + + // Flag & 0xFF?? = MarkAttachmentType + if ($flag & 0xFF00) { + // "a lookup must ignore any mark glyphs that are not in the specified mark attachment class" + // $this->MarkAttachmentType is already adjusted for this i.e. contains all Marks except those in the MarkAttachmentClassDef table + $MarkAttachmentType = $flag >> 8; + $ignoreflag = $flag; + $str = $this->MarkAttachmentType[$MarkAttachmentType]; + } + + // Flag & 0x0010 = UseMarkFilteringSet + if ($flag & 0x0010) { + throw new \Mpdf\MpdfException("This font " . $this->fontkey . " contains MarkGlyphSets - Not tested yet"); + $str = $this->MarkGlyphSets[$MarkFilteringSet]; + } + + // If Ignore Marks set, supercedes any above + // Flag & 0x0008 = Ignore Marks - (unless already done with MarkAttachmentType) + if (($flag & 0x0008) == 0x0008 && ($flag & 0xFF00) == 0) { + $ignoreflag = 8; + $str = $this->GlyphClassMarks; + } + + // Flag & 0x0004 = Ignore Ligatures + if (($flag & 0x0004) == 0x0004) { + $ignoreflag += 4; + if ($str) { + $str .= "|"; + } + $str .= $this->GlyphClassLigatures; + } + // Flag & 0x0002 = Ignore BaseGlyphs + if (($flag & 0x0002) == 0x0002) { + $ignoreflag += 2; + if ($str) { + $str .= "|"; + } + $str .= $this->GlyphClassBases; + } + if ($str) { + // This originally returned e.g. ((?:(?:[IGNORE8]))*) when NOT specific to a Lookup e.g. rtlSub in + // arabictypesetting.GSUB.arab.DFLT.php + // This would save repeatedly saving long text strings if used multiple times + // When writing e.g. arabictypesetting.GSUB.arab.DFLT.php to file, included as $ignore[8] + // Would need to also write the $ignore array to that file + // // If UseMarkFilteringSet (specific to the Lookup) return the string + // if (($flag & 0x0010) && ($flag & 0x0008) != 0x0008) { + // return "((?:(?:" . $str . "))*)"; + // } + // else { return "((?:(?:" . "[IGNORE".$ignoreflag."]" . "))*)"; } + // // e.g. ((?:(?: 0031| 0032| 0033| 0034| 0045))*) + // But never finished coding it to add the $ignore array to the file, and it doesn't seem to occur often enough to be worth + // writing. So just output it as a string: + return "((?:(?:" . $str . "))*)"; + } else { + return "()"; + } + } + + // GSUB Patterns + + /* + BACKTRACK INPUT LOOKAHEAD + ================================== ================== ================================== + (FEEB|FEEC)(ign) ¦(FD12|FD13)(ign) ¦(0612)¦(ign) (0613)¦(ign) (FD12|FD13)¦(ign) (FEEB|FEEC) + ---------------- ---------------- ----- ------------ --------------- --------------- + Backtrack 1 Backtrack 2 Input 1 Input 2 Lookahead 1 Lookahead 2 + -------- --- --------- --- ---- --- ---- --- --------- --- ------- + \${1} \${2} \${3} \${4} \${5+} \${6+} \${7+} \${8+} + + nBacktrack = 2 nInput = 2 nLookahead = 2 + + nBsubs = 2xnBack nIsubs = (nBsubs+) nLsubs = (nBsubs+nIsubs+) 2xnLookahead + "\${1}\${2} " (nInput*2)-1 "\${5+} \${6+}" + "REPL" + + ¦\${1}\${2} ¦\${3}\${4} ¦REPL¦\${5+} \${6+}¦\${7+} \${8+}¦ + + + INPUT nInput = 5 + ============================================================ + ¦(0612)¦(ign) (0613)¦(ign) (0614)¦(ign) (0615)¦(ign) (0615)¦ + \${1} \${2} \${3} \${4} \${5} \${6} \${7} \${8} \${9} (All backreference numbers are + nBsubs) + ----- ------------ ------------ ------------ ------------ + Input 1 Input 2 Input 3 Input 4 Input 5 + + A====== SequenceIndex=1 ; Lookup match nGlyphs=1 + B=================== SequenceIndex=1 ; Lookup match nGlyphs=2 + C=============================== SequenceIndex=1 ; Lookup match nGlyphs=3 + D======================= SequenceIndex=2 ; Lookup match nGlyphs=2 + E===================================== SequenceIndex=2 ; Lookup match nGlyphs=3 + F====================== SequenceIndex=4 ; Lookup match nGlyphs=2 + + All backreference numbers are + nBsubs + A - "REPL\${2} \${3}\${4} \${5}\${6} \${7}\${8} \${9}" + B - "REPL\${2}\${4} \${5}\${6} \${7}\${8} \${9}" + C - "REPL\${2}\${4}\${6} \${7}\${8} \${9}" + D - "\${1} REPL\${2}\${4}\${6} \${7}\${8} \${9}" + E - "\${1} REPL\${2}\${4}\${6}\${8} \${9}" + F - "\${1}\${2} \${3}\${4} \${5} REPL\${6}\${8}" + */ + + function _makeGSUBcontextInputMatch($inputGlyphs, $ignore, $lookupGlyphs, $seqIndex) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context + // $lookupGlyphs = array of glyphs (single Glyphs) making up Lookup Input sequence + $mLen = count($lookupGlyphs); // nGlyphs in the secondary Lookup match + $nInput = count($inputGlyphs); // nGlyphs in the Primary Input sequence + $str = ""; + for ($i = 0; $i < $nInput; $i++) { + if ($i > 0) { + $str .= $ignore . " "; + } + if ($i >= $seqIndex && $i < ($seqIndex + $mLen)) { + $str .= "(" . $lookupGlyphs[($i - $seqIndex)] . ")"; + } else { + $str .= "(" . $inputGlyphs[($i)] . ")"; + } + } + + return $str; + } + + function _makeGSUBinputMatch($inputGlyphs, $ignore) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(0612)¦(ignore) (0613)¦(ignore) (0614)¦ + // $inputGlyphs = array of glyphs(glyphstrings) making up Input sequence in Context + // $lookupGlyphs = array of glyphs making up Lookup Input sequence - if applicable + $str = ""; + for ($i = 1; $i <= count($inputGlyphs); $i++) { + if ($i > 1) { + $str .= $ignore . " "; + } + $str .= "(" . $inputGlyphs[($i - 1)] . ")"; + } + + return $str; + } + + function _makeGSUBbacktrackMatch($backtrackGlyphs, $ignore) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(FEEB|FEEC)(ignore) ¦(FD12|FD13)(ignore) ¦ + // $backtrackGlyphs = array of glyphstrings making up Backtrack sequence + // 3 2 1 0 + // each item being e.g. E0AD|E0AF|F1FD + $str = ""; + for ($i = (count($backtrackGlyphs) - 1); $i >= 0; $i--) { + $str .= "(" . $backtrackGlyphs[$i] . ")" . $ignore . " "; + } + + return $str; + } + + function _makeGSUBlookaheadMatch($lookaheadGlyphs, $ignore) + { + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // Returns e.g. ¦(ignore) (FD12|FD13)¦(ignore) (FEEB|FEEC)¦ + // $lookaheadGlyphs = array of glyphstrings making up Lookahead sequence + // 0 1 2 3 + // each item being e.g. E0AD|E0AF|F1FD + $str = ""; + for ($i = 0; $i < count($lookaheadGlyphs); $i++) { + $str .= $ignore . " (" . $lookaheadGlyphs[$i] . ")"; + } + + return $str; + } + + function _makeGSUBinputReplacement($nInput, $REPL, $ignore, $nBsubs, $mLen, $seqIndex) + { + // Returns e.g. "REPL\${6}\${8}" or "\${1}\${2} \${3} REPL\${4}\${6}\${8} \${9}" + // $nInput nGlyphs in the Primary Input sequence + // $REPL replacement glyphs from secondary lookup + // $ignore = "((?:(?: FBA1| FBA2| FBA3))*)" or "()" + // $nBsubs Number of Backtrack substitutions (= 2x Number of Backtrack glyphs) + // $mLen nGlyphs in the secondary Lookup match - if no secondary lookup, should=$nInput + // $seqIndex Sequence Index to apply the secondary match + if ($ignore == "()") { + $ign = false; + } else { + $ign = true; + } + $str = ""; + if ($nInput == 1) { + $str = $REPL; + } elseif ($nInput > 1) { + if ($mLen == $nInput) { // whole string replaced + $str = $REPL; + if ($ign) { + // for every nInput over 1, add another replacement backreference, to move IGNORES after replacement + for ($x = 2; $x <= $nInput; $x++) { + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + } + } else { // if only part of string replaced: + for ($x = 1; $x < ($seqIndex + 1); $x++) { + if ($x == 1) { + $str .= '\\' . ($nBsubs + 1); + } else { + if ($ign) { + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1))); + } + } + if ($seqIndex > 0) { + $str .= " "; + } + $str .= $REPL; + if ($ign) { + for ($x = (max(($seqIndex + 1), 2)); $x < ($seqIndex + 1 + $mLen); $x++) { // move IGNORES after replacement + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + } + for ($x = ($seqIndex + 1 + $mLen); $x <= $nInput; $x++) { + if ($ign) { + $str .= '\\' . ($nBsubs + (2 * ($x - 1))); + } + $str .= ' \\' . ($nBsubs + 1 + (2 * ($x - 1))); + } + } + } + + return $str; + } + + function _getCoverage($convert2hex = true, $mode = 1) + { + $g = []; + $ctr = 0; + $CoverageFormat = $this->read_ushort(); + if ($CoverageFormat == 1) { + $CoverageGlyphCount = $this->read_ushort(); + for ($gid = 0; $gid < $CoverageGlyphCount; $gid++) { + $glyphID = $this->read_ushort(); + $uni = $this->glyphToChar[$glyphID][0]; + if ($convert2hex) { + $g[] = unicode_hex($uni); + } elseif ($mode == 2) { + $g[$uni] = $ctr; + $ctr++; + } else { + $g[] = $glyphID; + } + } + } + if ($CoverageFormat == 2) { + $RangeCount = $this->read_ushort(); + for ($r = 0; $r < $RangeCount; $r++) { + $start = $this->read_ushort(); + $end = $this->read_ushort(); + $StartCoverageIndex = $this->read_ushort(); // n/a + for ($glyphID = $start; $glyphID <= $end; $glyphID++) { + $uni = $this->glyphToChar[$glyphID][0]; + if ($convert2hex) { + $g[] = unicode_hex($uni); + } elseif ($mode == 2) { + $uni = $g[$uni] = $ctr; + $ctr++; + } else { + $g[] = $glyphID; + } + } + } + } + + return $g; + } + + function _getClasses($offset) + { + $this->seek($offset); + $ClassFormat = $this->read_ushort(); + $GlyphByClass = []; + if ($ClassFormat == 1) { + $StartGlyph = $this->read_ushort(); + $GlyphCount = $this->read_ushort(); + for ($i = 0; $i < $GlyphCount; $i++) { + $startGlyphID = $StartGlyph + $i; + $endGlyphID = $StartGlyph + $i; + $class = $this->read_ushort(); + for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { + if (isset($this->glyphToChar[$g][0])) { + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]); + } + } + } + } elseif ($ClassFormat == 2) { + $tableCount = $this->read_ushort(); + for ($i = 0; $i < $tableCount; $i++) { + $startGlyphID = $this->read_ushort(); + $endGlyphID = $this->read_ushort(); + $class = $this->read_ushort(); + for ($g = $startGlyphID; $g <= $endGlyphID; $g++) { + if ($this->glyphToChar[$g][0]) { + $GlyphByClass[$class][] = unicode_hex($this->glyphToChar[$g][0]); + } + } + } + } + $gbc = []; + foreach ($GlyphByClass as $class => $garr) { + $gbc[$class] = implode('|', $garr); + } + + return $gbc; + } + + function _getGPOStables() + { + /////////////////////////////////// + // GPOS - Glyph Positioning + /////////////////////////////////// + if (!isset($this->tables["GPOS"])) { + return [[], [], []]; + } + + $ffeats = []; + $gpos_offset = $this->seek_table("GPOS"); + $this->skip(4); + $ScriptList_offset = $gpos_offset + $this->read_ushort(); + $FeatureList_offset = $gpos_offset + $this->read_ushort(); + $LookupList_offset = $gpos_offset + $this->read_ushort(); + + // ScriptList + $this->seek($ScriptList_offset); + $ScriptCount = $this->read_ushort(); + for ($i = 0; $i < $ScriptCount; $i++) { + $ScriptTag = $this->read_tag(); // = "beng", "deva" etc. + $ScriptTableOffset = $this->read_ushort(); + $ffeats[$ScriptTag] = $ScriptList_offset + $ScriptTableOffset; + } + + // Script Table + foreach ($ffeats as $t => $o) { + $ls = []; + $this->seek($o); + $DefLangSys_offset = $this->read_ushort(); + if ($DefLangSys_offset > 0) { + $ls['DFLT'] = $DefLangSys_offset + $o; + } + $LangSysCount = $this->read_ushort(); + for ($i = 0; $i < $LangSysCount; $i++) { + $LangTag = $this->read_tag(); // = + $LangTableOffset = $this->read_ushort(); + $ls[$LangTag] = $o + $LangTableOffset; + } + $ffeats[$t] = $ls; + } + + // Get FeatureIndexList + // LangSys Table - from first listed langsys + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = []; + $langsystable_offset = $o; + $this->seek($langsystable_offset); + $LookUpOrder = $this->read_ushort(); //==NULL + $ReqFeatureIndex = $this->read_ushort(); + if ($ReqFeatureIndex != 0xFFFF) { + $FeatureIndex[] = $ReqFeatureIndex; + } + $FeatureCount = $this->read_ushort(); + for ($i = 0; $i < $FeatureCount; $i++) { + $FeatureIndex[] = $this->read_ushort(); // = index of feature + } + $ffeats[$st][$t] = $FeatureIndex; + } + } + // Feauture List => LookupListIndex es + $this->seek($FeatureList_offset); + $FeatureCount = $this->read_ushort(); + $Feature = []; + for ($i = 0; $i < $FeatureCount; $i++) { + $tag = $this->read_tag(); + if ($tag === 'kern') { + $this->haskernGPOS = true; + } + $Feature[$i] = ['tag' => $tag]; + $Feature[$i]['offset'] = $FeatureList_offset + $this->read_ushort(); + } + + for ($i = 0; $i < $FeatureCount; $i++) { + $this->seek($Feature[$i]['offset']); + $this->read_ushort(); // null + $Feature[$i]['LookupCount'] = $Lookupcount = $this->read_ushort(); + $Feature[$i]['LookupListIndex'] = []; + for ($c = 0; $c < $Lookupcount; $c++) { + $Feature[$i]['LookupListIndex'][] = $this->read_ushort(); + } + } + + foreach ($ffeats as $st => $scripts) { + foreach ($scripts as $t => $o) { + $FeatureIndex = $ffeats[$st][$t]; + foreach ($FeatureIndex as $k => $fi) { + $ffeats[$st][$t][$k] = $Feature[$fi]; + } + } + } + + $gpos = []; + $GPOSScriptLang = []; + foreach ($ffeats as $st => $scripts) { + + foreach ($scripts as $t => $langsys) { + + $lg = []; + foreach ($langsys as $ft) { + $lg[$ft['LookupListIndex'][0]] = $ft; + } + + // list of Lookups in order they need to be run i.e. order listed in Lookup table + ksort($lg); + foreach ($lg as $ft) { + $gpos[$st][$t][$ft['tag']] = $ft['LookupListIndex']; + } + if (!isset($GPOSScriptLang[$st])) { + $GPOSScriptLang[$st] = ''; + } + $GPOSScriptLang[$st] .= $t . ' '; + } + } + + // Get metadata and offsets for whole Lookup List table + $this->seek($LookupList_offset); + $LookupCount = $this->read_ushort(); + $Lookup = []; + $Offsets = []; + $SubtableCount = []; + + for ($i = 0; $i < $LookupCount; $i++) { + $Offsets[$i] = $LookupList_offset + $this->read_ushort(); + } + + for ($i = 0; $i < $LookupCount; $i++) { + $this->seek($Offsets[$i]); + $Lookup[$i]['Type'] = $this->read_ushort(); + $Lookup[$i]['Flag'] = $flag = $this->read_ushort(); + $Lookup[$i]['SubtableCount'] = $SubtableCount[$i] = $this->read_ushort(); + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $Lookup[$i]['Subtables'][$c] = $Offsets[$i] + $this->read_ushort(); + } + // MarkFilteringSet = Index (base 0) into GDEF mark glyph sets structure + if (($flag & 0x0010) === 0x0010) { + $Lookup[$i]['MarkFilteringSet'] = $this->read_ushort(); + } else { + $Lookup[$i]['MarkFilteringSet'] = ''; + } + + // Lookup Type 9: Extension + if ($Lookup[$i]['Type'] == 9) { + // Overwrites new offset (32-bit) for each subtable, and a new lookup Type + for ($c = 0; $c < $SubtableCount[$i]; $c++) { + $this->seek($Lookup[$i]['Subtables'][$c]); + $ExtensionPosFormat = $this->read_ushort(); + $type = $this->read_ushort(); + $Lookup[$i]['Subtables'][$c] = $Lookup[$i]['Subtables'][$c] + $this->read_ulong(); + } + $Lookup[$i]['Type'] = $type; + } + } + + // Process Whole LookupList - Get LuCoverage = Lookup coverage just for first glyph + $this->LuCoverage = []; + for ($i = 0; $i < $LookupCount; $i++) { + for ($c = 0; $c < $Lookup[$i]['SubtableCount']; $c++) { + $this->seek($Lookup[$i]['Subtables'][$c]); + $PosFormat = $this->read_ushort(); + + if ($Lookup[$i]['Type'] == 7 && $PosFormat == 3) { + $this->skip(4); + } elseif ($Lookup[$i]['Type'] == 8 && $PosFormat == 3) { + $BacktrackGlyphCount = $this->read_ushort(); + $this->skip(2 * $BacktrackGlyphCount + 2); + } + // NB Coverage only looks at glyphs for position 1 (i.e. 7.3 and 8.3) // NEEDS TO READ ALL ******************** + // NB For e.g. Type 4, this may be the Coverage for the Mark + $Coverage = $Lookup[$i]['Subtables'][$c] + $this->read_ushort(); + $this->seek($Coverage); + $glyphs = $this->_getCoverage(false, 2); + $this->LuCoverage[$i][$c] = $glyphs; + } + } + + $this->fontCache->jsonWrite($this->fontkey . '.GPOSdata.json', $this->LuCoverage); + + return [$GPOSScriptLang, $gpos, $Lookup]; + } + + function makeSubset($file, &$subset, $TTCfontID = 0, $debug = false, $useOTL = false) + { + $this->useOTL = $useOTL; + $this->filename = $file; + $this->fh = fopen($file, 'rb'); + + if (!$this->fh) { + throw new \Mpdf\MpdfException(sprintf('Unable to open file %s', $file)); + } + + $this->_pos = 0; + $this->charWidths = ''; + $this->glyphPos = []; + $this->charToGlyph = []; + $this->tables = []; + $this->otables = []; + $this->ascent = 0; + $this->descent = 0; + $this->strikeoutSize = 0; + $this->strikeoutPosition = 0; + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->skip(4); + $this->maxUni = 0; + + if ($TTCfontID > 0) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000], true)) { + throw new \Mpdf\MpdfException(sprintf('Error parsing TrueType Collection: version=%s - %s', $version, $file)); + } + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + $this->seek($this->TTCFonts[$TTCfontID]['offset']); + $this->version = $version = $this->read_ulong(); // TTFont version again now + } + $this->readTableDirectory($debug); + + // head - Font header table + $this->seek_table('head'); + $this->skip(50); + $indexToLocFormat = $this->read_ushort(); + $glyphDataFormat = $this->read_ushort(); + + // hhea - Horizontal header table + $this->seek_table('hhea'); + $this->skip(32); + $metricDataFormat = $this->read_ushort(); + $orignHmetrics = $numberOfHMetrics = $this->read_ushort(); + + // maxp - Maximum profile table + $this->seek_table('maxp'); + $this->skip(4); + $numGlyphs = $this->read_ushort(); + + // cmap - Character to glyph index mapping table + $cmap_offset = $this->seek_table('cmap'); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i = 0; $i < $cmapTableCount; $i++) { + $platformID = $this->read_ushort(); + $encodingID = $this->read_ushort(); + $offset = $this->read_ulong(); + $save_pos = $this->_pos; + if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } + $this->seek($save_pos); + } + + if (!$unicode_cmap_offset) { + throw new \Mpdf\MpdfException(sprintf('Font "%s" does not have Unicode cmap (platform 3, encoding 1, format 4, or platform 0 [any encoding] format 4)', $this->filename)); + } + + $glyphToChar = []; + $charToGlyph = []; + $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph); + + // Map Unmapped glyphs - from $numGlyphs + if ($useOTL) { + $bctr = 0xE000; + for ($gid = 1; $gid < $numGlyphs; $gid++) { + if (!isset($glyphToChar[$gid])) { + while (isset($charToGlyph[$bctr])) { + $bctr++; + } // Avoid overwriting a glyph already mapped in PUA + if ($bctr > 0xF8FF) { + throw new \Mpdf\MpdfException($file . " : WARNING - Font cannot map all included glyphs into Private Use Area U+E000 - U+F8FF; cannot use useOTL on this font"); + } + $glyphToChar[$gid][] = $bctr; + $charToGlyph[$bctr] = $gid; + $bctr++; + } + } + } + + $this->charToGlyph = $charToGlyph; + $this->glyphToChar = $glyphToChar; + + // hmtx - Horizontal metrics table + $scale = 1; // not used + $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale); + + // loca - Index to location + $this->getLOCA($indexToLocFormat, $numGlyphs); + + $subsetglyphs = [0 => 0, 1 => 1, 2 => 2]; + $subsetCharToGlyph = []; + foreach ($subset as $code) { + if (isset($this->charToGlyph[$code])) { + $subsetglyphs[$this->charToGlyph[$code]] = $code; // Old Glyph ID => Unicode + $subsetCharToGlyph[$code] = $this->charToGlyph[$code]; // Unicode to old GlyphID + } + $this->maxUni = max($this->maxUni, $code); + } + + list($start, $dummy) = $this->get_table_pos('glyf'); + + $glyphSet = []; + ksort($subsetglyphs); + $n = 0; + $fsLastCharIndex = 0; // maximum Unicode index (character code) in this font, according to the cmap subtable for platform ID 3 and platform- specific encoding ID 0 or 1. + foreach ($subsetglyphs as $originalGlyphIdx => $uni) { + $fsLastCharIndex = max($fsLastCharIndex, $uni); + $glyphSet[$originalGlyphIdx] = $n; // old glyphID to new glyphID + $n++; + } + + $codeToGlyph = []; + ksort($subsetCharToGlyph); + foreach ($subsetCharToGlyph as $uni => $originalGlyphIdx) { + $codeToGlyph[$uni] = $glyphSet[$originalGlyphIdx]; + } + $this->codeToGlyph = $codeToGlyph; + + ksort($subsetglyphs); + foreach ($subsetglyphs as $originalGlyphIdx => $uni) { + $this->getGlyphs($originalGlyphIdx, $start, $glyphSet, $subsetglyphs); + } + + $numGlyphs = $numberOfHMetrics = count($subsetglyphs); + + // name - table copied from the original + // MS spec says that "Platform and encoding ID's in the name table should be consistent with those in the cmap table. + // If they are not, the font will not load in Windows" + // Doesn't seem to be a problem? + $this->add('name', $this->get_table('name')); + + // tables copied from the original + $tags = ['cvt ', 'fpgm', 'prep', 'gasp']; + foreach ($tags as $tag) { + if (isset($this->tables[$tag])) { + $this->add($tag, $this->get_table($tag)); + } + } + + // post - PostScript + if (isset($this->tables['post'])) { + $opost = $this->get_table('post'); + $post = "\x00\x03\x00\x00" . substr($opost, 4, 12) . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + $this->add('post', $post); + } + + // Sort CID2GID map into segments of contiguous codes + ksort($codeToGlyph); + unset($codeToGlyph[0]); + + $rangeid = 0; + $range = []; + $prevcid = -2; + $prevglidx = -1; + + // for each character + foreach ($codeToGlyph as $cid => $glidx) { + if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) { + $range[$rangeid][] = $glidx; + } else { + // new range + $rangeid = $cid; + $range[$rangeid] = []; + $range[$rangeid][] = $glidx; + } + $prevcid = $cid; + $prevglidx = $glidx; + } + + // cmap - Character to glyph mapping + $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF + $searchRange = 1; + $entrySelector = 0; + + while ($searchRange * 2 <= $segCount) { + $searchRange *= 2; + ++$entrySelector; + } + + $searchRange *= 2; + $rangeShift = $segCount * 2 - $searchRange; + $length = 16 + (8 * $segCount) + ($numGlyphs + 1); + $cmap = [ + 0, 3, // Index : version, number of encoding subtables + 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0 + 0, 28, // Encoding Subtable : offset (hi,lo) + 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3 + 0, 28, // Encoding Subtable : offset (hi,lo) + 3, 1, // Encoding Subtable : platform (MS=3), encoding 1 + 0, 28, // Encoding Subtable : offset (hi,lo) + 4, $length, 0, // Format 4 Mapping subtable: format, length, language + $segCount * 2, + $searchRange, + $entrySelector, + $rangeShift, + ]; + + // endCode(s) + foreach ($range as $start => $subrange) { + $endCode = $start + (count($subrange) - 1); + $cmap[] = $endCode; // endCode(s) + } + + $cmap[] = 0xFFFF; // endCode of last Segment + $cmap[] = 0; // reservedPad + + // startCode(s) + foreach ($range as $start => $subrange) { + $cmap[] = $start; // startCode(s) + } + + $cmap[] = 0xFFFF; // startCode of last Segment + + // idDelta(s) + foreach ($range as $start => $subrange) { + $idDelta = -($start - $subrange[0]); + $n += count($subrange); + $cmap[] = $idDelta; // idDelta(s) + } + + $cmap[] = 1; // idDelta of last Segment + // idRangeOffset(s) + + foreach ($range as $subrange) { + $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0 + } + + $cmap[] = 0; // idRangeOffset of last Segment + foreach ($range as $subrange) { + foreach ($subrange as $glidx) { + $cmap[] = $glidx; + } + } + + $cmap[] = 0; // Mapping for last character + $cmapstr = ''; + + foreach ($cmap as $cm) { + $cmapstr .= pack('n', $cm); + } + $this->add('cmap', $cmapstr); + + // glyf - Glyph data + list($glyfOffset, $glyfLength) = $this->get_table_pos('glyf'); + if ($glyfLength < $this->maxStrLenRead) { + $glyphData = $this->get_table('glyf'); + } + + $offsets = []; + $glyf = ''; + $pos = 0; + $hmtxstr = ''; + $xMinT = 0; + $yMinT = 0; + $xMaxT = 0; + $yMaxT = 0; + $advanceWidthMax = 0; + $minLeftSideBearing = 0; + $minRightSideBearing = 0; + $xMaxExtent = 0; + $maxPoints = 0; // points in non-compound glyph + $maxContours = 0; // contours in non-compound glyph + $maxComponentPoints = 0; // points in compound glyph + $maxComponentContours = 0; // contours in compound glyph + $maxComponentElements = 0; // number of glyphs referenced at top level + $maxComponentDepth = 0; // levels of recursion, set to 0 if font has only simple glyphs + $this->glyphdata = []; + + foreach ($subsetglyphs as $originalGlyphIdx => $uni) { + // hmtx - Horizontal Metrics + $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx); + $hmtxstr .= $hm; + + $offsets[] = $pos; + $glyphPos = $this->glyphPos[$originalGlyphIdx]; + $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos; + if ($glyfLength < $this->maxStrLenRead) { + $data = substr($glyphData, $glyphPos, $glyphLen); + } else { + if ($glyphLen > 0) { + $data = $this->get_chunk($glyfOffset + $glyphPos, $glyphLen); + } else { + $data = ''; + } + } + + if ($glyphLen > 0) { + if (_RECALC_PROFILE) { + $xMin = $this->unpack_short(substr($data, 2, 2)); + $yMin = $this->unpack_short(substr($data, 4, 2)); + $xMax = $this->unpack_short(substr($data, 6, 2)); + $yMax = $this->unpack_short(substr($data, 8, 2)); + $xMinT = min($xMinT, $xMin); + $yMinT = min($yMinT, $yMin); + $xMaxT = max($xMaxT, $xMax); + $yMaxT = max($yMaxT, $yMax); + $aw = $this->unpack_short(substr($hm, 0, 2)); + $lsb = $this->unpack_short(substr($hm, 2, 2)); + $advanceWidthMax = max($advanceWidthMax, $aw); + $minLeftSideBearing = min($minLeftSideBearing, $lsb); + $minRightSideBearing = min($minRightSideBearing, ($aw - $lsb - ($xMax - $xMin))); + $xMaxExtent = max($xMaxExtent, ($lsb + ($xMax - $xMin))); + } + $up = unpack("n", substr($data, 0, 2)); + } + if ($glyphLen > 2 && ($up[1] & (1 << 15))) { // If number of contours <= -1 i.e. composiste glyph + $pos_in_glyph = 10; + $flags = GlyphOperator::MORE; + $nComponentElements = 0; + while ($flags & GlyphOperator::MORE) { + $nComponentElements += 1; // number of glyphs referenced at top level + $up = unpack("n", substr($data, $pos_in_glyph, 2)); + $flags = $up[1]; + $up = unpack("n", substr($data, $pos_in_glyph + 2, 2)); + $glyphIdx = $up[1]; + $this->glyphdata[$originalGlyphIdx]['compGlyphs'][] = $glyphIdx; + $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]); + $pos_in_glyph += 4; + if ($flags & GlyphOperator::WORDS) { + $pos_in_glyph += 4; + } else { + $pos_in_glyph += 2; + } + if ($flags & GlyphOperator::SCALE) { + $pos_in_glyph += 2; + } elseif ($flags & GlyphOperator::XYSCALE) { + $pos_in_glyph += 4; + } elseif ($flags & GlyphOperator::TWOBYTWO) { + $pos_in_glyph += 8; + } + } + $maxComponentElements = max($maxComponentElements, $nComponentElements); + + } // Simple Glyph + elseif (_RECALC_PROFILE && $glyphLen > 2 && $up[1] < (1 << 15) && $up[1] > 0) { // Number of contours > 0 simple glyph + $nContours = $up[1]; + $this->glyphdata[$originalGlyphIdx]['nContours'] = $nContours; + $maxContours = max($maxContours, $nContours); + + // Count number of points in simple glyph + $pos_in_glyph = 10 + ($nContours * 2) - 2; // Last endContourPoint + $up = unpack("n", substr($data, $pos_in_glyph, 2)); + $points = $up[1] + 1; + $this->glyphdata[$originalGlyphIdx]['nPoints'] = $points; + $maxPoints = max($maxPoints, $points); + } + + $glyf .= $data; + $pos += $glyphLen; + if ($pos % 4 != 0) { + $padding = 4 - ($pos % 4); + $glyf .= str_repeat("\0", $padding); + $pos += $padding; + } + } + + if (_RECALC_PROFILE) { + foreach ($this->glyphdata as $originalGlyphIdx => $val) { + $maxdepth = $depth = -1; + $points = 0; + $contours = 0; + $this->getGlyphData($originalGlyphIdx, $maxdepth, $depth, $points, $contours); + $maxComponentDepth = max($maxComponentDepth, $maxdepth); + $maxComponentPoints = max($maxComponentPoints, $points); + $maxComponentContours = max($maxComponentContours, $contours); + } + } + + $offsets[] = $pos; + $this->add('glyf', $glyf); + + // hmtx - Horizontal Metrics + $this->add('hmtx', $hmtxstr); + + // loca - Index to location + $locastr = ''; + if ((($pos + 1) >> 1) > 0xFFFF) { + $indexToLocFormat = 1; // long format + foreach ($offsets as $offset) { + $locastr .= pack("N", $offset); + } + } else { + $indexToLocFormat = 0; // short format + foreach ($offsets as $offset) { + $locastr .= pack("n", ($offset / 2)); + } + } + $this->add('loca', $locastr); + + // head - Font header + $head = $this->get_table('head'); + $head = $this->_set_ushort($head, 50, $indexToLocFormat); + + if (_RECALC_PROFILE) { + $head = $this->_set_short($head, 36, $xMinT); // for all glyph bounding boxes + $head = $this->_set_short($head, 38, $yMinT); // for all glyph bounding boxes + $head = $this->_set_short($head, 40, $xMaxT); // for all glyph bounding boxes + $head = $this->_set_short($head, 42, $yMaxT); // for all glyph bounding boxes + $head[17] = chr($head[17] & ~(1 << 4)); // Unset Bit 4 (as hdmx/LTSH tables not included) + } + + $this->add('head', $head); + + // hhea - Horizontal Header + $hhea = $this->get_table('hhea'); + $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics); + if (_RECALC_PROFILE) { + $hhea = $this->_set_ushort($hhea, 10, $advanceWidthMax); + $hhea = $this->_set_short($hhea, 12, $minLeftSideBearing); + $hhea = $this->_set_short($hhea, 14, $minRightSideBearing); + $hhea = $this->_set_short($hhea, 16, $xMaxExtent); + } + $this->add('hhea', $hhea); + + // maxp - Maximum Profile + $maxp = $this->get_table('maxp'); + $maxp = $this->_set_ushort($maxp, 4, $numGlyphs); + if (_RECALC_PROFILE) { + $maxp = $this->_set_ushort($maxp, 6, $maxPoints); // points in non-compound glyph + $maxp = $this->_set_ushort($maxp, 8, $maxContours); // contours in non-compound glyph + $maxp = $this->_set_ushort($maxp, 10, $maxComponentPoints); // points in compound glyph + $maxp = $this->_set_ushort($maxp, 12, $maxComponentContours); // contours in compound glyph + $maxp = $this->_set_ushort($maxp, 28, $maxComponentElements); // number of glyphs referenced at top level + $maxp = $this->_set_ushort($maxp, 30, $maxComponentDepth); // levels of recursion, set to 0 if font has only simple glyphs + } + $this->add('maxp', $maxp); + + // OS/2 - OS/2 + if (isset($this->tables['OS/2'])) { + $os2_offset = $this->seek_table("OS/2"); + if (_RECALC_PROFILE) { + $fsSelection = $this->get_ushort($os2_offset + 62); + $fsSelection = ($fsSelection & ~(1 << 6)); // 2-byte bit field containing information concerning the nature of the font patterns + // bit#0 = Italic; bit#5=Bold + // Match name table's font subfamily string + // Clear bit#6 used for 'Regular' and optional + } + + // NB Currently this method never subsets characters above BMP + // Could set nonBMP bit according to $this->maxUni + $nonBMP = $this->get_ushort($os2_offset + 46); + $nonBMP = ($nonBMP & ~(1 << 9)); // Unset Bit 57 (indicates non-BMP) - for interactive forms + + $os2 = $this->get_table('OS/2'); + if (_RECALC_PROFILE) { + $os2 = $this->_set_ushort($os2, 62, $fsSelection); + $os2 = $this->_set_ushort($os2, 66, $fsLastCharIndex); + $os2 = $this->_set_ushort($os2, 42, 0x0000); // ulCharRange (ulUnicodeRange) bits 24-31 | 16-23 + $os2 = $this->_set_ushort($os2, 44, 0x0000); // ulCharRange (Unicode ranges) bits 8-15 | 0-7 + $os2 = $this->_set_ushort($os2, 46, $nonBMP); // ulCharRange (Unicode ranges) bits 56-63 | 48-55 + $os2 = $this->_set_ushort($os2, 48, 0x0000); // ulCharRange (Unicode ranges) bits 40-47 | 32-39 + $os2 = $this->_set_ushort($os2, 50, 0x0000); // ulCharRange (Unicode ranges) bits 88-95 | 80-87 + $os2 = $this->_set_ushort($os2, 52, 0x0000); // ulCharRange (Unicode ranges) bits 72-79 | 64-71 + $os2 = $this->_set_ushort($os2, 54, 0x0000); // ulCharRange (Unicode ranges) bits 120-127 | 112-119 + $os2 = $this->_set_ushort($os2, 56, 0x0000); // ulCharRange (Unicode ranges) bits 104-111 | 96-103 + } + $os2 = $this->_set_ushort($os2, 46, $nonBMP); // Unset Bit 57 (indicates non-BMP) - for interactive forms + + $this->add('OS/2', $os2); + } + + fclose($this->fh); + + // Put the TTF file together + $stm = ''; + $this->endTTFile($stm); + + return $stm; + } + + function makeSubsetSIP($file, &$subset, $TTCfontID = 0, $debug = false, $useOTL = 0) + { + $this->fh = fopen($file, 'rb'); + + if (!$this->fh) { + throw new \Mpdf\MpdfException(sprintf('Unable to open file "%s"', $file)); + } + + $this->filename = $file; + $this->_pos = 0; + $this->useOTL = $useOTL; // mPDF 5.7.1 + $this->charWidths = ''; + $this->glyphPos = []; + $this->charToGlyph = []; + $this->tables = []; + $this->otables = []; + $this->ascent = 0; + $this->descent = 0; + $this->strikeoutSize = 0; + $this->strikeoutPosition = 0; + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->skip(4); + + if ($TTCfontID > 0) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000])) { + throw new \Mpdf\MpdfException("ERROR - Error parsing TrueType Collection: version=" . $version . " - " . $file); + } + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + $this->seek($this->TTCFonts[$TTCfontID]['offset']); + $this->version = $version = $this->read_ulong(); // TTFont version again now + } + $this->readTableDirectory($debug); + + // head - Font header table + $this->seek_table('head'); + $this->skip(50); + $indexToLocFormat = $this->read_ushort(); + $glyphDataFormat = $this->read_ushort(); + + // hhea - Horizontal header table + $this->seek_table('hhea'); + $this->skip(32); + $metricDataFormat = $this->read_ushort(); + $orignHmetrics = $numberOfHMetrics = $this->read_ushort(); + + // maxp - Maximum profile table + $this->seek_table('maxp'); + $this->skip(4); + $numGlyphs = $this->read_ushort(); + + // cmap - Character to glyph index mapping table + $cmap_offset = $this->seek_table('cmap'); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i = 0; $i < $cmapTableCount; $i++) { + + $platformID = $this->read_ushort(); + $encodingID = $this->read_ushort(); + $offset = $this->read_ulong(); + $save_pos = $this->_pos; + + if (($platformID == 3 && $encodingID == 10) || $platformID == 0) { // Microsoft, Unicode Format 12 table HKCS + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 12) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } + + if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + $unicode_cmap_offset = $cmap_offset + $offset; + } + } + + $this->seek($save_pos); + } + + if (!$unicode_cmap_offset) { + throw new \Mpdf\MpdfException(sprintf('Font "%s" does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)', $file)); + } + + // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above + if ($format == 12) { + $this->maxUniChar = 0; + $this->seek($unicode_cmap_offset + 4); + $length = $this->read_ulong(); + $limit = $unicode_cmap_offset + $length; + $this->skip(4); + + $nGroups = $this->read_ulong(); + + $glyphToChar = []; + $charToGlyph = []; + for ($i = 0; $i < $nGroups; $i++) { + $startCharCode = $this->read_ulong(); + $endCharCode = $this->read_ulong(); + $startGlyphCode = $this->read_ulong(); + $offset = 0; + for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) { + $glyph = $startGlyphCode + $offset; + $offset++; + // ZZZ98 + if ($unichar < 0x30000) { + $charToGlyph[$unichar] = $glyph; + $this->maxUniChar = max($unichar, $this->maxUniChar); + $glyphToChar[$glyph][] = $unichar; + } + } + } + } else { + $glyphToChar = []; + $charToGlyph = []; + $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph); + } + + // Map Unmapped glyphs - from $numGlyphs + if ($useOTL) { + $bctr = 0xE000; + for ($gid = 1; $gid < $numGlyphs; $gid++) { + if (!isset($glyphToChar[$gid])) { + while (isset($charToGlyph[$bctr])) { + $bctr++; + } // Avoid overwriting a glyph already mapped in PUA + // ZZZ98 + if ($bctr > 0xF8FF && $bctr < 0x2CEB0) { + $bctr = 0x2CEB0; + while (isset($charToGlyph[$bctr])) { + $bctr++; + } + } + $glyphToChar[$gid][] = $bctr; + $charToGlyph[$bctr] = $gid; + $this->maxUniChar = max($bctr, $this->maxUniChar); + $bctr++; + } + } + } + + // hmtx - Horizontal metrics table + $scale = 1; // not used here + $this->getHMTX($numberOfHMetrics, $numGlyphs, $glyphToChar, $scale); + + // loca - Index to location + $this->getLOCA($indexToLocFormat, $numGlyphs); + + $glyphMap = [0 => 0]; + $glyphSet = [0 => 0]; + $codeToGlyph = []; + + // Set a substitute if ASCII characters do not have glyphs + if (isset($charToGlyph[0x3F])) { + $subs = $charToGlyph[0x3F]; + } else { // Question mark + $subs = $charToGlyph[32]; + } + + foreach ($subset as $code) { + if (isset($charToGlyph[$code])) { + $originalGlyphIdx = $charToGlyph[$code]; + } elseif ($code < 128) { + $originalGlyphIdx = $subs; + } else { + $originalGlyphIdx = 0; + } + if (!isset($glyphSet[$originalGlyphIdx])) { + $glyphSet[$originalGlyphIdx] = count($glyphMap); + $glyphMap[] = $originalGlyphIdx; + } + $codeToGlyph[$code] = $glyphSet[$originalGlyphIdx]; + } + + list($start, $dummy) = $this->get_table_pos('glyf'); + + $n = 0; + while ($n < count($glyphMap)) { + $originalGlyphIdx = $glyphMap[$n]; + $glyphPos = $this->glyphPos[$originalGlyphIdx]; + $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos; + ++$n; + if (!$glyphLen) { + continue; + } + $this->seek($start + $glyphPos); + $numberOfContours = $this->read_short(); + if ($numberOfContours < 0) { + $this->skip(8); + $flags = GlyphOperator::MORE; + while ($flags & GlyphOperator::MORE) { + $flags = $this->read_ushort(); + $glyphIdx = $this->read_ushort(); + if (!isset($glyphSet[$glyphIdx])) { + $glyphSet[$glyphIdx] = count($glyphMap); + $glyphMap[] = $glyphIdx; + } + if ($flags & GlyphOperator::WORDS) { + $this->skip(4); + } else { + $this->skip(2); + } + if ($flags & GlyphOperator::SCALE) { + $this->skip(2); + } elseif ($flags & GlyphOperator::XYSCALE) { + $this->skip(4); + } elseif ($flags & GlyphOperator::TWOBYTWO) { + $this->skip(8); + } + } + } + } + + $numGlyphs = $n = count($glyphMap); + $numberOfHMetrics = $n; + + // MS spec says that "Platform and encoding ID's in the name table should be consistent with those in the cmap table. + // If they are not, the font will not load in Windows" + // Doesn't seem to be a problem? + // Needs to have a name entry in 3,0 (e.g. symbol) - original font will be 3,1 (i.e. Unicode) + $name = $this->get_table('name'); + $name_offset = $this->seek_table("name"); + $format = $this->read_ushort(); + $numRecords = $this->read_ushort(); + $string_data_offset = $name_offset + $this->read_ushort(); + for ($i = 0; $i < $numRecords; $i++) { + $platformId = $this->read_ushort(); + $encodingId = $this->read_ushort(); + if ($platformId == 3 && $encodingId == 1) { + $pos = 6 + ($i * 12) + 2; + $name = $this->_set_ushort($name, $pos, 0x00); // Change encoding to 3,0 rather than 3,1 + } + $this->skip(8); + } + $this->add('name', $name); + + // OS/2 + if (isset($this->tables['OS/2'])) { + $os2 = $this->get_table('OS/2'); + $os2 = $this->_set_ushort($os2, 42, 0x00); // ulCharRange (Unicode ranges) + $os2 = $this->_set_ushort($os2, 44, 0x00); // ulCharRange (Unicode ranges) + $os2 = $this->_set_ushort($os2, 46, 0x00); // ulCharRange (Unicode ranges) + $os2 = $this->_set_ushort($os2, 48, 0x00); // ulCharRange (Unicode ranges) + + $os2 = $this->_set_ushort($os2, 50, 0x00); // ulCharRange (Unicode ranges) + $os2 = $this->_set_ushort($os2, 52, 0x00); // ulCharRange (Unicode ranges) + $os2 = $this->_set_ushort($os2, 54, 0x00); // ulCharRange (Unicode ranges) + $os2 = $this->_set_ushort($os2, 56, 0x00); // ulCharRange (Unicode ranges) + // Set Symbol character only in ulCodePageRange + $os2 = $this->_set_ushort($os2, 78, 0x8000); // ulCodePageRange = Bit #31 Symbol **** 78 = Bit 16-31 + $os2 = $this->_set_ushort($os2, 80, 0x0000); // ulCodePageRange = Bit #31 Symbol **** 80 = Bit 0-15 + $os2 = $this->_set_ushort($os2, 82, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 82 = Bits 48-63 + $os2 = $this->_set_ushort($os2, 84, 0x0000); // ulCodePageRange = Bit #32- Symbol **** 84 = Bits 32-47 + + $os2 = $this->_set_ushort($os2, 64, 0x01); // FirstCharIndex + $os2 = $this->_set_ushort($os2, 66, count($subset)); // LastCharIndex + // Set PANOSE first bit to 5 for Symbol + $os2 = $this->splice($os2, 32, chr(5) . chr(0) . chr(1) . chr(0) . chr(1) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0)); + $this->add('OS/2', $os2); + } + + //tables copied from the original + $tags = ['cvt ', 'fpgm', 'prep', 'gasp']; + foreach ($tags as $tag) { // 1.02 + if (isset($this->tables[$tag])) { + $this->add($tag, $this->get_table($tag)); + } + } + + // post - PostScript + if (isset($this->tables['post'])) { + $opost = $this->get_table('post'); + $post = "\x00\x03\x00\x00" . substr($opost, 4, 12) . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + } + $this->add('post', $post); + + // hhea - Horizontal Header + $hhea = $this->get_table('hhea'); + $hhea = $this->_set_ushort($hhea, 34, $numberOfHMetrics); + $this->add('hhea', $hhea); + + // maxp - Maximum Profile + $maxp = $this->get_table('maxp'); + $maxp = $this->_set_ushort($maxp, 4, $numGlyphs); + $this->add('maxp', $maxp); + + // CMap table Formats [1,0,]6 and [3,0,]4 + // Sort CID2GID map into segments of contiguous codes + $rangeid = 0; + $range = []; + $prevcid = -2; + $prevglidx = -1; + + // for each character + foreach ($subset as $cid => $code) { + $glidx = $codeToGlyph[$code]; + if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) { + $range[$rangeid][] = $glidx; + } else { + // new range + $rangeid = $cid; + $range[$rangeid] = []; + $range[$rangeid][] = $glidx; + } + $prevcid = $cid; + $prevglidx = $glidx; + } + + // cmap - Character to glyph mapping + $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF + $searchRange = 1; + $entrySelector = 0; + + while ($searchRange * 2 <= $segCount) { + $searchRange = $searchRange * 2; + $entrySelector = $entrySelector + 1; + } + + $searchRange = $searchRange * 2; + $rangeShift = $segCount * 2 - $searchRange; + $length = 16 + (8 * $segCount) + ($numGlyphs + 1); + $cmap = [ + 4, $length, 0, // Format 4 Mapping subtable: format, length, language + $segCount * 2, + $searchRange, + $entrySelector, + $rangeShift, + ]; + + // endCode(s) + foreach ($range as $start => $subrange) { + $endCode = $start + (count($subrange) - 1); + $cmap[] = $endCode; // endCode(s) + } + $cmap[] = 0xFFFF; // endCode of last Segment + $cmap[] = 0; // reservedPad + + // startCode(s) + foreach ($range as $start => $subrange) { + $cmap[] = $start; // startCode(s) + } + $cmap[] = 0xFFFF; // startCode of last Segment + + // idDelta(s) + foreach ($range as $start => $subrange) { + $idDelta = -($start - $subrange[0]); + $n += count($subrange); + $cmap[] = $idDelta; // idDelta(s) + } + $cmap[] = 1; // idDelta of last Segment + + // idRangeOffset(s) + foreach ($range as $subrange) { + $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0 + } + + $cmap[] = 0; // idRangeOffset of last Segment + foreach ($range as $subrange) { + foreach ($subrange as $glidx) { + $cmap[] = $glidx; + } + } + + $cmap[] = 0; // Mapping for last character + $cmapstr4 = ''; + foreach ($cmap as $cm) { + $cmapstr4 .= pack("n", $cm); + } + + // cmap - Character to glyph mapping + $entryCount = count($subset); + $length = 10 + $entryCount * 2; + + $off = 20 + $length; + $hoff = $off >> 16; + $loff = $off & 0xFFFF; + + $cmap = [ + 0, 2, // Index : version, number of subtables + 1, 0, // Subtable : platform, encoding + 0, 20, // offset (hi,lo) + 3, 0, // Subtable : platform, encoding // See note above for 'name' + $hoff, $loff, // offset (hi,lo) + 6, $length, // Format 6 Mapping table: format, length + 0, 1, // language, First char code + $entryCount, + ]; + + $cmapstr = ''; + foreach ($subset as $code) { + $cmap[] = $codeToGlyph[$code]; + } + + foreach ($cmap as $cm) { + $cmapstr .= pack("n", $cm); + } + + $cmapstr .= $cmapstr4; + $this->add('cmap', $cmapstr); + + // hmtx - Horizontal Metrics + $hmtxstr = ''; + for ($n = 0; $n < $numGlyphs; $n++) { + $originalGlyphIdx = $glyphMap[$n]; + $hm = $this->getHMetric($orignHmetrics, $originalGlyphIdx); + $hmtxstr .= $hm; + } + $this->add('hmtx', $hmtxstr); + + // glyf - Glyph data + list($glyfOffset, $glyfLength) = $this->get_table_pos('glyf'); + if ($glyfLength < $this->maxStrLenRead) { + $glyphData = $this->get_table('glyf'); + } + + $offsets = []; + $glyf = ''; + $pos = 0; + for ($n = 0; $n < $numGlyphs; $n++) { + + $offsets[] = $pos; + $originalGlyphIdx = $glyphMap[$n]; + $glyphPos = $this->glyphPos[$originalGlyphIdx]; + $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos; + + if ($glyfLength < $this->maxStrLenRead) { + $data = substr($glyphData, $glyphPos, $glyphLen); + } else { + if ($glyphLen > 0) { + $data = $this->get_chunk($glyfOffset + $glyphPos, $glyphLen); + } else { + $data = ''; + } + } + + if ($glyphLen > 0) { + $up = unpack('n', substr($data, 0, 2)); + } + + if ($glyphLen > 2 && ($up[1] & (1 << 15))) { + + $pos_in_glyph = 10; + $flags = GlyphOperator::MORE; + + while ($flags & GlyphOperator::MORE) { + $up = unpack('n', substr($data, $pos_in_glyph, 2)); + $flags = $up[1]; + $up = unpack('n', substr($data, $pos_in_glyph + 2, 2)); + $glyphIdx = $up[1]; + $data = $this->_set_ushort($data, $pos_in_glyph + 2, $glyphSet[$glyphIdx]); + $pos_in_glyph += 4; + + if ($flags & GlyphOperator::WORDS) { + $pos_in_glyph += 4; + } else { + $pos_in_glyph += 2; + } + + if ($flags & GlyphOperator::SCALE) { + $pos_in_glyph += 2; + } elseif ($flags & GlyphOperator::XYSCALE) { + $pos_in_glyph += 4; + } elseif ($flags & GlyphOperator::TWOBYTWO) { + $pos_in_glyph += 8; + } + } + } + + $glyf .= $data; + $pos += $glyphLen; + + if ($pos % 4 != 0) { + $padding = 4 - ($pos % 4); + $glyf .= str_repeat("\0", $padding); + $pos += $padding; + } + } + + $offsets[] = $pos; + $this->add('glyf', $glyf); + + // loca - Index to location + $locastr = ''; + if ((($pos + 1) >> 1) > 0xFFFF) { + $indexToLocFormat = 1; // long format + foreach ($offsets as $offset) { + $locastr .= pack("N", $offset); + } + } else { + $indexToLocFormat = 0; // short format + foreach ($offsets as $offset) { + $locastr .= pack("n", ($offset / 2)); + } + } + + $this->add('loca', $locastr); + + // head - Font header + $head = $this->get_table('head'); + $head = $this->_set_ushort($head, 50, $indexToLocFormat); + $this->add('head', $head); + + fclose($this->fh); + + $stm = ''; + $this->endTTFile($stm); + + return $stm; + } + + function getGlyphData($originalGlyphIdx, &$maxdepth, &$depth, &$points, &$contours) + { + $depth++; + $maxdepth = max($maxdepth, $depth); + + if (count($this->glyphdata[$originalGlyphIdx]['compGlyphs'])) { + foreach ($this->glyphdata[$originalGlyphIdx]['compGlyphs'] as $glyphIdx) { + $this->getGlyphData($glyphIdx, $maxdepth, $depth, $points, $contours); + } + } elseif (($this->glyphdata[$originalGlyphIdx]['nContours'] > 0) && $depth > 0) { // simple + $contours += $this->glyphdata[$originalGlyphIdx]['nContours']; + $points += $this->glyphdata[$originalGlyphIdx]['nPoints']; + } + + $depth--; + } + + function getGlyphs($originalGlyphIdx, &$start, &$glyphSet, &$subsetglyphs) + { + $glyphPos = $this->glyphPos[$originalGlyphIdx]; + $glyphLen = $this->glyphPos[$originalGlyphIdx + 1] - $glyphPos; + + if (!$glyphLen) { + return; + } + + $this->seek($start + $glyphPos); + $numberOfContours = $this->read_short(); + + if ($numberOfContours < 0) { + $this->skip(8); + $flags = GlyphOperator::MORE; + while ($flags & GlyphOperator::MORE) { + $flags = $this->read_ushort(); + $glyphIdx = $this->read_ushort(); + if (!isset($glyphSet[$glyphIdx])) { + $glyphSet[$glyphIdx] = count($subsetglyphs); // old glyphID to new glyphID + $subsetglyphs[$glyphIdx] = true; + } + $savepos = ftell($this->fh); + $this->getGlyphs($glyphIdx, $start, $glyphSet, $subsetglyphs); + $this->seek($savepos); + if ($flags & GlyphOperator::WORDS) { + $this->skip(4); + } else { + $this->skip(2); + } + if ($flags & GlyphOperator::SCALE) { + $this->skip(2); + } elseif ($flags & GlyphOperator::XYSCALE) { + $this->skip(4); + } elseif ($flags & GlyphOperator::TWOBYTWO) { + $this->skip(8); + } + } + } + } + + function getHMTX($numberOfHMetrics, $numGlyphs, &$glyphToChar, $scale) + { + $start = $this->seek_table('hmtx'); + $aw = 0; + $this->charWidths = str_pad('', 256 * 256 * 2, "\x00"); + + if ($this->maxUniChar > 65536) { + $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00"); + } // Plane 1 SMP + + if ($this->maxUniChar > 131072) { + $this->charWidths .= str_pad('', 256 * 256 * 2, "\x00"); + } // Plane 2 SMP + + $nCharWidths = 0; + if (($numberOfHMetrics * 4) < $this->maxStrLenRead) { + $data = $this->get_chunk($start, $numberOfHMetrics * 4); + $arr = unpack('n*', $data); + } else { + $this->seek($start); + } + + for ($glyph = 0; $glyph < $numberOfHMetrics; $glyph++) { + + if (($numberOfHMetrics * 4) < $this->maxStrLenRead) { + $aw = $arr[($glyph * 2) + 1]; + } else { + $aw = $this->read_ushort(); + $lsb = $this->read_ushort(); + } + if (isset($glyphToChar[$glyph]) || $glyph == 0) { + if ($aw >= (1 << 15)) { + $aw = 0; + } + + // 1.03 Some (arabic) fonts have -ve values for width + // although should be unsigned value - comes out as e.g. 65108 (intended -50) + if ($glyph === 0) { + $this->defaultWidth = $scale * $aw; + continue; + } + + foreach ($glyphToChar[$glyph] as $char) { + if ($char != 0 && $char != 65535) { + $w = (int) round($scale * $aw); + if ($w === 0) { + $w = 65535; + } + if ($char < 196608) { + $this->charWidths[$char * 2] = chr($w >> 8); + $this->charWidths[$char * 2 + 1] = chr($w & 0xFF); + $nCharWidths++; + } + } + } + } + } + + $data = $this->get_chunk(($start + $numberOfHMetrics * 4), ($numGlyphs * 2)); + $arr = unpack("n*", $data); + $diff = $numGlyphs - $numberOfHMetrics; + $w = (int) round($scale * $aw); + if ($w === 0) { + $w = 65535; + } + for ($pos = 0; $pos < $diff; $pos++) { + $glyph = $pos + $numberOfHMetrics; + if (isset($glyphToChar[$glyph])) { + foreach ($glyphToChar[$glyph] as $char) { + if ($char != 0 && $char != 65535) { + if ($char < 196608) { + $this->charWidths[$char * 2] = chr($w >> 8); + $this->charWidths[$char * 2 + 1] = chr($w & 0xFF); + $nCharWidths++; + } + } + } + } + } + + // NB 65535 is a set width of 0 + // First bytes define number of chars in font + $this->charWidths[0] = chr($nCharWidths >> 8); + $this->charWidths[1] = chr($nCharWidths & 0xFF); + } + + function getHMetric($numberOfHMetrics, $gid) + { + $start = $this->seek_table("hmtx"); + if ($gid < $numberOfHMetrics) { + $this->seek($start + ($gid * 4)); + $hm = fread($this->fh, 4); + } else { + $this->seek($start + (($numberOfHMetrics - 1) * 4)); + $hm = fread($this->fh, 2); + $this->seek($start + ($numberOfHMetrics * 2) + ($gid * 2)); + $hm .= fread($this->fh, 2); + } + + return $hm; + } + + function getLOCA($indexToLocFormat, $numGlyphs) + { + $start = $this->seek_table('loca'); + $this->glyphPos = []; + if ($indexToLocFormat == 0) { + $data = $this->get_chunk($start, ($numGlyphs * 2) + 2); + $arr = unpack("n*", $data); + for ($n = 0; $n <= $numGlyphs; $n++) { + $this->glyphPos[] = ($arr[$n + 1] * 2); + } + } elseif ($indexToLocFormat == 1) { + $data = $this->get_chunk($start, ($numGlyphs * 4) + 4); + $arr = unpack("N*", $data); + for ($n = 0; $n <= $numGlyphs; $n++) { + $this->glyphPos[] = ($arr[$n + 1]); + } + } else { + throw new \Mpdf\MpdfException('Unknown location table format ' . $indexToLocFormat); + } + } + + /** + * CMAP Format 4 + */ + function getCMAP4($unicode_cmap_offset, &$glyphToChar, &$charToGlyph) + { + $this->maxUniChar = 0; + $this->seek($unicode_cmap_offset + 2); + $length = $this->read_ushort(); + $limit = $unicode_cmap_offset + $length; + $this->skip(2); + + $segCount = $this->read_ushort() / 2; + $this->skip(6); + $endCount = []; + + for ($i = 0; $i < $segCount; $i++) { + $endCount[] = $this->read_ushort(); + } + + $this->skip(2); + $startCount = []; + + for ($i = 0; $i < $segCount; $i++) { + $startCount[] = $this->read_ushort(); + } + + $idDelta = []; + + for ($i = 0; $i < $segCount; $i++) { + $idDelta[] = $this->read_short(); + } // ???? was unsigned short + + $idRangeOffset_start = $this->_pos; + $idRangeOffset = []; + + for ($i = 0; $i < $segCount; $i++) { + $idRangeOffset[] = $this->read_ushort(); + } + + for ($n = 0; $n < $segCount; $n++) { + $endpoint = ($endCount[$n] + 1); + for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) { + if ($idRangeOffset[$n] == 0) { + $glyph = ($unichar + $idDelta[$n]) & 0xFFFF; + } else { + $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n]; + $offset = $idRangeOffset_start + 2 * $n + $offset; + if ($offset >= $limit) { + $glyph = 0; + } else { + $glyph = $this->get_ushort($offset); + if ($glyph != 0) { + $glyph = ($glyph + $idDelta[$n]) & 0xFFFF; + } + } + } + $charToGlyph[$unichar] = $glyph; + if ($unichar < 196608) { + $this->maxUniChar = max($unichar, $this->maxUniChar); + } + $glyphToChar[$glyph][] = $unichar; + } + } + } + + function endTTFile(&$stm) + { + $stm = ''; + $numTables = count($this->otables); + $searchRange = 1; + $entrySelector = 0; + while ($searchRange * 2 <= $numTables) { + $searchRange *= 2; + $entrySelector += 1; + } + $searchRange *= 16; + $rangeShift = $numTables * 16 - $searchRange; + + // Header + if (_TTF_MAC_HEADER) { + $stm .= pack('Nnnnn', 0x74727565, $numTables, $searchRange, $entrySelector, $rangeShift); // Mac + } else { + $stm .= pack('Nnnnn', 0x00010000, $numTables, $searchRange, $entrySelector, $rangeShift); // Windows + } + + // Table directory + $tables = $this->otables; + ksort($tables); + $offset = 12 + $numTables * 16; + foreach ($tables as $tag => $data) { + if ($tag === 'head') { + $head_start = $offset; + } + $stm .= $tag; + $checksum = $this->calcChecksum($data); + $stm .= pack('nn', $checksum[0], $checksum[1]); + $stm .= pack('NN', $offset, strlen($data)); + $paddedLength = (strlen($data) + 3) & ~3; + $offset += $paddedLength; + } + + // Table data + foreach ($tables as $tag => $data) { + $data .= "\0\0\0"; + $stm .= substr($data, 0, (strlen($data) & ~3)); + } + + $checksum = $this->calcChecksum($stm); + $checksum = $this->sub32([0xB1B0, 0xAFBA], $checksum); + $chk = pack("nn", $checksum[0], $checksum[1]); + $stm = $this->splice($stm, ($head_start + 8), $chk); + + return $stm; + } + + function repackageTTF($file, $TTCfontID = 0, $debug = false, $useOTL = false) + { + $this->useOTL = $useOTL; + $this->filename = $file; + $this->fh = fopen($file, 'rb'); + + if (!$this->fh) { + throw new \Mpdf\MpdfException(sprintf('Unable to open file "%s"', $file)); + } + + $this->_pos = 0; + $this->charWidths = ''; + $this->glyphPos = []; + $this->charToGlyph = []; + $this->tables = []; + $this->otables = []; + $this->ascent = 0; + $this->descent = 0; + $this->strikeoutSize = 0; + $this->strikeoutPosition = 0; + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->skip(4); + $this->maxUni = 0; + + if ($TTCfontID > 0) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000], true)) { + throw new \Mpdf\MpdfException(sprintf('Error parsing TrueType Collection: version=%s - %s', $version, $file)); + } + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + $this->seek($this->TTCFonts[$TTCfontID]['offset']); + $this->version = $version = $this->read_ulong(); // TTFont version again now + } + + $this->readTableDirectory($debug); + $tags = ['OS/2', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'cvt ', 'fpgm', 'gasp', 'prep']; + + foreach ($tags as $tag) { + if (isset($this->tables[$tag])) { + $this->add($tag, $this->get_table($tag)); + } + } + + if ($useOTL) { + + // maxp - Maximum profile table + $this->seek_table('maxp'); + $this->skip(4); + $numGlyphs = $this->read_ushort(); + + // cmap - Character to glyph index mapping table + $cmap_offset = $this->seek_table('cmap'); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i = 0; $i < $cmapTableCount; $i++) { + $platformID = $this->read_ushort(); + $encodingID = $this->read_ushort(); + $offset = $this->read_ulong(); + $save_pos = $this->_pos; + if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } + $this->seek($save_pos); + } + + if (!$unicode_cmap_offset) { + throw new \Mpdf\MpdfException(sprintf('Font "%s" does not have cmap for Unicode (platform 3, encoding 1, format 4, or platform 0, any encoding, format 4)', $this->filename)); + } + + $glyphToChar = []; + $charToGlyph = []; + $this->getCMAP4($unicode_cmap_offset, $glyphToChar, $charToGlyph); + + // Map Unmapped glyphs - from $numGlyphs + $bctr = 0xE000; + for ($gid = 1; $gid < $numGlyphs; $gid++) { + if (!isset($glyphToChar[$gid])) { + while (isset($charToGlyph[$bctr])) { + $bctr++; + } // Avoid overwriting a glyph already mapped in PUA (6,400) + if ($bctr > 0xF8FF) { + throw new \Mpdf\MpdfException("Problem. Trying to repackage TF file; not enough space for unmapped glyphs"); + } + $glyphToChar[$gid][] = $bctr; + $charToGlyph[$bctr] = $gid; + $bctr++; + } + } + + // Sort CID2GID map into segments of contiguous codes + unset($charToGlyph[65535]); + unset($charToGlyph[0]); + + ksort($charToGlyph); + $rangeid = 0; + $range = []; + $prevcid = -2; + $prevglidx = -1; + + // for each character + foreach ($charToGlyph as $cid => $glidx) { + if ($cid == ($prevcid + 1) && $glidx == ($prevglidx + 1)) { + $range[$rangeid][] = $glidx; + } else { + // new range + $rangeid = $cid; + $range[$rangeid] = []; + $range[$rangeid][] = $glidx; + } + $prevcid = $cid; + $prevglidx = $glidx; + } + + // CMap table + // cmap - Character to glyph mapping + $segCount = count($range) + 1; // + 1 Last segment has missing character 0xFFFF + $searchRange = 1; + $entrySelector = 0; + + while ($searchRange * 2 <= $segCount) { + $searchRange *= 2; + ++$entrySelector; + } + + $searchRange *= 2; + $rangeShift = $segCount * 2 - $searchRange; + $length = 16 + (8 * $segCount) + ($numGlyphs + 1); + $cmap = [0, 3, // Index : version, number of encoding subtables + 0, 0, // Encoding Subtable : platform (UNI=0), encoding 0 + 0, 28, // Encoding Subtable : offset (hi,lo) + 0, 3, // Encoding Subtable : platform (UNI=0), encoding 3 + 0, 28, // Encoding Subtable : offset (hi,lo) + 3, 1, // Encoding Subtable : platform (MS=3), encoding 1 + 0, 28, // Encoding Subtable : offset (hi,lo) + 4, $length, 0, // Format 4 Mapping subtable: format, length, language + $segCount * 2, + $searchRange, + $entrySelector, + $rangeShift]; + + // endCode(s) + foreach ($range as $start => $subrange) { + $endCode = $start + (count($subrange) - 1); + $cmap[] = $endCode; // endCode(s) + } + $cmap[] = 0xFFFF; // endCode of last Segment + $cmap[] = 0; // reservedPad + + // startCode(s) + foreach ($range as $start => $subrange) { + $cmap[] = $start; // startCode(s) + } + $cmap[] = 0xFFFF; // startCode of last Segment + + // idDelta(s) + foreach ($range as $start => $subrange) { + $idDelta = -($start - $subrange[0]); + $cmap[] = $idDelta; // idDelta(s) + } + + $cmap[] = 1; // idDelta of last Segment + // idRangeOffset(s) + foreach ($range as $subrange) { + $cmap[] = 0; // idRangeOffset[segCount] Offset in bytes to glyph indexArray, or 0 + } + + $cmap[] = 0; // idRangeOffset of last Segment + foreach ($range as $subrange) { + foreach ($subrange as $glidx) { + $cmap[] = $glidx; + } + } + $cmap[] = 0; // Mapping for last character + $cmapstr = ''; + foreach ($cmap as $cm) { + $cmapstr .= pack('n', $cm); + } + + $this->add('cmap', $cmapstr); + } else { + $this->add('cmap', $this->get_table('cmap')); + } + + fclose($this->fh); + $stm = ''; + $this->endTTFile($stm); + + return $stm; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/TTFontFileAnalysis.php b/lib/MPDF/vendor/mpdf/mpdf/src/TTFontFileAnalysis.php new file mode 100644 index 0000000..2851ea0 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/TTFontFileAnalysis.php @@ -0,0 +1,454 @@ +<?php + +namespace Mpdf; + +class TTFontFileAnalysis extends TTFontFile +{ + + // Used to get font information from files in directory + function extractCoreInfo($file, $TTCfontID = 0) + { + $this->filename = $file; + $this->fh = fopen($file, 'rb'); + if (!$this->fh) { + throw new \Mpdf\MpdfException('ERROR - Can\'t open file ' . $file); + } + $this->_pos = 0; + $this->charWidths = ''; + $this->glyphPos = []; + $this->charToGlyph = []; + $this->tables = []; + $this->otables = []; + $this->ascent = 0; + $this->descent = 0; + $this->numTTCFonts = 0; + $this->TTCFonts = []; + $this->version = $version = $this->read_ulong(); + $this->panose = []; // mPDF 5.0 + + if ($version == 0x4F54544F) { + throw new \Mpdf\MpdfException('ERROR - NOT ADDED as Postscript outlines are not supported - ' . $file); + } + + if ($version == 0x74746366) { + if ($TTCfontID > 0) { + $this->version = $version = $this->read_ulong(); // TTC Header version now + if (!in_array($version, [0x00010000, 0x00020000])) { + throw new \Mpdf\MpdfException("ERROR - NOT ADDED as Error parsing TrueType Collection: version=" . $version . " - " . $file); + } + } else { + throw new \Mpdf\MpdfException("ERROR - Error parsing TrueType Collection - " . $file); + } + $this->numTTCFonts = $this->read_ulong(); + for ($i = 1; $i <= $this->numTTCFonts; $i++) { + $this->TTCFonts[$i]['offset'] = $this->read_ulong(); + } + $this->seek($this->TTCFonts[$TTCfontID]['offset']); + $this->version = $version = $this->read_ulong(); // TTFont version again now + $this->readTableDirectory(false); + } else { + if (!in_array($version, [0x00010000, 0x74727565])) { + throw new \Mpdf\MpdfException("ERROR - NOT ADDED as Not a TrueType font: version=" . $version . " - " . $file); + } + $this->readTableDirectory(false); + } + + /* Included for testing... + $cmap_offset = $this->seek_table("cmap"); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i=0;$i<$cmapTableCount;$i++) { + $x[$i]['platformId'] = $this->read_ushort(); + $x[$i]['encodingId'] = $this->read_ushort(); + $x[$i]['offset'] = $this->read_ulong(); + $save_pos = $this->_pos; + $x[$i]['format'] = $this->get_ushort($cmap_offset + $x[$i]['offset'] ); + $this->seek($save_pos ); + } + print_r($x); exit; + */ + /////////////////////////////////// + // name - Naming table + /////////////////////////////////// + + /* Test purposes - displays table of names + $name_offset = $this->seek_table("name"); + $format = $this->read_ushort(); + if ($format != 0 && $format != 1) // mPDF 5.3.73 + die("Unknown name table format ".$format); + $numRecords = $this->read_ushort(); + $string_data_offset = $name_offset + $this->read_ushort(); + for ($i=0;$i<$numRecords; $i++) { + $x[$i]['platformId'] = $this->read_ushort(); + $x[$i]['encodingId'] = $this->read_ushort(); + $x[$i]['languageId'] = $this->read_ushort(); + $x[$i]['nameId'] = $this->read_ushort(); + $x[$i]['length'] = $this->read_ushort(); + $x[$i]['offset'] = $this->read_ushort(); + + $N = ''; + if ($x[$i]['platformId'] == 1 && $x[$i]['encodingId'] == 0 && $x[$i]['languageId'] == 0) { // Roman + $opos = $this->_pos; + $N = $this->get_chunk($string_data_offset + $x[$i]['offset'] , $x[$i]['length'] ); + $this->_pos = $opos; + $this->seek($opos); + } + else { // Unicode + $opos = $this->_pos; + $this->seek($string_data_offset + $x[$i]['offset'] ); + $length = $x[$i]['length'] ; + if ($length % 2 != 0) + $length -= 1; + // die("PostScript name is UTF-16BE string of odd length"); + $length /= 2; + $N = ''; + while ($length > 0) { + $char = $this->read_ushort(); + $N .= (chr($char)); + $length -= 1; + } + $this->_pos = $opos; + $this->seek($opos); + } + $x[$i]['names'][$nameId] = $N; + } + print_r($x); exit; + */ + + $name_offset = $this->seek_table("name"); + $format = $this->read_ushort(); + if ($format != 0 && $format != 1) { // mPDF 5.3.73 + throw new \Mpdf\MpdfException("ERROR - NOT ADDED as Unknown name table format " . $format . " - " . $file); + } + $numRecords = $this->read_ushort(); + $string_data_offset = $name_offset + $this->read_ushort(); + $names = [1 => '', 2 => '', 3 => '', 4 => '', 6 => '']; + $K = array_keys($names); + $nameCount = count($names); + for ($i = 0; $i < $numRecords; $i++) { + $platformId = $this->read_ushort(); + $encodingId = $this->read_ushort(); + $languageId = $this->read_ushort(); + $nameId = $this->read_ushort(); + $length = $this->read_ushort(); + $offset = $this->read_ushort(); + if (!in_array($nameId, $K)) { + continue; + } + $N = ''; + if ($platformId == 3 && $encodingId == 1 && $languageId == 0x409) { // Microsoft, Unicode, US English, PS Name + $opos = $this->_pos; + $this->seek($string_data_offset + $offset); + if ($length % 2 != 0) { + $length += 1; + } + $length /= 2; + $N = ''; + while ($length > 0) { + $char = $this->read_ushort(); + $N .= (chr($char)); + $length -= 1; + } + $this->_pos = $opos; + $this->seek($opos); + } elseif ($platformId == 1 && $encodingId == 0 && $languageId == 0) { // Macintosh, Roman, English, PS Name + $opos = $this->_pos; + $N = $this->get_chunk($string_data_offset + $offset, $length); + $this->_pos = $opos; + $this->seek($opos); + } + if ($N && $names[$nameId] == '') { + $names[$nameId] = $N; + $nameCount -= 1; + if ($nameCount == 0) { + break; + } + } + } + if ($names[6]) { + $psName = preg_replace('/ /', '-', $names[6]); + } elseif ($names[4]) { + $psName = preg_replace('/ /', '-', $names[4]); + } elseif ($names[1]) { + $psName = preg_replace('/ /', '-', $names[1]); + } else { + $psName = ''; + } + if (!$names[1] && !$psName) { + throw new \Mpdf\MpdfException("ERROR - NOT ADDED as Could not find valid font name - " . $file); + } + $this->name = $psName; + if ($names[1]) { + $this->familyName = $names[1]; + } else { + $this->familyName = $psName; + } + if ($names[2]) { + $this->styleName = $names[2]; + } else { + $this->styleName = 'Regular'; + } + + /////////////////////////////////// + // head - Font header table + /////////////////////////////////// + $this->seek_table("head"); + $ver_maj = $this->read_ushort(); + $ver_min = $this->read_ushort(); + if ($ver_maj != 1) { + throw new \Mpdf\MpdfException('ERROR - NOT ADDED as Unknown head table version ' . $ver_maj . '.' . $ver_min . " - " . $file); + } + $this->fontRevision = $this->read_ushort() . $this->read_ushort(); + $this->skip(4); + $magic = $this->read_ulong(); + if ($magic != 0x5F0F3CF5) { + throw new \Mpdf\MpdfException('ERROR - NOT ADDED as Invalid head table magic ' . $magic . " - " . $file); + } + $this->skip(2); + $this->unitsPerEm = $unitsPerEm = $this->read_ushort(); + $scale = 1000 / $unitsPerEm; + $this->skip(24); + $macStyle = $this->read_short(); + $this->skip(4); + $indexLocFormat = $this->read_short(); + + /////////////////////////////////// + // OS/2 - OS/2 and Windows metrics table + /////////////////////////////////// + $sFamily = ''; + $panose = ''; + $fsSelection = ''; + if (isset($this->tables["OS/2"])) { + $this->seek_table("OS/2"); + $this->skip(30); + $sF = $this->read_short(); + $sFamily = ($sF >> 8); + $this->_pos += 10; //PANOSE = 10 byte length + $panose = fread($this->fh, 10); + $this->panose = []; + for ($p = 0; $p < strlen($panose); $p++) { + $this->panose[] = ord($panose[$p]); + } + $this->skip(20); + $fsSelection = $this->read_short(); + } + + /////////////////////////////////// + // post - PostScript table + /////////////////////////////////// + $this->seek_table("post"); + $this->skip(4); + $this->italicAngle = $this->read_short() + $this->read_ushort() / 65536.0; + $this->skip(4); + $isFixedPitch = $this->read_ulong(); + + + + /////////////////////////////////// + // cmap - Character to glyph index mapping table + /////////////////////////////////// + $cmap_offset = $this->seek_table("cmap"); + $this->skip(2); + $cmapTableCount = $this->read_ushort(); + $unicode_cmap_offset = 0; + for ($i = 0; $i < $cmapTableCount; $i++) { + $platformID = $this->read_ushort(); + $encodingID = $this->read_ushort(); + $offset = $this->read_ulong(); + $save_pos = $this->_pos; + if (($platformID == 3 && $encodingID == 1) || $platformID == 0) { // Microsoft, Unicode + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 4) { + if (!$unicode_cmap_offset) { + $unicode_cmap_offset = $cmap_offset + $offset; + } + } + } elseif ((($platformID == 3 && $encodingID == 10) || $platformID == 0)) { // Microsoft, Unicode Format 12 table HKCS + $format = $this->get_ushort($cmap_offset + $offset); + if ($format == 12) { + $unicode_cmap_offset = $cmap_offset + $offset; + break; + } + } + $this->seek($save_pos); + } + + if (!$unicode_cmap_offset) { + throw new \Mpdf\MpdfException('ERROR - Font (' . $this->filename . ') NOT ADDED as it is not Unicode encoded, and cannot be used by mPDF'); + } + + $rtl = false; + $indic = false; + $cjk = false; + $sip = false; + $smp = false; + $pua = false; + $puaag = false; + $glyphToChar = []; + $unAGlyphs = ''; + // Format 12 CMAP does characters above Unicode BMP i.e. some HKCS characters U+20000 and above + if ($format == 12) { + $this->seek($unicode_cmap_offset + 4); + $length = $this->read_ulong(); + $limit = $unicode_cmap_offset + $length; + $this->skip(4); + $nGroups = $this->read_ulong(); + for ($i = 0; $i < $nGroups; $i++) { + $startCharCode = $this->read_ulong(); + $endCharCode = $this->read_ulong(); + $startGlyphCode = $this->read_ulong(); + if (($endCharCode > 0x20000 && $endCharCode < 0x2A6DF) || ($endCharCode > 0x2F800 && $endCharCode < 0x2FA1F)) { + $sip = true; + } + if ($endCharCode > 0x10000 && $endCharCode < 0x1FFFF) { + $smp = true; + } + if (($endCharCode > 0x0590 && $endCharCode < 0x077F) || ($endCharCode > 0xFE70 && $endCharCode < 0xFEFF) || ($endCharCode > 0xFB50 && $endCharCode < 0xFDFF)) { + $rtl = true; + } + if ($endCharCode > 0x0900 && $endCharCode < 0x0DFF) { + $indic = true; + } + if ($endCharCode > 0xE000 && $endCharCode < 0xF8FF) { + $pua = true; + if ($endCharCode > 0xF500 && $endCharCode < 0xF7FF) { + $puaag = true; + } + } + if (($endCharCode > 0x2E80 && $endCharCode < 0x4DC0) || ($endCharCode > 0x4E00 && $endCharCode < 0xA4CF) || ($endCharCode > 0xAC00 && $endCharCode < 0xD7AF) || ($endCharCode > 0xF900 && $endCharCode < 0xFAFF) || ($endCharCode > 0xFE30 && $endCharCode < 0xFE4F)) { + $cjk = true; + } + + $offset = 0; + // Get each glyphToChar - only point if going to analyse un-mapped Arabic Glyphs + if (isset($this->tables['post'])) { + for ($unichar = $startCharCode; $unichar <= $endCharCode; $unichar++) { + $glyph = $startGlyphCode + $offset; + $offset++; + $glyphToChar[$glyph][] = $unichar; + } + } + } + } else { // Format 4 CMap + $this->seek($unicode_cmap_offset + 2); + $length = $this->read_ushort(); + $limit = $unicode_cmap_offset + $length; + $this->skip(2); + + $segCount = $this->read_ushort() / 2; + $this->skip(6); + $endCount = []; + for ($i = 0; $i < $segCount; $i++) { + $endCount[] = $this->read_ushort(); + } + $this->skip(2); + $startCount = []; + for ($i = 0; $i < $segCount; $i++) { + $startCount[] = $this->read_ushort(); + } + $idDelta = []; + for ($i = 0; $i < $segCount; $i++) { + $idDelta[] = $this->read_short(); + } + $idRangeOffset_start = $this->_pos; + $idRangeOffset = []; + for ($i = 0; $i < $segCount; $i++) { + $idRangeOffset[] = $this->read_ushort(); + } + + for ($n = 0; $n < $segCount; $n++) { + if (($endCount[$n] > 0x0590 && $endCount[$n] < 0x077F) || ($endCount[$n] > 0xFE70 && $endCount[$n] < 0xFEFF) || ($endCount[$n] > 0xFB50 && $endCount[$n] < 0xFDFF)) { + $rtl = true; + } + if ($endCount[$n] > 0x0900 && $endCount[$n] < 0x0DFF) { + $indic = true; + } + if (($endCount[$n] > 0x2E80 && $endCount[$n] < 0x4DC0) || ($endCount[$n] > 0x4E00 && $endCount[$n] < 0xA4CF) || ($endCount[$n] > 0xAC00 && $endCount[$n] < 0xD7AF) || ($endCount[$n] > 0xF900 && $endCount[$n] < 0xFAFF) || ($endCount[$n] > 0xFE30 && $endCount[$n] < 0xFE4F)) { + $cjk = true; + } + if ($endCount[$n] > 0xE000 && $endCount[$n] < 0xF8FF) { + $pua = true; + if ($endCount[$n] > 0xF500 && $endCount[$n] < 0xF7FF) { + $puaag = true; + } + } + // Get each glyphToChar - only point if going to analyse un-mapped Arabic Glyphs + if (isset($this->tables['post'])) { + $endpoint = ($endCount[$n] + 1); + for ($unichar = $startCount[$n]; $unichar < $endpoint; $unichar++) { + if ($idRangeOffset[$n] == 0) { + $glyph = ($unichar + $idDelta[$n]) & 0xFFFF; + } else { + $offset = ($unichar - $startCount[$n]) * 2 + $idRangeOffset[$n]; + $offset = $idRangeOffset_start + 2 * $n + $offset; + if ($offset >= $limit) { + $glyph = 0; + } else { + $glyph = $this->get_ushort($offset); + if ($glyph != 0) { + $glyph = ($glyph + $idDelta[$n]) & 0xFFFF; + } + } + } + $glyphToChar[$glyph][] = $unichar; + } + } + } + } + + + $bold = false; + $italic = false; + $ftype = ''; + if ($macStyle & (1 << 0)) { + $bold = true; + } // bit 0 bold + elseif ($fsSelection & (1 << 5)) { + $bold = true; + } // 5 BOLD Characters are emboldened + + if ($macStyle & (1 << 1)) { + $italic = true; + } // bit 1 italic + elseif ($fsSelection & (1 << 0)) { + $italic = true; + } // 0 ITALIC Font contains Italic characters, otherwise they are upright + elseif ($this->italicAngle <> 0) { + $italic = true; + } + + if ($isFixedPitch) { + $ftype = 'mono'; + } elseif ($sFamily > 0 && $sFamily < 8) { + $ftype = 'serif'; + } elseif ($sFamily == 8) { + $ftype = 'sans'; + } elseif ($sFamily == 10) { + $ftype = 'cursive'; + } + // Use PANOSE + if ($panose) { + $bFamilyType = ord($panose[0]); + if ($bFamilyType == 2) { + $bSerifStyle = ord($panose[1]); + if (!$ftype) { + if ($bSerifStyle > 1 && $bSerifStyle < 11) { + $ftype = 'serif'; + } elseif ($bSerifStyle > 10) { + $ftype = 'sans'; + } + } + $bProportion = ord($panose[3]); + if ($bProportion == 9 || $bProportion == 1) { + $ftype = 'mono'; + } // ==1 i.e. No Fit needed for OCR-a and -b + } elseif ($bFamilyType == 3) { + $ftype = 'cursive'; + } + } + + fclose($this->fh); + return [$this->familyName, $bold, $italic, $ftype, $TTCfontID, $rtl, $indic, $cjk, $sip, $smp, $puaag, $pua, $unAGlyphs]; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/TableOfContents.php b/lib/MPDF/vendor/mpdf/mpdf/src/TableOfContents.php new file mode 100644 index 0000000..82ce748 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/TableOfContents.php @@ -0,0 +1,908 @@ +<?php + +namespace Mpdf; + +use Mpdf\Utils\Arrays; +use DeepCopy\DeepCopy; + +class TableOfContents +{ + + private $mpdf; + + private $sizeConverter; + + var $_toc; + + var $TOCmark; + + var $TOCoutdent; // mPDF 5.6.31 + + var $TOCpreHTML; + + var $TOCpostHTML; + + var $TOCbookmarkText; + + var $TOCusePaging; + + var $TOCuseLinking; + + var $TOCorientation; + + var $TOC_margin_left; + + var $TOC_margin_right; + + var $TOC_margin_top; + + var $TOC_margin_bottom; + + var $TOC_margin_header; + + var $TOC_margin_footer; + + var $TOC_odd_header_name; + + var $TOC_even_header_name; + + var $TOC_odd_footer_name; + + var $TOC_even_footer_name; + + var $TOC_odd_header_value; + + var $TOC_even_header_value; + + var $TOC_odd_footer_value; + + var $TOC_even_footer_value; + + var $TOC_page_selector; + + var $TOC_resetpagenum; // mPDF 6 + + var $TOC_pagenumstyle; // mPDF 6 + + var $TOC_suppress; // mPDF 6 + + var $TOCsheetsize; + + var $m_TOC; + + /** + * @var bool Determine if the TOC should be cloned to calculate the correct page numbers + */ + protected $tocTocPaintBegun = false; + + public function __construct(Mpdf $mpdf, SizeConverter $sizeConverter) + { + $this->mpdf = $mpdf; + $this->sizeConverter = $sizeConverter; + + $this->_toc = []; + $this->TOCmark = 0; + $this->m_TOC = []; + } + + /** + * Mark the TOC Paint as having begun + */ + public function beginTocPaint() + { + $this->tocTocPaintBegun = true; + } + + public function TOCpagebreak( + $tocfont = '', + $tocfontsize = '', + $tocindent = '', + $TOCusePaging = true, + $TOCuseLinking = '', + $toc_orientation = '', + $toc_mgl = '', + $toc_mgr = '', + $toc_mgt = '', + $toc_mgb = '', + $toc_mgh = '', + $toc_mgf = '', + $toc_ohname = '', + $toc_ehname = '', + $toc_ofname = '', + $toc_efname = '', + $toc_ohvalue = 0, + $toc_ehvalue = 0, + $toc_ofvalue = 0, + $toc_efvalue = 0, + $toc_preHTML = '', + $toc_postHTML = '', + $toc_bookmarkText = '', + $resetpagenum = '', + $pagenumstyle = '', + $suppress = '', + $orientation = '', + $mgl = '', + $mgr = '', + $mgt = '', + $mgb = '', + $mgh = '', + $mgf = '', + $ohname = '', + $ehname = '', + $ofname = '', + $efname = '', + $ohvalue = 0, + $ehvalue = 0, + $ofvalue = 0, + $efvalue = 0, + $toc_id = 0, + $pagesel = '', + $toc_pagesel = '', + $sheetsize = '', + $toc_sheetsize = '', + $tocoutdent = '', + $toc_resetpagenum = '', + $toc_pagenumstyle = '', + $toc_suppress = '' + ) { + + if (strtoupper($toc_id) == 'ALL') { + $toc_id = '_mpdf_all'; + } elseif (!$toc_id) { + $toc_id = 0; + } else { + $toc_id = strtolower($toc_id); + } + + if ($TOCusePaging === false || strtolower($TOCusePaging) == "off" || $TOCusePaging === 0 || $TOCusePaging === "0" || $TOCusePaging === "") { + $TOCusePaging = false; + } else { + $TOCusePaging = true; + } + if (!$TOCuseLinking) { + $TOCuseLinking = false; + } + if ($toc_id) { + $this->m_TOC[$toc_id]['TOCmark'] = $this->mpdf->page; + $this->m_TOC[$toc_id]['TOCoutdent'] = $tocoutdent; + $this->m_TOC[$toc_id]['TOCorientation'] = $toc_orientation; + $this->m_TOC[$toc_id]['TOCuseLinking'] = $TOCuseLinking; + $this->m_TOC[$toc_id]['TOCusePaging'] = $TOCusePaging; + + if ($toc_preHTML) { + $this->m_TOC[$toc_id]['TOCpreHTML'] = $toc_preHTML; + } + if ($toc_postHTML) { + $this->m_TOC[$toc_id]['TOCpostHTML'] = $toc_postHTML; + } + if ($toc_bookmarkText) { + $this->m_TOC[$toc_id]['TOCbookmarkText'] = $toc_bookmarkText; + } + + $this->m_TOC[$toc_id]['TOC_margin_left'] = $toc_mgl; + $this->m_TOC[$toc_id]['TOC_margin_right'] = $toc_mgr; + $this->m_TOC[$toc_id]['TOC_margin_top'] = $toc_mgt; + $this->m_TOC[$toc_id]['TOC_margin_bottom'] = $toc_mgb; + $this->m_TOC[$toc_id]['TOC_margin_header'] = $toc_mgh; + $this->m_TOC[$toc_id]['TOC_margin_footer'] = $toc_mgf; + $this->m_TOC[$toc_id]['TOC_odd_header_name'] = $toc_ohname; + $this->m_TOC[$toc_id]['TOC_even_header_name'] = $toc_ehname; + $this->m_TOC[$toc_id]['TOC_odd_footer_name'] = $toc_ofname; + $this->m_TOC[$toc_id]['TOC_even_footer_name'] = $toc_efname; + $this->m_TOC[$toc_id]['TOC_odd_header_value'] = $toc_ohvalue; + $this->m_TOC[$toc_id]['TOC_even_header_value'] = $toc_ehvalue; + $this->m_TOC[$toc_id]['TOC_odd_footer_value'] = $toc_ofvalue; + $this->m_TOC[$toc_id]['TOC_even_footer_value'] = $toc_efvalue; + $this->m_TOC[$toc_id]['TOC_page_selector'] = $toc_pagesel; + $this->m_TOC[$toc_id]['TOC_resetpagenum'] = $toc_resetpagenum; // mPDF 6 + $this->m_TOC[$toc_id]['TOC_pagenumstyle'] = $toc_pagenumstyle; // mPDF 6 + $this->m_TOC[$toc_id]['TOC_suppress'] = $toc_suppress; // mPDF 6 + $this->m_TOC[$toc_id]['TOCsheetsize'] = $toc_sheetsize; + } else { + $this->TOCmark = $this->mpdf->page; + $this->TOCoutdent = $tocoutdent; + $this->TOCorientation = $toc_orientation; + $this->TOCuseLinking = $TOCuseLinking; + $this->TOCusePaging = $TOCusePaging; + + if ($toc_preHTML) { + $this->TOCpreHTML = $toc_preHTML; + } + if ($toc_postHTML) { + $this->TOCpostHTML = $toc_postHTML; + } + if ($toc_bookmarkText) { + $this->TOCbookmarkText = $toc_bookmarkText; + } + + $this->TOC_margin_left = $toc_mgl; + $this->TOC_margin_right = $toc_mgr; + $this->TOC_margin_top = $toc_mgt; + $this->TOC_margin_bottom = $toc_mgb; + $this->TOC_margin_header = $toc_mgh; + $this->TOC_margin_footer = $toc_mgf; + $this->TOC_odd_header_name = $toc_ohname; + $this->TOC_even_header_name = $toc_ehname; + $this->TOC_odd_footer_name = $toc_ofname; + $this->TOC_even_footer_name = $toc_efname; + $this->TOC_odd_header_value = $toc_ohvalue; + $this->TOC_even_header_value = $toc_ehvalue; + $this->TOC_odd_footer_value = $toc_ofvalue; + $this->TOC_even_footer_value = $toc_efvalue; + $this->TOC_page_selector = $toc_pagesel; + $this->TOC_resetpagenum = $toc_resetpagenum; // mPDF 6 + $this->TOC_pagenumstyle = $toc_pagenumstyle; // mPDF 6 + $this->TOC_suppress = $toc_suppress; // mPDF 6 + $this->TOCsheetsize = $toc_sheetsize; + } + } + + /** + * Initiate, and Mark a place for the Table of Contents to be inserted + */ + public function TOC( + $tocfont = '', + $tocfontsize = 0, + $tocindent = 0, + $resetpagenum = '', + $pagenumstyle = '', + $suppress = '', + $toc_orientation = '', + $TOCusePaging = true, + $TOCuseLinking = false, + $toc_id = 0, + $tocoutdent = '', + $toc_resetpagenum = '', + $toc_pagenumstyle = '', + $toc_suppress = '' + ) { + + if (strtoupper($toc_id) == 'ALL') { + $toc_id = '_mpdf_all'; + } elseif (!$toc_id) { + $toc_id = 0; + } else { + $toc_id = strtolower($toc_id); + } + // To use odd and even pages + // Cannot start table of contents on an even page + if (($this->mpdf->mirrorMargins) && (($this->mpdf->page) % 2 == 0)) { // EVEN + if ($this->mpdf->ColActive) { + if (count($this->mpdf->columnbuffer)) { + $this->mpdf->printcolumnbuffer(); + } + } + $this->mpdf->AddPage($this->mpdf->CurOrientation, '', $resetpagenum, $pagenumstyle, $suppress); + } else { + $this->mpdf->PageNumSubstitutions[] = ['from' => $this->mpdf->page, 'reset' => $resetpagenum, 'type' => $pagenumstyle, 'suppress' => $suppress]; + } + if ($toc_id) { + $this->m_TOC[$toc_id]['TOCmark'] = $this->mpdf->page; + $this->m_TOC[$toc_id]['TOCoutdent'] = $tocoutdent; + $this->m_TOC[$toc_id]['TOCorientation'] = $toc_orientation; + $this->m_TOC[$toc_id]['TOCuseLinking'] = $TOCuseLinking; + $this->m_TOC[$toc_id]['TOCusePaging'] = $TOCusePaging; + $this->m_TOC[$toc_id]['TOC_resetpagenum'] = $toc_resetpagenum; // mPDF 6 + $this->m_TOC[$toc_id]['TOC_pagenumstyle'] = $toc_pagenumstyle; // mPDF 6 + $this->m_TOC[$toc_id]['TOC_suppress'] = $toc_suppress; // mPDF 6 + } else { + $this->TOCmark = $this->mpdf->page; + $this->TOCoutdent = $tocoutdent; + $this->TOCorientation = $toc_orientation; + $this->TOCuseLinking = $TOCuseLinking; + $this->TOCusePaging = $TOCusePaging; + $this->TOC_resetpagenum = $toc_resetpagenum; // mPDF 6 + $this->TOC_pagenumstyle = $toc_pagenumstyle; // mPDF 6 + $this->TOC_suppress = $toc_suppress; // mPDF 6 + } + } + + public function insertTOC() + { + /* + * Fix the TOC page numbering problem + * + * To do this, the current class is deep cloned and then the TOC functionality run. The correct page + * numbers are calculated when the TOC pages are moved into position in the cloned object (see Mpdf::MovePages). + * It's then a matter of copying the correct page numbers to the original object and letting the TOC functionality + * run as per normal. + * + * See https://github.com/mpdf/mpdf/issues/642 + */ + if (!$this->tocTocPaintBegun) { + $copier = new DeepCopy(true); + $tocClassClone = $copier->copy($this); + $tocClassClone->beginTocPaint(); + $tocClassClone->insertTOC(); + $this->_toc = $tocClassClone->_toc; + } + + $notocs = 0; + if ($this->TOCmark) { + $notocs = 1; + } + $notocs += count($this->m_TOC); + + if ($notocs == 0) { + return; + } + + if (count($this->m_TOC)) { + reset($this->m_TOC); + } + $added_toc_pages = 0; + + if ($this->mpdf->ColActive) { + $this->mpdf->SetColumns(0); + } + if (($this->mpdf->mirrorMargins) && (($this->mpdf->page) % 2 == 1)) { // ODD + $this->mpdf->AddPage($this->mpdf->CurOrientation); + $extrapage = true; + } else { + $extrapage = false; + } + + for ($toci = 0; $toci < $notocs; $toci++) { + if ($toci == 0 && $this->TOCmark) { + $toc_id = 0; + $toc_page = $this->TOCmark; + $tocoutdent = $this->TOCoutdent; + $toc_orientation = $this->TOCorientation; + $TOCuseLinking = $this->TOCuseLinking; + $TOCusePaging = $this->TOCusePaging; + $toc_preHTML = $this->TOCpreHTML; + $toc_postHTML = $this->TOCpostHTML; + $toc_bookmarkText = $this->TOCbookmarkText; + $toc_mgl = $this->TOC_margin_left; + $toc_mgr = $this->TOC_margin_right; + $toc_mgt = $this->TOC_margin_top; + $toc_mgb = $this->TOC_margin_bottom; + $toc_mgh = $this->TOC_margin_header; + $toc_mgf = $this->TOC_margin_footer; + $toc_ohname = $this->TOC_odd_header_name; + $toc_ehname = $this->TOC_even_header_name; + $toc_ofname = $this->TOC_odd_footer_name; + $toc_efname = $this->TOC_even_footer_name; + $toc_ohvalue = $this->TOC_odd_header_value; + $toc_ehvalue = $this->TOC_even_header_value; + $toc_ofvalue = $this->TOC_odd_footer_value; + $toc_efvalue = $this->TOC_even_footer_value; + $toc_page_selector = $this->TOC_page_selector; + $toc_resetpagenum = $this->TOC_resetpagenum; // mPDF 6 + $toc_pagenumstyle = $this->TOC_pagenumstyle; // mPDF 6 + $toc_suppress = $this->TOC_suppress; // mPDF 6 + $toc_sheet_size = (isset($this->TOCsheetsize) ? $this->TOCsheetsize : ''); + } else { + $arr = current($this->m_TOC); + + $toc_id = key($this->m_TOC); + $toc_page = $this->m_TOC[$toc_id]['TOCmark']; + $tocoutdent = $this->m_TOC[$toc_id]['TOCoutdent']; + $toc_orientation = $this->m_TOC[$toc_id]['TOCorientation']; + $TOCuseLinking = $this->m_TOC[$toc_id]['TOCuseLinking']; + $TOCusePaging = $this->m_TOC[$toc_id]['TOCusePaging']; + if (isset($this->m_TOC[$toc_id]['TOCpreHTML'])) { + $toc_preHTML = $this->m_TOC[$toc_id]['TOCpreHTML']; + } else { + $toc_preHTML = ''; + } + if (isset($this->m_TOC[$toc_id]['TOCpostHTML'])) { + $toc_postHTML = $this->m_TOC[$toc_id]['TOCpostHTML']; + } else { + $toc_postHTML = ''; + } + if (isset($this->m_TOC[$toc_id]['TOCbookmarkText'])) { + $toc_bookmarkText = $this->m_TOC[$toc_id]['TOCbookmarkText']; + } else { + $toc_bookmarkText = ''; + } // *BOOKMARKS* + $toc_mgl = $this->m_TOC[$toc_id]['TOC_margin_left']; + $toc_mgr = $this->m_TOC[$toc_id]['TOC_margin_right']; + $toc_mgt = $this->m_TOC[$toc_id]['TOC_margin_top']; + $toc_mgb = $this->m_TOC[$toc_id]['TOC_margin_bottom']; + $toc_mgh = $this->m_TOC[$toc_id]['TOC_margin_header']; + $toc_mgf = $this->m_TOC[$toc_id]['TOC_margin_footer']; + $toc_ohname = $this->m_TOC[$toc_id]['TOC_odd_header_name']; + $toc_ehname = $this->m_TOC[$toc_id]['TOC_even_header_name']; + $toc_ofname = $this->m_TOC[$toc_id]['TOC_odd_footer_name']; + $toc_efname = $this->m_TOC[$toc_id]['TOC_even_footer_name']; + $toc_ohvalue = $this->m_TOC[$toc_id]['TOC_odd_header_value']; + $toc_ehvalue = $this->m_TOC[$toc_id]['TOC_even_header_value']; + $toc_ofvalue = $this->m_TOC[$toc_id]['TOC_odd_footer_value']; + $toc_efvalue = $this->m_TOC[$toc_id]['TOC_even_footer_value']; + $toc_page_selector = $this->m_TOC[$toc_id]['TOC_page_selector']; + $toc_resetpagenum = $this->m_TOC[$toc_id]['TOC_resetpagenum']; // mPDF 6 + $toc_pagenumstyle = $this->m_TOC[$toc_id]['TOC_pagenumstyle']; // mPDF 6 + $toc_suppress = $this->m_TOC[$toc_id]['TOC_suppress']; // mPDF 6 + $toc_sheet_size = (isset($this->m_TOC[$toc_id]['TOCsheetsize']) ? $this->m_TOC[$toc_id]['TOCsheetsize'] : ''); + next($this->m_TOC); + } + + // mPDF 5.6.31 + if (!$toc_orientation) { + $toc_orientation = $this->mpdf->DefOrientation; + } + + // mPDF 6 number style and suppress now picked up from section preceding ToC + list($tp_pagenumstyle, $tp_suppress, $tp_reset) = $this->mpdf->docPageSettings($toc_page - 1); + + if ($toc_resetpagenum) { + $tp_reset = $toc_resetpagenum; // mPDF 6 + } + if ($toc_pagenumstyle) { + $tp_pagenumstyle = $toc_pagenumstyle; // mPDF 6 + } + if ($toc_suppress || $toc_suppress === '0') { + $tp_suppress = $toc_suppress; // mPDF 6 + } + + $this->mpdf->AddPage($toc_orientation, '', $tp_reset, $tp_pagenumstyle, $tp_suppress, $toc_mgl, $toc_mgr, $toc_mgt, $toc_mgb, $toc_mgh, $toc_mgf, $toc_ohname, $toc_ehname, $toc_ofname, $toc_efname, $toc_ohvalue, $toc_ehvalue, $toc_ofvalue, $toc_efvalue, $toc_page_selector, $toc_sheet_size); // mPDF 6 + + $this->mpdf->writingToC = true; // mPDF 5.6.38 + + /* + * Ensure the TOC Page Number Style doesn't effect the TOC Numbering (added automatically in `AddPage()` above) + * Ensure the page numbers show in the TOC when the 'suppress' setting is enabled + * @see https://github.com/mpdf/mpdf/issues/792 + * @see https://github.com/mpdf/mpdf/issues/777 + */ + if (isset($tocClassClone)) { + $this->mpdf->PageNumSubstitutions = array_map(function ($sub) { + $sub['suppress'] = ''; + return $sub; + }, $tocClassClone->mpdf->PageNumSubstitutions); + } + + // mPDF 5.6.31 + $tocstart = count($this->mpdf->pages); + if (isset($toc_preHTML) && $toc_preHTML) { + $this->mpdf->WriteHTML($toc_preHTML); + } + + // mPDF 5.6.19 + $html = '<div class="mpdf_toc" id="mpdf_toc_' . $toc_id . '">'; + foreach ($this->_toc as $t) { + if ($t['toc_id'] === '_mpdf_all' || $t['toc_id'] === $toc_id) { + $html .= '<div class="mpdf_toc_level_' . $t['l'] . '">'; + if ($TOCuseLinking) { + $html .= '<a class="mpdf_toc_a" href="#__mpdfinternallink_' . $t['link'] . '">'; + } + $html .= '<span class="mpdf_toc_t_level_' . $t['l'] . '">' . $t['t'] . '</span>'; + if ($TOCuseLinking) { + $html .= '</a>'; + } + if (!$tocoutdent) { + $tocoutdent = '0'; + } + if ($TOCusePaging) { + $html .= ' <dottab outdent="' . $tocoutdent . '" /> '; + if ($TOCuseLinking) { + $html .= '<a class="mpdf_toc_a" href="#__mpdfinternallink_' . $t['link'] . '">'; + } + $html .= '<span class="mpdf_toc_p_level_' . $t['l'] . '">' . $this->mpdf->docPageNum($t['p']) . '</span>'; + if ($TOCuseLinking) { + $html .= '</a>'; + } + } + $html .= '</div>'; + } + } + $html .= '</div>'; + $this->mpdf->WriteHTML($html); + + if (isset($toc_postHTML) && $toc_postHTML) { + $this->mpdf->WriteHTML($toc_postHTML); + } + $this->mpdf->writingToC = false; // mPDF 5.6.38 + $this->mpdf->AddPage($toc_orientation, 'E'); + + $n_toc = $this->mpdf->page - $tocstart + 1; + + if ($toci == 0 && $this->TOCmark) { + $TOC_start = $tocstart; + $TOC_end = $this->mpdf->page; + $TOC_npages = $n_toc; + } else { + $this->m_TOC[$toc_id]['start'] = $tocstart; + $this->m_TOC[$toc_id]['end'] = $this->mpdf->page; + $this->m_TOC[$toc_id]['npages'] = $n_toc; + } + } + + $s = ''; + + $s .= $this->mpdf->PrintBodyBackgrounds(); + + $s .= $this->mpdf->PrintPageBackgrounds(); + $this->mpdf->pages[$this->mpdf->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->mpdf->uniqstr . ')/', "\n" . $s . "\n" . '\\1', $this->mpdf->pages[$this->mpdf->page]); + $this->mpdf->pageBackgrounds = []; + + //Page footer + $this->mpdf->InFooter = true; + $this->mpdf->Footer(); + $this->mpdf->InFooter = false; + + // 2nd time through to move pages etc. + $added_toc_pages = 0; + if (count($this->m_TOC)) { + reset($this->m_TOC); + } + + for ($toci = 0; $toci < $notocs; $toci++) { + if ($toci == 0 && $this->TOCmark) { + $toc_id = 0; + $toc_page = $this->TOCmark + $added_toc_pages; + $toc_orientation = $this->TOCorientation; + $TOCuseLinking = $this->TOCuseLinking; + $TOCusePaging = $this->TOCusePaging; + $toc_bookmarkText = $this->TOCbookmarkText; // *BOOKMARKS* + + $tocstart = $TOC_start; + $tocend = $n = $TOC_end; + $n_toc = $TOC_npages; + } else { + $arr = current($this->m_TOC); + + $toc_id = key($this->m_TOC); + $toc_page = $this->m_TOC[$toc_id]['TOCmark'] + $added_toc_pages; + $toc_orientation = $this->m_TOC[$toc_id]['TOCorientation']; + $TOCuseLinking = $this->m_TOC[$toc_id]['TOCuseLinking']; + $TOCusePaging = $this->m_TOC[$toc_id]['TOCusePaging']; + $toc_bookmarkText = Arrays::get($this->m_TOC[$toc_id], 'TOCbookmarkText', null); // *BOOKMARKS* + + $tocstart = $this->m_TOC[$toc_id]['start']; + $tocend = $n = $this->m_TOC[$toc_id]['end']; + $n_toc = $this->m_TOC[$toc_id]['npages']; + + next($this->m_TOC); + } + + // Now pages moved + $added_toc_pages += $n_toc; + + $this->mpdf->MovePages($toc_page, $tocstart, $tocend); + $this->mpdf->pgsIns[$toc_page] = $tocend - $tocstart + 1; + + /* -- BOOKMARKS -- */ + // Insert new Bookmark for Bookmark + if ($toc_bookmarkText) { + $insert = -1; + foreach ($this->mpdf->BMoutlines as $i => $o) { + if ($o['p'] < $toc_page) { // i.e. before point of insertion + $insert = $i; + } + } + $txt = $this->mpdf->purify_utf8_text($toc_bookmarkText); + if ($this->mpdf->text_input_as_HTML) { + $txt = $this->mpdf->all_entities_to_utf8($txt); + } + $newBookmark[0] = ['t' => $txt, 'l' => 0, 'y' => 0, 'p' => $toc_page]; + array_splice($this->mpdf->BMoutlines, ($insert + 1), 0, $newBookmark); + } + /* -- END BOOKMARKS -- */ + } + + // Delete empty page that was inserted earlier + if ($extrapage) { + unset($this->mpdf->pages[count($this->mpdf->pages)]); + $this->mpdf->page--; // Reset page pointer + } + + /* Fix the over adjustment of the TOC and Page Substitutions values */ + if (isset($tocClassClone)) { + $this->_toc = $tocClassClone->_toc; + $this->mpdf->PageNumSubstitutions = $tocClassClone->mpdf->PageNumSubstitutions; + unset($tocClassClone); + } + } + + public function openTagTOC($attr) + { + if (isset($attr['OUTDENT']) && $attr['OUTDENT']) { + $tocoutdent = $attr['OUTDENT']; + } else { + $tocoutdent = ''; + } // mPDF 5.6.19 + if (isset($attr['RESETPAGENUM']) && $attr['RESETPAGENUM']) { + $resetpagenum = $attr['RESETPAGENUM']; + } else { + $resetpagenum = ''; + } + if (isset($attr['PAGENUMSTYLE']) && $attr['PAGENUMSTYLE']) { + $pagenumstyle = $attr['PAGENUMSTYLE']; + } else { + $pagenumstyle = ''; + } + if (isset($attr['SUPPRESS']) && $attr['SUPPRESS']) { + $suppress = $attr['SUPPRESS']; + } else { + $suppress = ''; + } + if (isset($attr['TOC-ORIENTATION']) && $attr['TOC-ORIENTATION']) { + $toc_orientation = $attr['TOC-ORIENTATION']; + } else { + $toc_orientation = ''; + } + if (isset($attr['PAGING']) && (strtoupper($attr['PAGING']) == 'OFF' || $attr['PAGING'] === '0')) { + $paging = false; + } else { + $paging = true; + } + if (isset($attr['LINKS']) && (strtoupper($attr['LINKS']) == 'ON' || $attr['LINKS'] == 1)) { + $links = true; + } else { + $links = false; + } + if (isset($attr['NAME']) && $attr['NAME']) { + $toc_id = strtolower($attr['NAME']); + } else { + $toc_id = 0; + } + $this->TOC('', 0, 0, $resetpagenum, $pagenumstyle, $suppress, $toc_orientation, $paging, $links, $toc_id, $tocoutdent); // mPDF 5.6.19 5.6.31 + } + + public function openTagTOCPAGEBREAK($attr) + { + if (isset($attr['NAME']) && $attr['NAME']) { + $toc_id = strtolower($attr['NAME']); + } else { + $toc_id = 0; + } + if ($toc_id) { + if (isset($attr['OUTDENT']) && $attr['OUTDENT']) { + $this->m_TOC[$toc_id]['TOCoutdent'] = $attr['OUTDENT']; + } else { + $this->m_TOC[$toc_id]['TOCoutdent'] = ''; + } // mPDF 5.6.19 + if (isset($attr['TOC-ORIENTATION']) && $attr['TOC-ORIENTATION']) { + $this->m_TOC[$toc_id]['TOCorientation'] = $attr['TOC-ORIENTATION']; + } else { + $this->m_TOC[$toc_id]['TOCorientation'] = ''; + } + if (isset($attr['PAGING']) && (strtoupper($attr['PAGING']) == 'OFF' || $attr['PAGING'] === '0')) { + $this->m_TOC[$toc_id]['TOCusePaging'] = false; + } else { + $this->m_TOC[$toc_id]['TOCusePaging'] = true; + } + if (isset($attr['LINKS']) && (strtoupper($attr['LINKS']) == 'ON' || $attr['LINKS'] == 1)) { + $this->m_TOC[$toc_id]['TOCuseLinking'] = true; + } else { + $this->m_TOC[$toc_id]['TOCuseLinking'] = false; + } + + $this->m_TOC[$toc_id]['TOC_margin_left'] = $this->m_TOC[$toc_id]['TOC_margin_right'] = $this->m_TOC[$toc_id]['TOC_margin_top'] = $this->m_TOC[$toc_id]['TOC_margin_bottom'] = $this->m_TOC[$toc_id]['TOC_margin_header'] = $this->m_TOC[$toc_id]['TOC_margin_footer'] = ''; + if (isset($attr['TOC-MARGIN-RIGHT'])) { + $this->m_TOC[$toc_id]['TOC_margin_right'] = $this->sizeConverter->convert($attr['TOC-MARGIN-RIGHT'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-LEFT'])) { + $this->m_TOC[$toc_id]['TOC_margin_left'] = $this->sizeConverter->convert($attr['TOC-MARGIN-LEFT'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-TOP'])) { + $this->m_TOC[$toc_id]['TOC_margin_top'] = $this->sizeConverter->convert($attr['TOC-MARGIN-TOP'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-BOTTOM'])) { + $this->m_TOC[$toc_id]['TOC_margin_bottom'] = $this->sizeConverter->convert($attr['TOC-MARGIN-BOTTOM'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-HEADER'])) { + $this->m_TOC[$toc_id]['TOC_margin_header'] = $this->sizeConverter->convert($attr['TOC-MARGIN-HEADER'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-FOOTER'])) { + $this->m_TOC[$toc_id]['TOC_margin_footer'] = $this->sizeConverter->convert($attr['TOC-MARGIN-FOOTER'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + $this->m_TOC[$toc_id]['TOC_odd_header_name'] = $this->m_TOC[$toc_id]['TOC_even_header_name'] = $this->m_TOC[$toc_id]['TOC_odd_footer_name'] = $this->m_TOC[$toc_id]['TOC_even_footer_name'] = ''; + if (isset($attr['TOC-ODD-HEADER-NAME']) && $attr['TOC-ODD-HEADER-NAME']) { + $this->m_TOC[$toc_id]['TOC_odd_header_name'] = $attr['TOC-ODD-HEADER-NAME']; + } + if (isset($attr['TOC-EVEN-HEADER-NAME']) && $attr['TOC-EVEN-HEADER-NAME']) { + $this->m_TOC[$toc_id]['TOC_even_header_name'] = $attr['TOC-EVEN-HEADER-NAME']; + } + if (isset($attr['TOC-ODD-FOOTER-NAME']) && $attr['TOC-ODD-FOOTER-NAME']) { + $this->m_TOC[$toc_id]['TOC_odd_footer_name'] = $attr['TOC-ODD-FOOTER-NAME']; + } + if (isset($attr['TOC-EVEN-FOOTER-NAME']) && $attr['TOC-EVEN-FOOTER-NAME']) { + $this->m_TOC[$toc_id]['TOC_even_footer_name'] = $attr['TOC-EVEN-FOOTER-NAME']; + } + $this->m_TOC[$toc_id]['TOC_odd_header_value'] = $this->m_TOC[$toc_id]['TOC_even_header_value'] = $this->m_TOC[$toc_id]['TOC_odd_footer_value'] = $this->m_TOC[$toc_id]['TOC_even_footer_value'] = 0; + if (isset($attr['TOC-ODD-HEADER-VALUE']) && ($attr['TOC-ODD-HEADER-VALUE'] == '1' || strtoupper($attr['TOC-ODD-HEADER-VALUE']) == 'ON')) { + $this->m_TOC[$toc_id]['TOC_odd_header_value'] = 1; + } elseif (isset($attr['TOC-ODD-HEADER-VALUE']) && ($attr['TOC-ODD-HEADER-VALUE'] == '-1' || strtoupper($attr['TOC-ODD-HEADER-VALUE']) == 'OFF')) { + $this->m_TOC[$toc_id]['TOC_odd_header_value'] = -1; + } + if (isset($attr['TOC-EVEN-HEADER-VALUE']) && ($attr['TOC-EVEN-HEADER-VALUE'] == '1' || strtoupper($attr['TOC-EVEN-HEADER-VALUE']) == 'ON')) { + $this->m_TOC[$toc_id]['TOC_even_header_value'] = 1; + } elseif (isset($attr['TOC-EVEN-HEADER-VALUE']) && ($attr['TOC-EVEN-HEADER-VALUE'] == '-1' || strtoupper($attr['TOC-EVEN-HEADER-VALUE']) == 'OFF')) { + $this->m_TOC[$toc_id]['TOC_even_header_value'] = -1; + } + if (isset($attr['TOC-ODD-FOOTER-VALUE']) && ($attr['TOC-ODD-FOOTER-VALUE'] == '1' || strtoupper($attr['TOC-ODD-FOOTER-VALUE']) == 'ON')) { + $this->m_TOC[$toc_id]['TOC_odd_footer_value'] = 1; + } elseif (isset($attr['TOC-ODD-FOOTER-VALUE']) && ($attr['TOC-ODD-FOOTER-VALUE'] == '-1' || strtoupper($attr['TOC-ODD-FOOTER-VALUE']) == 'OFF')) { + $this->m_TOC[$toc_id]['TOC_odd_footer_value'] = -1; + } + if (isset($attr['TOC-EVEN-FOOTER-VALUE']) && ($attr['TOC-EVEN-FOOTER-VALUE'] == '1' || strtoupper($attr['TOC-EVEN-FOOTER-VALUE']) == 'ON')) { + $this->m_TOC[$toc_id]['TOC_even_footer_value'] = 1; + } elseif (isset($attr['TOC-EVEN-FOOTER-VALUE']) && ($attr['TOC-EVEN-FOOTER-VALUE'] == '-1' || strtoupper($attr['TOC-EVEN-FOOTER-VALUE']) == 'OFF')) { + $this->m_TOC[$toc_id]['TOC_even_footer_value'] = -1; + } + if (isset($attr['TOC-RESETPAGENUM']) && $attr['TOC-RESETPAGENUM']) { + $this->m_TOC[$toc_id]['TOC_resetpagenum'] = $attr['TOC-RESETPAGENUM']; + } else { + $this->m_TOC[$toc_id]['TOC_resetpagenum'] = ''; + } // mPDF 6 + if (isset($attr['TOC-PAGENUMSTYLE']) && $attr['TOC-PAGENUMSTYLE']) { + $this->m_TOC[$toc_id]['TOC_pagenumstyle'] = $attr['TOC-PAGENUMSTYLE']; + } else { + $this->m_TOC[$toc_id]['TOC_pagenumstyle'] = ''; + } // mPDF 6 + if (isset($attr['TOC-SUPPRESS']) && ($attr['TOC-SUPPRESS'] || $attr['TOC-SUPPRESS'] === '0')) { + $this->m_TOC[$toc_id]['TOC_suppress'] = $attr['TOC-SUPPRESS']; + } else { + $this->m_TOC[$toc_id]['TOC_suppress'] = ''; + } // mPDF 6 + if (isset($attr['TOC-PAGE-SELECTOR']) && $attr['TOC-PAGE-SELECTOR']) { + $this->m_TOC[$toc_id]['TOC_page_selector'] = $attr['TOC-PAGE-SELECTOR']; + } else { + $this->m_TOC[$toc_id]['TOC_page_selector'] = ''; + } + if (isset($attr['TOC-SHEET-SIZE']) && $attr['TOC-SHEET-SIZE']) { + $this->m_TOC[$toc_id]['TOCsheetsize'] = $attr['TOC-SHEET-SIZE']; + } else { + $this->m_TOC[$toc_id]['TOCsheetsize'] = ''; + } + + + if (isset($attr['TOC-PREHTML']) && $attr['TOC-PREHTML']) { + $this->m_TOC[$toc_id]['TOCpreHTML'] = htmlspecialchars_decode($attr['TOC-PREHTML'], ENT_QUOTES); + } + if (isset($attr['TOC-POSTHTML']) && $attr['TOC-POSTHTML']) { + $this->m_TOC[$toc_id]['TOCpostHTML'] = htmlspecialchars_decode($attr['TOC-POSTHTML'], ENT_QUOTES); + } + + if (isset($attr['TOC-BOOKMARKTEXT']) && $attr['TOC-BOOKMARKTEXT']) { + $this->m_TOC[$toc_id]['TOCbookmarkText'] = htmlspecialchars_decode($attr['TOC-BOOKMARKTEXT'], ENT_QUOTES); + } // *BOOKMARKS* + } else { + if (isset($attr['OUTDENT']) && $attr['OUTDENT']) { + $this->TOCoutdent = $attr['OUTDENT']; + } else { + $this->TOCoutdent = ''; + } // mPDF 5.6.19 + if (isset($attr['TOC-ORIENTATION']) && $attr['TOC-ORIENTATION']) { + $this->TOCorientation = $attr['TOC-ORIENTATION']; + } else { + $this->TOCorientation = ''; + } + if (isset($attr['PAGING']) && (strtoupper($attr['PAGING']) == 'OFF' || $attr['PAGING'] === '0')) { + $this->TOCusePaging = false; + } else { + $this->TOCusePaging = true; + } + if (isset($attr['LINKS']) && (strtoupper($attr['LINKS']) == 'ON' || $attr['LINKS'] == 1)) { + $this->TOCuseLinking = true; + } else { + $this->TOCuseLinking = false; + } + + $this->TOC_margin_left = $this->TOC_margin_right = $this->TOC_margin_top = $this->TOC_margin_bottom = $this->TOC_margin_header = $this->TOC_margin_footer = ''; + if (isset($attr['TOC-MARGIN-RIGHT'])) { + $this->TOC_margin_right = $this->sizeConverter->convert($attr['TOC-MARGIN-RIGHT'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-LEFT'])) { + $this->TOC_margin_left = $this->sizeConverter->convert($attr['TOC-MARGIN-LEFT'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-TOP'])) { + $this->TOC_margin_top = $this->sizeConverter->convert($attr['TOC-MARGIN-TOP'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-BOTTOM'])) { + $this->TOC_margin_bottom = $this->sizeConverter->convert($attr['TOC-MARGIN-BOTTOM'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-HEADER'])) { + $this->TOC_margin_header = $this->sizeConverter->convert($attr['TOC-MARGIN-HEADER'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['TOC-MARGIN-FOOTER'])) { + $this->TOC_margin_footer = $this->sizeConverter->convert($attr['TOC-MARGIN-FOOTER'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + $this->TOC_odd_header_name = $this->TOC_even_header_name = $this->TOC_odd_footer_name = $this->TOC_even_footer_name = ''; + if (isset($attr['TOC-ODD-HEADER-NAME']) && $attr['TOC-ODD-HEADER-NAME']) { + $this->TOC_odd_header_name = $attr['TOC-ODD-HEADER-NAME']; + } + if (isset($attr['TOC-EVEN-HEADER-NAME']) && $attr['TOC-EVEN-HEADER-NAME']) { + $this->TOC_even_header_name = $attr['TOC-EVEN-HEADER-NAME']; + } + if (isset($attr['TOC-ODD-FOOTER-NAME']) && $attr['TOC-ODD-FOOTER-NAME']) { + $this->TOC_odd_footer_name = $attr['TOC-ODD-FOOTER-NAME']; + } + if (isset($attr['TOC-EVEN-FOOTER-NAME']) && $attr['TOC-EVEN-FOOTER-NAME']) { + $this->TOC_even_footer_name = $attr['TOC-EVEN-FOOTER-NAME']; + } + $this->TOC_odd_header_value = $this->TOC_even_header_value = $this->TOC_odd_footer_value = $this->TOC_even_footer_value = 0; + if (isset($attr['TOC-ODD-HEADER-VALUE']) && ($attr['TOC-ODD-HEADER-VALUE'] == '1' || strtoupper($attr['TOC-ODD-HEADER-VALUE']) == 'ON')) { + $this->TOC_odd_header_value = 1; + } elseif (isset($attr['TOC-ODD-HEADER-VALUE']) && ($attr['TOC-ODD-HEADER-VALUE'] == '-1' || strtoupper($attr['TOC-ODD-HEADER-VALUE']) == 'OFF')) { + $this->TOC_odd_header_value = -1; + } + if (isset($attr['TOC-EVEN-HEADER-VALUE']) && ($attr['TOC-EVEN-HEADER-VALUE'] == '1' || strtoupper($attr['TOC-EVEN-HEADER-VALUE']) == 'ON')) { + $this->TOC_even_header_value = 1; + } elseif (isset($attr['TOC-EVEN-HEADER-VALUE']) && ($attr['TOC-EVEN-HEADER-VALUE'] == '-1' || strtoupper($attr['TOC-EVEN-HEADER-VALUE']) == 'OFF')) { + $this->TOC_even_header_value = -1; + } + + if (isset($attr['TOC-ODD-FOOTER-VALUE']) && ($attr['TOC-ODD-FOOTER-VALUE'] == '1' || strtoupper($attr['TOC-ODD-FOOTER-VALUE']) == 'ON')) { + $this->TOC_odd_footer_value = 1; + } elseif (isset($attr['TOC-ODD-FOOTER-VALUE']) && ($attr['TOC-ODD-FOOTER-VALUE'] == '-1' || strtoupper($attr['TOC-ODD-FOOTER-VALUE']) == 'OFF')) { + $this->TOC_odd_footer_value = -1; + } + if (isset($attr['TOC-EVEN-FOOTER-VALUE']) && ($attr['TOC-EVEN-FOOTER-VALUE'] == '1' || strtoupper($attr['TOC-EVEN-FOOTER-VALUE']) == 'ON')) { + $this->TOC_even_footer_value = 1; + } elseif (isset($attr['TOC-EVEN-FOOTER-VALUE']) && ($attr['TOC-EVEN-FOOTER-VALUE'] == '-1' || strtoupper($attr['TOC-EVEN-FOOTER-VALUE']) == 'OFF')) { + $this->TOC_even_footer_value = -1; + } + if (isset($attr['TOC-PAGE-SELECTOR']) && $attr['TOC-PAGE-SELECTOR']) { + $this->TOC_page_selector = $attr['TOC-PAGE-SELECTOR']; + } else { + $this->TOC_page_selector = ''; + } + if (isset($attr['TOC-RESETPAGENUM']) && $attr['TOC-RESETPAGENUM']) { + $this->TOC_resetpagenum = $attr['TOC-RESETPAGENUM']; + } else { + $this->TOC_resetpagenum = ''; + } // mPDF 6 + if (isset($attr['TOC-PAGENUMSTYLE']) && $attr['TOC-PAGENUMSTYLE']) { + $this->TOC_pagenumstyle = $attr['TOC-PAGENUMSTYLE']; + } else { + $this->TOC_pagenumstyle = ''; + } // mPDF 6 + if (isset($attr['TOC-SUPPRESS']) && ($attr['TOC-SUPPRESS'] || $attr['TOC-SUPPRESS'] === '0')) { + $this->TOC_suppress = $attr['TOC-SUPPRESS']; + } else { + $this->TOC_suppress = ''; + } // mPDF 6 + if (isset($attr['TOC-SHEET-SIZE']) && $attr['TOC-SHEET-SIZE']) { + $this->TOCsheetsize = $attr['TOC-SHEET-SIZE']; + } else { + $this->TOCsheetsize = ''; + } + + if (isset($attr['TOC-PREHTML']) && $attr['TOC-PREHTML']) { + $this->TOCpreHTML = htmlspecialchars_decode($attr['TOC-PREHTML'], ENT_QUOTES); + } + if (isset($attr['TOC-POSTHTML']) && $attr['TOC-POSTHTML']) { + $this->TOCpostHTML = htmlspecialchars_decode($attr['TOC-POSTHTML'], ENT_QUOTES); + } + if (isset($attr['TOC-BOOKMARKTEXT']) && $attr['TOC-BOOKMARKTEXT']) { + $this->TOCbookmarkText = htmlspecialchars_decode($attr['TOC-BOOKMARKTEXT'], ENT_QUOTES); + } + } + + if ($this->mpdf->y == $this->mpdf->tMargin && (!$this->mpdf->mirrorMargins || ($this->mpdf->mirrorMargins && $this->mpdf->page % 2 == 1))) { + if ($toc_id) { + $this->m_TOC[$toc_id]['TOCmark'] = $this->mpdf->page; + } else { + $this->TOCmark = $this->mpdf->page; + } + // Don't add a page + if ($this->mpdf->page == 1 && count($this->mpdf->PageNumSubstitutions) == 0) { + $resetpagenum = ''; + $pagenumstyle = ''; + $suppress = ''; + if (isset($attr['RESETPAGENUM'])) { + $resetpagenum = $attr['RESETPAGENUM']; + } + if (isset($attr['PAGENUMSTYLE'])) { + $pagenumstyle = $attr['PAGENUMSTYLE']; + } + if (isset($attr['SUPPRESS'])) { + $suppress = $attr['SUPPRESS']; + } + if (!$suppress) { + $suppress = 'off'; + } + $this->mpdf->PageNumSubstitutions[] = ['from' => 1, 'reset' => $resetpagenum, 'type' => $pagenumstyle, 'suppress' => $suppress]; + } + return [true, $toc_id]; + } + + // No break - continues as PAGEBREAK... + return [false, $toc_id]; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag.php new file mode 100644 index 0000000..6886d1c --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag.php @@ -0,0 +1,253 @@ +<?php + +namespace Mpdf; + +use Mpdf\Strict; + +use Mpdf\Color\ColorConverter; + +use Mpdf\Image\ImageProcessor; + +use Mpdf\Language\LanguageToFontInterface; + +class Tag +{ + + use Strict; + + /** + * @var \Mpdf\Mpdf + */ + private $mpdf; + + /** + * @var \Mpdf\Cache + */ + private $cache; + + /** + * @var \Mpdf\CssManager + */ + private $cssManager; + + /** + * @var \Mpdf\Form + */ + private $form; + + /** + * @var \Mpdf\Otl + */ + private $otl; + + /** + * @var \Mpdf\TableOfContents + */ + private $tableOfContents; + + /** + * @var \Mpdf\SizeConverter + */ + private $sizeConverter; + + /** + * @var \Mpdf\Color\ColorConverter + */ + private $colorConverter; + + /** + * @var \Mpdf\Image\ImageProcessor + */ + private $imageProcessor; + + /** + * @var \Mpdf\Language\LanguageToFontInterface + */ + private $languageToFont; + + /** + * @param \Mpdf\Mpdf $mpdf + * @param \Mpdf\Cache $cache + * @param \Mpdf\CssManager $cssManager + * @param \Mpdf\Form $form + * @param \Mpdf\Otl $otl + * @param \Mpdf\TableOfContents $tableOfContents + * @param \Mpdf\SizeConverter $sizeConverter + * @param \Mpdf\Color\ColorConverter $colorConverter + * @param \Mpdf\Image\ImageProcessor $imageProcessor + * @param \Mpdf\Language\LanguageToFontInterface $languageToFont + */ + public function __construct( + Mpdf $mpdf, + Cache $cache, + CssManager $cssManager, + Form $form, + Otl $otl, + TableOfContents $tableOfContents, + SizeConverter $sizeConverter, + ColorConverter $colorConverter, + ImageProcessor $imageProcessor, + LanguageToFontInterface $languageToFont + ) { + + $this->mpdf = $mpdf; + $this->cache = $cache; + $this->cssManager = $cssManager; + $this->form = $form; + $this->otl = $otl; + $this->tableOfContents = $tableOfContents; + $this->sizeConverter = $sizeConverter; + $this->colorConverter = $colorConverter; + $this->imageProcessor = $imageProcessor; + $this->languageToFont = $languageToFont; + } + + /** + * @param string $tag The tag name + * @return \Mpdf\Tag\Tag + */ + private function getTagInstance($tag) + { + $className = self::getTagClassName($tag); + if (class_exists($className)) { + return new $className( + $this->mpdf, + $this->cache, + $this->cssManager, + $this->form, + $this->otl, + $this->tableOfContents, + $this->sizeConverter, + $this->colorConverter, + $this->imageProcessor, + $this->languageToFont + ); + } + } + + /** + * Returns the fully qualified name of the class handling the rendering of the given tag + * + * @param string $tag The tag name + * @return string The fully qualified name + */ + public static function getTagClassName($tag) + { + static $map = [ + 'BARCODE' => 'BarCode', + 'BLOCKQUOTE' => 'BlockQuote', + 'COLUMN_BREAK' => 'ColumnBreak', + 'COLUMNBREAK' => 'ColumnBreak', + 'DOTTAB' => 'DotTab', + 'FIELDSET' => 'FieldSet', + 'FIGCAPTION' => 'FigCaption', + 'FORMFEED' => 'FormFeed', + 'HGROUP' => 'HGroup', + 'INDEXENTRY' => 'IndexEntry', + 'INDEXINSERT' => 'IndexInsert', + 'NEWCOLUMN' => 'NewColumn', + 'NEWPAGE' => 'NewPage', + 'PAGEFOOTER' => 'PageFooter', + 'PAGEHEADER' => 'PageHeader', + 'PAGE_BREAK' => 'PageBreak', + 'PAGEBREAK' => 'PageBreak', + 'SETHTMLPAGEFOOTER' => 'SetHtmlPageFooter', + 'SETHTMLPAGEHEADER' => 'SetHtmlPageHeader', + 'SETPAGEFOOTER' => 'SetPageFooter', + 'SETPAGEHEADER' => 'SetPageHeader', + 'TBODY' => 'TBody', + 'TFOOT' => 'TFoot', + 'THEAD' => 'THead', + 'TEXTAREA' => 'TextArea', + 'TEXTCIRCLE' => 'TextCircle', + 'TOCENTRY' => 'TocEntry', + 'TOCPAGEBREAK' => 'TocPageBreak', + 'VAR' => 'VarTag', + 'WATERMARKIMAGE' => 'WatermarkImage', + 'WATERMARKTEXT' => 'WatermarkText', + ]; + + $className = 'Mpdf\Tag\\'; + $className .= isset($map[$tag]) ? $map[$tag] : ucfirst(strtolower($tag)); + + return $className; + } + + public function OpenTag($tag, $attr, &$ahtml, &$ihtml) + { + // Correct for tags where HTML5 specifies optional end tags excluding table elements (cf WriteHTML() ) + if ($this->mpdf->allow_html_optional_endtags) { + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['tag'])) { + $closed = false; + // li end tag may be omitted if immediately followed by another li element + if (!$closed && $this->mpdf->blk[$this->mpdf->blklvl]['tag'] == 'LI' && $tag == 'LI') { + $this->CloseTag('LI', $ahtml, $ihtml); + $closed = true; + } + // dt end tag may be omitted if immediately followed by another dt element or a dd element + if (!$closed && $this->mpdf->blk[$this->mpdf->blklvl]['tag'] == 'DT' && ($tag == 'DT' || $tag == 'DD')) { + $this->CloseTag('DT', $ahtml, $ihtml); + $closed = true; + } + // dd end tag may be omitted if immediately followed by another dd element or a dt element + if (!$closed && $this->mpdf->blk[$this->mpdf->blklvl]['tag'] == 'DD' && ($tag == 'DT' || $tag == 'DD')) { + $this->CloseTag('DD', $ahtml, $ihtml); + $closed = true; + } + // p end tag may be omitted if immediately followed by an address, article, aside, blockquote, div, dl, + // fieldset, form, h1, h2, h3, h4, h5, h6, hgroup, hr, main, nav, ol, p, pre, section, table, ul + if (!$closed && $this->mpdf->blk[$this->mpdf->blklvl]['tag'] == 'P' + && ($tag == 'P' || $tag == 'DIV' || $tag == 'H1' || $tag == 'H2' || $tag == 'H3' + || $tag == 'H4' || $tag == 'H5' || $tag == 'H6' || $tag == 'UL' || $tag == 'OL' + || $tag == 'TABLE' || $tag == 'PRE' || $tag == 'FORM' || $tag == 'ADDRESS' || $tag == 'BLOCKQUOTE' + || $tag == 'CENTER' || $tag == 'DL' || $tag == 'HR' || $tag == 'ARTICLE' || $tag == 'ASIDE' + || $tag == 'FIELDSET' || $tag == 'HGROUP' || $tag == 'MAIN' || $tag == 'NAV' || $tag == 'SECTION')) { + $this->CloseTag('P', $ahtml, $ihtml); + $closed = true; + } + // option end tag may be omitted if immediately followed by another option element + // (or if it is immediately followed by an optgroup element) + if (!$closed && $this->mpdf->blk[$this->mpdf->blklvl]['tag'] == 'OPTION' && $tag == 'OPTION') { + $this->CloseTag('OPTION', $ahtml, $ihtml); + $closed = true; + } + // Table elements - see also WriteHTML() + if (!$closed && ($tag == 'TD' || $tag == 'TH') && $this->mpdf->lastoptionaltag == 'TD') { + $this->CloseTag($this->mpdf->lastoptionaltag, $ahtml, $ihtml); + $closed = true; + } // *TABLES* + if (!$closed && ($tag == 'TD' || $tag == 'TH') && $this->mpdf->lastoptionaltag == 'TH') { + $this->CloseTag($this->mpdf->lastoptionaltag, $ahtml, $ihtml); + $closed = true; + } // *TABLES* + if (!$closed && $tag == 'TR' && $this->mpdf->lastoptionaltag == 'TR') { + $this->CloseTag($this->mpdf->lastoptionaltag, $ahtml, $ihtml); + $closed = true; + } // *TABLES* + if (!$closed && $tag == 'TR' && $this->mpdf->lastoptionaltag == 'TD') { + $this->CloseTag($this->mpdf->lastoptionaltag, $ahtml, $ihtml); + $this->CloseTag('TR', $ahtml, $ihtml); + $this->CloseTag('THEAD', $ahtml, $ihtml); + $closed = true; + } // *TABLES* + if (!$closed && $tag == 'TR' && $this->mpdf->lastoptionaltag == 'TH') { + $this->CloseTag($this->mpdf->lastoptionaltag, $ahtml, $ihtml); + $this->CloseTag('TR', $ahtml, $ihtml); + $this->CloseTag('THEAD', $ahtml, $ihtml); + $closed = true; + } // *TABLES* + } + } + + if ($object = $this->getTagInstance($tag)) { + return $object->open($attr, $ahtml, $ihtml); + } + } + + public function CloseTag($tag, &$ahtml, &$ihtml) + { + if ($object = $this->getTagInstance($tag)) { + return $object->close($ahtml, $ihtml); + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/A.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/A.php new file mode 100644 index 0000000..6a6488b --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/A.php @@ -0,0 +1,50 @@ +<?php + +namespace Mpdf\Tag; + +class A extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + if (isset($attr['NAME']) && $attr['NAME'] != '') { + $e = ''; + /* -- BOOKMARKS -- */ + if ($this->mpdf->anchor2Bookmark) { + $objattr = []; + $objattr['CONTENT'] = htmlspecialchars_decode($attr['NAME'], ENT_QUOTES); + $objattr['type'] = 'bookmark'; + if (!empty($attr['LEVEL'])) { + $objattr['bklevel'] = $attr['LEVEL']; + } else { + $objattr['bklevel'] = 0; + } + $e = "\xbb\xa4\xactype=bookmark,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + } + /* -- END BOOKMARKS -- */ + if ($this->mpdf->tableLevel) { // *TABLES* + $this->mpdf->_saveCellTextBuffer($e, '', $attr['NAME']); // *TABLES* + } // *TABLES* + else { // *TABLES* + $this->mpdf->_saveTextBuffer($e, '', $attr['NAME']); //an internal link (adds a space for recognition) + } // *TABLES* + } + if (isset($attr['HREF'])) { + $this->mpdf->InlineProperties['A'] = $this->mpdf->saveInlineProperties(); + $properties = $this->cssManager->MergeCSS('INLINE', 'A', $attr); + if (!empty($properties)) { + $this->mpdf->setCSS($properties, 'INLINE'); + } + $this->mpdf->HREF = $attr['HREF']; // mPDF 5.7.4 URLs + } + } + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->HREF = ''; + if (isset($this->mpdf->InlineProperties['A'])) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties['A']); + } + unset($this->mpdf->InlineProperties['A']); + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Acronym.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Acronym.php new file mode 100644 index 0000000..e33eea5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Acronym.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Acronym extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Address.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Address.php new file mode 100644 index 0000000..44b5b3c --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Address.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Address extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Annotation.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Annotation.php new file mode 100644 index 0000000..6ed5e0e --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Annotation.php @@ -0,0 +1,99 @@ +<?php + +namespace Mpdf\Tag; + +class Annotation extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + //if (isset($attr['CONTENT']) && !$this->mpdf->writingHTMLheader && !$this->mpdf->writingHTMLfooter) { // Stops annotations in FixedPos + if (isset($attr['CONTENT'])) { + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $objattr['CONTENT'] = htmlspecialchars_decode($attr['CONTENT'], ENT_QUOTES); + $objattr['type'] = 'annot'; + $objattr['POPUP'] = ''; + } else { + return; + } + if (isset($attr['POS-X'])) { + $objattr['POS-X'] = $attr['POS-X']; + } else { + $objattr['POS-X'] = 0; + } + if (isset($attr['POS-Y'])) { + $objattr['POS-Y'] = $attr['POS-Y']; + } else { + $objattr['POS-Y'] = 0; + } + if (isset($attr['ICON'])) { + $objattr['ICON'] = $attr['ICON']; + } else { + $objattr['ICON'] = 'Note'; + } + if (isset($attr['AUTHOR'])) { + $objattr['AUTHOR'] = $attr['AUTHOR']; + } elseif (isset($attr['TITLE'])) { + $objattr['AUTHOR'] = $attr['TITLE']; + } else { + $objattr['AUTHOR'] = ''; + } + if (isset($attr['FILE'])) { + $objattr['FILE'] = $attr['FILE']; + } else { + $objattr['FILE'] = ''; + } + if (isset($attr['SUBJECT'])) { + $objattr['SUBJECT'] = $attr['SUBJECT']; + } else { + $objattr['SUBJECT'] = ''; + } + if (isset($attr['OPACITY']) && $attr['OPACITY'] > 0 && $attr['OPACITY'] <= 1) { + $objattr['OPACITY'] = $attr['OPACITY']; + } elseif ($this->mpdf->annotMargin) { + $objattr['OPACITY'] = 1; + } else { + $objattr['OPACITY'] = $this->mpdf->annotOpacity; + } + if (isset($attr['COLOR'])) { + $cor = $this->colorConverter->convert($attr['COLOR'], $this->mpdf->PDFAXwarnings); + if ($cor) { + $objattr['COLOR'] = $cor; + } else { + $objattr['COLOR'] = $this->colorConverter->convert('yellow', $this->mpdf->PDFAXwarnings); + } + } else { + $objattr['COLOR'] = $this->colorConverter->convert('yellow', $this->mpdf->PDFAXwarnings); + } + + if (isset($attr['POPUP']) && !empty($attr['POPUP'])) { + $pop = preg_split('/\s+/', trim($attr['POPUP'])); + if (count($pop) > 1) { + $objattr['POPUP'] = $pop; + } else { + $objattr['POPUP'] = true; + } + } + $e = "\xbb\xa4\xactype=annot,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + if ($this->mpdf->tableLevel) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][] = [$e]; + } // *TABLES* + else { // *TABLES* + $this->mpdf->textbuffer[] = [$e]; + } // *TABLES* + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Article.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Article.php new file mode 100644 index 0000000..180d69d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Article.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Article extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Aside.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Aside.php new file mode 100644 index 0000000..fa7e697 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Aside.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Aside extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/B.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/B.php new file mode 100644 index 0000000..15e2eb3 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/B.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class B extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BarCode.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BarCode.php new file mode 100644 index 0000000..e80fe59 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BarCode.php @@ -0,0 +1,256 @@ +<?php + +namespace Mpdf\Tag; + +class BarCode extends Tag +{ + + /** + * @var \Mpdf\Barcode + */ + protected $barcode; + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->ignorefollowingspaces = false; + if (!empty($attr['CODE'])) { + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['padding_top'] = 0; + $objattr['padding_bottom'] = 0; + $objattr['padding_left'] = 0; + $objattr['padding_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $objattr['code'] = $attr['CODE']; + + if (isset($attr['TYPE'])) { + $objattr['btype'] = strtoupper(trim($attr['TYPE'])); + } else { + $objattr['btype'] = 'EAN13'; + } // default + if (preg_match('/^(EAN13|ISBN|ISSN|EAN8|UPCA|UPCE)P([25])$/', $objattr['btype'], $m)) { + $objattr['btype'] = $m[1]; + $objattr['bsupp'] = $m[2]; + if (preg_match('/^(\S+)\s+(.*)$/', $objattr['code'], $mm)) { + $objattr['code'] = $mm[1]; + $objattr['bsupp_code'] = $mm[2]; + } + } else { + $objattr['bsupp'] = 0; + } + + if (isset($attr['TEXT']) && $attr['TEXT'] == 1) { + $objattr['showtext'] = 1; + } else { + $objattr['showtext'] = 0; + } + if (isset($attr['SIZE']) && $attr['SIZE'] > 0) { + $objattr['bsize'] = $attr['SIZE']; + } else { + $objattr['bsize'] = 1; + } + if (isset($attr['HEIGHT']) && $attr['HEIGHT'] > 0) { + $objattr['bheight'] = $attr['HEIGHT']; + } else { + $objattr['bheight'] = 1; + } + if (isset($attr['PR']) && $attr['PR'] > 0) { + $objattr['pr_ratio'] = $attr['PR']; + } else { + $objattr['pr_ratio'] = ''; + } + $properties = $this->cssManager->MergeCSS('', 'BARCODE', $attr); + if (isset($properties ['DISPLAY']) && strtolower($properties ['DISPLAY']) === 'none') { + return; + } + if (isset($properties['MARGIN-TOP'])) { + $objattr['margin_top'] = $this->sizeConverter->convert( + $properties['MARGIN-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-BOTTOM'])) { + $objattr['margin_bottom'] = $this->sizeConverter->convert( + $properties['MARGIN-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-LEFT'])) { + $objattr['margin_left'] = $this->sizeConverter->convert( + $properties['MARGIN-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-RIGHT'])) { + $objattr['margin_right'] = $this->sizeConverter->convert( + $properties['MARGIN-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['PADDING-TOP'])) { + $objattr['padding_top'] = $this->sizeConverter->convert( + $properties['PADDING-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-BOTTOM'])) { + $objattr['padding_bottom'] = $this->sizeConverter->convert( + $properties['PADDING-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-LEFT'])) { + $objattr['padding_left'] = $this->sizeConverter->convert( + $properties['PADDING-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-RIGHT'])) { + $objattr['padding_right'] = $this->sizeConverter->convert( + $properties['PADDING-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['BORDER-TOP'])) { + $objattr['border_top'] = $this->mpdf->border_details($properties['BORDER-TOP']); + } + if (isset($properties['BORDER-BOTTOM'])) { + $objattr['border_bottom'] = $this->mpdf->border_details($properties['BORDER-BOTTOM']); + } + if (isset($properties['BORDER-LEFT'])) { + $objattr['border_left'] = $this->mpdf->border_details($properties['BORDER-LEFT']); + } + if (isset($properties['BORDER-RIGHT'])) { + $objattr['border_right'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); + } + + if (isset($properties['VERTICAL-ALIGN'])) { + $objattr['vertical-align'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } + if (isset($properties['COLOR']) && $properties['COLOR'] != '') { + $objattr['color'] = $this->colorConverter->convert($properties['COLOR'], $this->mpdf->PDFAXwarnings); + } else { + $objattr['color'] = false; + } + if (isset($properties['BACKGROUND-COLOR']) && $properties['BACKGROUND-COLOR'] != '') { + $objattr['bgcolor'] = $this->colorConverter->convert($properties['BACKGROUND-COLOR'], $this->mpdf->PDFAXwarnings); + } else { + $objattr['bgcolor'] = false; + } + + $this->barcode = new \Mpdf\Barcode(); + + if (in_array($objattr['btype'], ['EAN13', 'ISBN', 'ISSN', 'UPCA', 'UPCE', 'EAN8'])) { + + $code = preg_replace('/\-/', '', $objattr['code']); + $arrcode = $this->barcode->getBarcodeArray($code, $objattr['btype']); + + if ($objattr['bsupp'] == 2 || $objattr['bsupp'] == 5) { // EAN-2 or -5 Supplement + $supparrcode = $this->barcode->getBarcodeArray($objattr['bsupp_code'], 'EAN' . $objattr['bsupp']); + $w = ($arrcode['maxw'] + $arrcode['lightmL'] + $arrcode['lightmR'] + + $supparrcode['maxw'] + $supparrcode['sepM']) * $arrcode['nom-X'] * $objattr['bsize']; + } else { + $w = ($arrcode['maxw'] + $arrcode['lightmL'] + $arrcode['lightmR']) * $arrcode['nom-X'] * $objattr['bsize']; + } + + $h = $arrcode['nom-H'] * $objattr['bsize'] * $objattr['bheight']; + // Add height for ISBN string + margin from top of bars + if (($objattr['showtext'] && $objattr['btype'] === 'EAN13') || $objattr['btype'] === 'ISBN' || $objattr['btype'] === 'ISSN') { + $tisbnm = 1.5 * $objattr['bsize']; // Top margin between TOP TEXT (isbn - if shown) & bars + $isbn_fontsize = 2.1 * $objattr['bsize']; + $h += $isbn_fontsize + $tisbnm; + } + + } elseif ($objattr['btype'] === 'QR') { // QR-code + $w = $h = $objattr['bsize'] * 25; // Factor of 25mm (default) + $objattr['errorlevel'] = 'L'; + if (isset($attr['ERROR'])) { + $objattr['errorlevel'] = $attr['ERROR']; + } + $objattr['disableborder'] = false; + if (isset($attr['DISABLEBORDER'])) { + $objattr['disableborder'] = (bool) $attr['DISABLEBORDER']; + } + + } elseif (in_array($objattr['btype'], ['IMB', 'RM4SCC', 'KIX', 'POSTNET', 'PLANET'])) { + + $arrcode = $this->barcode->getBarcodeArray($objattr['code'], $objattr['btype']); + + $w = ($arrcode['maxw'] * $arrcode['nom-X'] * $objattr['bsize']) + $arrcode['quietL'] + $arrcode['quietR']; + $h = ($arrcode['nom-H'] * $objattr['bsize']) + (2 * $arrcode['quietTB']); + + } elseif (in_array($objattr['btype'], ['C128A', 'C128B', 'C128C', 'EAN128A', 'EAN128B', 'EAN128C', + 'C39', 'C39+', 'C39E', 'C39E+', 'S25', 'S25+', 'I25', 'I25+', 'I25B', + 'I25B+', 'C93', 'MSI', 'MSI+', 'CODABAR', 'CODE11'])) { + + $arrcode = $this->barcode->getBarcodeArray($objattr['code'], $objattr['btype'], $objattr['pr_ratio']); + $w = ($arrcode['maxw'] + $arrcode['lightmL'] + $arrcode['lightmR']) * $arrcode['nom-X'] * $objattr['bsize']; + $h = ((2 * $arrcode['lightTB'] * $arrcode['nom-X']) + $arrcode['nom-H']) * $objattr['bsize'] * $objattr['bheight']; + + } else { + return; + } + + $extraheight = $objattr['padding_top'] + $objattr['padding_bottom'] + $objattr['margin_top'] + + $objattr['margin_bottom'] + $objattr['border_top']['w'] + $objattr['border_bottom']['w']; + $extrawidth = $objattr['padding_left'] + $objattr['padding_right'] + $objattr['margin_left'] + + $objattr['margin_right'] + $objattr['border_left']['w'] + $objattr['border_right']['w']; + + $objattr['type'] = 'barcode'; + $objattr['height'] = $h + $extraheight; + $objattr['width'] = $w + $extrawidth; + $objattr['barcode_height'] = $h; + $objattr['barcode_width'] = $w; + + /* -- CSS-IMAGE-FLOAT -- */ + if (!$this->mpdf->ColActive && !$this->mpdf->tableLevel && !$this->mpdf->listlvl && !$this->mpdf->kwt) { + if (isset($properties['FLOAT']) && (strtoupper($properties['FLOAT']) === 'RIGHT' || strtoupper($properties['FLOAT']) === 'LEFT')) { + $objattr['float'] = strtoupper(substr($properties['FLOAT'], 0, 1)); + } + } + /* -- END CSS-IMAGE-FLOAT -- */ + + $e = "\xbb\xa4\xactype=barcode,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + + /* -- TABLES -- */ + // Output it to buffers + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($e, $this->mpdf->HREF); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $objattr['width']; + } else { + /* -- END TABLES -- */ + $this->mpdf->_saveTextBuffer($e, $this->mpdf->HREF); + } // *TABLES* + } + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bdi.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bdi.php new file mode 100644 index 0000000..3de3ec5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bdi.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Bdi extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bdo.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bdo.php new file mode 100644 index 0000000..6435d3f --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bdo.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Bdo extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Big.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Big.php new file mode 100644 index 0000000..fffd893 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Big.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Big extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BlockQuote.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BlockQuote.php new file mode 100644 index 0000000..44287ca --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BlockQuote.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class BlockQuote extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BlockTag.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BlockTag.php new file mode 100644 index 0000000..cbe4bc5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/BlockTag.php @@ -0,0 +1,1352 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Conversion\DecToAlpha; +use Mpdf\Conversion\DecToRoman; + +use Mpdf\Utils\Arrays; +use Mpdf\Utils\UtfString; + +abstract class BlockTag extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + + // mPDF 6 Lists + $this->mpdf->lastoptionaltag = ''; + + // mPDF 6 bidi + // Block + // If unicode-bidi set on current clock, any embedding levels, isolates, or overrides are closed (not inherited) + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['bidicode'])) { + $blockpost = $this->mpdf->_setBidiCodes('end', $this->mpdf->blk[$this->mpdf->blklvl]['bidicode']); + if ($blockpost) { + $this->mpdf->OTLdata = []; + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($blockpost); + } else { + $this->mpdf->_saveTextBuffer($blockpost); + } + } + } + + + $p = $this->cssManager->PreviewBlockCSS($tag, $attr); + if (isset($p['DISPLAY']) && strtolower($p['DISPLAY']) === 'none') { + $this->mpdf->blklvl++; + $this->mpdf->blk[$this->mpdf->blklvl]['hide'] = true; + $this->mpdf->blk[$this->mpdf->blklvl]['tag'] = $tag; // mPDF 6 + return; + } + if ($tag === 'CAPTION') { + // position is written in AdjstHTML + $divpos = 'T'; + if (isset($attr['POSITION']) && strtolower($attr['POSITION']) === 'bottom') { + $divpos = 'B'; + } + + $cappos = 'T'; + if (isset($attr['ALIGN']) && strtolower($attr['ALIGN']) === 'bottom') { + $cappos = 'B'; + } elseif (isset($p['CAPTION-SIDE']) && strtolower($p['CAPTION-SIDE']) === 'bottom') { + $cappos = 'B'; + } + if (isset($attr['ALIGN'])) { + unset($attr['ALIGN']); + } + if ($cappos != $divpos) { + $this->mpdf->blklvl++; + $this->mpdf->blk[$this->mpdf->blklvl]['hide'] = true; + $this->mpdf->blk[$this->mpdf->blklvl]['tag'] = $tag; // mPDF 6 + return; + } + } + + /* -- FORMS -- */ + if ($tag === 'FORM') { + $this->form->formMethod = 'POST'; + if (isset($attr['METHOD']) && strtolower($attr['METHOD']) === 'get') { + $this->form->formMethod = 'GET'; + } + + $this->form->formAction = ''; + if (isset($attr['ACTION'])) { + $this->form->formAction = $attr['ACTION']; + } + } + /* -- END FORMS -- */ + + + /* -- CSS-POSITION -- */ + if ((isset($p['POSITION']) + && (strtolower($p['POSITION']) === 'fixed' + || strtolower($p['POSITION']) === 'absolute')) + && $this->mpdf->blklvl == 0) { + if ($this->mpdf->inFixedPosBlock) { + throw new \Mpdf\MpdfException('Cannot nest block with position:fixed or position:absolute'); + } + $this->mpdf->inFixedPosBlock = true; + return; + } + /* -- END CSS-POSITION -- */ + // Start Block + $this->mpdf->ignorefollowingspaces = true; + + $lastbottommargin = 0; + if ($this->mpdf->blockjustfinished && !count($this->mpdf->textbuffer) + && $this->mpdf->y != $this->mpdf->tMargin + && $this->mpdf->collapseBlockMargins) { + $lastbottommargin = $this->mpdf->lastblockbottommargin; + } + $this->mpdf->lastblockbottommargin = 0; + $this->mpdf->blockjustfinished = false; + + + $this->mpdf->InlineBDF = []; // mPDF 6 + $this->mpdf->InlineBDFctr = 0; // mPDF 6 + $this->mpdf->InlineProperties = []; + $this->mpdf->divbegin = true; + + $this->mpdf->linebreakjustfinished = false; + + /* -- TABLES -- */ + if ($this->mpdf->tableLevel) { + // If already something on the line + if ($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] > 0 && !$this->mpdf->nestedtablejustfinished) { + $this->mpdf->_saveCellTextBuffer("\n"); + if (!isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'])) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } elseif ($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] < $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] = 0; // reset + } + // Cannot set block properties inside table - use Bold to indicate h1-h6 + if ($tag === 'CENTER' && $this->mpdf->tdbegin) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['a'] = self::ALIGN['center']; + } + + $this->mpdf->InlineProperties['BLOCKINTABLE'] = $this->mpdf->saveInlineProperties(); + $properties = $this->cssManager->MergeCSS('', $tag, $attr); + if (!empty($properties)) { + $this->mpdf->setCSS($properties, 'INLINE'); + } + + // mPDF 6 Lists + if ($tag === 'UL' || $tag === 'OL') { + $this->mpdf->listlvl++; + if (isset($attr['START'])) { + $this->mpdf->listcounter[$this->mpdf->listlvl] = (int) $attr['START'] - 1; + } else { + $this->mpdf->listcounter[$this->mpdf->listlvl] = 0; + } + $this->mpdf->listitem = []; + if ($tag === 'OL') { + $this->mpdf->listtype[$this->mpdf->listlvl] = 'decimal'; + } elseif ($tag === 'UL') { + if ($this->mpdf->listlvl % 3 == 1) { + $this->mpdf->listtype[$this->mpdf->listlvl] = 'disc'; + } elseif ($this->mpdf->listlvl % 3 == 2) { + $this->mpdf->listtype[$this->mpdf->listlvl] = 'circle'; + } else { + $this->mpdf->listtype[$this->mpdf->listlvl] = 'square'; + } + } + } + + // mPDF 6 Lists - in Tables + if ($tag === 'LI') { + + if ($this->mpdf->listlvl == 0) { //in case of malformed HTML code. Example:(...)</p><li>Content</li><p>Paragraph1</p>(...) + $this->mpdf->listlvl++; // first depth level + $this->mpdf->listcounter[$this->mpdf->listlvl] = 0; + } + + $this->mpdf->listcounter[$this->mpdf->listlvl]++; + $this->mpdf->listitem = []; + //if in table - output here as a tabletextbuffer + //position:inside OR position:outside (always output in table as position:inside) + + $decToAlpha = new DecToAlpha(); + $decToRoman = new DecToRoman(); + + switch ($this->mpdf->listtype[$this->mpdf->listlvl]) { + case 'upper-alpha': + case 'upper-latin': + case 'A': + $blt = $decToAlpha->convert($this->mpdf->listcounter[$this->mpdf->listlvl]) . $this->mpdf->list_number_suffix; + break; + case 'lower-alpha': + case 'lower-latin': + case 'a': + $blt = $decToAlpha->convert($this->mpdf->listcounter[$this->mpdf->listlvl], false) . $this->mpdf->list_number_suffix; + break; + case 'upper-roman': + case 'I': + $blt = $decToRoman->convert($this->mpdf->listcounter[$this->mpdf->listlvl]) . $this->mpdf->list_number_suffix; + break; + case 'lower-roman': + case 'i': + $blt = $decToRoman->convert($this->mpdf->listcounter[$this->mpdf->listlvl]) . $this->mpdf->list_number_suffix; + break; + case 'decimal': + case '1': + $blt = $this->mpdf->listcounter[$this->mpdf->listlvl] . $this->mpdf->list_number_suffix; + break; + default: + $blt = '-'; + if ($this->mpdf->listlvl % 3 == 1 && $this->mpdf->_charDefined($this->mpdf->CurrentFont['cw'], 8226)) { + $blt = "\xe2\x80\xa2"; + } // • + elseif ($this->mpdf->listlvl % 3 == 2 && $this->mpdf->_charDefined($this->mpdf->CurrentFont['cw'], 9900)) { + $blt = "\xe2\x9a\xac"; + } // ⚬ + elseif ($this->mpdf->listlvl % 3 == 0 && $this->mpdf->_charDefined($this->mpdf->CurrentFont['cw'], 9642)) { + $blt = "\xe2\x96\xaa"; + } // ▪ + break; + } + + // change to   spaces + if ($this->mpdf->usingCoreFont) { + $ls = str_repeat(chr(160) . chr(160), ($this->mpdf->listlvl - 1) * 2) . $blt . ' '; + } else { + $ls = str_repeat("\xc2\xa0\xc2\xa0", ($this->mpdf->listlvl - 1) * 2) . $blt . ' '; + } + $this->mpdf->_saveCellTextBuffer($ls); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $this->mpdf->GetStringWidth($ls); + } + + return; + } + /* -- END TABLES -- */ + + if ($this->mpdf->lastblocklevelchange == 1) { + $blockstate = 1; + } // Top margins/padding only + elseif ($this->mpdf->lastblocklevelchange < 1) { + $blockstate = 0; + } // NO margins/padding + + $this->mpdf->printbuffer($this->mpdf->textbuffer, $blockstate); + $this->mpdf->textbuffer = []; + + $save_blklvl = $this->mpdf->blklvl; + $save_blk = $this->mpdf->blk; + + $this->mpdf->Reset(); + + $pagesel = ''; + /* -- CSS-PAGE -- */ + if (isset($p['PAGE'])) { + $pagesel = $p['PAGE']; + } // mPDF 6 (uses $p - preview of properties so blklvl can be incremented after page-break) + /* -- END CSS-PAGE -- */ + + // If page-box has changed AND/OR PAGE-BREAK-BEFORE + // mPDF 6 (uses $p - preview of properties so blklvl can be imcremented after page-break) + if (!$this->mpdf->tableLevel && (($pagesel && (!isset($this->mpdf->page_box['current']) + || $pagesel != $this->mpdf->page_box['current'])) + || (isset($p['PAGE-BREAK-BEFORE']) + && $p['PAGE-BREAK-BEFORE']))) { + // mPDF 6 pagebreaktype + $startpage = $this->mpdf->page; + $pagebreaktype = $this->mpdf->defaultPagebreakType; + $this->mpdf->lastblocklevelchange = -1; + if ($this->mpdf->ColActive) { + $pagebreaktype = 'cloneall'; + } + if ($pagesel && (!isset($this->mpdf->page_box['current']) || $pagesel != $this->mpdf->page_box['current'])) { + $pagebreaktype = 'cloneall'; + } + $this->mpdf->_preForcedPagebreak($pagebreaktype); + + if (isset($p['PAGE-BREAK-BEFORE'])) { + if (strtoupper($p['PAGE-BREAK-BEFORE']) === 'RIGHT') { + $this->mpdf->AddPage( + $this->mpdf->CurOrientation, + 'NEXT-ODD', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + 0, + 0, + 0, + 0, + $pagesel + ); + } elseif (strtoupper($p['PAGE-BREAK-BEFORE']) === 'LEFT') { + $this->mpdf->AddPage( + $this->mpdf->CurOrientation, + 'NEXT-EVEN', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + 0, + 0, + 0, + 0, + $pagesel + ); + } elseif (strtoupper($p['PAGE-BREAK-BEFORE']) === 'ALWAYS') { + $this->mpdf->AddPage($this->mpdf->CurOrientation, '', '', '', '', '', '', '', '', '', '', '', '', '', '', 0, 0, 0, 0, $pagesel); + } elseif ($this->mpdf->page_box['current'] != $pagesel) { + $this->mpdf->AddPage($this->mpdf->CurOrientation, '', '', '', '', '', '', '', '', '', '', '', '', '', '', 0, 0, 0, 0, $pagesel); + } // *CSS-PAGE* + } /* -- CSS-PAGE -- */ + // Must Add new page if changed page properties + elseif (!isset($this->mpdf->page_box['current']) || $pagesel != $this->mpdf->page_box['current']) { + $this->mpdf->AddPage($this->mpdf->CurOrientation, '', '', '', '', '', '', '', '', '', '', '', '', '', '', 0, 0, 0, 0, $pagesel); + } + /* -- END CSS-PAGE -- */ + + // mPDF 6 pagebreaktype + $this->mpdf->_postForcedPagebreak($pagebreaktype, $startpage, $save_blk, $save_blklvl); + } + + // mPDF 6 pagebreaktype - moved after pagebreak + $this->mpdf->blklvl++; + $currblk = & $this->mpdf->blk[$this->mpdf->blklvl]; + $this->mpdf->initialiseBlock($currblk); + $prevblk = & $this->mpdf->blk[$this->mpdf->blklvl - 1]; + $currblk['tag'] = $tag; + $currblk['attr'] = $attr; + + $properties = $this->cssManager->MergeCSS('BLOCK', $tag, $attr); // mPDF 6 - moved to after page-break-before + // mPDF 6 page-break-inside:avoid + if (isset($properties['PAGE-BREAK-INSIDE']) && strtoupper($properties['PAGE-BREAK-INSIDE']) === 'AVOID' + && !$this->mpdf->ColActive && !$this->mpdf->keep_block_together && !isset($attr['PAGEBREAKAVOIDCHECKED'])) { + // avoid re-iterating using PAGEBREAKAVOIDCHECKED; set in CloseTag + $currblk['keep_block_together'] = 1; + $currblk['array_i'] = $ihtml; // mPDF 6 + $this->mpdf->kt_y00 = $this->mpdf->y; + $this->mpdf->kt_p00 = $this->mpdf->page; + $this->mpdf->keep_block_together = 1; + } + if ($lastbottommargin && !empty($properties['MARGIN-TOP']) && empty($properties['FLOAT'])) { + $currblk['lastbottommargin'] = $lastbottommargin; + } + + if (isset($properties['Z-INDEX']) && $this->mpdf->current_layer == 0) { + $v = (int) $properties['Z-INDEX']; + if ($v > 0) { + $currblk['z-index'] = $v; + $this->mpdf->BeginLayer($v); + } + } + + + // mPDF 6 Lists + // List-type set by attribute + if ($tag === 'OL' || $tag === 'UL' || $tag === 'LI') { + if (!empty($attr['TYPE'])) { + $listtype = $attr['TYPE']; + switch ($listtype) { + case 'A': + $listtype = 'upper-latin'; + break; + case 'a': + $listtype = 'lower-latin'; + break; + case 'I': + $listtype = 'upper-roman'; + break; + case 'i': + $listtype = 'lower-roman'; + break; + case '1': + $listtype = 'decimal'; + break; + } + $currblk['list_style_type'] = $listtype; + } + } + + $this->mpdf->setCSS($properties, 'BLOCK', $tag); //name(id/class/style) found in the CSS array! + $currblk['InlineProperties'] = $this->mpdf->saveInlineProperties(); + + if (isset($properties['VISIBILITY'])) { + $v = strtolower($properties['VISIBILITY']); + if (($v === 'hidden' || $v === 'printonly' || $v === 'screenonly') && $this->mpdf->visibility === 'visible' && !$this->mpdf->tableLevel) { + $currblk['visibility'] = $v; + $this->mpdf->SetVisibility($v); + } + } + + // mPDF 6 + if (!empty($attr['ALIGN'])) { + $currblk['block-align'] = self::ALIGN[strtolower($attr['ALIGN'])]; + } + + + if (isset($properties['HEIGHT'])) { + $currblk['css_set_height'] = $this->sizeConverter->convert( + $properties['HEIGHT'], + $this->mpdf->h - $this->mpdf->tMargin - $this->mpdf->bMargin, + $this->mpdf->FontSize, + false + ); + if (($currblk['css_set_height'] + $this->mpdf->y) > $this->mpdf->PageBreakTrigger + && $this->mpdf->y > $this->mpdf->tMargin + 5 + && $currblk['css_set_height'] < ($this->mpdf->h - ($this->mpdf->tMargin + $this->mpdf->bMargin))) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } + } else { + $currblk['css_set_height'] = false; + } + + + // Added mPDF 3.0 Float DIV + if (isset($prevblk['blockContext'])) { + $currblk['blockContext'] = $prevblk['blockContext']; + } // *CSS-FLOAT* + + if (isset($properties['CLEAR'])) { + $this->mpdf->ClearFloats(strtoupper($properties['CLEAR']), $this->mpdf->blklvl - 1); + } // *CSS-FLOAT* + + $currblk['padding_left'] = is_numeric($currblk['padding_left']) ? $currblk['padding_left'] : 0; + $currblk['padding_right'] = is_numeric($currblk['padding_right']) ? $currblk['padding_right'] : 0; + + $container_w = $prevblk['inner_width']; + $bdr = $currblk['border_right']['w']; + $bdl = $currblk['border_left']['w']; + $pdr = $currblk['padding_right']; + $pdl = $currblk['padding_left']; + + $setwidth = 0; + if (isset($currblk['css_set_width'])) { + $setwidth = $currblk['css_set_width']; + } + + /* -- CSS-FLOAT -- */ + if (isset($properties['FLOAT']) && strtoupper($properties['FLOAT']) === 'RIGHT' && !$this->mpdf->ColActive) { + + // Cancel Keep-Block-together + $currblk['keep_block_together'] = false; + $this->mpdf->kt_y00 = ''; + $this->mpdf->keep_block_together = 0; + + $this->mpdf->blockContext++; + $currblk['blockContext'] = $this->mpdf->blockContext; + + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->mpdf->GetFloatDivInfo($this->mpdf->blklvl - 1); + + // DIV is too narrow for text to fit! + $maxw = $container_w - $l_width - $r_width; + $doubleCharWidth = (2 * $this->mpdf->GetCharWidth('W', false)); + if (($setwidth + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) > $maxw + || ($maxw - ($currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr)) < (2 * $this->mpdf->GetCharWidth('W', false))) { + // Too narrow to fit - try to move down past L or R float + if ($l_max < $r_max && ($setwidth + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) <= ($container_w - $r_width) + && (($container_w - $r_width) - ($currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr)) > $doubleCharWidth) { + $this->mpdf->ClearFloats('LEFT', $this->mpdf->blklvl - 1); + } elseif ($r_max < $l_max && ($setwidth + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) <= ($container_w - $l_width) + && (($container_w - $l_width) - ($currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr)) > $doubleCharWidth) { + $this->mpdf->ClearFloats('RIGHT', $this->mpdf->blklvl - 1); + } else { + $this->mpdf->ClearFloats('BOTH', $this->mpdf->blklvl - 1); + } + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->mpdf->GetFloatDivInfo($this->mpdf->blklvl - 1); + } + + if ($r_exists) { + $currblk['margin_right'] += $r_width; + } + + $currblk['float'] = 'R'; + $currblk['float_start_y'] = $this->mpdf->y; + + if (isset($currblk['css_set_width'])) { + $currblk['margin_left'] = $container_w - ($setwidth + $bdl + $pdl + $bdr + $pdr + $currblk['margin_right']); + $currblk['float_width'] = ($setwidth + $bdl + $pdl + $bdr + $pdr + $currblk['margin_right']); + } else { + // *** If no width set - would need to buffer and keep track of max width, then Right-align if not full width + // and do borders and backgrounds - For now - just set to maximum width left + + if ($l_exists) { + $currblk['margin_left'] += $l_width; + } + $currblk['css_set_width'] = $container_w - ($currblk['margin_left'] + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr); + + $currblk['float_width'] = ($currblk['css_set_width'] + $bdl + $pdl + $bdr + $pdr + $currblk['margin_right']); + } + + } elseif (isset($properties['FLOAT']) && strtoupper($properties['FLOAT']) === 'LEFT' && !$this->mpdf->ColActive) { + // Cancel Keep-Block-together + $currblk['keep_block_together'] = false; + $this->mpdf->kt_y00 = ''; + $this->mpdf->keep_block_together = 0; + + $this->mpdf->blockContext++; + $currblk['blockContext'] = $this->mpdf->blockContext; + + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->mpdf->GetFloatDivInfo($this->mpdf->blklvl - 1); + + // DIV is too narrow for text to fit! + $maxw = $container_w - $l_width - $r_width; + $doubleCharWidth = (2 * $this->mpdf->GetCharWidth('W', false)); + if (($setwidth + $currblk['margin_left'] + $bdl + $pdl + $bdr + $pdr) > $maxw + || ($maxw - ($currblk['margin_left'] + $bdl + $pdl + $bdr + $pdr)) < (2 * $this->mpdf->GetCharWidth('W', false))) { + // Too narrow to fit - try to move down past L or R float + if ($l_max < $r_max && ($setwidth + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) <= ($container_w - $r_width) + && (($container_w - $r_width) - ($currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr)) > $doubleCharWidth) { + $this->mpdf->ClearFloats('LEFT', $this->mpdf->blklvl - 1); + } elseif ($r_max < $l_max && ($setwidth + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) <= ($container_w - $l_width) + && (($container_w - $l_width) - ($currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr)) > $doubleCharWidth) { + $this->mpdf->ClearFloats('RIGHT', $this->mpdf->blklvl - 1); + } else { + $this->mpdf->ClearFloats('BOTH', $this->mpdf->blklvl - 1); + } + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->mpdf->GetFloatDivInfo($this->mpdf->blklvl - 1); + } + + if ($l_exists) { + $currblk['margin_left'] += $l_width; + } + + $currblk['float'] = 'L'; + $currblk['float_start_y'] = $this->mpdf->y; + if ($setwidth) { + $currblk['margin_right'] = $container_w - ($setwidth + $bdl + $pdl + $bdr + $pdr + $currblk['margin_left']); + $currblk['float_width'] = ($setwidth + $bdl + $pdl + $bdr + $pdr + $currblk['margin_left']); + } else { + // *** If no width set - would need to buffer and keep track of max width, then Right-align if not full width + // and do borders and backgrounds - For now - just set to maximum width left + + if ($r_exists) { + $currblk['margin_right'] += $r_width; + } + $currblk['css_set_width'] = $container_w - ($currblk['margin_left'] + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr); + + $currblk['float_width'] = ($currblk['css_set_width'] + $bdl + $pdl + $bdr + $pdr + $currblk['margin_left']); + } + } else { + // Don't allow overlap - if floats present - adjust padding to avoid overlap with Floats + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->mpdf->GetFloatDivInfo($this->mpdf->blklvl - 1); + $maxw = $container_w - $l_width - $r_width; + + $pdl = is_numeric($pdl) ? $pdl : 0; + $pdr = is_numeric($pdr) ? $pdr : 0; + + $doubleCharWidth = (2 * $this->mpdf->GetCharWidth('W', false)); + if (($setwidth + $currblk['margin_left'] + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) > $maxw + || ($maxw - ($currblk['margin_right'] + $currblk['margin_left'] + $bdl + $pdl + $bdr + $pdr)) < $doubleCharWidth) { + // Too narrow to fit - try to move down past L or R float + if ($l_max < $r_max && ($setwidth + $currblk['margin_left'] + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) <= ($container_w - $r_width) + && (($container_w - $r_width) - ($currblk['margin_right'] + $currblk['margin_left'] + $bdl + $pdl + $bdr + $pdr)) > $doubleCharWidth) { + $this->mpdf->ClearFloats('LEFT', $this->mpdf->blklvl - 1); + } elseif ($r_max < $l_max && ($setwidth + $currblk['margin_left'] + $currblk['margin_right'] + $bdl + $pdl + $bdr + $pdr) <= ($container_w - $l_width) + && (($container_w - $l_width) - ($currblk['margin_right'] + $currblk['margin_left'] + $bdl + $pdl + $bdr + $pdr)) > $doubleCharWidth) { + $this->mpdf->ClearFloats('RIGHT', $this->mpdf->blklvl - 1); + } else { + $this->mpdf->ClearFloats('BOTH', $this->mpdf->blklvl - 1); + } + list($l_exists, $r_exists, $l_max, $r_max, $l_width, $r_width) = $this->mpdf->GetFloatDivInfo($this->mpdf->blklvl - 1); + } + if ($r_exists) { + $currblk['padding_right'] = max($r_width - $currblk['margin_right'] - $bdr, $pdr); + } + if ($l_exists) { + $currblk['padding_left'] = max($l_width - $currblk['margin_left'] - $bdl, $pdl); + } + } + /* -- END CSS-FLOAT -- */ + + + /* -- BORDER-RADIUS -- */ + // Automatically increase padding if required for border-radius + if ($this->mpdf->autoPadding && !$this->mpdf->ColActive) { + $currblk['border_radius_TL_H'] = Arrays::get($currblk, 'border_radius_TL_H', 0); + $currblk['border_radius_TL_V'] = Arrays::get($currblk, 'border_radius_TL_V', 0); + $currblk['border_radius_TR_H'] = Arrays::get($currblk, 'border_radius_TR_H', 0); + $currblk['border_radius_TR_V'] = Arrays::get($currblk, 'border_radius_TR_V', 0); + $currblk['border_radius_BL_H'] = Arrays::get($currblk, 'border_radius_BL_H', 0); + $currblk['border_radius_BL_V'] = Arrays::get($currblk, 'border_radius_BL_V', 0); + $currblk['border_radius_BR_H'] = Arrays::get($currblk, 'border_radius_BR_H', 0); + $currblk['border_radius_BR_V'] = Arrays::get($currblk, 'border_radius_BR_V', 0); + + if ($currblk['border_radius_TL_H'] > $currblk['padding_left'] && $currblk['border_radius_TL_V'] > $currblk['padding_top']) { + if ($currblk['border_radius_TL_H'] > $currblk['border_radius_TL_V']) { + $this->mpdf->_borderPadding( + $currblk['border_radius_TL_H'], + $currblk['border_radius_TL_V'], + $currblk['padding_left'], + $currblk['padding_top'] + ); + } else { + $this->mpdf->_borderPadding( + $currblk['border_radius_TL_V'], + $currblk['border_radius_TL_H'], + $currblk['padding_top'], + $currblk['padding_left'] + ); + } + } + if ($currblk['border_radius_TR_H'] > $currblk['padding_right'] && $currblk['border_radius_TR_V'] > $currblk['padding_top']) { + if ($currblk['border_radius_TR_H'] > $currblk['border_radius_TR_V']) { + $this->mpdf->_borderPadding( + $currblk['border_radius_TR_H'], + $currblk['border_radius_TR_V'], + $currblk['padding_right'], + $currblk['padding_top'] + ); + } else { + $this->mpdf->_borderPadding( + $currblk['border_radius_TR_V'], + $currblk['border_radius_TR_H'], + $currblk['padding_top'], + $currblk['padding_right'] + ); + } + } + if ($currblk['border_radius_BL_H'] > $currblk['padding_left'] && $currblk['border_radius_BL_V'] > $currblk['padding_bottom']) { + if ($currblk['border_radius_BL_H'] > $currblk['border_radius_BL_V']) { + $this->mpdf->_borderPadding( + $currblk['border_radius_BL_H'], + $currblk['border_radius_BL_V'], + $currblk['padding_left'], + $currblk['padding_bottom'] + ); + } else { + $this->mpdf->_borderPadding( + $currblk['border_radius_BL_V'], + $currblk['border_radius_BL_H'], + $currblk['padding_bottom'], + $currblk['padding_left'] + ); + } + } + if ($currblk['border_radius_BR_H'] > $currblk['padding_right'] && $currblk['border_radius_BR_V'] > $currblk['padding_bottom']) { + if ($currblk['border_radius_BR_H'] > $currblk['border_radius_BR_V']) { + $this->mpdf->_borderPadding( + $currblk['border_radius_BR_H'], + $currblk['border_radius_BR_V'], + $currblk['padding_right'], + $currblk['padding_bottom'] + ); + } else { + $this->mpdf->_borderPadding( + $currblk['border_radius_BR_V'], + $currblk['border_radius_BR_H'], + $currblk['padding_bottom'], + $currblk['padding_right'] + ); + } + } + } + /* -- END BORDER-RADIUS -- */ + + // Hanging indent - if negative indent: ensure padding is >= indent + if (!isset($currblk['text_indent'])) { + $currblk['text_indent'] = null; + } + if (!isset($currblk['inner_width'])) { + $currblk['inner_width'] = null; + } + $cbti = $this->sizeConverter->convert( + $currblk['text_indent'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + if ($cbti < 0) { + $hangind = -$cbti; + if (isset($currblk['direction']) && $currblk['direction'] === 'rtl') { // *OTL* + $currblk['padding_right'] = max($currblk['padding_right'], $hangind); // *OTL* + } // *OTL* + else { // *OTL* + $currblk['padding_left'] = max($currblk['padding_left'], $hangind); + } // *OTL* + } + + if (isset($currblk['css_set_width'])) { + if (isset($properties['MARGIN-LEFT'], $properties['MARGIN-RIGHT']) + && strtolower($properties['MARGIN-LEFT']) === 'auto' && strtolower($properties['MARGIN-RIGHT']) === 'auto') { + // Try to reduce margins to accomodate - if still too wide, set margin-right/left=0 (reduces width) + $anyextra = $prevblk['inner_width'] - ($currblk['css_set_width'] + $currblk['border_left']['w'] + + $currblk['padding_left'] + $currblk['border_right']['w'] + $currblk['padding_right']); + if ($anyextra > 0) { + $currblk['margin_left'] = $currblk['margin_right'] = $anyextra / 2; + } else { + $currblk['margin_left'] = $currblk['margin_right'] = 0; + } + } elseif (isset($properties['MARGIN-LEFT']) && strtolower($properties['MARGIN-LEFT']) === 'auto') { + // Try to reduce margin-left to accomodate - if still too wide, set margin-left=0 (reduces width) + $currblk['margin_left'] = $prevblk['inner_width'] - ($currblk['css_set_width'] + + $currblk['border_left']['w'] + $currblk['padding_left'] + $currblk['border_right']['w'] + + $currblk['padding_right'] + $currblk['margin_right']); + if ($currblk['margin_left'] < 0) { + $currblk['margin_left'] = 0; + } + } elseif (isset($properties['MARGIN-RIGHT']) && strtolower($properties['MARGIN-RIGHT']) === 'auto') { + // Try to reduce margin-right to accomodate - if still too wide, set margin-right=0 (reduces width) + $currblk['margin_right'] = $prevblk['inner_width'] - ($currblk['css_set_width'] + + $currblk['border_left']['w'] + $currblk['padding_left'] + + $currblk['border_right']['w'] + $currblk['padding_right'] + $currblk['margin_left']); + if ($currblk['margin_right'] < 0) { + $currblk['margin_right'] = 0; + } + } else { + if ($currblk['direction'] === 'rtl') { // *OTL* + // Try to reduce margin-left to accomodate - if still too wide, set margin-left=0 (reduces width) + $currblk['margin_left'] = $prevblk['inner_width'] - ($currblk['css_set_width'] + + $currblk['border_left']['w'] + $currblk['padding_left'] + $currblk['border_right']['w'] + + $currblk['padding_right'] + $currblk['margin_right']); // *OTL* + if ($currblk['margin_left'] < 0) { // *OTL* + $currblk['margin_left'] = 0; // *OTL* + } // *OTL* + } // *OTL* + else { // *OTL* + // Try to reduce margin-right to accomodate - if still too wide, set margin-right=0 (reduces width) + $currblk['margin_right'] = $prevblk['inner_width'] - ($currblk['css_set_width'] + + $currblk['border_left']['w'] + $currblk['padding_left'] + $currblk['border_right']['w'] + + $currblk['padding_right'] + $currblk['margin_left']); + if ($currblk['margin_right'] < 0) { + $currblk['margin_right'] = 0; + } + } // *OTL* + } + } + + $currblk['outer_left_margin'] = $prevblk['outer_left_margin'] + $currblk['margin_left'] + + $prevblk['border_left']['w'] + $prevblk['padding_left']; + + $currblk['outer_right_margin'] = $prevblk['outer_right_margin'] + $currblk['margin_right'] + + $prevblk['border_right']['w'] + $prevblk['padding_right']; + + $currblk['width'] = $this->mpdf->pgwidth - ($currblk['outer_right_margin'] + $currblk['outer_left_margin']); + + $currblk['inner_width'] = $currblk['width'] + - ($currblk['border_left']['w'] + $currblk['padding_left'] + $currblk['border_right']['w'] + $currblk['padding_right']); + + // Check DIV is not now too narrow to fit text + $mw = 2 * $this->mpdf->GetCharWidth('W', false); + if ($currblk['inner_width'] < $mw) { + $currblk['padding_left'] = 0; + $currblk['padding_right'] = 0; + $currblk['border_left']['w'] = 0.2; + $currblk['border_right']['w'] = 0.2; + $currblk['margin_left'] = 0; + $currblk['margin_right'] = 0; + $currblk['outer_left_margin'] = $prevblk['outer_left_margin'] + $currblk['margin_left'] + + $prevblk['border_left']['w'] + $prevblk['padding_left']; + $currblk['outer_right_margin'] = $prevblk['outer_right_margin'] + $currblk['margin_right'] + + $prevblk['border_right']['w'] + $prevblk['padding_right']; + $currblk['width'] = $this->mpdf->pgwidth - ($currblk['outer_right_margin'] + $currblk['outer_left_margin']); + $currblk['inner_width'] = $this->mpdf->pgwidth - ($currblk['outer_right_margin'] + + $currblk['outer_left_margin'] + $currblk['border_left']['w'] + $currblk['padding_left'] + + $currblk['border_right']['w'] + $currblk['padding_right']); + // if ($currblk['inner_width'] < $mw) { throw new \Mpdf\MpdfException("DIV is too narrow for text to fit!"); } + } + + $this->mpdf->x = $this->mpdf->lMargin + $currblk['outer_left_margin']; + + /* -- BACKGROUNDS -- */ + if (!empty($properties['BACKGROUND-IMAGE']) && !$this->mpdf->kwt && !$this->mpdf->ColActive && !$this->mpdf->keep_block_together) { + $ret = $this->mpdf->SetBackground($properties, $currblk['inner_width']); + if ($ret) { + $currblk['background-image'] = $ret; + } + } + /* -- END BACKGROUNDS -- */ + + /* -- TABLES -- */ + if ($this->mpdf->use_kwt && isset($attr['KEEP-WITH-TABLE']) && !$this->mpdf->ColActive && !$this->mpdf->keep_block_together) { + $this->mpdf->kwt = true; + $this->mpdf->kwt_y0 = $this->mpdf->y; + //$this->mpdf->kwt_x0 = $this->mpdf->x; + $this->mpdf->kwt_x0 = $this->mpdf->lMargin; // mPDF 6 + $this->mpdf->kwt_height = 0; + $this->mpdf->kwt_buffer = []; + $this->mpdf->kwt_Links = []; + $this->mpdf->kwt_Annots = []; + $this->mpdf->kwt_moved = false; + $this->mpdf->kwt_saved = false; + $this->mpdf->kwt_Reference = []; + $this->mpdf->kwt_BMoutlines = []; + $this->mpdf->kwt_toc = []; + } else { + /* -- END TABLES -- */ + $this->mpdf->kwt = false; + } // *TABLES* + + // Save x,y coords in case we need to print borders... + $currblk['y0'] = $this->mpdf->y; + $currblk['initial_y0'] = $this->mpdf->y; // mPDF 6 + $currblk['x0'] = $this->mpdf->x; + $currblk['initial_x0'] = $this->mpdf->x; // mPDF 6 + $currblk['initial_startpage'] = $this->mpdf->page; + $currblk['startpage'] = $this->mpdf->page; // mPDF 6 + $this->mpdf->oldy = $this->mpdf->y; + + $this->mpdf->lastblocklevelchange = 1; + + // mPDF 6 Lists + if ($tag === 'OL' || $tag === 'UL') { + $this->mpdf->listlvl++; + if (!empty($attr['START'])) { + $this->mpdf->listcounter[$this->mpdf->listlvl] = (int) $attr['START'] - 1; + } else { + $this->mpdf->listcounter[$this->mpdf->listlvl] = 0; + } + $this->mpdf->listitem = []; + + // List-type + if (empty($currblk['list_style_type'])) { + if ($tag === 'OL') { + $currblk['list_style_type'] = 'decimal'; + } elseif ($tag === 'UL') { + if ($this->mpdf->listlvl % 3 == 1) { + $currblk['list_style_type'] = 'disc'; + } elseif ($this->mpdf->listlvl % 3 == 2) { + $currblk['list_style_type'] = 'circle'; + } else { + $currblk['list_style_type'] = 'square'; + } + } + } + + // List-image + if (empty($currblk['list_style_image'])) { + $currblk['list_style_image'] = 'none'; + } + + // List-position + if (empty($currblk['list_style_position'])) { + $currblk['list_style_position'] = 'outside'; + } + + // Default indentation using padding + if (strtolower($this->mpdf->list_auto_mode) === 'mpdf' && isset($currblk['list_style_position']) + && $currblk['list_style_position'] === 'outside' && isset($currblk['list_style_image']) + && $currblk['list_style_image'] === 'none' && (!isset($currblk['list_style_type']) + || !preg_match('/U\+([a-fA-F0-9]+)/i', $currblk['list_style_type']))) { + $autopadding = $this->mpdf->_getListMarkerWidth($currblk, $ahtml, $ihtml); + if ($this->mpdf->listlvl > 1 || $this->mpdf->list_indent_first_level) { + $autopadding += $this->sizeConverter->convert( + $this->mpdf->list_indent_default, + $currblk['inner_width'], + $this->mpdf->FontSize, + false + ); + } + // autopadding value is applied to left or right according + // to dir of block. Once a CSS value is set for padding it overrides this default value. + if (isset($properties['PADDING-RIGHT']) && $properties['PADDING-RIGHT'] === 'auto' + && isset($currblk['direction']) && $currblk['direction'] === 'rtl') { + $currblk['padding_right'] = $autopadding; + } elseif (isset($properties['PADDING-LEFT']) && $properties['PADDING-LEFT'] === 'auto') { + $currblk['padding_left'] = $autopadding; + } + } else { + // Initial default value is set by $this->mpdf->list_indent_default in config.php; this value is applied to left or right according + // to dir of block. Once a CSS value is set for padding it overrides this default value. + if (isset($properties['PADDING-RIGHT']) && $properties['PADDING-RIGHT'] === 'auto' + && isset($currblk['direction']) && $currblk['direction'] === 'rtl') { + $currblk['padding_right'] = $this->sizeConverter->convert( + $this->mpdf->list_indent_default, + $currblk['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($properties['PADDING-LEFT']) && $properties['PADDING-LEFT'] === 'auto') { + $currblk['padding_left'] = $this->sizeConverter->convert( + $this->mpdf->list_indent_default, + $currblk['inner_width'], + $this->mpdf->FontSize, + false + ); + } + } + } + + // mPDF 6 Lists + if ($tag === 'LI') { + if ($this->mpdf->listlvl == 0) { // in case of malformed HTML code. Example:(...)</p><li>Content</li><p>Paragraph1</p>(...) + $this->mpdf->listlvl++; // first depth level + $this->mpdf->listcounter[$this->mpdf->listlvl] = 0; + } + + if (!isset($attr['PAGEBREAKAVOIDCHECKED']) || !$attr['PAGEBREAKAVOIDCHECKED']) { + $this->mpdf->listcounter[$this->mpdf->listlvl]++; + } + + $this->mpdf->listitem = []; + + // Listitem-type + $this->mpdf->_setListMarker($currblk['list_style_type'], $currblk['list_style_image'], $currblk['list_style_position']); + } + + // mPDF 6 Bidirectional formatting for block elements + $bdf = false; + $bdf2 = ''; + $popd = ''; + + // Get current direction + $currdir = 'ltr'; + if (isset($currblk['direction'])) { + $currdir = $currblk['direction']; + } + if (isset($attr['DIR']) && $attr['DIR'] != '') { + $currdir = strtolower($attr['DIR']); + } + if (isset($properties['DIRECTION'])) { + $currdir = strtolower($properties['DIRECTION']); + } + + // mPDF 6 bidi + // cf. http://www.w3.org/TR/css3-writing-modes/#unicode-bidi + if (isset($properties ['UNICODE-BIDI']) + && (strtolower($properties ['UNICODE-BIDI']) === 'bidi-override' || strtolower($properties ['UNICODE-BIDI']) === 'isolate-override')) { + if ($currdir === 'rtl') { + $bdf = 0x202E; + $popd = 'RLOPDF'; + } // U+202E RLO + else { + $bdf = 0x202D; + $popd = 'LROPDF'; + } // U+202D LRO + } elseif (isset($properties ['UNICODE-BIDI']) && strtolower($properties ['UNICODE-BIDI']) === 'plaintext') { + $bdf = 0x2068; + $popd = 'FSIPDI'; // U+2068 FSI + } + if ($bdf) { + if ($bdf2) { + $bdf2 = UtfString::code2utf($bdf); + } + $this->mpdf->OTLdata = []; + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer(UtfString::code2utf($bdf) . $bdf2); + } else { + $this->mpdf->_saveTextBuffer(UtfString::code2utf($bdf) . $bdf2); + } + $this->mpdf->biDirectional = true; + $currblk['bidicode'] = $popd; + } + } + + public function close(&$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + + // mPDF 6 bidi + // Block + // If unicode-bidi set, any embedding levels, isolates, or overrides started by this box are closed + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['bidicode'])) { + $blockpost = $this->mpdf->_setBidiCodes('end', $this->mpdf->blk[$this->mpdf->blklvl]['bidicode']); + if ($blockpost) { + $this->mpdf->OTLdata = []; + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($blockpost); + } else { + $this->mpdf->_saveTextBuffer($blockpost); + } + } + } + + $this->mpdf->ignorefollowingspaces = true; //Eliminate exceeding left-side spaces + $this->mpdf->blockjustfinished = true; + + $this->mpdf->lastblockbottommargin = $this->mpdf->blk[$this->mpdf->blklvl]['margin_bottom']; + // mPDF 6 Lists + if ($tag === 'UL' || $tag === 'OL') { + if ($this->mpdf->listlvl > 0 && $this->mpdf->tableLevel) { + if (isset($this->mpdf->listtype[$this->mpdf->listlvl])) { + unset($this->mpdf->listtype[$this->mpdf->listlvl]); + } + } + $this->mpdf->listlvl--; + $this->mpdf->listitem = []; + } + if ($tag === 'LI') { + $this->mpdf->listitem = []; + } + + if (preg_match('/^H\d/', $tag) && !$this->mpdf->tableLevel && !$this->mpdf->writingToC) { + if (isset($this->mpdf->h2toc[$tag]) || isset($this->mpdf->h2bookmarks[$tag])) { + $content = ''; + if (count($this->mpdf->textbuffer) == 1) { + $content = $this->mpdf->textbuffer[0][0]; + } else { + for ($i = 0; $i < count($this->mpdf->textbuffer); $i++) { + if (0 !== strpos($this->mpdf->textbuffer[$i][0], "\xbb\xa4\xac")) { //inline object + $content .= $this->mpdf->textbuffer[$i][0]; + } + } + } + /* -- TOC -- */ + if (isset($this->mpdf->h2toc[$tag])) { + $objattr = []; + $objattr['type'] = 'toc'; + $objattr['toclevel'] = $this->mpdf->h2toc[$tag]; + $objattr['CONTENT'] = htmlspecialchars($content); + $e = "\xbb\xa4\xactype=toc,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + array_unshift($this->mpdf->textbuffer, [$e]); + } + /* -- END TOC -- */ + /* -- BOOKMARKS -- */ + if (isset($this->mpdf->h2bookmarks[$tag])) { + $objattr = []; + $objattr['type'] = 'bookmark'; + $objattr['bklevel'] = $this->mpdf->h2bookmarks[$tag]; + $objattr['CONTENT'] = $content; + $e = "\xbb\xa4\xactype=toc,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + array_unshift($this->mpdf->textbuffer, [$e]); + } + /* -- END BOOKMARKS -- */ + } + } + + /* -- TABLES -- */ + if ($this->mpdf->tableLevel) { + if ($this->mpdf->linebreakjustfinished) { + $this->mpdf->blockjustfinished = false; + } + if (isset($this->mpdf->InlineProperties['BLOCKINTABLE'])) { + if ($this->mpdf->InlineProperties['BLOCKINTABLE']) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties['BLOCKINTABLE']); + } + unset($this->mpdf->InlineProperties['BLOCKINTABLE']); + } + if ($tag === 'PRE') { + $this->mpdf->ispre = false; + } + return; + } + /* -- END TABLES -- */ + $this->mpdf->lastoptionaltag = ''; + $this->mpdf->divbegin = false; + + $this->mpdf->linebreakjustfinished = false; + + $this->mpdf->x = $this->mpdf->lMargin + $this->mpdf->blk[$this->mpdf->blklvl]['outer_left_margin']; + + /* -- CSS-FLOAT -- */ + // If float contained in a float, need to extend bottom to allow for it + $currpos = $this->mpdf->page * 1000 + $this->mpdf->y; + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['float_endpos']) && $this->mpdf->blk[$this->mpdf->blklvl]['float_endpos'] > $currpos) { + $old_page = $this->mpdf->page; + $new_page = (int) ($this->mpdf->blk[$this->mpdf->blklvl]['float_endpos'] / 1000); + if ($old_page != $new_page) { + $s = $this->mpdf->PrintPageBackgrounds(); + // Writes after the marker so not overwritten later by page background etc. + $this->mpdf->pages[$this->mpdf->page] = preg_replace( + '/(___BACKGROUND___PATTERNS' . $this->mpdf->uniqstr . ')/', + '\\1' . "\n" . $s . "\n", + $this->mpdf->pages[$this->mpdf->page] + ); + $this->mpdf->pageBackgrounds = []; + $this->mpdf->page = $new_page; + $this->mpdf->ResetMargins(); + $this->mpdf->Reset(); + $this->mpdf->pageoutput[$this->mpdf->page] = []; + } + // mod changes operands to integers before processing + $this->mpdf->y = (($this->mpdf->blk[$this->mpdf->blklvl]['float_endpos'] * 1000) % 1000000) / 1000; + } + /* -- END CSS-FLOAT -- */ + + + //Print content + $blockstate = 0; + if ($this->mpdf->lastblocklevelchange == 1) { + $blockstate = 3; + } // Top & bottom margins/padding + elseif ($this->mpdf->lastblocklevelchange == -1) { + $blockstate = 2; + } // Bottom margins/padding only + + // called from after e.g. </table> </div> </div> ... Outputs block margin/border and padding + if (count($this->mpdf->textbuffer) && $this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1]) { + if (0 !== strpos($this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][0], "\xbb\xa4\xac")) { // not special content + // Right trim last content and adjust OTLdata + if (preg_match('/[ ]+$/', $this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][0], $m)) { + $strip = strlen($m[0]); + $this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][0] = substr( + $this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][0], + 0, + strlen($this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][0]) - $strip + ); + /* -- OTL -- */ + if (!empty($this->mpdf->CurrentFont['useOTL'])) { + $this->otl->trimOTLdata($this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][18], false); // mPDF 6 ZZZ99K + } + /* -- END OTL -- */ + } + } + } + + if (count($this->mpdf->textbuffer) == 0 && $this->mpdf->lastblocklevelchange != 0) { + /*$this->mpdf->newFlowingBlock( + $this->mpdf->blk[$this->mpdf->blklvl]['width'], + $this->mpdf->lineheight, + '', + false, + 2, + true, + (isset($this->mpdf->blk[$this->mpdf->blklvl]['direction']) ? $this->mpdf->blk[$this->mpdf->blklvl]['direction'] : 'ltr') + );*/ + + $this->mpdf->newFlowingBlock( + $this->mpdf->blk[$this->mpdf->blklvl]['width'], + $this->mpdf->lineheight, + '', + false, + $blockstate, + true, + (isset($this->mpdf->blk[$this->mpdf->blklvl]['direction']) ? $this->mpdf->blk[$this->mpdf->blklvl]['direction'] : 'ltr') + ); + + $this->mpdf->finishFlowingBlock(true); // true = END of flowing block + $this->mpdf->PaintDivBB('', $blockstate); + } else { + $this->mpdf->printbuffer($this->mpdf->textbuffer, $blockstate); + } + + + $this->mpdf->textbuffer = []; + + if ($this->mpdf->kwt) { + $this->mpdf->kwt_height = $this->mpdf->y - $this->mpdf->kwt_y0; + } + + /* -- CSS-IMAGE-FLOAT -- */ + $this->mpdf->printfloatbuffer(); + /* -- END CSS-IMAGE-FLOAT -- */ + + if ($tag === 'PRE') { + $this->mpdf->ispre = false; + } + + /* -- CSS-FLOAT -- */ + if ($this->mpdf->blk[$this->mpdf->blklvl]['float'] === 'R') { + // If width not set, here would need to adjust and output buffer + $s = $this->mpdf->PrintPageBackgrounds(); + // Writes after the marker so not overwritten later by page background etc. + $this->mpdf->pages[$this->mpdf->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->mpdf->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->mpdf->pages[$this->mpdf->page]); + $this->mpdf->pageBackgrounds = []; + $this->mpdf->Reset(); + $this->mpdf->pageoutput[$this->mpdf->page] = []; + + for ($i = ($this->mpdf->blklvl - 1); $i >= 0; $i--) { + if (isset($this->mpdf->blk[$i]['float_endpos'])) { + $this->mpdf->blk[$i]['float_endpos'] = max($this->mpdf->blk[$i]['float_endpos'], $this->mpdf->page * 1000 + $this->mpdf->y); + } else { + $this->mpdf->blk[$i]['float_endpos'] = $this->mpdf->page * 1000 + $this->mpdf->y; + } + } + + $this->mpdf->floatDivs[] = [ + 'side' => 'R', + 'startpage' => $this->mpdf->blk[$this->mpdf->blklvl]['startpage'], + 'y0' => $this->mpdf->blk[$this->mpdf->blklvl]['float_start_y'], + 'startpos' => $this->mpdf->blk[$this->mpdf->blklvl]['startpage'] * 1000 + $this->mpdf->blk[$this->mpdf->blklvl]['float_start_y'], + 'endpage' => $this->mpdf->page, + 'y1' => $this->mpdf->y, + 'endpos' => $this->mpdf->page * 1000 + $this->mpdf->y, + 'w' => $this->mpdf->blk[$this->mpdf->blklvl]['float_width'], + 'blklvl' => $this->mpdf->blklvl, + 'blockContext' => $this->mpdf->blk[$this->mpdf->blklvl - 1]['blockContext'] + ]; + + $this->mpdf->y = $this->mpdf->blk[$this->mpdf->blklvl]['float_start_y']; + $this->mpdf->page = $this->mpdf->blk[$this->mpdf->blklvl]['startpage']; + $this->mpdf->ResetMargins(); + $this->mpdf->pageoutput[$this->mpdf->page] = []; + } + if ($this->mpdf->blk[$this->mpdf->blklvl]['float'] === 'L') { + // If width not set, here would need to adjust and output buffer + $s = $this->mpdf->PrintPageBackgrounds(); + // Writes after the marker so not overwritten later by page background etc. + $this->mpdf->pages[$this->mpdf->page] = preg_replace('/(___BACKGROUND___PATTERNS' . $this->mpdf->uniqstr . ')/', '\\1' . "\n" . $s . "\n", $this->mpdf->pages[$this->mpdf->page]); + $this->mpdf->pageBackgrounds = []; + $this->mpdf->Reset(); + $this->mpdf->pageoutput[$this->mpdf->page] = []; + + for ($i = ($this->mpdf->blklvl - 1); $i >= 0; $i--) { + if (isset($this->mpdf->blk[$i]['float_endpos'])) { + $this->mpdf->blk[$i]['float_endpos'] = max($this->mpdf->blk[$i]['float_endpos'], $this->mpdf->page * 1000 + $this->mpdf->y); + } else { + $this->mpdf->blk[$i]['float_endpos'] = $this->mpdf->page * 1000 + $this->mpdf->y; + } + } + + $this->mpdf->floatDivs[] = [ + 'side' => 'L', + 'startpage' => $this->mpdf->blk[$this->mpdf->blklvl]['startpage'], + 'y0' => $this->mpdf->blk[$this->mpdf->blklvl]['float_start_y'], + 'startpos' => $this->mpdf->blk[$this->mpdf->blklvl]['startpage'] * 1000 + $this->mpdf->blk[$this->mpdf->blklvl]['float_start_y'], + 'endpage' => $this->mpdf->page, + 'y1' => $this->mpdf->y, + 'endpos' => $this->mpdf->page * 1000 + $this->mpdf->y, + 'w' => $this->mpdf->blk[$this->mpdf->blklvl]['float_width'], + 'blklvl' => $this->mpdf->blklvl, + 'blockContext' => $this->mpdf->blk[$this->mpdf->blklvl - 1]['blockContext'] + ]; + + $this->mpdf->y = $this->mpdf->blk[$this->mpdf->blklvl]['float_start_y']; + $this->mpdf->page = $this->mpdf->blk[$this->mpdf->blklvl]['startpage']; + $this->mpdf->ResetMargins(); + $this->mpdf->pageoutput[$this->mpdf->page] = []; + } + /* -- END CSS-FLOAT -- */ + + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['visibility']) && $this->mpdf->blk[$this->mpdf->blklvl]['visibility'] !== 'visible') { + $this->mpdf->SetVisibility('visible'); + } + + $page_break_after = ''; + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['page_break_after'])) { + $page_break_after = $this->mpdf->blk[$this->mpdf->blklvl]['page_break_after']; + } + + // Reset values + $this->mpdf->Reset(); + + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['z-index']) && $this->mpdf->blk[$this->mpdf->blklvl]['z-index'] > 0) { + $this->mpdf->EndLayer(); + } + + // mPDF 6 page-break-inside:avoid + if ($this->mpdf->blk[$this->mpdf->blklvl]['keep_block_together']) { + $movepage = false; + // If page-break-inside:avoid section has broken to new page but fits on one side - then move: + if (($this->mpdf->page - $this->mpdf->kt_p00) == 1 && $this->mpdf->y < $this->mpdf->kt_y00) { + $movepage = true; + } + if (($this->mpdf->page - $this->mpdf->kt_p00) > 0) { + for ($i = $this->mpdf->page; $i > $this->mpdf->kt_p00; $i--) { + unset($this->mpdf->pages[$i]); + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['bb_painted'][$i])) { + unset($this->mpdf->blk[$this->mpdf->blklvl]['bb_painted'][$i]); + } + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['marginCorrected'][$i])) { + unset($this->mpdf->blk[$this->mpdf->blklvl]['marginCorrected'][$i]); + } + if (isset($this->mpdf->pageoutput[$i])) { + unset($this->mpdf->pageoutput[$i]); + } + } + $this->mpdf->page = $this->mpdf->kt_p00; + } + $this->mpdf->keep_block_together = 0; + $this->mpdf->pageoutput[$this->mpdf->page] = []; + + $this->mpdf->y = $this->mpdf->kt_y00; + $ihtml = $this->mpdf->blk[$this->mpdf->blklvl]['array_i'] - 1; + + $ahtml[$ihtml + 1] .= ' pagebreakavoidchecked="true";'; // avoid re-iterating; read in OpenTag() + + unset($this->mpdf->blk[$this->mpdf->blklvl]); + $this->mpdf->blklvl--; + + for ($blklvl = 1; $blklvl <= $this->mpdf->blklvl; $blklvl++) { + $this->mpdf->blk[$blklvl]['y0'] = $this->mpdf->blk[$blklvl]['initial_y0']; + $this->mpdf->blk[$blklvl]['x0'] = $this->mpdf->blk[$blklvl]['initial_x0']; + $this->mpdf->blk[$blklvl]['startpage'] = $this->mpdf->blk[$blklvl]['initial_startpage']; + } + + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['x0'])) { + $this->mpdf->x = $this->mpdf->blk[$this->mpdf->blklvl]['x0']; + } else { + $this->mpdf->x = $this->mpdf->lMargin; + } + + $this->mpdf->lastblocklevelchange = 0; + $this->mpdf->ResetMargins(); + if ($movepage) { + $this->mpdf->AddPage(); + } + return; + } + + if ($this->mpdf->blklvl > 0) { // ==0 SHOULDN'T HAPPEN - NOT XHTML + if ($this->mpdf->blk[$this->mpdf->blklvl]['tag'] == $tag) { + unset($this->mpdf->blk[$this->mpdf->blklvl]); + $this->mpdf->blklvl--; + } + //else { echo $tag; exit; } // debug - forces error if incorrectly nested html tags + } + + $this->mpdf->lastblocklevelchange = -1; + // Reset Inline-type properties + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['InlineProperties'])) { + $this->mpdf->restoreInlineProperties($this->mpdf->blk[$this->mpdf->blklvl]['InlineProperties']); + } + + $this->mpdf->x = $this->mpdf->lMargin + $this->mpdf->blk[$this->mpdf->blklvl]['outer_left_margin']; + + if (!$this->mpdf->tableLevel && $page_break_after) { + $save_blklvl = $this->mpdf->blklvl; + $save_blk = $this->mpdf->blk; + $save_silp = $this->mpdf->saveInlineProperties(); + $save_ilp = $this->mpdf->InlineProperties; + $save_bflp = $this->mpdf->InlineBDF; + $save_bflpc = $this->mpdf->InlineBDFctr; // mPDF 6 + // mPDF 6 pagebreaktype + $startpage = $this->mpdf->page; + $pagebreaktype = $this->mpdf->defaultPagebreakType; + if ($this->mpdf->ColActive) { + $pagebreaktype = 'cloneall'; + } + + // mPDF 6 pagebreaktype + $this->mpdf->_preForcedPagebreak($pagebreaktype); + + if ($page_break_after === 'RIGHT') { + $this->mpdf->AddPage($this->mpdf->CurOrientation, 'NEXT-ODD'); + } elseif ($page_break_after === 'LEFT') { + $this->mpdf->AddPage($this->mpdf->CurOrientation, 'NEXT-EVEN'); + } else { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } + + // mPDF 6 pagebreaktype + $this->mpdf->_postForcedPagebreak($pagebreaktype, $startpage, $save_blk, $save_blklvl); + + $this->mpdf->InlineProperties = $save_ilp; + $this->mpdf->InlineBDF = $save_bflp; + $this->mpdf->InlineBDFctr = $save_bflpc; // mPDF 6 + $this->mpdf->restoreInlineProperties($save_silp); + } + // mPDF 6 bidi + // Block + // If unicode-bidi set, any embedding levels, isolates, or overrides reopened in the continuing block + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['bidicode'])) { + $blockpre = $this->mpdf->_setBidiCodes('start', $this->mpdf->blk[$this->mpdf->blklvl]['bidicode']); + if ($blockpre) { + $this->mpdf->OTLdata = []; + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($blockpre); + } else { + $this->mpdf->_saveTextBuffer($blockpre); + } + } + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bookmark.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bookmark.php new file mode 100644 index 0000000..b414cf4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Bookmark.php @@ -0,0 +1,32 @@ +<?php + +namespace Mpdf\Tag; + +class Bookmark extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + if (isset($attr['CONTENT'])) { + $objattr = []; + $objattr['CONTENT'] = htmlspecialchars_decode($attr['CONTENT'], ENT_QUOTES); + $objattr['type'] = 'bookmark'; + if (!empty($attr['LEVEL'])) { + $objattr['bklevel'] = $attr['LEVEL']; + } else { + $objattr['bklevel'] = 0; + } + $e = "\xbb\xa4\xactype=bookmark,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + if ($this->mpdf->tableLevel) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][] = [$e]; + } // *TABLES* + else { // *TABLES* + $this->mpdf->textbuffer[] = [$e]; + } // *TABLES* + } + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Br.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Br.php new file mode 100644 index 0000000..125a18a --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Br.php @@ -0,0 +1,88 @@ +<?php + +namespace Mpdf\Tag; + +class Br extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + // Added mPDF 3.0 Float DIV - CLEAR + if (isset($attr['STYLE'])) { + $properties = $this->cssManager->readInlineCSS($attr['STYLE']); + if (isset($properties['CLEAR'])) { + $this->mpdf->ClearFloats(strtoupper($properties['CLEAR']), $this->mpdf->blklvl); + } // *CSS-FLOAT* + } + + // mPDF 6 bidi + // Inline + // If unicode-bidi set, any embedding levels, isolates, or overrides started by + // the inline box are closed at the br and reopened on the other side + $blockpre = ''; + $blockpost = ''; + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['bidicode'])) { + $blockpre = $this->mpdf->_setBidiCodes('end', $this->mpdf->blk[$this->mpdf->blklvl]['bidicode']); + $blockpost = $this->mpdf->_setBidiCodes('start', $this->mpdf->blk[$this->mpdf->blklvl]['bidicode']); + } + + // Inline + // If unicode-bidi set, any embedding levels, isolates, or overrides started by + // the inline box are closed at the br and reopened on the other side + $inlinepre = ''; + $inlinepost = ''; + $iBDF = []; + if (count($this->mpdf->InlineBDF)) { + foreach ($this->mpdf->InlineBDF as $k => $ib) { + foreach ($ib as $ib2) { + $iBDF[$ib2[1]] = $ib2[0]; + } + } + if (count($iBDF)) { + ksort($iBDF); + for ($i = count($iBDF) - 1; $i >= 0; $i--) { + $inlinepre .= $this->mpdf->_setBidiCodes('end', $iBDF[$i]); + } + for ($i = 0; $i < count($iBDF); $i++) { + $inlinepost .= $this->mpdf->_setBidiCodes('start', $iBDF[$i]); + } + } + } + + /* -- TABLES -- */ + if ($this->mpdf->tableLevel) { + if ($this->mpdf->blockjustfinished) { + $this->mpdf->_saveCellTextBuffer($blockpre . $inlinepre . "\n" . $inlinepost . $blockpost); + } + + $this->mpdf->_saveCellTextBuffer($blockpre . $inlinepre . "\n" . $inlinepost . $blockpost); + if (!isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'])) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } elseif ($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] < $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] = 0; // reset + } else { + /* -- END TABLES -- */ + if (count($this->mpdf->textbuffer)) { + $this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][0] = preg_replace( + '/ $/', + '', + $this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][0] + ); + if (!empty($this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][18])) { + $this->otl->trimOTLdata($this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1][18], false); + } // *OTL* + } + $this->mpdf->_saveTextBuffer($blockpre . $inlinepre . "\n" . $inlinepost . $blockpost); + } // *TABLES* + $this->mpdf->ignorefollowingspaces = true; + $this->mpdf->blockjustfinished = false; + + $this->mpdf->linebreakjustfinished = true; + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Caption.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Caption.php new file mode 100644 index 0000000..ffcb99a --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Caption.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Caption extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Center.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Center.php new file mode 100644 index 0000000..36fbe77 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Center.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Center extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Cite.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Cite.php new file mode 100644 index 0000000..869cfc4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Cite.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Cite extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Code.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Code.php new file mode 100644 index 0000000..47776e7 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Code.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Code extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/ColumnBreak.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/ColumnBreak.php new file mode 100644 index 0000000..5b2ff05 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/ColumnBreak.php @@ -0,0 +1,8 @@ +<?php + +namespace Mpdf\Tag; + +class ColumnBreak extends NewColumn +{ + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Columns.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Columns.php new file mode 100644 index 0000000..0e6bed4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Columns.php @@ -0,0 +1,67 @@ +<?php + +namespace Mpdf\Tag; + +class Columns extends Tag +{ + /** + * @param string $tag + * @return \Mpdf\Tag\Tag + */ + private function getTagInstance($tag) + { + $className = \Mpdf\Tag::getTagClassName($tag); + if (class_exists($className)) { + return new $className( + $this->mpdf, + $this->cache, + $this->cssManager, + $this->form, + $this->otl, + $this->tableOfContents, + $this->sizeConverter, + $this->colorConverter, + $this->imageProcessor, + $this->languageToFont + ); + } + + return null; + } + + public function open($attr, &$ahtml, &$ihtml) + { + if (isset($attr['COLUMN-COUNT']) && ($attr['COLUMN-COUNT'] || $attr['COLUMN-COUNT'] === '0')) { + // Close any open block tags + for ($b = $this->mpdf->blklvl; $b > 0; $b--) { + if ($t = $this->getTagInstance($this->mpdf->blk[$b]['tag'])) { + $t->close($ahtml, $ihtml); + } + } + if (!empty($this->mpdf->textbuffer)) { //Output previously buffered content + $this->mpdf->printbuffer($this->mpdf->textbuffer); + $this->mpdf->textbuffer = []; + } + + if (!empty($attr['VALIGN'])) { + if ($attr['VALIGN'] === 'J') { + $valign = 'J'; + } else { + $valign = self::ALIGN[$attr['VALIGN']]; + } + } else { + $valign = ''; + } + if (!empty($attr['COLUMN-GAP'])) { + $this->mpdf->SetColumns($attr['COLUMN-COUNT'], $valign, $attr['COLUMN-GAP']); + } else { + $this->mpdf->SetColumns($attr['COLUMN-COUNT'], $valign); + } + } + $this->mpdf->ignorefollowingspaces = true; + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dd.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dd.php new file mode 100644 index 0000000..ce0ccfb --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dd.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Dd extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Del.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Del.php new file mode 100644 index 0000000..8af9cd0 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Del.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Del extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Details.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Details.php new file mode 100644 index 0000000..bd81da0 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Details.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Details extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Div.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Div.php new file mode 100644 index 0000000..30fb2b7 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Div.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Div extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dl.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dl.php new file mode 100644 index 0000000..80bafc2 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dl.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Dl extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/DotTab.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/DotTab.php new file mode 100644 index 0000000..278ce38 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/DotTab.php @@ -0,0 +1,68 @@ +<?php + +namespace Mpdf\Tag; + +class DotTab extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $objattr = []; + $objattr['type'] = 'dottab'; + $dots = str_repeat('.', 3) . ' '; // minimum number of dots + $objattr['width'] = $this->mpdf->GetStringWidth($dots); + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['height'] = 0; + $objattr['colorarray'] = $this->mpdf->colorarray; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $objattr['vertical_align'] = 'BS'; // mPDF 6 DOTTAB + + $properties = $this->cssManager->MergeCSS('INLINE', 'DOTTAB', $attr); + if (isset($properties['OUTDENT'])) { + $objattr['outdent'] = $this->sizeConverter->convert( + $properties['OUTDENT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['OUTDENT'])) { + $objattr['outdent'] = $this->sizeConverter->convert( + $attr['OUTDENT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } else { + $objattr['outdent'] = 0; + } + + $objattr['fontfamily'] = $this->mpdf->FontFamily; + $objattr['fontsize'] = $this->mpdf->FontSizePt; + + $e = "\xbb\xa4\xactype=dottab,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + /* -- TABLES -- */ + // Output it to buffers + if ($this->mpdf->tableLevel) { + if (!isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'])) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } elseif ($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] < $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] = 0; // reset + $this->mpdf->_saveCellTextBuffer($e); + } else { + /* -- END TABLES -- */ + $this->mpdf->_saveTextBuffer($e); + } // *TABLES* + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dt.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dt.php new file mode 100644 index 0000000..22492fb --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Dt.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Dt extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Em.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Em.php new file mode 100644 index 0000000..a353bd2 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Em.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Em extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FieldSet.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FieldSet.php new file mode 100644 index 0000000..33356f1 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FieldSet.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class FieldSet extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FigCaption.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FigCaption.php new file mode 100644 index 0000000..1e738a5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FigCaption.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class FigCaption extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Figure.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Figure.php new file mode 100644 index 0000000..1074a63 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Figure.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Figure extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Font.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Font.php new file mode 100644 index 0000000..e290414 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Font.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Font extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Footer.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Footer.php new file mode 100644 index 0000000..46abbc4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Footer.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Footer extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Form.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Form.php new file mode 100644 index 0000000..588000e --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Form.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Form extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FormFeed.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FormFeed.php new file mode 100644 index 0000000..6b2f46b --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/FormFeed.php @@ -0,0 +1,293 @@ +<?php + +namespace Mpdf\Tag; + +class FormFeed extends Tag +{ + public $toc_id; + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + if (isset($attr['SHEET-SIZE'])) { + // Convert to same types as accepted in initial mPDF() A4, A4-L, or array(w,h) + $prop = preg_split('/\s+/', trim($attr['SHEET-SIZE'])); + if (count($prop) == 2) { + $newformat = [$this->sizeConverter->convert($prop[0]), $this->sizeConverter->convert($prop[1])]; + } else { + $newformat = $attr['SHEET-SIZE']; + } + } else { + $newformat = ''; + } + + $save_blklvl = $this->mpdf->blklvl; + $save_blk = $this->mpdf->blk; + $save_silp = $this->mpdf->saveInlineProperties(); + $save_ilp = $this->mpdf->InlineProperties; + $save_bflp = $this->mpdf->InlineBDF; + $save_bflpc = $this->mpdf->InlineBDFctr; // mPDF 6 + + $mgr = $mgl = $mgt = $mgb = $mgh = $mgf = ''; + if (isset($attr['MARGIN-RIGHT'])) { + $mgr = $this->sizeConverter->convert($attr['MARGIN-RIGHT'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['MARGIN-LEFT'])) { + $mgl = $this->sizeConverter->convert($attr['MARGIN-LEFT'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['MARGIN-TOP'])) { + $mgt = $this->sizeConverter->convert($attr['MARGIN-TOP'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['MARGIN-BOTTOM'])) { + $mgb = $this->sizeConverter->convert($attr['MARGIN-BOTTOM'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['MARGIN-HEADER'])) { + $mgh = $this->sizeConverter->convert($attr['MARGIN-HEADER'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + if (isset($attr['MARGIN-FOOTER'])) { + $mgf = $this->sizeConverter->convert($attr['MARGIN-FOOTER'], $this->mpdf->w, $this->mpdf->FontSize, false); + } + $ohname = $ehname = $ofname = $efname = ''; + if (isset($attr['ODD-HEADER-NAME'])) { + $ohname = $attr['ODD-HEADER-NAME']; + } + if (isset($attr['EVEN-HEADER-NAME'])) { + $ehname = $attr['EVEN-HEADER-NAME']; + } + if (isset($attr['ODD-FOOTER-NAME'])) { + $ofname = $attr['ODD-FOOTER-NAME']; + } + if (isset($attr['EVEN-FOOTER-NAME'])) { + $efname = $attr['EVEN-FOOTER-NAME']; + } + $ohvalue = $ehvalue = $ofvalue = $efvalue = 0; + if (isset($attr['ODD-HEADER-VALUE']) && ($attr['ODD-HEADER-VALUE'] == '1' || strtoupper($attr['ODD-HEADER-VALUE']) === 'ON')) { + $ohvalue = 1; + } elseif (isset($attr['ODD-HEADER-VALUE']) && ($attr['ODD-HEADER-VALUE'] == '-1' || strtoupper($attr['ODD-HEADER-VALUE']) === 'OFF')) { + $ohvalue = -1; + } + if (isset($attr['EVEN-HEADER-VALUE']) && ($attr['EVEN-HEADER-VALUE'] == '1' || strtoupper($attr['EVEN-HEADER-VALUE']) === 'ON')) { + $ehvalue = 1; + } elseif (isset($attr['EVEN-HEADER-VALUE']) && ($attr['EVEN-HEADER-VALUE'] == '-1' || strtoupper($attr['EVEN-HEADER-VALUE']) === 'OFF')) { + $ehvalue = -1; + } + if (isset($attr['ODD-FOOTER-VALUE']) && ($attr['ODD-FOOTER-VALUE'] == '1' || strtoupper($attr['ODD-FOOTER-VALUE']) === 'ON')) { + $ofvalue = 1; + } elseif (isset($attr['ODD-FOOTER-VALUE']) && ($attr['ODD-FOOTER-VALUE'] == '-1' || strtoupper($attr['ODD-FOOTER-VALUE']) === 'OFF')) { + $ofvalue = -1; + } + if (isset($attr['EVEN-FOOTER-VALUE']) && ($attr['EVEN-FOOTER-VALUE'] == '1' || strtoupper($attr['EVEN-FOOTER-VALUE']) === 'ON')) { + $efvalue = 1; + } elseif (isset($attr['EVEN-FOOTER-VALUE']) && ($attr['EVEN-FOOTER-VALUE'] == '-1' || strtoupper($attr['EVEN-FOOTER-VALUE']) === 'OFF')) { + $efvalue = -1; + } + + if (isset($attr['ORIENTATION']) && (strtoupper($attr['ORIENTATION']) === 'L' || strtoupper($attr['ORIENTATION']) === 'LANDSCAPE')) { + $orient = 'L'; + } elseif (isset($attr['ORIENTATION']) && (strtoupper($attr['ORIENTATION']) === 'P' || strtoupper($attr['ORIENTATION']) === 'PORTRAIT')) { + $orient = 'P'; + } else { + $orient = $this->mpdf->CurOrientation; + } + + $pagesel = ''; + if (!empty($attr['PAGE-SELECTOR'])) { + $pagesel = $attr['PAGE-SELECTOR']; + } + + // mPDF 6 pagebreaktype + $pagebreaktype = $this->mpdf->defaultPagebreakType; + if ($tag === 'FORMFEED') { + $pagebreaktype = 'slice'; + } // can be overridden by PAGE-BREAK-TYPE + $startpage = $this->mpdf->page; + if (isset($attr['PAGE-BREAK-TYPE'])) { + if (strtolower($attr['PAGE-BREAK-TYPE']) === 'cloneall' + || strtolower($attr['PAGE-BREAK-TYPE']) === 'clonebycss' + || strtolower($attr['PAGE-BREAK-TYPE']) === 'slice') { + $pagebreaktype = strtolower($attr['PAGE-BREAK-TYPE']); + } + } + if ($tag === 'TOCPAGEBREAK') { + $pagebreaktype = 'cloneall'; + } elseif ($this->mpdf->ColActive) { + $pagebreaktype = 'cloneall'; + } // Any change in headers/footers (may need to _getHtmlHeight), or page size/orientation, @page selector, or margins - force cloneall + elseif ($mgr !== '' || $mgl !== '' || $mgt !== '' || $mgb !== '' || $mgh !== '' || $mgf !== '' || + $ohname !== '' || $ehname !== '' || $ofname !== '' || $efname !== '' || + $ohvalue || $ehvalue || $ofvalue || $efvalue || + $orient != $this->mpdf->CurOrientation || $newformat || $pagesel) { + $pagebreaktype = 'cloneall'; + } + + // mPDF 6 pagebreaktype + $this->mpdf->_preForcedPagebreak($pagebreaktype); + + $this->mpdf->ignorefollowingspaces = true; + + + $resetpagenum = ''; + $pagenumstyle = ''; + $suppress = ''; + if (isset($attr['RESETPAGENUM'])) { + $resetpagenum = $attr['RESETPAGENUM']; + } + if (isset($attr['PAGENUMSTYLE'])) { + $pagenumstyle = $attr['PAGENUMSTYLE']; + } + if (isset($attr['SUPPRESS'])) { + $suppress = $attr['SUPPRESS']; + } + + $type = ''; + if ($tag === 'TOCPAGEBREAK') { + $type = 'NEXT-ODD'; + } elseif (isset($attr['TYPE'])) { + $type = strtoupper($attr['TYPE']); + } + + if ($type === 'E' || $type === 'EVEN') { + $this->mpdf->AddPage( + $orient, + 'E', + $resetpagenum, + $pagenumstyle, + $suppress, + $mgl, + $mgr, + $mgt, + $mgb, + $mgh, + $mgf, + $ohname, + $ehname, + $ofname, + $efname, + $ohvalue, + $ehvalue, + $ofvalue, + $efvalue, + $pagesel, + $newformat + ); + } elseif ($type === 'O' || $type === 'ODD') { + $this->mpdf->AddPage( + $orient, + 'O', + $resetpagenum, + $pagenumstyle, + $suppress, + $mgl, + $mgr, + $mgt, + $mgb, + $mgh, + $mgf, + $ohname, + $ehname, + $ofname, + $efname, + $ohvalue, + $ehvalue, + $ofvalue, + $efvalue, + $pagesel, + $newformat + ); + } elseif ($type === 'NEXT-ODD') { + $this->mpdf->AddPage( + $orient, + 'NEXT-ODD', + $resetpagenum, + $pagenumstyle, + $suppress, + $mgl, + $mgr, + $mgt, + $mgb, + $mgh, + $mgf, + $ohname, + $ehname, + $ofname, + $efname, + $ohvalue, + $ehvalue, + $ofvalue, + $efvalue, + $pagesel, + $newformat + ); + } elseif ($type === 'NEXT-EVEN') { + $this->mpdf->AddPage( + $orient, + 'NEXT-EVEN', + $resetpagenum, + $pagenumstyle, + $suppress, + $mgl, + $mgr, + $mgt, + $mgb, + $mgh, + $mgf, + $ohname, + $ehname, + $ofname, + $efname, + $ohvalue, + $ehvalue, + $ofvalue, + $efvalue, + $pagesel, + $newformat + ); + } else { + $this->mpdf->AddPage( + $orient, + '', + $resetpagenum, + $pagenumstyle, + $suppress, + $mgl, + $mgr, + $mgt, + $mgb, + $mgh, + $mgf, + $ohname, + $ehname, + $ofname, + $efname, + $ohvalue, + $ehvalue, + $ofvalue, + $efvalue, + $pagesel, + $newformat + ); + } + + /* -- TOC -- */ + if ($tag === 'TOCPAGEBREAK') { + if ($this->toc_id) { + $this->tableOfContents->m_TOC[$this->toc_id]['TOCmark'] = $this->mpdf->page; + } else { + $this->tableOfContents->TOCmark = $this->mpdf->page; + } + } + /* -- END TOC -- */ + + // mPDF 6 pagebreaktype + $this->mpdf->_postForcedPagebreak($pagebreaktype, $startpage, $save_blk, $save_blklvl); + + $this->mpdf->InlineProperties = $save_ilp; + $this->mpdf->InlineBDF = $save_bflp; + $this->mpdf->InlineBDFctr = $save_bflpc; // mPDF 6 + $this->mpdf->restoreInlineProperties($save_silp); + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H1.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H1.php new file mode 100644 index 0000000..5174afb --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H1.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class H1 extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H2.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H2.php new file mode 100644 index 0000000..199bb55 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H2.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class H2 extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H3.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H3.php new file mode 100644 index 0000000..b9db924 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H3.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class H3 extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H4.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H4.php new file mode 100644 index 0000000..3083ba1 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H4.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class H4 extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H5.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H5.php new file mode 100644 index 0000000..50b78d7 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H5.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class H5 extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H6.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H6.php new file mode 100644 index 0000000..f33dacf --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/H6.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class H6 extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/HGroup.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/HGroup.php new file mode 100644 index 0000000..e519b93 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/HGroup.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class HGroup extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Header.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Header.php new file mode 100644 index 0000000..f948559 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Header.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Header extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Hr.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Hr.php new file mode 100644 index 0000000..5155c63 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Hr.php @@ -0,0 +1,126 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Utils\NumericString; + +class Hr extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + + // Added mPDF 3.0 Float DIV - CLEAR + if (isset($attr['STYLE'])) { + $properties = $this->cssManager->readInlineCSS($attr['STYLE']); + if (isset($properties['CLEAR'])) { + $this->mpdf->ClearFloats(strtoupper($properties['CLEAR']), $this->mpdf->blklvl); + } // *CSS-FLOAT* + } + + $this->mpdf->ignorefollowingspaces = true; + + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $properties = $this->cssManager->MergeCSS('', 'HR', $attr); + if (isset($properties['MARGIN-TOP'])) { + $objattr['margin_top'] = $this->sizeConverter->convert( + $properties['MARGIN-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-BOTTOM'])) { + $objattr['margin_bottom'] = $this->sizeConverter->convert( + $properties['MARGIN-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['WIDTH'])) { + $objattr['width'] = $this->sizeConverter->convert($properties['WIDTH'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']); + } elseif (isset($attr['WIDTH']) && $attr['WIDTH'] != '') { + $objattr['width'] = $this->sizeConverter->convert($attr['WIDTH'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']); + } + if (isset($properties['TEXT-ALIGN'])) { + $objattr['align'] = self::ALIGN[strtolower($properties['TEXT-ALIGN'])]; + } elseif (isset($attr['ALIGN']) && $attr['ALIGN'] != '') { + $objattr['align'] = self::ALIGN[strtolower($attr['ALIGN'])]; + } + + if (isset($properties['MARGIN-LEFT']) && strtolower($properties['MARGIN-LEFT']) === 'auto') { + $objattr['align'] = 'R'; + } + if (isset($properties['MARGIN-RIGHT']) && strtolower($properties['MARGIN-RIGHT']) === 'auto') { + $objattr['align'] = 'L'; + if (isset($properties['MARGIN-RIGHT']) && strtolower($properties['MARGIN-RIGHT']) === 'auto' + && isset($properties['MARGIN-LEFT']) && strtolower($properties['MARGIN-LEFT']) === 'auto') { + $objattr['align'] = 'C'; + } + } + if (isset($properties['COLOR'])) { + $objattr['color'] = $this->colorConverter->convert($properties['COLOR'], $this->mpdf->PDFAXwarnings); + } elseif (isset($attr['COLOR']) && $attr['COLOR'] != '') { + $objattr['color'] = $this->colorConverter->convert($attr['COLOR'], $this->mpdf->PDFAXwarnings); + } + if (isset($properties['HEIGHT'])) { + $objattr['linewidth'] = $this->sizeConverter->convert( + $properties['HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + + /* -- TABLES -- */ + if ($this->mpdf->tableLevel) { + $objattr['W-PERCENT'] = 100; + if (isset($properties['WIDTH']) && NumericString::containsPercentChar($properties['WIDTH'])) { + $properties['WIDTH'] = NumericString::removePercentChar($properties['WIDTH']); // make "90%" become simply "90" + $objattr['W-PERCENT'] = $properties['WIDTH']; + } + if (isset($attr['WIDTH']) && NumericString::containsPercentChar($attr['WIDTH'])) { + $attr['WIDTH'] = NumericString::removePercentChar($attr['WIDTH']); // make "90%" become simply "90" + $objattr['W-PERCENT'] = $attr['WIDTH']; + } + } + /* -- END TABLES -- */ + + $objattr['type'] = 'hr'; + $objattr['height'] = $objattr['linewidth'] + $objattr['margin_top'] + $objattr['margin_bottom']; + $e = "\xbb\xa4\xactype=image,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + + /* -- TABLES -- */ + // Output it to buffers + if ($this->mpdf->tableLevel) { + if ($this->mpdf->cell) { + if (!isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'])) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } elseif ($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] < $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] = 0; // reset + $this->mpdf->_saveCellTextBuffer($e, $this->mpdf->HREF); + } + } else { + /* -- END TABLES -- */ + $this->mpdf->_saveTextBuffer($e, $this->mpdf->HREF); + } // *TABLES* + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/I.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/I.php new file mode 100644 index 0000000..8c03c2d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/I.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class I extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Img.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Img.php new file mode 100644 index 0000000..cd7fc0d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Img.php @@ -0,0 +1,458 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; + +class Img extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->ignorefollowingspaces = false; + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['padding_top'] = 0; + $objattr['padding_bottom'] = 0; + $objattr['padding_left'] = 0; + $objattr['padding_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + if (isset($attr['SRC'])) { + $srcpath = $attr['SRC']; + $orig_srcpath = (isset($attr['ORIG_SRC']) ? $attr['ORIG_SRC'] : ''); + $properties = $this->cssManager->MergeCSS('', 'IMG', $attr); + if (isset($properties ['DISPLAY']) && strtolower($properties ['DISPLAY']) === 'none') { + return; + } + if (isset($properties['Z-INDEX']) && $this->mpdf->current_layer == 0) { + $v = (int) $properties['Z-INDEX']; + if ($v > 0) { + $objattr['z-index'] = $v; + } + } + + $objattr['visibility'] = 'visible'; + if (isset($properties['VISIBILITY'])) { + $v = strtolower($properties['VISIBILITY']); + if (($v === 'hidden' || $v === 'printonly' || $v === 'screenonly') && $this->mpdf->visibility === 'visible') { + $objattr['visibility'] = $v; + } + } + + // VSPACE and HSPACE converted to margins in MergeCSS + if (isset($properties['MARGIN-TOP'])) { + $objattr['margin_top'] = $this->sizeConverter->convert( + $properties['MARGIN-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-BOTTOM'])) { + $objattr['margin_bottom'] = $this->sizeConverter->convert( + $properties['MARGIN-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-LEFT'])) { + $objattr['margin_left'] = $this->sizeConverter->convert( + $properties['MARGIN-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-RIGHT'])) { + $objattr['margin_right'] = $this->sizeConverter->convert( + $properties['MARGIN-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['PADDING-TOP'])) { + $objattr['padding_top'] = $this->sizeConverter->convert( + $properties['PADDING-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-BOTTOM'])) { + $objattr['padding_bottom'] = $this->sizeConverter->convert( + $properties['PADDING-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-LEFT'])) { + $objattr['padding_left'] = $this->sizeConverter->convert( + $properties['PADDING-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-RIGHT'])) { + $objattr['padding_right'] = $this->sizeConverter->convert( + $properties['PADDING-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['BORDER-TOP'])) { + $objattr['border_top'] = $this->mpdf->border_details($properties['BORDER-TOP']); + } + if (isset($properties['BORDER-BOTTOM'])) { + $objattr['border_bottom'] = $this->mpdf->border_details($properties['BORDER-BOTTOM']); + } + if (isset($properties['BORDER-LEFT'])) { + $objattr['border_left'] = $this->mpdf->border_details($properties['BORDER-LEFT']); + } + if (isset($properties['BORDER-RIGHT'])) { + $objattr['border_right'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); + } + + if (isset($properties['VERTICAL-ALIGN'])) { + $objattr['vertical-align'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } + $w = 0; + $h = 0; + if (isset($properties['WIDTH'])) { + $w = $this->sizeConverter->convert( + $properties['WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['WIDTH'])) { + $w = $this->sizeConverter->convert( + $attr['WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['HEIGHT'])) { + $h = $this->sizeConverter->convert( + $properties['HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['HEIGHT'])) { + $h = $this->sizeConverter->convert( + $attr['HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + $maxw = $maxh = $minw = $minh = false; + if (isset($properties['MAX-WIDTH'])) { + $maxw = $this->sizeConverter->convert( + $properties['MAX-WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['MAX-WIDTH'])) { + $maxw = $this->sizeConverter->convert( + $attr['MAX-WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MAX-HEIGHT'])) { + $maxh = $this->sizeConverter->convert( + $properties['MAX-HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['MAX-HEIGHT'])) { + $maxh = $this->sizeConverter->convert( + $attr['MAX-HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MIN-WIDTH'])) { + $minw = $this->sizeConverter->convert( + $properties['MIN-WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['MIN-WIDTH'])) { + $minw = $this->sizeConverter->convert( + $attr['MIN-WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MIN-HEIGHT'])) { + $minh = $this->sizeConverter->convert( + $properties['MIN-HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['MIN-HEIGHT'])) { + $minh = $this->sizeConverter->convert( + $attr['MIN-HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['OPACITY']) && $properties['OPACITY'] > 0 && $properties['OPACITY'] <= 1) { + $objattr['opacity'] = $properties['OPACITY']; + } + if ($this->mpdf->HREF) { + if (strpos($this->mpdf->HREF, '.') === false && strpos($this->mpdf->HREF, '@') !== 0) { + $href = $this->mpdf->HREF; + while (array_key_exists($href, $this->mpdf->internallink)) { + $href = '#' . $href; + } + $this->mpdf->internallink[$href] = $this->mpdf->AddLink(); + $objattr['link'] = $this->mpdf->internallink[$href]; + } else { + $objattr['link'] = $this->mpdf->HREF; + } + } + $extraheight = $objattr['padding_top'] + $objattr['padding_bottom'] + $objattr['margin_top'] + + $objattr['margin_bottom'] + $objattr['border_top']['w'] + $objattr['border_bottom']['w']; + + $extrawidth = $objattr['padding_left'] + $objattr['padding_right'] + $objattr['margin_left'] + + $objattr['margin_right'] + $objattr['border_left']['w'] + $objattr['border_right']['w']; + + // mPDF 5.7.3 TRANSFORMS + if (isset($properties['BACKGROUND-COLOR']) && $properties['BACKGROUND-COLOR'] != '') { + $objattr['bgcolor'] = $this->colorConverter->convert($properties['BACKGROUND-COLOR'], $this->mpdf->PDFAXwarnings); + } + + /* -- BACKGROUNDS -- */ + if (isset($properties['GRADIENT-MASK']) && preg_match('/(-moz-)*(repeating-)*(linear|radial)-gradient/', $properties['GRADIENT-MASK'])) { + $objattr['GRADIENT-MASK'] = $properties['GRADIENT-MASK']; + } + /* -- END BACKGROUNDS -- */ + + // mPDF 6 + $interpolation = false; + if (!empty($properties['IMAGE-RENDERING'])) { + $interpolation = false; + if (strtolower($properties['IMAGE-RENDERING']) === 'crisp-edges') { + $interpolation = false; + } elseif (strtolower($properties['IMAGE-RENDERING']) === 'optimizequality') { + $interpolation = true; + } elseif (strtolower($properties['IMAGE-RENDERING']) === 'smooth') { + $interpolation = true; + } elseif (strtolower($properties['IMAGE-RENDERING']) === 'auto') { + $interpolation = $this->mpdf->interpolateImages; + } + $info['interpolation'] = $interpolation; + } + + // Image file + $info = $this->imageProcessor->getImage($srcpath, true, true, $orig_srcpath, $interpolation); // mPDF 6 + if (!$info) { + $info = $this->imageProcessor->getImage($this->mpdf->noImageFile); + if ($info) { + $srcpath = $this->mpdf->noImageFile; + $w = ($info['w'] * (25.4 / $this->mpdf->img_dpi)); + $h = ($info['h'] * (25.4 / $this->mpdf->img_dpi)); + } + } + if (!$info) { + return; + } + + $image_orientation = 0; + if (isset($attr['ROTATE'])) { + $image_orientation = $attr['ROTATE']; + } elseif (isset($properties['IMAGE-ORIENTATION'])) { + $image_orientation = $properties['IMAGE-ORIENTATION']; + } + if ($image_orientation) { + if ($image_orientation == 90 || $image_orientation == -90 || $image_orientation == 270) { + $tmpw = $info['w']; + $info['w'] = $info['h']; + $info['h'] = $tmpw; + } + $objattr['ROTATE'] = $image_orientation; + } + + $objattr['file'] = $srcpath; + //Default width and height calculation if needed + if ($w == 0 && $h == 0) { + /* -- IMAGES-WMF -- */ + if ($info['type'] === 'wmf') { + // WMF units are twips (1/20pt) + // divide by 20 to get points + // divide by k to get user units + $w = abs($info['w']) / (20 * Mpdf::SCALE); + $h = abs($info['h']) / (20 * Mpdf::SCALE); + } else { /* -- END IMAGES-WMF -- */ + if ($info['type'] === 'svg') { + // SVG units are pixels + $w = abs($info['w']) / Mpdf::SCALE; + $h = abs($info['h']) / Mpdf::SCALE; + } else { + //Put image at default image dpi + $w = ($info['w'] / Mpdf::SCALE) * (72 / $this->mpdf->img_dpi); + $h = ($info['h'] / Mpdf::SCALE) * (72 / $this->mpdf->img_dpi); + } + } + if (isset($properties['IMAGE-RESOLUTION'])) { + if (preg_match('/from-image/i', $properties['IMAGE-RESOLUTION']) && isset($info['set-dpi']) && $info['set-dpi'] > 0) { + $w *= $this->mpdf->img_dpi / $info['set-dpi']; + $h *= $this->mpdf->img_dpi / $info['set-dpi']; + } elseif (preg_match('/(\d+)dpi/i', $properties['IMAGE-RESOLUTION'], $m)) { + $dpi = $m[1]; + if ($dpi > 0) { + $w *= $this->mpdf->img_dpi / $dpi; + $h *= $this->mpdf->img_dpi / $dpi; + } + } + } + } + // IF WIDTH OR HEIGHT SPECIFIED + if ($w == 0) { + $w = $info['h'] ? abs($h * $info['w'] / $info['h']) : INF; + } + + if ($h == 0) { + $h = $info['w'] ? abs($w * $info['h'] / $info['w']) : INF; + } + + if ($minw && $w < $minw) { + $w = $minw; + $h = $info['w'] ? abs($w * $info['h'] / $info['w']) : INF; + } + if ($maxw && $w > $maxw) { + $w = $maxw; + $h = $info['w'] ? abs($w * $info['h'] / $info['w']) : INF; + } + if ($minh && $h < $minh) { + $h = $minh; + $w = $info['h'] ? abs($h * $info['w'] / $info['h']) : INF; + } + if ($maxh && $h > $maxh) { + $h = $maxh; + $w = $info['h'] ? abs($h * $info['w'] / $info['h']) : INF; + } + + // Resize to maximum dimensions of page + $maxWidth = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']; + $maxHeight = $this->mpdf->h - ($this->mpdf->tMargin + $this->mpdf->bMargin + 1); + if ($this->mpdf->fullImageHeight) { + $maxHeight = $this->mpdf->fullImageHeight; + } + if (($w + $extrawidth) > ($maxWidth + 0.0001)) { // mPDF 5.7.4 0.0001 to allow for rounding errors when w==maxWidth + $w = $maxWidth - $extrawidth; + $h = abs($w * $info['h'] / $info['w']); + } + + if ($h + $extraheight > $maxHeight) { + $h = $maxHeight - $extraheight; + $w = abs($h * $info['w'] / $info['h']); + } + $objattr['type'] = 'image'; + $objattr['itype'] = $info['type']; + + $objattr['orig_h'] = $info['h']; + $objattr['orig_w'] = $info['w']; + /* -- IMAGES-WMF -- */ + if ($info['type'] === 'wmf') { + $objattr['wmf_x'] = $info['x']; + $objattr['wmf_y'] = $info['y']; + } else { /* -- END IMAGES-WMF -- */ + if ($info['type'] === 'svg') { + $objattr['wmf_x'] = $info['x']; + $objattr['wmf_y'] = $info['y']; + } + } + $objattr['height'] = $h + $extraheight; + $objattr['width'] = $w + $extrawidth; + $objattr['image_height'] = $h; + $objattr['image_width'] = $w; + /* -- CSS-IMAGE-FLOAT -- */ + if (!$this->mpdf->ColActive && !$this->mpdf->tableLevel && !$this->mpdf->listlvl && !$this->mpdf->kwt) { + if (isset($properties['FLOAT']) && (strtoupper($properties['FLOAT']) === 'RIGHT' || strtoupper($properties['FLOAT']) === 'LEFT')) { + $objattr['float'] = strtoupper(substr($properties['FLOAT'], 0, 1)); + } + } + /* -- END CSS-IMAGE-FLOAT -- */ + // mPDF 5.7.3 TRANSFORMS + if (isset($properties['TRANSFORM']) && !$this->mpdf->ColActive && !$this->mpdf->kwt) { + $objattr['transform'] = $properties['TRANSFORM']; + } + + $e = "\xbb\xa4\xactype=image,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + + /* -- TABLES -- */ + // Output it to buffers + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($e, $this->mpdf->HREF); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $objattr['width']; + } else { + /* -- END TABLES -- */ + $this->mpdf->_saveTextBuffer($e, $this->mpdf->HREF); + } // *TABLES* + /* -- ANNOTATIONS -- */ + if ($this->mpdf->title2annots && isset($attr['TITLE'])) { + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $objattr['CONTENT'] = $attr['TITLE']; + $objattr['type'] = 'annot'; + $objattr['POS-X'] = 0; + $objattr['POS-Y'] = 0; + $objattr['ICON'] = 'Comment'; + $objattr['AUTHOR'] = ''; + $objattr['SUBJECT'] = ''; + $objattr['OPACITY'] = $this->mpdf->annotOpacity; + $objattr['COLOR'] = $this->colorConverter->convert('yellow', $this->mpdf->PDFAXwarnings); + $e = "\xbb\xa4\xactype=annot,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + if ($this->mpdf->tableLevel) { // *TABLES* + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][] = [$e]; // *TABLES* + } // *TABLES* + else { // *TABLES* + $this->mpdf->textbuffer[] = [$e]; + } // *TABLES* + } + /* -- END ANNOTATIONS -- */ + } + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/IndexEntry.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/IndexEntry.php new file mode 100644 index 0000000..2ca06a4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/IndexEntry.php @@ -0,0 +1,32 @@ +<?php + +namespace Mpdf\Tag; + +class IndexEntry extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + if (!empty($attr['CONTENT'])) { + if (!empty($attr['XREF'])) { + $this->mpdf->IndexEntry(htmlspecialchars_decode($attr['CONTENT'], ENT_QUOTES), $attr['XREF']); + return; + } + $objattr = []; + $objattr['CONTENT'] = htmlspecialchars_decode($attr['CONTENT'], ENT_QUOTES); + $objattr['type'] = 'indexentry'; + $objattr['vertical-align'] = 'T'; + $e = "\xbb\xa4\xactype=indexentry,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + if ($this->mpdf->tableLevel) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][] = [$e]; + } // *TABLES* + else { // *TABLES* + $this->mpdf->textbuffer[] = [$e]; + } // *TABLES* + } + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/IndexInsert.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/IndexInsert.php new file mode 100644 index 0000000..2704b4f --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/IndexInsert.php @@ -0,0 +1,33 @@ +<?php + +namespace Mpdf\Tag; + +class IndexInsert extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $indexCollationLocale = ''; + if (isset($attr['COLLATION'])) { + $indexCollationLocale = $attr['COLLATION']; + } + + $indexCollationGroup = ''; + if (isset($attr['COLLATION-GROUP'])) { + $indexCollationGroup = $attr['COLLATION-GROUP']; + } + + $usedivletters = 1; + if (isset($attr['USEDIVLETTERS']) && (strtoupper($attr['USEDIVLETTERS']) === 'OFF' + || $attr['USEDIVLETTERS'] == -1 + || $attr['USEDIVLETTERS'] === '0')) { + $usedivletters = 0; + } + $links = isset($attr['LINKS']) && (strtoupper($attr['LINKS']) === 'ON' || $attr['LINKS'] == 1); + $this->mpdf->InsertIndex($usedivletters, $links, $indexCollationLocale, $indexCollationGroup); + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/InlineTag.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/InlineTag.php new file mode 100644 index 0000000..69ea5f5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/InlineTag.php @@ -0,0 +1,232 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Utils\UtfString; + +abstract class InlineTag extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + + /* -- ANNOTATIONS -- */ + if ($this->mpdf->title2annots && isset($attr['TITLE'])) { + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + + $objattr['CONTENT'] = $attr['TITLE']; + $objattr['type'] = 'annot'; + $objattr['POS-X'] = 0; + $objattr['POS-Y'] = 0; + $objattr['ICON'] = 'Comment'; + $objattr['AUTHOR'] = ''; + $objattr['SUBJECT'] = ''; + $objattr['OPACITY'] = $this->mpdf->annotOpacity; + $objattr['COLOR'] = $this->colorConverter->convert('yellow', $this->mpdf->PDFAXwarnings); + $annot = "\xbb\xa4\xactype=annot,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + } + /* -- END ANNOTATIONS -- */ + + // mPDF 5.7.3 Inline tags + if (!isset($this->mpdf->InlineProperties[$tag])) { + $this->mpdf->InlineProperties[$tag] = [$this->mpdf->saveInlineProperties()]; + } else { + $this->mpdf->InlineProperties[$tag][] = $this->mpdf->saveInlineProperties(); + } + if (isset($annot)) { // *ANNOTATIONS* + if (!isset($this->mpdf->InlineAnnots[$tag])) { + $this->mpdf->InlineAnnots[$tag] = []; + } // *ANNOTATIONS* + $this->mpdf->InlineAnnots[$tag][] = $annot; + } // *ANNOTATIONS* + + $properties = $this->cssManager->MergeCSS('INLINE', $tag, $attr); + if (!empty($properties)) { + $this->mpdf->setCSS($properties, 'INLINE'); + } + + // mPDF 6 Bidirectional formatting for inline elements + $bdf = false; + $bdf2 = ''; + $popd = ''; + + // Get current direction + $currdir = 'ltr'; + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['direction'])) { + $currdir = $this->mpdf->blk[$this->mpdf->blklvl]['direction']; + } + if ($this->mpdf->tableLevel + && isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['direction']) + && $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['direction'] === 'rtl') { + $currdir = 'rtl'; + } + if (isset($attr['DIR']) && $attr['DIR'] != '') { + $currdir = strtolower($attr['DIR']); + } + if (isset($properties['DIRECTION'])) { + $currdir = strtolower($properties['DIRECTION']); + } + + // mPDF 6 bidi + // cf. http://www.w3.org/TR/css3-writing-modes/#unicode-bidi + if ($tag === 'BDO') { + if (isset($attr['DIR']) && strtolower($attr['DIR']) === 'rtl') { + $bdf = 0x202E; + $popd = 'RLOPDF'; + } // U+202E RLO + elseif (isset($attr['DIR']) && strtolower($attr['DIR']) === 'ltr') { + $bdf = 0x202D; + $popd = 'LROPDF'; + } // U+202D LRO + } elseif ($tag === 'BDI') { + if (isset($attr['DIR']) && strtolower($attr['DIR']) === 'rtl') { + $bdf = 0x2067; + $popd = 'RLIPDI'; + } // U+2067 RLI + elseif (isset($attr['DIR']) && strtolower($attr['DIR']) === 'ltr') { + $bdf = 0x2066; + $popd = 'LRIPDI'; + } // U+2066 LRI + else { + $bdf = 0x2068; + $popd = 'FSIPDI'; + } // U+2068 FSI + } elseif (isset($properties ['UNICODE-BIDI']) && strtolower($properties ['UNICODE-BIDI']) === 'bidi-override') { + if ($currdir === 'rtl') { + $bdf = 0x202E; + $popd = 'RLOPDF'; + } // U+202E RLO + else { + $bdf = 0x202D; + $popd = 'LROPDF'; + } // U+202D LRO + } elseif (isset($properties ['UNICODE-BIDI']) && strtolower($properties ['UNICODE-BIDI']) === 'embed') { + if ($currdir === 'rtl') { + $bdf = 0x202B; + $popd = 'RLEPDF'; + } // U+202B RLE + else { + $bdf = 0x202A; + $popd = 'LREPDF'; + } // U+202A LRE + } elseif (isset($properties ['UNICODE-BIDI']) && strtolower($properties ['UNICODE-BIDI']) === 'isolate') { + if ($currdir === 'rtl') { + $bdf = 0x2067; + $popd = 'RLIPDI'; + } // U+2067 RLI + else { + $bdf = 0x2066; + $popd = 'LRIPDI'; + } // U+2066 LRI + } elseif (isset($properties ['UNICODE-BIDI']) && strtolower($properties ['UNICODE-BIDI']) === 'isolate-override') { + if ($currdir === 'rtl') { + $bdf = 0x2067; + $bdf2 = 0x202E; + $popd = 'RLIRLOPDFPDI'; + } // U+2067 RLI // U+202E RLO + else { + $bdf = 0x2066; + $bdf2 = 0x202D; + $popd = 'LRILROPDFPDI'; + } // U+2066 LRI // U+202D LRO + } elseif (isset($properties ['UNICODE-BIDI']) && strtolower($properties ['UNICODE-BIDI']) === 'plaintext') { + $bdf = 0x2068; + $popd = 'FSIPDI'; // U+2068 FSI + } else { + if (isset($attr['DIR']) && strtolower($attr['DIR']) === 'rtl') { + $bdf = 0x202B; + $popd = 'RLEPDF'; + } // U+202B RLE + elseif (isset($attr['DIR']) && strtolower($attr['DIR']) === 'ltr') { + $bdf = 0x202A; + $popd = 'LREPDF'; + } // U+202A LRE + } + + if ($bdf) { + // mPDF 5.7.3 Inline tags + if (!isset($this->mpdf->InlineBDF[$tag])) { + $this->mpdf->InlineBDF[$tag] = [[$popd, $this->mpdf->InlineBDFctr]]; + } else { + $this->mpdf->InlineBDF[$tag][] = [$popd, $this->mpdf->InlineBDFctr]; + } + $this->mpdf->InlineBDFctr++; + if ($bdf2) { + $bdf2 = UtfString::code2utf($bdf); + } + $this->mpdf->OTLdata = []; + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer(UtfString::code2utf($bdf) . $bdf2); + } else { + $this->mpdf->_saveTextBuffer(UtfString::code2utf($bdf) . $bdf2); + } + $this->mpdf->biDirectional = true; + } + } + + public function close(&$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + + $annot = false; // mPDF 6 + $bdf = false; // mPDF 6 + + // mPDF 5.7.3 Inline tags + if ($tag === 'PROGRESS' || $tag === 'METER') { + if (!empty($this->mpdf->InlineProperties[$tag])) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties[$tag]); + } + unset($this->mpdf->InlineProperties[$tag]); + if (!empty($this->mpdf->InlineAnnots[$tag])) { + $annot = $this->mpdf->InlineAnnots[$tag]; + } // *ANNOTATIONS* + unset($this->mpdf->InlineAnnots[$tag]); // *ANNOTATIONS* + } else { + if (isset($this->mpdf->InlineProperties[$tag]) && count($this->mpdf->InlineProperties[$tag])) { + $tmpProps = array_pop($this->mpdf->InlineProperties[$tag]); // mPDF 5.7.4 + $this->mpdf->restoreInlineProperties($tmpProps); + } + if (isset($this->mpdf->InlineAnnots[$tag]) && count($this->mpdf->InlineAnnots[$tag])) { // *ANNOTATIONS* + $annot = array_pop($this->mpdf->InlineAnnots[$tag]); // *ANNOTATIONS* + } // *ANNOTATIONS* + if (isset($this->mpdf->InlineBDF[$tag]) && count($this->mpdf->InlineBDF[$tag])) { // mPDF 6 + $bdfarr = array_pop($this->mpdf->InlineBDF[$tag]); + $bdf = $bdfarr[0]; + } + } + + /* -- ANNOTATIONS -- */ + if ($annot) { // mPDF 6 + if ($this->mpdf->tableLevel) { // *TABLES* + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][] = [$annot]; // *TABLES* + } // *TABLES* + else { // *TABLES* + $this->mpdf->textbuffer[] = [$annot]; + } // *TABLES* + } + /* -- END ANNOTATIONS -- */ + + // mPDF 6 bidi + // mPDF 6 Bidirectional formatting for inline elements + if ($bdf) { + $popf = $this->mpdf->_setBidiCodes('end', $bdf); + $this->mpdf->OTLdata = []; + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($popf); + } else { + $this->mpdf->_saveTextBuffer($popf); + } + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Input.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Input.php new file mode 100644 index 0000000..2b5b534 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Input.php @@ -0,0 +1,425 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; +use Mpdf\Utils\UtfString; + +class Input extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + $this->mpdf->ignorefollowingspaces = false; + if (!isset($attr['TYPE'])) { + $attr['TYPE'] = 'TEXT'; + } + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $objattr['type'] = 'input'; + if (isset($attr['DISABLED'])) { + $objattr['disabled'] = true; + } + if (isset($attr['READONLY'])) { + $objattr['readonly'] = true; + } + if (isset($attr['REQUIRED'])) { + $objattr['required'] = true; + } + if (isset($attr['SPELLCHECK']) && strtolower($attr['SPELLCHECK']) === 'true') { + $objattr['spellcheck'] = true; + } + if (isset($attr['TITLE'])) { + $objattr['title'] = $attr['TITLE']; + } elseif (isset($attr['ALT'])) { + $objattr['title'] = $attr['ALT']; + } else { + $objattr['title'] = ''; + } + $objattr['title'] = UtfString::strcode2utf($objattr['title']); + $objattr['title'] = $this->mpdf->lesser_entity_decode($objattr['title']); + if ($this->mpdf->onlyCoreFonts) { + $objattr['title'] = mb_convert_encoding($objattr['title'], $this->mpdf->mb_enc, 'UTF-8'); + } + if ($this->mpdf->useActiveForms && isset($attr['NAME'])) { + $objattr['fieldname'] = $attr['NAME']; + } + if (isset($attr['VALUE'])) { + $attr['VALUE'] = UtfString::strcode2utf($attr['VALUE']); + $attr['VALUE'] = $this->mpdf->lesser_entity_decode($attr['VALUE']); + if ($this->mpdf->onlyCoreFonts) { + $attr['VALUE'] = mb_convert_encoding($attr['VALUE'], $this->mpdf->mb_enc, 'UTF-8'); + } + $objattr['value'] = $attr['VALUE']; + } + + $this->mpdf->InlineProperties['INPUT'] = $this->mpdf->saveInlineProperties(); + $properties = $this->cssManager->MergeCSS('', 'INPUT', $attr); + $objattr['vertical-align'] = ''; + + if (isset($properties['FONT-FAMILY'])) { + $this->mpdf->SetFont($properties['FONT-FAMILY'], $this->mpdf->FontStyle, 0, false); + } + if (isset($properties['FONT-SIZE'])) { + $mmsize = $this->sizeConverter->convert($properties['FONT-SIZE'], $this->mpdf->default_font_size / Mpdf::SCALE); + $this->mpdf->SetFontSize($mmsize * Mpdf::SCALE, false); + } + if (isset($properties['COLOR'])) { + $objattr['color'] = $this->colorConverter->convert($properties['COLOR'], $this->mpdf->PDFAXwarnings); + } + $objattr['fontfamily'] = $this->mpdf->FontFamily; + $objattr['fontsize'] = $this->mpdf->FontSizePt; + if ($this->mpdf->useActiveForms) { + if (isset($attr['ALIGN'])) { + $objattr['text_align'] = self::ALIGN[strtolower($attr['ALIGN'])]; + } elseif (isset($properties['TEXT-ALIGN'])) { + $objattr['text_align'] = self::ALIGN[strtolower($properties['TEXT-ALIGN'])]; + } + if (isset($properties['BORDER-TOP-COLOR'])) { + $objattr['border-col'] = $this->colorConverter->convert($properties['BORDER-TOP-COLOR'], $this->mpdf->PDFAXwarnings); + } + if (isset($properties['BACKGROUND-COLOR'])) { + $objattr['background-col'] = $this->colorConverter->convert($properties['BACKGROUND-COLOR'], $this->mpdf->PDFAXwarnings); + } + } + + $type = ''; + $texto = ''; + $height = $this->mpdf->FontSize; + $width = 0; + $spacesize = $this->mpdf->GetCharWidth(' ', false); + + $w = 0; + if (isset($properties['WIDTH'])) { + $w = $this->sizeConverter->convert($properties['WIDTH'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']); + } + + if ($properties['VERTICAL-ALIGN']) { + $objattr['vertical-align'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } + + switch (strtoupper($attr['TYPE'])) { + case 'HIDDEN': + $this->mpdf->ignorefollowingspaces = true; //Eliminate exceeding left-side spaces + if ($this->mpdf->useActiveForms) { + $this->form->SetFormText(0, 0, $objattr['fieldname'], $objattr['value'], $objattr['value'], '', 0, '', true); + } + if ($this->mpdf->InlineProperties[$tag]) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties[$tag]); + } + unset($this->mpdf->InlineProperties[$tag]); + return; + + case 'CHECKBOX': //Draw Checkbox + $type = 'CHECKBOX'; + if (isset($attr['CHECKED'])) { + $objattr['checked'] = true; + } else { + $objattr['checked'] = false; + } + $width = $this->mpdf->FontSize; + $height = $this->mpdf->FontSize; + break; + + case 'RADIO': //Draw Radio button + $type = 'RADIO'; + if (isset($attr['CHECKED'])) { + $objattr['checked'] = true; + } + $width = $this->mpdf->FontSize; + $height = $this->mpdf->FontSize; + break; + + /* -- IMAGES-CORE -- */ + case 'IMAGE': // Draw an Image button + if (isset($attr['SRC'])) { + $type = 'IMAGE'; + $srcpath = $attr['SRC']; + $orig_srcpath = $attr['ORIG_SRC']; + // VSPACE and HSPACE converted to margins in MergeCSS + if (isset($properties['MARGIN-TOP'])) { + $objattr['margin_top'] = $this->sizeConverter->convert( + $properties['MARGIN-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-BOTTOM'])) { + $objattr['margin_bottom'] = $this->sizeConverter->convert( + $properties['MARGIN-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-LEFT'])) { + $objattr['margin_left'] = $this->sizeConverter->convert( + $properties['MARGIN-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-RIGHT'])) { + $objattr['margin_right'] = $this->sizeConverter->convert( + $properties['MARGIN-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['BORDER-TOP'])) { + $objattr['border_top'] = $this->mpdf->border_details($properties['BORDER-TOP']); + } + if (isset($properties['BORDER-BOTTOM'])) { + $objattr['border_bottom'] = $this->mpdf->border_details($properties['BORDER-BOTTOM']); + } + if (isset($properties['BORDER-LEFT'])) { + $objattr['border_left'] = $this->mpdf->border_details($properties['BORDER-LEFT']); + } + if (isset($properties['BORDER-RIGHT'])) { + $objattr['border_right'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); + } + + $objattr['padding_top'] = 0; + $objattr['padding_bottom'] = 0; + $objattr['padding_left'] = 0; + $objattr['padding_right'] = 0; + + if (isset($properties['VERTICAL-ALIGN'])) { + $objattr['vertical-align'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } + + $w = 0; + $h = 0; + if (isset($properties['WIDTH'])) { + $w = $this->sizeConverter->convert($properties['WIDTH'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']); + } + if (isset($properties['HEIGHT'])) { + $h = $this->sizeConverter->convert($properties['HEIGHT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']); + } + + $extraheight = $objattr['margin_top'] + $objattr['margin_bottom'] + $objattr['border_top']['w'] + $objattr['border_bottom']['w']; + $extrawidth = $objattr['margin_left'] + $objattr['margin_right'] + $objattr['border_left']['w'] + $objattr['border_right']['w']; + + // Image file + $info = $this->imageProcessor->getImage($srcpath, true, true, $orig_srcpath); + if (!$info) { + $info = $this->imageProcessor->getImage($this->mpdf->noImageFile); + if ($info) { + $srcpath = $this->mpdf->noImageFile; + $w = ($info['w'] * (25.4 / $this->mpdf->img_dpi)); + $h = ($info['h'] * (25.4 / $this->mpdf->img_dpi)); + } + } + if (!$info) { + break; + } + if ($info['cs'] === 'Indexed') { + $objattr['Indexed'] = true; + } + $objattr['file'] = $srcpath; + //Default width and height calculation if needed + if ($w == 0 && $h == 0) { + /* -- IMAGES-WMF -- */ + if ($info['type'] === 'wmf') { + // WMF units are twips (1/20pt) + // divide by 20 to get points + // divide by k to get user units + $w = abs($info['w']) / (20 * Mpdf::SCALE); + $h = abs($info['h']) / (20 * Mpdf::SCALE); + } else { /* -- END IMAGES-WMF -- */ + if ($info['type'] === 'svg') { + // SVG units are pixels + $w = abs($info['w']) / Mpdf::SCALE; + $h = abs($info['h']) / Mpdf::SCALE; + } else { + //Put image at default image dpi + $w = ($info['w'] / Mpdf::SCALE) * (72 / $this->mpdf->img_dpi); + $h = ($info['h'] / Mpdf::SCALE) * (72 / $this->mpdf->img_dpi); + } + } + if (isset($properties['IMAGE-RESOLUTION'])) { + if (preg_match('/from-image/i', $properties['IMAGE-RESOLUTION']) && isset($info['set-dpi']) && $info['set-dpi'] > 0) { + $w *= $this->mpdf->img_dpi / $info['set-dpi']; + $h *= $this->mpdf->img_dpi / $info['set-dpi']; + } elseif (preg_match('/(\d+)dpi/i', $properties['IMAGE-RESOLUTION'], $m)) { + $dpi = $m[1]; + if ($dpi > 0) { + $w *= $this->mpdf->img_dpi / $dpi; + $h *= $this->mpdf->img_dpi / $dpi; + } + } + } + } + // IF WIDTH OR HEIGHT SPECIFIED + if ($w == 0) { + $w = $h * $info['w'] / $info['h']; + } + if ($h == 0) { + $h = $w * $info['h'] / $info['w']; + } + // Resize to maximum dimensions of page + $maxWidth = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']; + $maxHeight = $this->mpdf->h - ($this->mpdf->tMargin + $this->mpdf->bMargin + 10); + if ($this->mpdf->fullImageHeight) { + $maxHeight = $this->mpdf->fullImageHeight; + } + if (($w + $extrawidth) > ($maxWidth + 0.0001)) { // mPDF 5.7.4 0.0001 to allow for rounding errors when w==maxWidth + $w = $maxWidth - $extrawidth; + $h = $w * $info['h'] / $info['w']; + } + if ($h + $extraheight > $maxHeight) { + $h = $maxHeight - $extraheight; + $w = $h * $info['w'] / $info['h']; + } + $height = $h + $extraheight; + $width = $w + $extrawidth; + $objattr['type'] = 'image'; + $objattr['itype'] = $info['type']; + $objattr['orig_h'] = $info['h']; + $objattr['orig_w'] = $info['w']; + /* -- IMAGES-WMF -- */ + if ($info['type'] === 'wmf') { + $objattr['wmf_x'] = $info['x']; + $objattr['wmf_y'] = $info['y']; + /* -- END IMAGES-WMF -- */ + } else { + if ($info['type'] === 'svg') { + $objattr['wmf_x'] = $info['x']; + $objattr['wmf_y'] = $info['y']; + } + } + $objattr['height'] = $h + $extraheight; + $objattr['width'] = $w + $extrawidth; + + $objattr['image_height'] = $h; + $objattr['image_width'] = $w; + $objattr['ID'] = $info['i']; + $texto = 'X'; + if ($this->mpdf->useActiveForms) { + if (isset($attr['ONCLICK'])) { + $objattr['onClick'] = $attr['ONCLICK']; + } + $objattr['type'] = 'input'; + $type = 'IMAGE'; + } + break; + } + /* -- END IMAGES-CORE -- */ + + case 'BUTTON': // Draw a button + case 'SUBMIT': + case 'RESET': + $type = strtoupper($attr['TYPE']); + if ($type === 'IMAGE') { + $type = 'BUTTON'; + } // src path not found + if (isset($attr['NOPRINT'])) { + $objattr['noprint'] = true; + } + if (!isset($attr['VALUE'])) { + $objattr['value'] = ucfirst(strtolower($type)); + } + + $texto = ' ' . $objattr['value'] . ' '; + + $width = $this->mpdf->GetStringWidth($texto) + ($this->form->form_element_spacing['button']['outer']['h'] * 2) + + ($this->form->form_element_spacing['button']['inner']['h'] * 2); + + $height = $this->mpdf->FontSize + ($this->form->form_element_spacing['button']['outer']['v'] * 2) + + ($this->form->form_element_spacing['button']['inner']['v'] * 2); + + if ($this->mpdf->useActiveForms && isset($attr['ONCLICK'])) { + $objattr['onClick'] = $attr['ONCLICK']; + } + break; + + case 'PASSWORD': + case 'TEXT': + default: + if ($type == '') { + $type = 'TEXT'; + } + if (strtoupper($attr['TYPE']) === 'PASSWORD') { + $type = 'PASSWORD'; + } + if (isset($attr['VALUE'])) { + if ($type === 'PASSWORD') { + $num_stars = mb_strlen($attr['VALUE'], $this->mpdf->mb_enc); + $texto = str_repeat('*', $num_stars); + } else { + $texto = $attr['VALUE']; + } + } + $xw = ($this->form->form_element_spacing['input']['outer']['h'] * 2) + ($this->form->form_element_spacing['input']['inner']['h'] * 2); + $xh = ($this->form->form_element_spacing['input']['outer']['v'] * 2) + ($this->form->form_element_spacing['input']['inner']['v'] * 2); + if ($w) { + $width = $w + $xw; + } else { + $width = (20 * $spacesize) + $xw; + } // Default width in chars + if (isset($attr['SIZE']) && ctype_digit($attr['SIZE'])) { + $width = ($attr['SIZE'] * $spacesize) + $xw; + } + $height = $this->mpdf->FontSize + $xh; + if (isset($attr['MAXLENGTH']) && ctype_digit($attr['MAXLENGTH'])) { + $objattr['maxlength'] = $attr['MAXLENGTH']; + } + if ($this->mpdf->useActiveForms) { + if (isset($attr['ONCALCULATE'])) { + $objattr['onCalculate'] = $attr['ONCALCULATE']; + } elseif (isset($attr['ONCHANGE'])) { + $objattr['onCalculate'] = $attr['ONCHANGE']; + } + if (isset($attr['ONVALIDATE'])) { + $objattr['onValidate'] = $attr['ONVALIDATE']; + } + if (isset($attr['ONKEYSTROKE'])) { + $objattr['onKeystroke'] = $attr['ONKEYSTROKE']; + } + if (isset($attr['ONFORMAT'])) { + $objattr['onFormat'] = $attr['ONFORMAT']; + } + } + break; + } + + $objattr['subtype'] = $type; + $objattr['text'] = $texto; + $objattr['width'] = $width; + $objattr['height'] = $height; + $e = "\xbb\xa4\xactype=input,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + + /* -- TABLES -- */ + // Output it to buffers + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($e, $this->mpdf->HREF); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $objattr['width']; + } else { + /* -- END TABLES -- */ + $this->mpdf->_saveTextBuffer($e, $this->mpdf->HREF); + } // *TABLES* + + if ($this->mpdf->InlineProperties[$tag]) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties[$tag]); + } + unset($this->mpdf->InlineProperties[$tag]); + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ins.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ins.php new file mode 100644 index 0000000..03ec244 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ins.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Ins extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Kbd.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Kbd.php new file mode 100644 index 0000000..10b7880 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Kbd.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Kbd extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Legend.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Legend.php new file mode 100644 index 0000000..90308d4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Legend.php @@ -0,0 +1,35 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; + +class Legend extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->InlineProperties['LEGEND'] = $this->mpdf->saveInlineProperties(); + $properties = $this->cssManager->MergeCSS('INLINE', 'LEGEND', $attr); + if (!empty($properties)) { + $this->mpdf->setCSS($properties, 'INLINE'); + } + } + + public function close(&$ahtml, &$ihtml) + { + if (count($this->mpdf->textbuffer) && !$this->mpdf->tableLevel) { + $leg = $this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1]; + unset($this->mpdf->textbuffer[count($this->mpdf->textbuffer) - 1]); + $this->mpdf->textbuffer = array_values($this->mpdf->textbuffer); + $this->mpdf->blk[$this->mpdf->blklvl]['border_legend'] = $leg; + $this->mpdf->blk[$this->mpdf->blklvl]['margin_top'] += ($leg[11] / 2) / Mpdf::SCALE; + $this->mpdf->blk[$this->mpdf->blklvl]['padding_top'] += ($leg[11] / 2) / Mpdf::SCALE; + } + if (isset($this->mpdf->InlineProperties['LEGEND'])) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties['LEGEND']); + } + unset($this->mpdf->InlineProperties['LEGEND']); + $this->mpdf->ignorefollowingspaces = true; //Eliminate exceeding left-side spaces + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Li.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Li.php new file mode 100644 index 0000000..361af06 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Li.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Li extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Main.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Main.php new file mode 100644 index 0000000..2e2fe39 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Main.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Main extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Mark.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Mark.php new file mode 100644 index 0000000..38c14a4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Mark.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Mark extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Meter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Meter.php new file mode 100644 index 0000000..e3f9297 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Meter.php @@ -0,0 +1,517 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; + +class Meter extends InlineTag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + $this->mpdf->inMeter = true; + + $max = 1; + if (!empty($attr['MAX'])) { + $max = $attr['MAX']; + } + + $min = 0; + if (!empty($attr['MIN']) && $tag === 'METER') { + $min = $attr['MIN']; + } + + if ($max < $min) { + $max = $min; + } + + $value = ''; + if (isset($attr['VALUE']) && ($attr['VALUE'] || $attr['VALUE'] === '0')) { + $value = $attr['VALUE']; + if ($value < $min) { + $value = $min; + } elseif ($value > $max) { + $value = $max; + } + } + + $low = $min; + if (!empty($attr['LOW'])) { + $low = $attr['LOW']; + } + if ($low < $min) { + $low = $min; + } elseif ($low > $max) { + $low = $max; + } + $high = $max; + if (!empty($attr['HIGH'])) { + $high = $attr['HIGH']; + } + if ($high < $low) { + $high = $low; + } elseif ($high > $max) { + $high = $max; + } + if (!empty($attr['OPTIMUM'])) { + $optimum = $attr['OPTIMUM']; + } else { + $optimum = $min + (($max - $min) / 2); + } + if ($optimum < $min) { + $optimum = $min; + } elseif ($optimum > $max) { + $optimum = $max; + } + $type = ''; + if (!empty($attr['TYPE'])) { + $type = $attr['TYPE']; + } + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['padding_top'] = 0; + $objattr['padding_bottom'] = 0; + $objattr['padding_left'] = 0; + $objattr['padding_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + + $properties = $this->cssManager->MergeCSS('INLINE', $tag, $attr); + if (isset($properties ['DISPLAY']) && strtolower($properties ['DISPLAY']) === 'none') { + return; + } + $objattr['visibility'] = 'visible'; + if (isset($properties['VISIBILITY'])) { + $v = strtolower($properties['VISIBILITY']); + if (($v === 'hidden' || $v === 'printonly' || $v === 'screenonly') && $this->mpdf->visibility === 'visible') { + $objattr['visibility'] = $v; + } + } + + if (isset($properties['MARGIN-TOP'])) { + $objattr['margin_top'] = $this->sizeConverter->convert( + $properties['MARGIN-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-BOTTOM'])) { + $objattr['margin_bottom'] = $this->sizeConverter->convert( + $properties['MARGIN-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-LEFT'])) { + $objattr['margin_left'] = $this->sizeConverter->convert( + $properties['MARGIN-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-RIGHT'])) { + $objattr['margin_right'] = $this->sizeConverter->convert( + $properties['MARGIN-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['PADDING-TOP'])) { + $objattr['padding_top'] = $this->sizeConverter->convert( + $properties['PADDING-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-BOTTOM'])) { + $objattr['padding_bottom'] = $this->sizeConverter->convert( + $properties['PADDING-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-LEFT'])) { + $objattr['padding_left'] = $this->sizeConverter->convert( + $properties['PADDING-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-RIGHT'])) { + $objattr['padding_right'] = $this->sizeConverter->convert( + $properties['PADDING-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['BORDER-TOP'])) { + $objattr['border_top'] = $this->mpdf->border_details($properties['BORDER-TOP']); + } + if (isset($properties['BORDER-BOTTOM'])) { + $objattr['border_bottom'] = $this->mpdf->border_details($properties['BORDER-BOTTOM']); + } + if (isset($properties['BORDER-LEFT'])) { + $objattr['border_left'] = $this->mpdf->border_details($properties['BORDER-LEFT']); + } + if (isset($properties['BORDER-RIGHT'])) { + $objattr['border_right'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); + } + + if (isset($properties['VERTICAL-ALIGN'])) { + $objattr['vertical-align'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } + $w = 0; + $h = 0; + if (isset($properties['WIDTH'])) { + $w = $this->sizeConverter->convert( + $properties['WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['WIDTH'])) { + $w = $this->sizeConverter->convert($attr['WIDTH'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + + if (isset($properties['HEIGHT'])) { + $h = $this->sizeConverter->convert( + $properties['HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } elseif (isset($attr['HEIGHT'])) { + $h = $this->sizeConverter->convert($attr['HEIGHT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + + if (isset($properties['OPACITY']) && $properties['OPACITY'] > 0 && $properties['OPACITY'] <= 1) { + $objattr['opacity'] = $properties['OPACITY']; + } + if ($this->mpdf->HREF) { + if (strpos($this->mpdf->HREF, '.') === false && strpos($this->mpdf->HREF, '@') !== 0) { + $href = $this->mpdf->HREF; + while (array_key_exists($href, $this->mpdf->internallink)) { + $href = '#' . $href; + } + $this->mpdf->internallink[$href] = $this->mpdf->AddLink(); + $objattr['link'] = $this->mpdf->internallink[$href]; + } else { + $objattr['link'] = $this->mpdf->HREF; + } + } + $extraheight = $objattr['padding_top'] + $objattr['padding_bottom'] + $objattr['margin_top'] + + $objattr['margin_bottom'] + $objattr['border_top']['w'] + $objattr['border_bottom']['w']; + + $extrawidth = $objattr['padding_left'] + $objattr['padding_right'] + $objattr['margin_left'] + + $objattr['margin_right'] + $objattr['border_left']['w'] + $objattr['border_right']['w']; + + $svg = $this->makeSVG($type, $value, $max, $min, $optimum, $low, $high); + //Save to local file + $srcpath = $this->cache->write('/_tempSVG' . uniqid(random_int(1, 100000), true) . '_' . strtolower($tag) . '.svg', $svg); + $orig_srcpath = $srcpath; + $this->mpdf->GetFullPath($srcpath); + + $info = $this->imageProcessor->getImage($srcpath, true, true, $orig_srcpath); + if (!$info) { + $info = $this->imageProcessor->getImage($this->mpdf->noImageFile); + if ($info) { + $srcpath = $this->mpdf->noImageFile; + $w = ($info['w'] * (25.4 / $this->mpdf->img_dpi)); + $h = ($info['h'] * (25.4 / $this->mpdf->img_dpi)); + } + } + if (!$info) { + return; + } + + $objattr['file'] = $srcpath; + + // Default width and height calculation if needed + if ($w == 0 && $h == 0) { + // SVG units are pixels + $w = $this->mpdf->FontSize / (10 / Mpdf::SCALE) * abs($info['w']) / Mpdf::SCALE; + $h = $this->mpdf->FontSize / (10 / Mpdf::SCALE) * abs($info['h']) / Mpdf::SCALE; + } + + // IF WIDTH OR HEIGHT SPECIFIED + if ($w == 0) { + $w = $info['h'] ? abs($h * $info['w'] / $info['h']) : INF; + } + if ($h == 0) { + $h = $info['w'] ? abs($w * $info['h'] / $info['w']) : INF; + } + + // Resize to maximum dimensions of page + $maxWidth = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']; + $maxHeight = $this->mpdf->h - ($this->mpdf->tMargin + $this->mpdf->bMargin + 1); + if ($this->mpdf->fullImageHeight) { + $maxHeight = $this->mpdf->fullImageHeight; + } + if (($w + $extrawidth) > ($maxWidth + 0.0001)) { // mPDF 5.7.4 0.0001 to allow for rounding errors when w==maxWidth + $w = $maxWidth - $extrawidth; + $h = abs($w * $info['h'] / $info['w']); + } + + if ($h + $extraheight > $maxHeight) { + $h = $maxHeight - $extraheight; + $w = abs($h * $info['w'] / $info['h']); + } + $objattr['type'] = 'image'; + $objattr['itype'] = $info['type']; + + $objattr['orig_h'] = $info['h']; + $objattr['orig_w'] = $info['w']; + $objattr['wmf_x'] = $info['x']; + $objattr['wmf_y'] = $info['y']; + $objattr['height'] = $h + $extraheight; + $objattr['width'] = $w + $extrawidth; + $objattr['image_height'] = $h; + $objattr['image_width'] = $w; + $e = "\xbb\xa4\xactype=image,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($e, $this->mpdf->HREF); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $objattr['width']; + } else { + $this->mpdf->_saveTextBuffer($e, $this->mpdf->HREF); + } + } + + public function close(&$ahtml, &$ihtml) + { + parent::close($ahtml, $ihtml); + $this->mpdf->ignorefollowingspaces = false; + $this->mpdf->inMeter = false; + } + + protected function makeSVG($type, $value, $max, $min, $optimum, $low, $high) + { + if ($type == '2') { + ///////////////////////////////////////////////////////////////////////////////////// + ///////// CUSTOM <meter type="2"> + ///////////////////////////////////////////////////////////////////////////////////// + $h = 10; + $w = 160; + $border_radius = 0.143; // Factor of Height + + $svg = '<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="' . $w . 'px" height="' . $h . 'px" viewBox="0 0 ' . $w . ' ' . $h . '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><g> + + +<defs> +<linearGradient id="GrGRAY" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(222, 222, 222)" /> +<stop offset="20%" stop-color="rgb(232, 232, 232)" /> +<stop offset="25%" stop-color="rgb(232, 232, 232)" /> +<stop offset="100%" stop-color="rgb(182, 182, 182)" /> +</linearGradient> + +</defs> +'; + $svg .= '<rect x="0" y="0" width="' . $w . '" height="' . $h . '" fill="#f4f4f4" stroke="none" />'; + + // LOW to HIGH region + //if ($low && $high && ($low != $min || $high != $max)) { + if ($low && $high) { + $barx = (($low - $min) / ($max - $min) ) * $w; + $barw = (($high - $low) / ($max - $min) ) * $w; + $svg .= '<rect x="' . $barx . '" y="0" width="' . $barw . '" height="' . $h . '" fill="url(#GrGRAY)" stroke="#888888" stroke-width="0.5px" />'; + } + + // OPTIMUM Marker (? AVERAGE) + if ($optimum) { + $barx = (($optimum - $min) / ($max - $min) ) * $w; + $barw = $h / 2; + $barcol = '#888888'; + $svg .= '<rect x="' . $barx . '" y="0" rx="' . ($h * $border_radius) . 'px" ry="' . ($h * $border_radius) . 'px" width="' . $barw . '" height="' . $h . '" fill="' . $barcol . '" stroke="none" />'; + } + + // VALUE Marker + if ($value) { + if ($min != $low && $value < $low) { + $col = 'orange'; + } elseif ($max != $high && $value > $high) { + $col = 'orange'; + } else { + $col = '#008800'; + } + $cx = (($value - $min) / ($max - $min) ) * $w; + $cy = $h / 2; + $rx = $h / 3.5; + $ry = $h / 2.2; + $svg .= '<ellipse fill="' . $col . '" stroke="#000000" stroke-width="0.5px" cx="' . $cx . '" cy="' . $cy . '" rx="' . $rx . '" ry="' . $ry . '"/>'; + } + + // BoRDER + $svg .= '<rect x="0" y="0" width="' . $w . '" height="' . $h . '" fill="none" stroke="#888888" stroke-width="0.5px" />'; + + $svg .= '</g></svg>'; + } elseif ($type == '3') { + ///////////////////////////////////////////////////////////////////////////////////// + ///////// CUSTOM <meter type="2"> + ///////////////////////////////////////////////////////////////////////////////////// + $h = 10; + $w = 100; + $border_radius = 0.143; // Factor of Height + + $svg = '<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="' . $w . 'px" height="' . $h . 'px" viewBox="0 0 ' . $w . ' ' . $h . '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><g> + + +<defs> +<linearGradient id="GrGRAY" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(222, 222, 222)" /> +<stop offset="20%" stop-color="rgb(232, 232, 232)" /> +<stop offset="25%" stop-color="rgb(232, 232, 232)" /> +<stop offset="100%" stop-color="rgb(182, 182, 182)" /> +</linearGradient> + +</defs> +'; + $svg .= '<rect x="0" y="0" width="' . $w . '" height="' . $h . '" fill="#f4f4f4" stroke="none" />'; + + // LOW to HIGH region + if ($low && $high && ($low != $min || $high != $max)) { + //if ($low && $high) { + $barx = (($low - $min) / ($max - $min) ) * $w; + $barw = (($high - $low) / ($max - $min) ) * $w; + $svg .= '<rect x="' . $barx . '" y="0" width="' . $barw . '" height="' . $h . '" fill="url(#GrGRAY)" stroke="#888888" stroke-width="0.5px" />'; + } + + // OPTIMUM Marker (? AVERAGE) + if ($optimum) { + $barx = (($optimum - $min) / ($max - $min) ) * $w; + $barw = $h / 2; + $barcol = '#888888'; + $svg .= '<rect x="' . $barx . '" y="0" rx="' . ($h * $border_radius) . 'px" ry="' . ($h * $border_radius) . 'px" width="' . $barw . '" height="' . $h . '" fill="' . $barcol . '" stroke="none" />'; + } + + // VALUE Marker + if ($value) { + if ($min != $low && $value < $low) { + $col = 'orange'; + } elseif ($max != $high && $value > $high) { + $col = 'orange'; + } else { + $col = 'orange'; + } + $cx = (($value - $min) / ($max - $min) ) * $w; + $cy = $h / 2; + $rx = $h / 2.2; + $ry = $h / 2.2; + $svg .= '<ellipse fill="' . $col . '" stroke="#000000" stroke-width="0.5px" cx="' . $cx . '" cy="' . $cy . '" rx="' . $rx . '" ry="' . $ry . '"/>'; + } + + // BoRDER + $svg .= '<rect x="0" y="0" width="' . $w . '" height="' . $h . '" fill="none" stroke="#888888" stroke-width="0.5px" />'; + + $svg .= '</g></svg>'; + } else { + ///////////////////////////////////////////////////////////////////////////////////// + ///////// DEFAULT <meter> + ///////////////////////////////////////////////////////////////////////////////////// + $h = 10; + $w = 50; + $border_radius = 0.143; // Factor of Height + + $svg = '<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg width="' . $w . 'px" height="' . $h . 'px" viewBox="0 0 ' . $w . ' ' . $h . '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><g> + +<defs> +<linearGradient id="GrGRAY" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(222, 222, 222)" /> +<stop offset="20%" stop-color="rgb(232, 232, 232)" /> +<stop offset="25%" stop-color="rgb(232, 232, 232)" /> +<stop offset="100%" stop-color="rgb(182, 182, 182)" /> +</linearGradient> + +<linearGradient id="GrRED" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(255, 162, 162)" /> +<stop offset="20%" stop-color="rgb(255, 218, 218)" /> +<stop offset="25%" stop-color="rgb(255, 218, 218)" /> +<stop offset="100%" stop-color="rgb(255, 0, 0)" /> +</linearGradient> + +<linearGradient id="GrGREEN" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(102, 230, 102)" /> +<stop offset="20%" stop-color="rgb(218, 255, 218)" /> +<stop offset="25%" stop-color="rgb(218, 255, 218)" /> +<stop offset="100%" stop-color="rgb(0, 148, 0)" /> +</linearGradient> + +<linearGradient id="GrBLUE" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(102, 102, 230)" /> +<stop offset="20%" stop-color="rgb(238, 238, 238)" /> +<stop offset="25%" stop-color="rgb(238, 238, 238)" /> +<stop offset="100%" stop-color="rgb(0, 0, 128)" /> +</linearGradient> + +<linearGradient id="GrORANGE" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(255, 186, 0)" /> +<stop offset="20%" stop-color="rgb(255, 238, 168)" /> +<stop offset="25%" stop-color="rgb(255, 238, 168)" /> +<stop offset="100%" stop-color="rgb(255, 155, 0)" /> +</linearGradient> +</defs> + +<rect x="0" y="0" rx="' . ($h * $border_radius) . 'px" ry="' . ($h * $border_radius) . 'px" width="' . $w . '" height="' . $h . '" fill="url(#GrGRAY)" stroke="none" /> +'; + + if ($value) { + $barw = (($value - $min) / ($max - $min) ) * $w; + if ($optimum < $low) { + if ($value < $low) { + $barcol = 'url(#GrGREEN)'; + } elseif ($value > $high) { + $barcol = 'url(#GrRED)'; + } else { + $barcol = 'url(#GrORANGE)'; + } + } elseif ($optimum > $high) { + if ($value < $low) { + $barcol = 'url(#GrRED)'; + } elseif ($value > $high) { + $barcol = 'url(#GrGREEN)'; + } else { + $barcol = 'url(#GrORANGE)'; + } + } else { + if ($value < $low) { + $barcol = 'url(#GrORANGE)'; + } elseif ($value > $high) { + $barcol = 'url(#GrORANGE)'; + } else { + $barcol = 'url(#GrGREEN)'; + } + } + $svg .= '<rect x="0" y="0" rx="' . ($h * $border_radius) . 'px" ry="' . ($h * $border_radius) . 'px" width="' . $barw . '" height="' . $h . '" fill="' . $barcol . '" stroke="none" />'; + } + + $svg .= '</g></svg>'; + } + + + return $svg; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Nav.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Nav.php new file mode 100644 index 0000000..7845cfa --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Nav.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Nav extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/NewColumn.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/NewColumn.php new file mode 100644 index 0000000..a9ff5a1 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/NewColumn.php @@ -0,0 +1,18 @@ +<?php + +namespace Mpdf\Tag; + +class NewColumn extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->ignorefollowingspaces = true; + $this->mpdf->NewColumn(); + $this->mpdf->ColumnAdjust = false; // disables all column height adjustment for the page. + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/NewPage.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/NewPage.php new file mode 100644 index 0000000..3a7f50f --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/NewPage.php @@ -0,0 +1,8 @@ +<?php + +namespace Mpdf\Tag; + +class NewPage extends FormFeed +{ + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ol.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ol.php new file mode 100644 index 0000000..75deba9 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ol.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Ol extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Option.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Option.php new file mode 100644 index 0000000..c5233d1 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Option.php @@ -0,0 +1,38 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Utils\UtfString; + +class Option extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->lastoptionaltag = ''; + $this->mpdf->selectoption['ACTIVE'] = true; + $this->mpdf->selectoption['currentSEL'] = false; + if (empty($this->mpdf->selectoption)) { + $this->mpdf->selectoption['MAXWIDTH'] = ''; + $this->mpdf->selectoption['SELECTED'] = ''; + } + if (isset($attr['SELECTED'])) { + $this->mpdf->selectoption['SELECTED'] = ''; + $this->mpdf->selectoption['currentSEL'] = true; + } + if (isset($attr['VALUE'])) { + $attr['VALUE'] = UtfString::strcode2utf($attr['VALUE']); + $attr['VALUE'] = $this->mpdf->lesser_entity_decode($attr['VALUE']); + if ($this->mpdf->onlyCoreFonts) { + $attr['VALUE'] = mb_convert_encoding($attr['VALUE'], $this->mpdf->mb_enc, 'UTF-8'); + } + } + $this->mpdf->selectoption['currentVAL'] = $attr['VALUE']; + } + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->selectoption['ACTIVE'] = false; + $this->mpdf->lastoptionaltag = ''; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/P.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/P.php new file mode 100644 index 0000000..61b2a80 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/P.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class P extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageBreak.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageBreak.php new file mode 100644 index 0000000..9f721c4 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageBreak.php @@ -0,0 +1,8 @@ +<?php + +namespace Mpdf\Tag; + +class PageBreak extends FormFeed +{ + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageFooter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageFooter.php new file mode 100644 index 0000000..7b18a61 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageFooter.php @@ -0,0 +1,153 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; + +class PageFooter extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + $this->mpdf->ignorefollowingspaces = true; + $pname = '_nonhtmldefault'; + if ($attr['NAME']) { + $pname = $attr['NAME']; + } // mPDF 6 + + $p = []; // mPDF 6 + $p['L'] = []; + $p['C'] = []; + $p['R'] = []; + $p['L']['font-style'] = ''; + $p['C']['font-style'] = ''; + $p['R']['font-style'] = ''; + + if (isset($attr['CONTENT-LEFT'])) { + $p['L']['content'] = $attr['CONTENT-LEFT']; + } + if (isset($attr['CONTENT-CENTER'])) { + $p['C']['content'] = $attr['CONTENT-CENTER']; + } + if (isset($attr['CONTENT-RIGHT'])) { + $p['R']['content'] = $attr['CONTENT-RIGHT']; + } + + if (isset($attr['HEADER-STYLE']) || isset($attr['FOOTER-STYLE'])) { // font-family,size,weight,style,color + if ($tag === 'PAGEHEADER') { + $properties = $this->cssManager->readInlineCSS($attr['HEADER-STYLE']); + } else { + $properties = $this->cssManager->readInlineCSS($attr['FOOTER-STYLE']); + } + if (isset($properties['FONT-FAMILY'])) { + $p['L']['font-family'] = $properties['FONT-FAMILY']; + $p['C']['font-family'] = $properties['FONT-FAMILY']; + $p['R']['font-family'] = $properties['FONT-FAMILY']; + } + if (isset($properties['FONT-SIZE'])) { + $p['L']['font-size'] = $this->sizeConverter->convert($properties['FONT-SIZE']) * Mpdf::SCALE; + $p['C']['font-size'] = $this->sizeConverter->convert($properties['FONT-SIZE']) * Mpdf::SCALE; + $p['R']['font-size'] = $this->sizeConverter->convert($properties['FONT-SIZE']) * Mpdf::SCALE; + } + if (isset($properties['FONT-WEIGHT']) && $properties['FONT-WEIGHT'] === 'bold') { + $p['L']['font-style'] = 'B'; + $p['C']['font-style'] = 'B'; + $p['R']['font-style'] = 'B'; + } + if (isset($properties['FONT-STYLE']) && $properties['FONT-STYLE'] === 'italic') { + $p['L']['font-style'] .= 'I'; + $p['C']['font-style'] .= 'I'; + $p['R']['font-style'] .= 'I'; + } + if (isset($properties['COLOR'])) { + $p['L']['color'] = $properties['COLOR']; + $p['C']['color'] = $properties['COLOR']; + $p['R']['color'] = $properties['COLOR']; + } + } + if (isset($attr['HEADER-STYLE-LEFT']) || isset($attr['FOOTER-STYLE-LEFT'])) { + if ($tag === 'PAGEHEADER') { + $properties = $this->cssManager->readInlineCSS($attr['HEADER-STYLE-LEFT']); + } else { + $properties = $this->cssManager->readInlineCSS($attr['FOOTER-STYLE-LEFT']); + } + if (isset($properties['FONT-FAMILY'])) { + $p['L']['font-family'] = $properties['FONT-FAMILY']; + } + if (isset($properties['FONT-SIZE'])) { + $p['L']['font-size'] = $this->sizeConverter->convert($properties['FONT-SIZE']) * Mpdf::SCALE; + } + if (isset($properties['FONT-WEIGHT']) && $properties['FONT-WEIGHT'] === 'bold') { + $p['L']['font-style'] = 'B'; + } + if (isset($properties['FONT-STYLE']) && $properties['FONT-STYLE'] === 'italic') { + $p['L']['font-style'] .='I'; + } + if (isset($properties['COLOR'])) { + $p['L']['color'] = $properties['COLOR']; + } + } + if (isset($attr['HEADER-STYLE-CENTER']) || isset($attr['FOOTER-STYLE-CENTER'])) { + if ($tag === 'PAGEHEADER') { + $properties = $this->cssManager->readInlineCSS($attr['HEADER-STYLE-CENTER']); + } else { + $properties = $this->cssManager->readInlineCSS($attr['FOOTER-STYLE-CENTER']); + } + if (isset($properties['FONT-FAMILY'])) { + $p['C']['font-family'] = $properties['FONT-FAMILY']; + } + if (isset($properties['FONT-SIZE'])) { + $p['C']['font-size'] = $this->sizeConverter->convert($properties['FONT-SIZE']) * Mpdf::SCALE; + } + if (isset($properties['FONT-WEIGHT']) && $properties['FONT-WEIGHT'] === 'bold') { + $p['C']['font-style'] = 'B'; + } + if (isset($properties['FONT-STYLE']) && $properties['FONT-STYLE'] === 'italic') { + $p['C']['font-style'] .= 'I'; + } + if (isset($properties['COLOR'])) { + $p['C']['color'] = $properties['COLOR']; + } + } + if (isset($attr['HEADER-STYLE-RIGHT']) || isset($attr['FOOTER-STYLE-RIGHT'])) { + if ($tag === 'PAGEHEADER') { + $properties = $this->cssManager->readInlineCSS($attr['HEADER-STYLE-RIGHT']); + } else { + $properties = $this->cssManager->readInlineCSS($attr['FOOTER-STYLE-RIGHT']); + } + if (isset($properties['FONT-FAMILY'])) { + $p['R']['font-family'] = $properties['FONT-FAMILY']; + } + if (isset($properties['FONT-SIZE'])) { + $p['R']['font-size'] = $this->sizeConverter->convert($properties['FONT-SIZE']) * Mpdf::SCALE; + } + if (isset($properties['FONT-WEIGHT']) && $properties['FONT-WEIGHT'] === 'bold') { + $p['R']['font-style'] = 'B'; + } + if (isset($properties['FONT-STYLE']) && $properties['FONT-STYLE'] === 'italic') { + $p['R']['font-style'] .= 'I'; + } + if (isset($properties['COLOR'])) { + $p['R']['color'] = $properties['COLOR']; + } + } + if (!empty($attr['LINE'])) { // 0|1|on|off + $lineset = 0; + if ($attr['LINE'] == '1' || strtoupper($attr['LINE']) === 'ON') { + $lineset = 1; + } + $p['line'] = $lineset; + } + // mPDF 6 + if ($tag === 'PAGEHEADER') { + $this->mpdf->DefHeaderByName($pname, $p); + } else { + $this->mpdf->DefFooterByName($pname, $p); + } + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageHeader.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageHeader.php new file mode 100644 index 0000000..9920ee7 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/PageHeader.php @@ -0,0 +1,8 @@ +<?php + +namespace Mpdf\Tag; + +class PageHeader extends PageFooter +{ + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Pre.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Pre.php new file mode 100644 index 0000000..ef21606 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Pre.php @@ -0,0 +1,13 @@ +<?php + +namespace Mpdf\Tag; + +class Pre extends BlockTag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->ispre = true; // ADDED - Prevents left trim of textbuffer in printbuffer() + parent::open($attr, $ahtml, $ihtml); // TODO: Change the autogenerated stub + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Progress.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Progress.php new file mode 100644 index 0000000..32e22da --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Progress.php @@ -0,0 +1,72 @@ +<?php + +namespace Mpdf\Tag; + +class Progress extends Meter +{ + protected function makeSVG($type, $value, $max, $min, $optimum, $low, $high) + { + $svg = ''; + + if ($type == '2') { + ///////////////////////////////////////////////////////////////////////////////////// + ///////// CUSTOM <progress type="2"> + ///////////////////////////////////////////////////////////////////////////////////// + } else { + ///////////////////////////////////////////////////////////////////////////////////// + ///////// DEFAULT <progress> + ///////////////////////////////////////////////////////////////////////////////////// + $h = 10; + $w = 100; + $border_radius = 0.143; // Factor of Height + + if ($value or $value === '0') { + $fill = 'url(#GrGRAY)'; + } else { + $fill = '#f8f8f8'; + } + + $svg = '<svg width="' . $w . 'px" height="' . $h . 'px" viewBox="0 0 ' . $w . ' ' . $h . '"><g> + +<defs> +<linearGradient id="GrGRAY" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(222, 222, 222)" /> +<stop offset="20%" stop-color="rgb(232, 232, 232)" /> +<stop offset="25%" stop-color="rgb(232, 232, 232)" /> +<stop offset="100%" stop-color="rgb(182, 182, 182)" /> +</linearGradient> + +<linearGradient id="GrGREEN" x1="0" y1="0" x2="0" y2="1" gradientUnits="boundingBox"> +<stop offset="0%" stop-color="rgb(102, 230, 102)" /> +<stop offset="20%" stop-color="rgb(218, 255, 218)" /> +<stop offset="25%" stop-color="rgb(218, 255, 218)" /> +<stop offset="100%" stop-color="rgb(0, 148, 0)" /> +</linearGradient> + +</defs> + +<rect x="0" y="0" rx="' . ($h * $border_radius) . 'px" ry="' . ($h * $border_radius) . 'px" width="' . $w . '" height="' . $h . '" fill="' . $fill . '" stroke="none" /> +'; + + if ($value) { + $barw = (($value - $min) / ($max - $min) ) * $w; + $barcol = 'url(#GrGREEN)'; + $svg .= '<rect x="0" y="0" rx="' . ($h * $border_radius) . 'px" ry="' . ($h * $border_radius) . 'px" width="' . $barw . '" height="' . $h . '" fill="' . $barcol . '" stroke="none" />'; + } + + + // Borders + $svg .= '<rect x="0" y="0" rx="' . ($h * $border_radius) . 'px" ry="' . ($h * $border_radius) . 'px" width="' . $w . '" height="' . $h . '" fill="none" stroke="#888888" stroke-width="0.5px" />'; + if ($value) { + // $svg .= '<rect x="0" y="0" rx="'.($h*$border_radius).'px" ry="'.($h*$border_radius).'px" width="'.$barw.'" height="'.$h.'" fill="none" stroke="#888888" stroke-width="0.5px" />'; + } + + + $svg .= '</g></svg>'; + } + + + return $svg; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Q.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Q.php new file mode 100644 index 0000000..3e05674 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Q.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Q extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/S.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/S.php new file mode 100644 index 0000000..37aa832 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/S.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class S extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Samp.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Samp.php new file mode 100644 index 0000000..c541cdd --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Samp.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Samp extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Section.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Section.php new file mode 100644 index 0000000..1c41a7b --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Section.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Section extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Select.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Select.php new file mode 100644 index 0000000..1d3c131 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Select.php @@ -0,0 +1,154 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; + +class Select extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->lastoptionaltag = ''; // Save current HTML specified optional endtag + $this->mpdf->InlineProperties['SELECT'] = $this->mpdf->saveInlineProperties(); + $properties = $this->cssManager->MergeCSS('', 'SELECT', $attr); + if (isset($properties['FONT-FAMILY'])) { + $this->mpdf->SetFont($properties['FONT-FAMILY'], $this->mpdf->FontStyle, 0, false); + } + if (isset($properties['FONT-SIZE'])) { + $mmsize = $this->sizeConverter->convert($properties['FONT-SIZE'], $this->mpdf->default_font_size / Mpdf::SCALE); + $this->mpdf->SetFontSize($mmsize * Mpdf::SCALE, false); + } + if (isset($attr['SPELLCHECK']) && strtolower($attr['SPELLCHECK']) === 'true') { + $this->mpdf->selectoption['SPELLCHECK'] = true; + } + + if (isset($properties['COLOR'])) { + $this->mpdf->selectoption['COLOR'] = $this->colorConverter->convert($properties['COLOR'], $this->mpdf->PDFAXwarnings); + } + $this->mpdf->specialcontent = 'type=select'; + if (isset($attr['DISABLED'])) { + $this->mpdf->selectoption['DISABLED'] = $attr['DISABLED']; + } + if (isset($attr['READONLY'])) { + $this->mpdf->selectoption['READONLY'] = $attr['READONLY']; + } + if (isset($attr['REQUIRED'])) { + $this->mpdf->selectoption['REQUIRED'] = $attr['REQUIRED']; + } + if (isset($attr['EDITABLE'])) { + $this->mpdf->selectoption['EDITABLE'] = $attr['EDITABLE']; + } + if (isset($attr['TITLE'])) { + $this->mpdf->selectoption['TITLE'] = $attr['TITLE']; + } + if (isset($attr['MULTIPLE'])) { + $this->mpdf->selectoption['MULTIPLE'] = $attr['MULTIPLE']; + } + if (isset($attr['SIZE']) && $attr['SIZE'] > 1) { + $this->mpdf->selectoption['SIZE'] = $attr['SIZE']; + } + if ($this->mpdf->useActiveForms) { + if (isset($attr['NAME'])) { + $this->mpdf->selectoption['NAME'] = $attr['NAME']; + } + if (isset($attr['ONCHANGE'])) { + $this->mpdf->selectoption['ONCHANGE'] = $attr['ONCHANGE']; + } + } + } + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->ignorefollowingspaces = false; + $this->mpdf->lastoptionaltag = ''; + $texto = ''; + $OTLdata = false; + if (isset($this->mpdf->selectoption['SELECTED'])) { + $texto = $this->mpdf->selectoption['SELECTED']; + } + if (isset($this->mpdf->selectoption['SELECTED-OTLDATA'])) { + $OTLdata = $this->mpdf->selectoption['SELECTED-OTLDATA']; + } + + if ($this->mpdf->useActiveForms) { + $w = $this->mpdf->selectoption['MAXWIDTH']; + } else { + $w = $this->mpdf->GetStringWidth($texto, true, $OTLdata); + } + if ($w == 0) { + $w = 5; + } + $objattr['type'] = 'select'; + $objattr['text'] = $texto; + $objattr['OTLdata'] = $OTLdata; + if (isset($this->mpdf->selectoption['NAME'])) { + $objattr['fieldname'] = $this->mpdf->selectoption['NAME']; + } + if (isset($this->mpdf->selectoption['READONLY'])) { + $objattr['readonly'] = true; + } + if (isset($this->mpdf->selectoption['REQUIRED'])) { + $objattr['required'] = true; + } + if (isset($this->mpdf->selectoption['SPELLCHECK'])) { + $objattr['spellcheck'] = true; + } + if (isset($this->mpdf->selectoption['EDITABLE'])) { + $objattr['editable'] = true; + } + if (isset($this->mpdf->selectoption['ONCHANGE'])) { + $objattr['onChange'] = $this->mpdf->selectoption['ONCHANGE']; + } + if (isset($this->mpdf->selectoption['ITEMS'])) { + $objattr['items'] = $this->mpdf->selectoption['ITEMS']; + } + if (isset($this->mpdf->selectoption['MULTIPLE'])) { + $objattr['multiple'] = $this->mpdf->selectoption['MULTIPLE']; + } + if (isset($this->mpdf->selectoption['DISABLED'])) { + $objattr['disabled'] = $this->mpdf->selectoption['DISABLED']; + } + if (isset($this->mpdf->selectoption['TITLE'])) { + $objattr['title'] = $this->mpdf->selectoption['TITLE']; + } + if (isset($this->mpdf->selectoption['COLOR'])) { + $objattr['color'] = $this->mpdf->selectoption['COLOR']; + } + if (isset($this->mpdf->selectoption['SIZE'])) { + $objattr['size'] = $this->mpdf->selectoption['SIZE']; + } + $rows = 1; + if (isset($objattr['size']) && $objattr['size'] > 1) { + $rows = $objattr['size']; + } + + $objattr['fontfamily'] = $this->mpdf->FontFamily; + $objattr['fontsize'] = $this->mpdf->FontSizePt; + + $objattr['width'] = $w + ($this->form->form_element_spacing['select']['outer']['h'] * 2) + + ($this->form->form_element_spacing['select']['inner']['h'] * 2) + ($this->mpdf->FontSize * 1.4); + + $objattr['height'] = ($this->mpdf->FontSize * $rows) + ($this->form->form_element_spacing['select']['outer']['v'] * 2) + + ($this->form->form_element_spacing['select']['inner']['v'] * 2); + + $e = "\xbb\xa4\xactype=select,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + + // Output it to buffers + if ($this->mpdf->tableLevel) { // *TABLES* + $this->mpdf->_saveCellTextBuffer($e, $this->mpdf->HREF); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $objattr['width']; // *TABLES* + } // *TABLES* + else { // *TABLES* + $this->mpdf->_saveTextBuffer($e, $this->mpdf->HREF); + } // *TABLES* + + $this->mpdf->selectoption = []; + $this->mpdf->specialcontent = ''; + + if ($this->mpdf->InlineProperties['SELECT']) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties['SELECT']); + } + unset($this->mpdf->InlineProperties['SELECT']); + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetHtmlPageFooter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetHtmlPageFooter.php new file mode 100644 index 0000000..6cb5242 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetHtmlPageFooter.php @@ -0,0 +1,73 @@ +<?php + +namespace Mpdf\Tag; + +class SetHtmlPageFooter extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + $this->mpdf->ignorefollowingspaces = true; + + $pname = '_default'; + if (!empty($attr['NAME'])) { + $pname = $attr['NAME']; + } elseif ($tag === 'SETPAGEHEADER' || $tag === 'SETPAGEFOOTER') { + $pname = '_nonhtmldefault'; + } // mPDF 6 + + if (!empty($attr['PAGE'])) { // O|odd|even|E|ALL|[blank] + $side = 'odd'; + if (strtoupper($attr['PAGE']) === 'O' || strtoupper($attr['PAGE']) === 'ODD') { + $side = 'odd'; + } elseif (strtoupper($attr['PAGE']) === 'E' || strtoupper($attr['PAGE']) === 'EVEN') { + $side = 'even'; + } elseif (strtoupper($attr['PAGE']) === 'ALL') { + $side = 'both'; + } + } else { + $side = 'odd'; + } + if (!empty($attr['VALUE'])) { // -1|1|on|off + $set = 1; + if ($attr['VALUE'] == '1' || strtoupper($attr['VALUE']) === 'ON') { + $set = 1; + } elseif ($attr['VALUE'] == '-1' || strtoupper($attr['VALUE']) === 'OFF') { + $set = 0; + } + } else { + $set = 1; + } + $write = 0; + if (!empty($attr['SHOW-THIS-PAGE']) && ($tag === 'SETHTMLPAGEHEADER' || $tag === 'SETPAGEHEADER')) { + $write = 1; + } + if ($side === 'odd' || $side === 'both') { + if ($set && ($tag === 'SETHTMLPAGEHEADER' || $tag === 'SETPAGEHEADER')) { + $this->mpdf->SetHTMLHeader($this->mpdf->pageHTMLheaders[$pname], 'O', $write); + } elseif ($set && ($tag === 'SETHTMLPAGEFOOTER' || $tag === 'SETPAGEFOOTER')) { + $this->mpdf->SetHTMLFooter($this->mpdf->pageHTMLfooters[$pname], 'O'); + } elseif ($tag === 'SETHTMLPAGEHEADER' || $tag === 'SETPAGEHEADER') { + $this->mpdf->SetHTMLHeader('', 'O'); + } else { + $this->mpdf->SetHTMLFooter('', 'O'); + } + } + if ($side === 'even' || $side === 'both') { + if ($set && ($tag === 'SETHTMLPAGEHEADER' || $tag === 'SETPAGEHEADER')) { + $this->mpdf->SetHTMLHeader($this->mpdf->pageHTMLheaders[$pname], 'E', $write); + } elseif ($set && ($tag === 'SETHTMLPAGEFOOTER' || $tag === 'SETPAGEFOOTER')) { + $this->mpdf->SetHTMLFooter($this->mpdf->pageHTMLfooters[$pname], 'E'); + } elseif ($tag === 'SETHTMLPAGEHEADER' || $tag === 'SETPAGEHEADER') { + $this->mpdf->SetHTMLHeader('', 'E'); + } else { + $this->mpdf->SetHTMLFooter('', 'E'); + } + } + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetHtmlPageHeader.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetHtmlPageHeader.php new file mode 100644 index 0000000..ea98783 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetHtmlPageHeader.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class SetHtmlPageHeader extends SetHtmlPageFooter +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetPageFooter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetPageFooter.php new file mode 100644 index 0000000..7596c80 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetPageFooter.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class SetPageFooter extends SetHtmlPageFooter +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetPageHeader.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetPageHeader.php new file mode 100644 index 0000000..ff83dbc --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SetPageHeader.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class SetPageHeader extends SetHtmlPageFooter +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Small.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Small.php new file mode 100644 index 0000000..686c1eb --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Small.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Small extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Span.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Span.php new file mode 100644 index 0000000..eaa3c6a --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Span.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Span extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Strike.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Strike.php new file mode 100644 index 0000000..65ae0f8 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Strike.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Strike extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Strong.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Strong.php new file mode 100644 index 0000000..5592c3d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Strong.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Strong extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Sub.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Sub.php new file mode 100644 index 0000000..66a99e1 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Sub.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Sub extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SubstituteTag.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SubstituteTag.php new file mode 100644 index 0000000..602aa51 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/SubstituteTag.php @@ -0,0 +1,18 @@ +<?php + +namespace Mpdf\Tag; + +abstract class SubstituteTag extends Tag +{ + + public function close(&$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + if ($this->mpdf->InlineProperties[$tag]) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties[$tag]); + } + unset($this->mpdf->InlineProperties[$tag]); + $ltag = strtolower($tag); + $this->mpdf->$ltag = false; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Summary.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Summary.php new file mode 100644 index 0000000..977cc44 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Summary.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Summary extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Sup.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Sup.php new file mode 100644 index 0000000..9722165 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Sup.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Sup extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TBody.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TBody.php new file mode 100644 index 0000000..25b4c45 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TBody.php @@ -0,0 +1,23 @@ +<?php + +namespace Mpdf\Tag; + +class TBody extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->tablethead = 0; + $this->mpdf->tabletfoot = 0; + $this->mpdf->lastoptionaltag = 'TBODY'; // Save current HTML specified optional endtag + $this->cssManager->tbCSSlvl++; + $this->cssManager->MergeCSS('TABLE', 'TBODY', $attr); + } + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->lastoptionaltag = ''; + unset($this->cssManager->tablecascadeCSS[$this->cssManager->tbCSSlvl]); + $this->cssManager->tbCSSlvl--; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TFoot.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TFoot.php new file mode 100644 index 0000000..d3bd8e8 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TFoot.php @@ -0,0 +1,59 @@ +<?php + +namespace Mpdf\Tag; + +// TODO: Extend THEAD instead? + +class TFoot extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->lastoptionaltag = 'TFOOT'; // Save current HTML specified optional endtag + $this->cssManager->tbCSSlvl++; + $this->mpdf->tabletfoot = 1; + $this->mpdf->tablethead = 0; + $properties = $this->cssManager->MergeCSS('TABLE', 'TFOOT', $attr); + if (isset($properties['FONT-WEIGHT'])) { + $this->mpdf->tfoot_font_weight = ''; + if (strtoupper($properties['FONT-WEIGHT']) === 'BOLD') { + $this->mpdf->tfoot_font_weight = 'B'; + } + } + + if (isset($properties['FONT-STYLE'])) { + $this->mpdf->tfoot_font_style = ''; + if (strtoupper($properties['FONT-STYLE']) === 'ITALIC') { + $this->mpdf->tfoot_font_style = 'I'; + } + } + if (isset($properties['FONT-VARIANT'])) { + $this->mpdf->tfoot_font_smCaps = ''; + if (strtoupper($properties['FONT-VARIANT']) === 'SMALL-CAPS') { + $this->mpdf->tfoot_font_smCaps = 'S'; + } + } + + if (isset($properties['VERTICAL-ALIGN'])) { + $this->mpdf->tfoot_valign_default = $properties['VERTICAL-ALIGN']; + } + if (isset($properties['TEXT-ALIGN'])) { + $this->mpdf->tfoot_textalign_default = $properties['TEXT-ALIGN']; + } + } + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->lastoptionaltag = ''; + unset($this->cssManager->tablecascadeCSS[$this->cssManager->tbCSSlvl]); + $this->cssManager->tbCSSlvl--; + $this->mpdf->tabletfoot = 0; + $this->mpdf->ResetStyles(); + $this->mpdf->tfoot_font_weight = ''; + $this->mpdf->tfoot_font_style = ''; + $this->mpdf->tfoot_font_smCaps = ''; + + $this->mpdf->tfoot_valign_default = ''; + $this->mpdf->tfoot_textalign_default = ''; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/THead.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/THead.php new file mode 100644 index 0000000..acd8015 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/THead.php @@ -0,0 +1,58 @@ +<?php + +namespace Mpdf\Tag; + +class THead extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->lastoptionaltag = 'THEAD'; // Save current HTML specified optional endtag + $this->cssManager->tbCSSlvl++; + $this->mpdf->tablethead = 1; + $this->mpdf->tabletfoot = 0; + $properties = $this->cssManager->MergeCSS('TABLE', 'THEAD', $attr); + if (isset($properties['FONT-WEIGHT'])) { + $this->mpdf->thead_font_weight = ''; + if (strtoupper($properties['FONT-WEIGHT']) === 'BOLD') { + $this->mpdf->thead_font_weight = 'B'; + } + } + + if (isset($properties['FONT-STYLE'])) { + $this->mpdf->thead_font_style = ''; + if (strtoupper($properties['FONT-STYLE']) === 'ITALIC') { + $this->mpdf->thead_font_style = 'I'; + } + } + if (isset($properties['FONT-VARIANT'])) { + $this->mpdf->thead_font_smCaps = ''; + if (strtoupper($properties['FONT-VARIANT']) === 'SMALL-CAPS') { + $this->mpdf->thead_font_smCaps = 'S'; + } + } + + if (isset($properties['VERTICAL-ALIGN'])) { + $this->mpdf->thead_valign_default = $properties['VERTICAL-ALIGN']; + } + if (isset($properties['TEXT-ALIGN'])) { + $this->mpdf->thead_textalign_default = $properties['TEXT-ALIGN']; + } + } + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->lastoptionaltag = ''; + unset($this->cssManager->tablecascadeCSS[$this->cssManager->tbCSSlvl]); + $this->cssManager->tbCSSlvl--; + $this->mpdf->tablethead = 0; + $this->mpdf->tabletheadjustfinished = true; + $this->mpdf->ResetStyles(); + $this->mpdf->thead_font_weight = ''; + $this->mpdf->thead_font_style = ''; + $this->mpdf->thead_font_smCaps = ''; + + $this->mpdf->thead_valign_default = ''; + $this->mpdf->thead_textalign_default = ''; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Table.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Table.php new file mode 100644 index 0000000..3261ed6 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Table.php @@ -0,0 +1,1272 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Css\Border; +use Mpdf\Mpdf; + +class Table extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->tdbegin = false; + $this->mpdf->lastoptionaltag = ''; + // Disable vertical justification in columns + if ($this->mpdf->ColActive) { + $this->mpdf->colvAlign = ''; + } // *COLUMNS* + if ($this->mpdf->lastblocklevelchange == 1) { + $blockstate = 1; + } // Top margins/padding only + elseif ($this->mpdf->lastblocklevelchange < 1) { + $blockstate = 0; + } // NO margins/padding + // called from block after new div e.g. <div> ... <table> ... Outputs block top margin/border and padding + if (count($this->mpdf->textbuffer) == 0 && $this->mpdf->lastblocklevelchange == 1 && !$this->mpdf->tableLevel && !$this->mpdf->kwt) { + $this->mpdf->newFlowingBlock($this->mpdf->blk[$this->mpdf->blklvl]['width'], $this->mpdf->lineheight, '', false, 1, true, $this->mpdf->blk[$this->mpdf->blklvl]['direction']); + $this->mpdf->finishFlowingBlock(true); // true = END of flowing block + } elseif (!$this->mpdf->tableLevel && count($this->mpdf->textbuffer)) { + $this->mpdf->printbuffer($this->mpdf->textbuffer, $blockstate); + } + + $this->mpdf->textbuffer = []; + $this->mpdf->lastblocklevelchange = -1; + + + + if ($this->mpdf->tableLevel) { // i.e. now a nested table coming... + // Save current level table + $this->mpdf->cell['PARENTCELL'] = $this->mpdf->saveInlineProperties(); + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['baseProperties'] = $this->mpdf->base_table_properties; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cells'] = $this->mpdf->cell; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['currrow'] = $this->mpdf->row; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['currcol'] = $this->mpdf->col; + } + $this->mpdf->tableLevel++; + $this->cssManager->tbCSSlvl++; + + if ($this->mpdf->tableLevel > 1) { // inherit table properties from cell in which nested + //$this->mpdf->base_table_properties['FONT-KERNING'] = ($this->mpdf->textvar & TextVars::FC_KERNING); // mPDF 6 + $this->mpdf->base_table_properties['LETTER-SPACING'] = $this->mpdf->lSpacingCSS; + $this->mpdf->base_table_properties['WORD-SPACING'] = $this->mpdf->wSpacingCSS; + // mPDF 6 + $direction = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['direction']; + $txta = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['a']; + $cellLineHeight = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['cellLineHeight']; + $cellLineStackingStrategy = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['cellLineStackingStrategy']; + $cellLineStackingShift = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['cellLineStackingShift']; + } + + if (isset($this->mpdf->tbctr[$this->mpdf->tableLevel])) { + $this->mpdf->tbctr[$this->mpdf->tableLevel] ++; + } else { + $this->mpdf->tbctr[$this->mpdf->tableLevel] = 1; + } + + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['level'] = $this->mpdf->tableLevel; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['levelid'] = $this->mpdf->tbctr[$this->mpdf->tableLevel]; + + if ($this->mpdf->tableLevel > $this->mpdf->innermostTableLevel) { + $this->mpdf->innermostTableLevel = $this->mpdf->tableLevel; + } + if ($this->mpdf->tableLevel > 1) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nestedpos'] = [ + $this->mpdf->row, + $this->mpdf->col, + $this->mpdf->tbctr[$this->mpdf->tableLevel - 1], + ]; + } + //++++++++++++++++++++++++++++ + + $this->mpdf->cell = []; + $this->mpdf->col = -1; //int + $this->mpdf->row = -1; //int + $table = &$this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]; + + // New table - any level + $table['direction'] = $this->mpdf->directionality; + $table['bgcolor'] = false; + $table['va'] = false; + $table['txta'] = false; + $table['topntail'] = false; + $table['thead-underline'] = false; + $table['border'] = false; + $table['border_details']['R']['w'] = 0; + $table['border_details']['L']['w'] = 0; + $table['border_details']['T']['w'] = 0; + $table['border_details']['B']['w'] = 0; + $table['border_details']['R']['style'] = ''; + $table['border_details']['L']['style'] = ''; + $table['border_details']['T']['style'] = ''; + $table['border_details']['B']['style'] = ''; + $table['max_cell_border_width']['R'] = 0; + $table['max_cell_border_width']['L'] = 0; + $table['max_cell_border_width']['T'] = 0; + $table['max_cell_border_width']['B'] = 0; + $table['padding']['L'] = false; + $table['padding']['R'] = false; + $table['padding']['T'] = false; + $table['padding']['B'] = false; + $table['margin']['L'] = false; + $table['margin']['R'] = false; + $table['margin']['T'] = false; + $table['margin']['B'] = false; + $table['a'] = false; + $table['border_spacing_H'] = false; + $table['border_spacing_V'] = false; + $table['decimal_align'] = false; + $this->mpdf->Reset(); + $this->mpdf->InlineProperties = []; + $this->mpdf->InlineBDF = []; // mPDF 6 + $this->mpdf->InlineBDFctr = 0; // mPDF 6 + $table['nc'] = $table['nr'] = 0; + $this->mpdf->tablethead = 0; + $this->mpdf->tabletfoot = 0; + $this->mpdf->tabletheadjustfinished = false; + + // mPDF 6 + if ($this->mpdf->tableLevel > 1) { // inherit table properties from cell in which nested + $table['direction'] = $direction; + $table['txta'] = $txta; + $table['cellLineHeight'] = $cellLineHeight; + $table['cellLineStackingStrategy'] = $cellLineStackingStrategy; + $table['cellLineStackingShift'] = $cellLineStackingShift; + } + + + $lastbottommargin = 0; + if ($this->mpdf->blockjustfinished && !count($this->mpdf->textbuffer) && $this->mpdf->y != $this->mpdf->tMargin && $this->mpdf->collapseBlockMargins && $this->mpdf->tableLevel == 1) { + $lastbottommargin = $this->mpdf->lastblockbottommargin; + } + $this->mpdf->lastblockbottommargin = 0; + $this->mpdf->blockjustfinished = false; + + if ($this->mpdf->tableLevel == 1) { + $table['headernrows'] = 0; + $table['footernrows'] = 0; + $this->mpdf->base_table_properties = []; + } + + // ADDED CSS FUNCIONS FOR TABLE + if ($this->cssManager->tbCSSlvl == 1) { + $properties = $this->cssManager->MergeCSS('TOPTABLE', 'TABLE', $attr); + } else { + $properties = $this->cssManager->MergeCSS('TABLE', 'TABLE', $attr); + } + + $w = ''; + if (isset($properties['WIDTH'])) { + $w = $properties['WIDTH']; + } elseif (!empty($attr['WIDTH'])) { + $w = $attr['WIDTH']; + } + + if (isset($attr['ALIGN']) && array_key_exists(strtolower($attr['ALIGN']), self::ALIGN)) { + $table['a'] = self::ALIGN[strtolower($attr['ALIGN'])]; + } + if (!$table['a']) { + if ($table['direction'] === 'rtl') { + $table['a'] = 'R'; + } else { + $table['a'] = 'L'; + } + } + + if (!empty($properties['DIRECTION'])) { + $table['direction'] = strtolower($properties['DIRECTION']); + } elseif (!empty($attr['DIR'])) { + $table['direction'] = strtolower($attr['DIR']); + } elseif ($this->mpdf->tableLevel == 1) { + $table['direction'] = $this->mpdf->blk[$this->mpdf->blklvl]['direction']; + } + + if (isset($properties['BACKGROUND-COLOR'])) { + $table['bgcolor'][-1] = $properties['BACKGROUND-COLOR']; + } elseif (isset($properties['BACKGROUND'])) { + $table['bgcolor'][-1] = $properties['BACKGROUND']; + } elseif (isset($attr['BGCOLOR'])) { + $table['bgcolor'][-1] = $attr['BGCOLOR']; + } + + if (isset($properties['VERTICAL-ALIGN']) && array_key_exists(strtolower($properties['VERTICAL-ALIGN']), self::ALIGN)) { + $table['va'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } + if (isset($properties['TEXT-ALIGN']) && array_key_exists(strtolower($properties['TEXT-ALIGN']), self::ALIGN)) { + $table['txta'] = self::ALIGN[strtolower($properties['TEXT-ALIGN'])]; + } + + if (!empty($properties['AUTOSIZE']) && $this->mpdf->tableLevel == 1) { + $this->mpdf->shrink_this_table_to_fit = $properties['AUTOSIZE']; + if ($this->mpdf->shrink_this_table_to_fit < 1) { + $this->mpdf->shrink_this_table_to_fit = 0; + } + } + if (!empty($properties['ROTATE']) && $this->mpdf->tableLevel == 1) { + $this->mpdf->table_rotate = $properties['ROTATE']; + } + if (isset($properties['TOPNTAIL'])) { + $table['topntail'] = $properties['TOPNTAIL']; + } + if (isset($properties['THEAD-UNDERLINE'])) { + $table['thead-underline'] = $properties['THEAD-UNDERLINE']; + } + + if (isset($properties['BORDER'])) { + $bord = $this->mpdf->border_details($properties['BORDER']); + if ($bord['s']) { + $table['border'] = Border::ALL; + $table['border_details']['R'] = $bord; + $table['border_details']['L'] = $bord; + $table['border_details']['T'] = $bord; + $table['border_details']['B'] = $bord; + } + } + if (isset($properties['BORDER-RIGHT'])) { + if ($table['direction'] === 'rtl') { // *OTL* + $table['border_details']['R'] = $this->mpdf->border_details($properties['BORDER-LEFT']); // *OTL* + } // *OTL* + else { // *OTL* + $table['border_details']['R'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); + } // *OTL* + $this->mpdf->setBorder($table['border'], Border::RIGHT, $table['border_details']['R']['s']); + } + if (isset($properties['BORDER-LEFT'])) { + if ($table['direction'] === 'rtl') { // *OTL* + $table['border_details']['L'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); // *OTL* + } // *OTL* + else { // *OTL* + $table['border_details']['L'] = $this->mpdf->border_details($properties['BORDER-LEFT']); + } // *OTL* + $this->mpdf->setBorder($table['border'], Border::LEFT, $table['border_details']['L']['s']); + } + if (isset($properties['BORDER-BOTTOM'])) { + $table['border_details']['B'] = $this->mpdf->border_details($properties['BORDER-BOTTOM']); + $this->mpdf->setBorder($table['border'], Border::BOTTOM, $table['border_details']['B']['s']); + } + if (isset($properties['BORDER-TOP'])) { + $table['border_details']['T'] = $this->mpdf->border_details($properties['BORDER-TOP']); + $this->mpdf->setBorder($table['border'], Border::TOP, $table['border_details']['T']['s']); + } + + $this->mpdf->table_border_css_set = 0; + if ($table['border']) { + $this->mpdf->table_border_css_set = 1; + } + + // mPDF 6 + if (!empty($properties['LANG'])) { + if ($this->mpdf->autoLangToFont && !$this->mpdf->usingCoreFont) { + if ($properties['LANG'] != $this->mpdf->default_lang && $properties['LANG'] !== 'UTF-8') { + list ($coreSuitable, $mpdf_pdf_unifont) = $this->languageToFont->getLanguageOptions($properties['LANG'], $this->mpdf->useAdobeCJK); + if ($mpdf_pdf_unifont) { + $properties['FONT-FAMILY'] = $mpdf_pdf_unifont; + } + } + } + $this->mpdf->currentLang = $properties['LANG']; + } + + + if (isset($properties['FONT-FAMILY'])) { + $this->mpdf->default_font = $properties['FONT-FAMILY']; + $this->mpdf->SetFont($this->mpdf->default_font, '', 0, false); + } + $this->mpdf->base_table_properties['FONT-FAMILY'] = $this->mpdf->FontFamily; + + if (isset($properties['FONT-SIZE'])) { + if ($this->mpdf->tableLevel > 1) { + $tableFontSize = $this->sizeConverter->convert($this->mpdf->base_table_properties['FONT-SIZE']); + $mmsize = $this->sizeConverter->convert($properties['FONT-SIZE'], $tableFontSize); + } else { + $mmsize = $this->sizeConverter->convert($properties['FONT-SIZE'], $this->mpdf->default_font_size / Mpdf::SCALE); + } + if ($mmsize) { + $this->mpdf->default_font_size = $mmsize * Mpdf::SCALE; + $this->mpdf->SetFontSize($this->mpdf->default_font_size, false); + } + } + $this->mpdf->base_table_properties['FONT-SIZE'] = $this->mpdf->FontSize . 'mm'; + + if (isset($properties['FONT-WEIGHT'])) { + if (strtoupper($properties['FONT-WEIGHT']) === 'BOLD') { + $this->mpdf->base_table_properties['FONT-WEIGHT'] = 'BOLD'; + } + } + if (isset($properties['FONT-STYLE'])) { + if (strtoupper($properties['FONT-STYLE']) === 'ITALIC') { + $this->mpdf->base_table_properties['FONT-STYLE'] = 'ITALIC'; + } + } + if (isset($properties['COLOR'])) { + $this->mpdf->base_table_properties['COLOR'] = $properties['COLOR']; + } + if (isset($properties['FONT-KERNING'])) { + $this->mpdf->base_table_properties['FONT-KERNING'] = $properties['FONT-KERNING']; + } + if (isset($properties['LETTER-SPACING'])) { + $this->mpdf->base_table_properties['LETTER-SPACING'] = $properties['LETTER-SPACING']; + } + if (isset($properties['WORD-SPACING'])) { + $this->mpdf->base_table_properties['WORD-SPACING'] = $properties['WORD-SPACING']; + } + // mPDF 6 + if (isset($properties['HYPHENS'])) { + $this->mpdf->base_table_properties['HYPHENS'] = $properties['HYPHENS']; + } + if (!empty($properties['LINE-HEIGHT'])) { + $table['cellLineHeight'] = $this->mpdf->fixLineheight($properties['LINE-HEIGHT']); + } elseif ($this->mpdf->tableLevel == 1) { + $table['cellLineHeight'] = $this->mpdf->blk[$this->mpdf->blklvl]['line_height']; + } + + if (!empty($properties['LINE-STACKING-STRATEGY'])) { + $table['cellLineStackingStrategy'] = strtolower($properties['LINE-STACKING-STRATEGY']); + } elseif ($this->mpdf->tableLevel == 1 && isset($this->mpdf->blk[$this->mpdf->blklvl]['line_stacking_strategy'])) { + $table['cellLineStackingStrategy'] = $this->mpdf->blk[$this->mpdf->blklvl]['line_stacking_strategy']; + } else { + $table['cellLineStackingStrategy'] = 'inline-line-height'; + } + + if (!empty($properties['LINE-STACKING-SHIFT'])) { + $table['cellLineStackingShift'] = strtolower($properties['LINE-STACKING-SHIFT']); + } elseif ($this->mpdf->tableLevel == 1 && isset($this->mpdf->blk[$this->mpdf->blklvl]['line_stacking_shift'])) { + $table['cellLineStackingShift'] = $this->mpdf->blk[$this->mpdf->blklvl]['line_stacking_shift']; + } else { + $table['cellLineStackingShift'] = 'consider-shifts'; + } + + if (isset($properties['PADDING-LEFT'])) { + $table['padding']['L'] = $this->sizeConverter->convert($properties['PADDING-LEFT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['PADDING-RIGHT'])) { + $table['padding']['R'] = $this->sizeConverter->convert($properties['PADDING-RIGHT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['PADDING-TOP'])) { + $table['padding']['T'] = $this->sizeConverter->convert($properties['PADDING-TOP'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['PADDING-BOTTOM'])) { + $table['padding']['B'] = $this->sizeConverter->convert($properties['PADDING-BOTTOM'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + + if (isset($properties['MARGIN-TOP'])) { + if ($lastbottommargin) { + $tmp = $this->sizeConverter->convert($properties['MARGIN-TOP'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + if ($tmp > $lastbottommargin) { + $properties['MARGIN-TOP'] = (int) $properties['MARGIN-TOP'] - $lastbottommargin; + } else { + $properties['MARGIN-TOP'] = 0; + } + } + $table['margin']['T'] = $this->sizeConverter->convert($properties['MARGIN-TOP'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + + if (isset($properties['MARGIN-BOTTOM'])) { + $table['margin']['B'] = $this->sizeConverter->convert($properties['MARGIN-BOTTOM'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['MARGIN-LEFT'])) { + $table['margin']['L'] = $this->sizeConverter->convert($properties['MARGIN-LEFT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + + if (isset($properties['MARGIN-RIGHT'])) { + $table['margin']['R'] = $this->sizeConverter->convert($properties['MARGIN-RIGHT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['MARGIN-LEFT'], $properties['MARGIN-RIGHT']) && strtolower($properties['MARGIN-LEFT']) === 'auto' && strtolower($properties['MARGIN-RIGHT']) === 'auto') { + $table['a'] = 'C'; + } elseif (isset($properties['MARGIN-LEFT']) && strtolower($properties['MARGIN-LEFT']) === 'auto') { + $table['a'] = 'R'; + } elseif (isset($properties['MARGIN-RIGHT']) && strtolower($properties['MARGIN-RIGHT']) === 'auto') { + $table['a'] = 'L'; + } + + if (isset($properties['BORDER-COLLAPSE']) && strtoupper($properties['BORDER-COLLAPSE']) === 'SEPARATE') { + $table['borders_separate'] = true; + } else { + $table['borders_separate'] = false; + } + + // mPDF 5.7.3 + + if (isset($properties['BORDER-SPACING-H'])) { + $table['border_spacing_H'] = $this->sizeConverter->convert($properties['BORDER-SPACING-H'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['BORDER-SPACING-V'])) { + $table['border_spacing_V'] = $this->sizeConverter->convert($properties['BORDER-SPACING-V'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + // mPDF 5.7.3 + if (!$table['borders_separate']) { + $table['border_spacing_H'] = $table['border_spacing_V'] = 0; + } + + if (isset($properties['EMPTY-CELLS'])) { + $table['empty_cells'] = strtolower($properties['EMPTY-CELLS']); // 'hide' or 'show' + } else { + $table['empty_cells'] = ''; + } + + if (isset($properties['PAGE-BREAK-INSIDE']) && strtoupper($properties['PAGE-BREAK-INSIDE']) === 'AVOID' && $this->mpdf->tableLevel == 1 && !$this->mpdf->writingHTMLfooter) { + $this->mpdf->table_keep_together = true; + } elseif ($this->mpdf->tableLevel == 1) { + $this->mpdf->table_keep_together = false; + } + if (isset($properties['PAGE-BREAK-AFTER']) && $this->mpdf->tableLevel == 1) { + $table['page_break_after'] = strtoupper($properties['PAGE-BREAK-AFTER']); + } + + /* -- BACKGROUNDS -- */ + if (isset($properties['BACKGROUND-GRADIENT']) && !$this->mpdf->kwt && !$this->mpdf->ColActive) { + $table['gradient'] = $properties['BACKGROUND-GRADIENT']; + } + + if (!empty($properties['BACKGROUND-IMAGE']) && !$this->mpdf->kwt && !$this->mpdf->ColActive) { + $ret = $this->mpdf->SetBackground($properties, $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']); + if ($ret) { + $table['background-image'] = $ret; + } + } + /* -- END BACKGROUNDS -- */ + + if (isset($properties['OVERFLOW'])) { + $table['overflow'] = strtolower($properties['OVERFLOW']); // 'hidden' 'wrap' or 'visible' or 'auto' + if (($this->mpdf->ColActive || $this->mpdf->tableLevel > 1) && $table['overflow'] === 'visible') { + unset($table['overflow']); + } + } + + if (isset($attr['CELLPADDING'])) { + $table['cell_padding'] = $attr['CELLPADDING']; + } else { + $table['cell_padding'] = false; + } + + if (isset($attr['BORDER']) && $attr['BORDER'] == '1') { + $this->mpdf->table_border_attr_set = 1; + $bord = $this->mpdf->border_details('#000000 1px solid'); + if ($bord['s']) { + $table['border'] = Border::ALL; + $table['border_details']['R'] = $bord; + $table['border_details']['L'] = $bord; + $table['border_details']['T'] = $bord; + $table['border_details']['B'] = $bord; + } + } else { + $this->mpdf->table_border_attr_set = 0; + } + + if ($w) { + $maxwidth = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']; + if ($table['borders_separate']) { + $tblblw = $table['margin']['L'] + $table['margin']['R'] + $table['border_details']['L']['w'] / 2 + $table['border_details']['R']['w'] / 2; + } else { + $tblblw = $table['margin']['L'] + $table['margin']['R'] + $table['max_cell_border_width']['L'] / 2 + $table['max_cell_border_width']['R'] / 2; + } + if (strpos($w, '%') && $this->mpdf->tableLevel == 1 && !$this->mpdf->ignore_table_percents) { + // % needs to be of inner box without table margins etc. + $maxwidth -= $tblblw; + $wmm = $this->sizeConverter->convert($w, $maxwidth, $this->mpdf->FontSize, false); + $table['w'] = $wmm + $tblblw; + } + if (strpos($w, '%') && $this->mpdf->tableLevel > 1 && !$this->mpdf->ignore_table_percents && $this->mpdf->keep_table_proportions) { + $table['wpercent'] = (int) $w; // makes 80% -> 80 + } + if (!strpos($w, '%') && !$this->mpdf->ignore_table_widths) { + $wmm = $this->sizeConverter->convert($w, $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + $table['w'] = $wmm + $tblblw; + } + if (!$this->mpdf->keep_table_proportions) { + if (isset($table['w']) && $table['w'] > $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']) { + $table['w'] = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']; + } + } + } + + if (isset($attr['AUTOSIZE']) && $this->mpdf->tableLevel == 1) { + $this->mpdf->shrink_this_table_to_fit = $attr['AUTOSIZE']; + if ($this->mpdf->shrink_this_table_to_fit < 1) { + $this->mpdf->shrink_this_table_to_fit = 1; + } + } + if (isset($attr['ROTATE']) && $this->mpdf->tableLevel == 1) { + $this->mpdf->table_rotate = $attr['ROTATE']; + } + + //++++++++++++++++++++++++++++ + if ($this->mpdf->table_rotate) { + $this->mpdf->tbrot_Links = []; + $this->mpdf->tbrot_Annots = []; + $this->mpdf->tbrotForms = []; + $this->mpdf->tbrot_BMoutlines = []; + $this->mpdf->tbrot_toc = []; + } + + if ($this->mpdf->kwt) { + if ($this->mpdf->table_rotate) { + $this->mpdf->table_keep_together = true; + } + $this->mpdf->kwt = false; + $this->mpdf->kwt_saved = true; + } + + //++++++++++++++++++++++++++++ + $this->mpdf->plainCell_properties = []; + unset($table); + } + + public function close(&$ahtml, &$ihtml) + { + + $this->mpdf->lastoptionaltag = ''; + unset($this->cssManager->tablecascadeCSS[$this->cssManager->tbCSSlvl]); + $this->cssManager->tbCSSlvl--; + $this->mpdf->ignorefollowingspaces = true; //Eliminate exceeding left-side spaces + // mPDF 5.7.3 + // In case a colspan (on a row after first row) exceeded number of columns in table + for ($k = 0; $k < $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nr']; $k++) { + for ($l = 0; $l < $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nc']; $l++) { + if (!isset($this->mpdf->cell[$k][$l])) { + for ($n = $l - 1; $n >= 0; $n--) { + if (isset($this->mpdf->cell[$k][$n]) && $this->mpdf->cell[$k][$n] != 0) { + break; + } + } + $this->mpdf->cell[$k][$l] = [ + 'a' => 'C', + 'va' => 'M', + 'R' => false, + 'nowrap' => false, + 'bgcolor' => false, + 'padding' => ['L' => false, 'R' => false, 'T' => false, 'B' => false], + 'gradient' => false, + 's' => 0, + 'maxs' => 0, + 'textbuffer' => [], + 'dfs' => $this->mpdf->FontSize, + ]; + + if (!$this->mpdf->simpleTables) { + $this->mpdf->cell[$k][$l]['border'] = 0; + $this->mpdf->cell[$k][$l]['border_details']['R'] = ['s' => 0, 'w' => 0, 'c' => false, 'style' => 'none', 'dom' => 0]; + $this->mpdf->cell[$k][$l]['border_details']['L'] = ['s' => 0, 'w' => 0, 'c' => false, 'style' => 'none', 'dom' => 0]; + $this->mpdf->cell[$k][$l]['border_details']['T'] = ['s' => 0, 'w' => 0, 'c' => false, 'style' => 'none', 'dom' => 0]; + $this->mpdf->cell[$k][$l]['border_details']['B'] = ['s' => 0, 'w' => 0, 'c' => false, 'style' => 'none', 'dom' => 0]; + $this->mpdf->cell[$k][$l]['border_details']['mbw'] = ['BL' => 0, 'BR' => 0, 'RT' => 0, 'RB' => 0, 'TL' => 0, 'TR' => 0, 'LT' => 0, 'LB' => 0]; + if ($this->mpdf->packTableData) { + $this->mpdf->cell[$k][$l]['borderbin'] = $this->mpdf->_packCellBorder($this->mpdf->cell[$k][$l]); + unset($this->mpdf->cell[$k][$l]['border'], $this->mpdf->cell[$k][$l]['border_details']); + } + } + } + } + } + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cells'] = $this->mpdf->cell; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['wc'] = array_pad( + [], + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nc'], + ['miw' => 0, 'maw' => 0] + ); + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['hr'] = array_pad( + [], + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nr'], + 0 + ); + + // Move table footer <tfoot> row to end of table + if (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot']) + && count($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot'])) { + $tfrows = []; + foreach ($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot'] as $r => $val) { + if ($val) { + $tfrows[] = $r; + } + } + $temp = []; + $temptf = []; + foreach ($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cells'] as $k => $row) { + if (in_array($k, $tfrows)) { + $temptf[] = $row; + } else { + $temp[] = $row; + } + } + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot'] = []; + for ($i = count($temp); $i < (count($temp) + count($temptf)); $i++) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot'][$i] = true; + } + // Update nestedpos row references + if (isset($this->mpdf->table[$this->mpdf->tableLevel + 1]) && count($this->mpdf->table[$this->mpdf->tableLevel + 1])) { + foreach ($this->mpdf->table[$this->mpdf->tableLevel + 1] as $nid => $nested) { + $this->mpdf->table[$this->mpdf->tableLevel + 1][$nid]['nestedpos'][0] -= count($temptf); + } + } + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cells'] = array_merge($temp, $temptf); + + // Update other arays set on row number + // [trbackground-images] [trgradients] + $temptrbgi = []; + $temptrbgg = []; + $temptrbgc = []; + if (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][-1])) { + $temptrbgc[-1] = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][-1]; + } + for ($k = 0; $k < $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nr']; $k++) { + if (!in_array($k, $tfrows)) { + $temptrbgi[] = isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trbackground-images'][$k]) + ? $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trbackground-images'][$k] + : null; + $temptrbgg[] = isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trgradients'][$k]) + ? $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trgradients'][$k] + : null; + $temptrbgc[] = isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][$k]) + ? $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][$k] + : null; + } + } + for ($k = 0; $k < $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nr']; $k++) { + if (in_array($k, $tfrows)) { + $temptrbgi[] = isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trbackground-images'][$k]) + ? $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trbackground-images'][$k] + : null; + $temptrbgg[] = isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trgradients'][$k]) + ? $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trgradients'][$k] + : null; + $temptrbgc[] = isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][$k]) + ? $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][$k] + : null; + } + } + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trbackground-images'] = $temptrbgi; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trgradients'] = $temptrbgg; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'] = $temptrbgc; + // Should Update all other arays set on row number, but cell properties have been set so not needed + // [bgcolor] [trborder-left] [trborder-right] [trborder-top] [trborder-bottom] + } + + if ($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['direction'] === 'rtl') { + $this->mpdf->_reverseTableDir($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]); + } + + // Fix Borders ********************************************* + $this->mpdf->_fixTableBorders($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]); + + if ($this->mpdf->ColActive) { + $this->mpdf->table_rotate = 0; + } // *COLUMNS* + if ($this->mpdf->table_rotate <> 0) { + $this->mpdf->tablebuffer = ''; + // Max width for rotated table + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 1); + $this->mpdf->tbrot_maxh = $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']; // Max width for rotated table + $this->mpdf->tbrot_align = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['a']; + } + $this->mpdf->shrin_k = 1; + + if ($this->mpdf->shrink_tables_to_fit < 1) { + $this->mpdf->shrink_tables_to_fit = 1; + } + if (!$this->mpdf->shrink_this_table_to_fit) { + $this->mpdf->shrink_this_table_to_fit = $this->mpdf->shrink_tables_to_fit; + } + + if ($this->mpdf->tableLevel > 1) { + // deal with nested table + + $this->mpdf->_tableColumnWidth($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]], true); + + $tmiw = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['miw']; + $tmaw = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['maw']; + $tl = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['tl']; + + // Go down to lower table level + $this->mpdf->tableLevel--; + + // Reset lower level table + $this->mpdf->base_table_properties = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['baseProperties']; + // mPDF 5.7.3 + $this->mpdf->default_font = $this->mpdf->base_table_properties['FONT-FAMILY']; + $this->mpdf->SetFont($this->mpdf->default_font, '', 0, false); + $this->mpdf->default_font_size = $this->sizeConverter->convert($this->mpdf->base_table_properties['FONT-SIZE']) * Mpdf::SCALE; + $this->mpdf->SetFontSize($this->mpdf->default_font_size, false); + + $this->mpdf->cell = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['cells']; + if (isset($this->mpdf->cell['PARENTCELL'])) { + if ($this->mpdf->cell['PARENTCELL']) { + $this->mpdf->restoreInlineProperties($this->mpdf->cell['PARENTCELL']); + } + unset($this->mpdf->cell['PARENTCELL']); + } + $this->mpdf->row = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['currrow']; + $this->mpdf->col = $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['currcol']; + $objattr = []; + $objattr['type'] = 'nestedtable'; + $objattr['nestedcontent'] = $this->mpdf->tbctr[$this->mpdf->tableLevel + 1]; + $objattr['table'] = $this->mpdf->tbctr[$this->mpdf->tableLevel]; + $objattr['row'] = $this->mpdf->row; + $objattr['col'] = $this->mpdf->col; + $objattr['level'] = $this->mpdf->tableLevel; + $e = "\xbb\xa4\xactype=nestedtable,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + $this->mpdf->_saveCellTextBuffer($e); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $tl; + if (!isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'])) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } elseif ($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] < $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] = 0; // reset + if ((isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmaw']) && $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmaw'] < $tmaw) + || !isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmaw'])) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmaw'] = $tmaw; + } + if ((isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmiw']) && $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmiw'] < $tmiw) + || !isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmiw'])) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['nestedmiw'] = $tmiw; + } + $this->mpdf->tdbegin = true; + $this->mpdf->nestedtablejustfinished = true; + $this->mpdf->ignorefollowingspaces = true; + return; + } + $this->mpdf->cMarginL = 0; + $this->mpdf->cMarginR = 0; + $this->mpdf->cMarginT = 0; + $this->mpdf->cMarginB = 0; + $this->mpdf->cellPaddingL = 0; + $this->mpdf->cellPaddingR = 0; + $this->mpdf->cellPaddingT = 0; + $this->mpdf->cellPaddingB = 0; + + if (isset($this->mpdf->table[1][1]['overflow']) && $this->mpdf->table[1][1]['overflow'] === 'visible') { + if ($this->mpdf->kwt || $this->mpdf->table_rotate || $this->mpdf->table_keep_together || $this->mpdf->ColActive) { + $this->mpdf->kwt = false; + $this->mpdf->table_rotate = 0; + $this->mpdf->table_keep_together = false; + //throw new \Mpdf\MpdfException("mPDF Warning: You cannot use CSS overflow:visible together with any of these functions: + // 'Keep-with-table', rotated tables, page-break-inside:avoid, or columns"); + } + $this->mpdf->_tableColumnWidth($this->mpdf->table[1][1], true); + $this->mpdf->_tableWidth($this->mpdf->table[1][1]); + } else { + if (!$this->mpdf->kwt_saved) { + $this->mpdf->kwt_height = 0; + } + + list($check, $tablemiw) = $this->mpdf->_tableColumnWidth($this->mpdf->table[1][1], true); + $save_table = $this->mpdf->table; + $reset_to_minimum_width = false; + $added_page = false; + + if ($check > 1) { + if ($check > $this->mpdf->shrink_this_table_to_fit && $this->mpdf->table_rotate) { + if ($this->mpdf->y != $this->mpdf->tMargin) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + $this->mpdf->kwt_moved = true; + } + $added_page = true; + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height; + //$check = $tablemiw/$this->mpdf->tbrot_maxw; // undo any shrink + $check = 1; // undo any shrink + } + $reset_to_minimum_width = true; + } + + if ($reset_to_minimum_width) { + $this->mpdf->shrin_k = $check; + + $this->mpdf->default_font_size /= $this->mpdf->shrin_k; + $this->mpdf->SetFontSize($this->mpdf->default_font_size, false); + + $this->mpdf->shrinkTable($this->mpdf->table[1][1], $this->mpdf->shrin_k); + + $this->mpdf->_tableColumnWidth($this->mpdf->table[1][1]); // repeat + // Starting at $this->mpdf->innermostTableLevel + // Shrink table values - and redo columnWidth + for ($lvl = 2; $lvl <= $this->mpdf->innermostTableLevel; $lvl++) { + for ($nid = 1; $nid <= $this->mpdf->tbctr[$lvl]; $nid++) { + $this->mpdf->shrinkTable($this->mpdf->table[$lvl][$nid], $this->mpdf->shrin_k); + $this->mpdf->_tableColumnWidth($this->mpdf->table[$lvl][$nid]); + } + } + } + + // Set table cell widths for top level table + // Use $shrin_k to resize but don't change again + $this->mpdf->SetLineHeight('', $this->mpdf->table[1][1]['cellLineHeight']); + + // Top level table + $this->mpdf->_tableWidth($this->mpdf->table[1][1]); + } + + // Now work through any nested tables setting child table[w'] = parent cell['w'] + // Now do nested tables _tableWidth + for ($lvl = 2; $lvl <= $this->mpdf->innermostTableLevel; $lvl++) { + for ($nid = 1; $nid <= $this->mpdf->tbctr[$lvl]; $nid++) { + // HERE set child table width = cell width + + list($parentrow, $parentcol, $parentnid) = $this->mpdf->table[$lvl][$nid]['nestedpos']; + + $c = & $this->mpdf->table[$lvl - 1][$parentnid]['cells'][$parentrow][$parentcol]; + + if (isset($c['colspan']) && $c['colspan'] > 1) { + $parentwidth = 0; + for ($cs = 0; $cs < $c['colspan']; $cs++) { + $parentwidth += $this->mpdf->table[$lvl - 1][$parentnid]['wc'][$parentcol + $cs]; + } + } else { + $parentwidth = $this->mpdf->table[$lvl - 1][$parentnid]['wc'][$parentcol]; + } + + //$parentwidth -= ALLOW FOR PADDING ETC. in parent cell + if (!$this->mpdf->simpleTables) { + if ($this->mpdf->packTableData) { + list($bt, $br, $bb, $bl) = $this->mpdf->_getBorderWidths($c['borderbin']); + } else { + $br = $c['border_details']['R']['w']; + $bl = $c['border_details']['L']['w']; + } + if ($this->mpdf->table[$lvl - 1][$parentnid]['borders_separate']) { + $parentwidth -= $br + $bl + $c['padding']['L'] + $c['padding']['R'] + $this->mpdf->table[$lvl - 1][$parentnid]['border_spacing_H']; + } else { + $parentwidth -= $br / 2 + $bl / 2 + $c['padding']['L'] + $c['padding']['R']; + } + } elseif ($this->mpdf->simpleTables) { + if ($this->mpdf->table[$lvl - 1][$parentnid]['borders_separate']) { + $parentwidth -= $this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['L']['w'] + + $this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['R']['w'] + $c['padding']['L'] + + $c['padding']['R'] + $this->mpdf->table[$lvl - 1][$parentnid]['border_spacing_H']; + } else { + $parentwidth -= $this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['L']['w'] / 2 + + $this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['R']['w'] / 2 + $c['padding']['L'] + $c['padding']['R']; + } + } + if (!empty($this->mpdf->table[$lvl][$nid]['wpercent']) && $lvl > 1) { + $this->mpdf->table[$lvl][$nid]['w'] = $parentwidth; + } elseif ($parentwidth > $this->mpdf->table[$lvl][$nid]['maw']) { + $this->mpdf->table[$lvl][$nid]['w'] = $this->mpdf->table[$lvl][$nid]['maw']; + } else { + $this->mpdf->table[$lvl][$nid]['w'] = $parentwidth; + } + unset($c); + $this->mpdf->_tableWidth($this->mpdf->table[$lvl][$nid]); + } + } + + // Starting at $this->mpdf->innermostTableLevel + // Cascade back up nested tables: setting heights back up the tree + for ($lvl = $this->mpdf->innermostTableLevel; $lvl > 0; $lvl--) { + for ($nid = 1; $nid <= $this->mpdf->tbctr[$lvl]; $nid++) { + list($tableheight, $maxrowheight, $fullpage, $remainingpage, $maxfirstrowheight) = $this->mpdf->_tableHeight($this->mpdf->table[$lvl][$nid]); + } + } + + if ($this->mpdf->table[1][1]['overflow'] === 'visible') { + if ($maxrowheight > $fullpage) { + throw new \Mpdf\MpdfException('mPDF Warning: A Table row is greater than available height. You cannot use CSS overflow:visible'); + } + if ($maxfirstrowheight > $remainingpage) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } + $r = 0; + $c = 0; + $p = 0; + $y = 0; + $finished = false; + while (!$finished) { + list($finished, $r, $c, $p, $y, $y0) = $this->mpdf->_tableWrite($this->mpdf->table[1][1], true, $r, $c, $p, $y); + if (!$finished) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + // If printed something on first spread, set same y + if ($r == 0 && $y0 > -1) { + $this->mpdf->y = $y0; + } + } + } + } else { + $recalculate = 1; + $forcerecalc = false; + // RESIZING ALGORITHM + if ($maxrowheight > $fullpage) { + $recalculate = $this->tbsqrt($maxrowheight / $fullpage, 1); + $forcerecalc = true; + } elseif ($this->mpdf->table_rotate) { // NB $remainingpage == $fullpage == the width of the page + if ($tableheight > $remainingpage) { + // If can fit on remainder of page whilst respecting autsize value.. + if (($this->mpdf->shrin_k * $this->tbsqrt($tableheight / $remainingpage, 1)) <= $this->mpdf->shrink_this_table_to_fit) { + $recalculate = $this->tbsqrt($tableheight / $remainingpage, 1); + } elseif (!$added_page) { + if ($this->mpdf->y != $this->mpdf->tMargin) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + $this->mpdf->kwt_moved = true; + } + $added_page = true; + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height; + // 0.001 to force it to recalculate + $recalculate = (1 / $this->mpdf->shrin_k) + 0.001; // undo any shrink + } + } else { + $recalculate = 1; + } + } elseif ($this->mpdf->table_keep_together || ($this->mpdf->table[1][1]['nr'] == 1 && !$this->mpdf->writingHTMLfooter)) { + if ($tableheight > $fullpage) { + if (($this->mpdf->shrin_k * $this->tbsqrt($tableheight / $fullpage, 1)) <= $this->mpdf->shrink_this_table_to_fit) { + $recalculate = $this->tbsqrt($tableheight / $fullpage, 1); + } elseif ($this->mpdf->tableMinSizePriority) { + $this->mpdf->table_keep_together = false; + $recalculate = 1.001; + } else { + if ($this->mpdf->y != $this->mpdf->tMargin) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + $this->mpdf->kwt_moved = true; + } + $added_page = true; + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height; + $recalculate = $this->tbsqrt($tableheight / $fullpage, 1); + } + } elseif ($tableheight > $remainingpage) { + // If can fit on remainder of page whilst respecting autsize value.. + if (($this->mpdf->shrin_k * $this->tbsqrt($tableheight / $remainingpage, 1)) <= $this->mpdf->shrink_this_table_to_fit) { + $recalculate = $this->tbsqrt($tableheight / $remainingpage, 1); + } else { + if ($this->mpdf->y != $this->mpdf->tMargin) { + // mPDF 6 + if ($this->mpdf->AcceptPageBreak()) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } elseif ($this->mpdf->ColActive && $tableheight > (($this->mpdf->h - $this->mpdf->bMargin) - $this->mpdf->y0)) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } + $this->mpdf->kwt_moved = true; + } + $added_page = true; + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height; + $recalculate = 1.001; + } + } else { + $recalculate = 1; + } + } else { + $recalculate = 1; + } + + if ($recalculate > $this->mpdf->shrink_this_table_to_fit && !$forcerecalc) { + $recalculate = $this->mpdf->shrink_this_table_to_fit; + } + + $iteration = 1; + + // RECALCULATE + while ($recalculate <> 1) { + $this->mpdf->shrin_k1 = $recalculate; + $this->mpdf->shrin_k *= $recalculate; + $this->mpdf->default_font_size /= $this->mpdf->shrin_k1; + $this->mpdf->SetFontSize($this->mpdf->default_font_size, false); + $this->mpdf->SetLineHeight('', $this->mpdf->table[1][1]['cellLineHeight']); + $this->mpdf->table = $save_table; + if ($this->mpdf->shrin_k <> 1) { + $this->mpdf->shrinkTable($this->mpdf->table[1][1], $this->mpdf->shrin_k); + } + $this->mpdf->_tableColumnWidth($this->mpdf->table[1][1]); // repeat + // Starting at $this->mpdf->innermostTableLevel + // Shrink table values - and redo columnWidth + for ($lvl = 2; $lvl <= $this->mpdf->innermostTableLevel; $lvl++) { + for ($nid = 1; $nid <= $this->mpdf->tbctr[$lvl]; $nid++) { + if ($this->mpdf->shrin_k <> 1) { + $this->mpdf->shrinkTable($this->mpdf->table[$lvl][$nid], $this->mpdf->shrin_k); + } + $this->mpdf->_tableColumnWidth($this->mpdf->table[$lvl][$nid]); + } + } + // Set table cell widths for top level table + // Top level table + $this->mpdf->_tableWidth($this->mpdf->table[1][1]); + + // Now work through any nested tables setting child table[w'] = parent cell['w'] + // Now do nested tables _tableWidth + for ($lvl = 2; $lvl <= $this->mpdf->innermostTableLevel; $lvl++) { + for ($nid = 1; $nid <= $this->mpdf->tbctr[$lvl]; $nid++) { + // HERE set child table width = cell width + + list($parentrow, $parentcol, $parentnid) = $this->mpdf->table[$lvl][$nid]['nestedpos']; + $c = & $this->mpdf->table[$lvl - 1][$parentnid]['cells'][$parentrow][$parentcol]; + + if (isset($c['colspan']) && $c['colspan'] > 1) { + $parentwidth = 0; + for ($cs = 0; $cs < $c['colspan']; $cs++) { + $parentwidth += $this->mpdf->table[$lvl - 1][$parentnid]['wc'][$parentcol + $cs]; + } + } else { + $parentwidth = $this->mpdf->table[$lvl - 1][$parentnid]['wc'][$parentcol]; + } + + //$parentwidth -= ALLOW FOR PADDING ETC.in parent cell + if (!$this->mpdf->simpleTables) { + if ($this->mpdf->packTableData) { + list($bt, $br, $bb, $bl) = $this->mpdf->_getBorderWidths($c['borderbin']); + } else { + $br = $c['border_details']['R']['w']; + $bl = $c['border_details']['L']['w']; + } + if ($this->mpdf->table[$lvl - 1][$parentnid]['borders_separate']) { + $parentwidth -= $br + $bl + $c['padding']['L'] + $c['padding']['R'] + $this->mpdf->table[$lvl - 1][$parentnid]['border_spacing_H']; + } else { + $parentwidth -= $br / 2 + $bl / 2 + $c['padding']['L'] + $c['padding']['R']; + } + } elseif ($this->mpdf->simpleTables) { + if ($this->mpdf->table[$lvl - 1][$parentnid]['borders_separate']) { + $parentwidth -= $this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['L']['w'] + + $this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['R']['w'] + $c['padding']['L'] + $c['padding']['R'] + + $this->mpdf->table[$lvl - 1][$parentnid]['border_spacing_H']; + } else { + $parentwidth -= ($this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['L']['w'] + + $this->mpdf->table[$lvl - 1][$parentnid]['simple']['border_details']['R']['w']) / 2 + $c['padding']['L'] + $c['padding']['R']; + } + } + if (!empty($this->mpdf->table[$lvl][$nid]['wpercent']) && $lvl > 1) { + $this->mpdf->table[$lvl][$nid]['w'] = $parentwidth; + } elseif ($parentwidth > $this->mpdf->table[$lvl][$nid]['maw']) { + $this->mpdf->table[$lvl][$nid]['w'] = $this->mpdf->table[$lvl][$nid]['maw']; + } else { + $this->mpdf->table[$lvl][$nid]['w'] = $parentwidth; + } + unset($c); + $this->mpdf->_tableWidth($this->mpdf->table[$lvl][$nid]); + } + } + + // Starting at $this->mpdf->innermostTableLevel + // Cascade back up nested tables: setting heights back up the tree + for ($lvl = $this->mpdf->innermostTableLevel; $lvl > 0; $lvl--) { + for ($nid = 1; $nid <= $this->mpdf->tbctr[$lvl]; $nid++) { + list($tableheight, $maxrowheight, $fullpage, $remainingpage, $maxfirstrowheight) = $this->mpdf->_tableHeight($this->mpdf->table[$lvl][$nid]); + } + } + + // RESIZING ALGORITHM + + if ($maxrowheight > $fullpage) { + $recalculate = $this->tbsqrt($maxrowheight / $fullpage, $iteration); + $iteration++; + } elseif ($this->mpdf->table_rotate && $tableheight > $remainingpage && !$added_page) { + // If can fit on remainder of page whilst respecting autosize value.. + if (($this->mpdf->shrin_k * $this->tbsqrt($tableheight / $remainingpage, $iteration)) <= $this->mpdf->shrink_this_table_to_fit) { + $recalculate = $this->tbsqrt($tableheight / $remainingpage, $iteration); + $iteration++; + } else { + if (!$added_page) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + $added_page = true; + $this->mpdf->kwt_moved = true; + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height; + } + // 0.001 to force it to recalculate + $recalculate = (1 / $this->mpdf->shrin_k) + 0.001; // undo any shrink + } + } elseif ($this->mpdf->table_keep_together || ($this->mpdf->table[1][1]['nr'] == 1 && !$this->mpdf->writingHTMLfooter)) { + if ($tableheight > $fullpage) { + if (($this->mpdf->shrin_k * $this->tbsqrt($tableheight / $fullpage, $iteration)) <= $this->mpdf->shrink_this_table_to_fit) { + $recalculate = $this->tbsqrt($tableheight / $fullpage, $iteration); + $iteration++; + } elseif ($this->mpdf->tableMinSizePriority) { + $this->mpdf->table_keep_together = false; + $recalculate = (1 / $this->mpdf->shrin_k) + 0.001; + } else { + if (!$added_page && $this->mpdf->y != $this->mpdf->tMargin) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + $added_page = true; + $this->mpdf->kwt_moved = true; + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height; + } + $recalculate = $this->tbsqrt($tableheight / $fullpage, $iteration); + $iteration++; + } + } elseif ($tableheight > $remainingpage) { + // If can fit on remainder of page whilst respecting autosize value.. + if (($this->mpdf->shrin_k * $this->tbsqrt($tableheight / $remainingpage, $iteration)) <= $this->mpdf->shrink_this_table_to_fit) { + $recalculate = $this->tbsqrt($tableheight / $remainingpage, $iteration); + $iteration++; + } else { + if (!$added_page) { + // mPDF 6 + if ($this->mpdf->AcceptPageBreak()) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } elseif ($this->mpdf->ColActive && $tableheight > (($this->mpdf->h - $this->mpdf->bMargin) - $this->mpdf->y0)) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } + $added_page = true; + $this->mpdf->kwt_moved = true; + $this->mpdf->tbrot_maxw = $this->mpdf->h - ($this->mpdf->y + $this->mpdf->bMargin + 5) - $this->mpdf->kwt_height; + } + + //$recalculate = $this->tbsqrt($tableheight / $fullpage, $iteration); $iteration++; + $recalculate = (1 / $this->mpdf->shrin_k) + 0.001; // undo any shrink + } + } else { + $recalculate = 1; + } + } else { + $recalculate = 1; + } + } + + if ($maxfirstrowheight > $remainingpage && !$added_page && !$this->mpdf->table_rotate && !$this->mpdf->ColActive + && !$this->mpdf->table_keep_together && !$this->mpdf->writingHTMLheader && !$this->mpdf->writingHTMLfooter) { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + $this->mpdf->kwt_moved = true; + } + + // keep-with-table: if page has advanced, print out buffer now, else done in fn. _Tablewrite() + if ($this->mpdf->kwt_saved && $this->mpdf->kwt_moved) { + $this->mpdf->printkwtbuffer(); + $this->mpdf->kwt_moved = false; + $this->mpdf->kwt_saved = false; + } + + // Recursively writes all tables starting at top level + $this->mpdf->_tableWrite($this->mpdf->table[1][1]); + + if ($this->mpdf->table_rotate && $this->mpdf->tablebuffer) { + $this->mpdf->PageBreakTrigger = $this->mpdf->h - $this->mpdf->bMargin; + $save_tr = $this->mpdf->table_rotate; + $save_y = $this->mpdf->y; + $this->mpdf->table_rotate = 0; + $this->mpdf->y = $this->mpdf->tbrot_y0; + $h = $this->mpdf->tbrot_w; + $this->mpdf->DivLn($h, $this->mpdf->blklvl); + + $this->mpdf->table_rotate = $save_tr; + $this->mpdf->y = $save_y; + + $this->mpdf->printtablebuffer(); + } + $this->mpdf->table_rotate = 0; + } + + + $this->mpdf->x = $this->mpdf->lMargin + $this->mpdf->blk[$this->mpdf->blklvl]['outer_left_margin']; + + $this->mpdf->maxPosR = max($this->mpdf->maxPosR, $this->mpdf->x + $this->mpdf->table[1][1]['w']); + + $this->mpdf->blockjustfinished = true; + $this->mpdf->lastblockbottommargin = $this->mpdf->table[1][1]['margin']['B']; + //Reset values + + $page_break_after = ''; + if (isset($this->mpdf->table[1][1]['page_break_after'])) { + $page_break_after = $this->mpdf->table[1][1]['page_break_after']; + } + + // Keep-with-table + $this->mpdf->kwt = false; + $this->mpdf->kwt_y0 = 0; + $this->mpdf->kwt_x0 = 0; + $this->mpdf->kwt_height = 0; + $this->mpdf->kwt_buffer = []; + $this->mpdf->kwt_Links = []; + $this->mpdf->kwt_Annots = []; + $this->mpdf->kwt_moved = false; + $this->mpdf->kwt_saved = false; + + $this->mpdf->kwt_Reference = []; + $this->mpdf->kwt_BMoutlines = []; + $this->mpdf->kwt_toc = []; + + $this->mpdf->shrin_k = 1; + $this->mpdf->shrink_this_table_to_fit = 0; + + $this->mpdf->table = []; //array + $this->mpdf->tableLevel = 0; + $this->mpdf->tbctr = []; + $this->mpdf->innermostTableLevel = 0; + $this->cssManager->tbCSSlvl = 0; + $this->cssManager->tablecascadeCSS = []; + + $this->mpdf->cell = []; //array + + $this->mpdf->col = -1; //int + $this->mpdf->row = -1; //int + + $this->mpdf->Reset(); + + $this->mpdf->cellPaddingL = 0; + $this->mpdf->cellPaddingT = 0; + $this->mpdf->cellPaddingR = 0; + $this->mpdf->cellPaddingB = 0; + $this->mpdf->cMarginL = 0; + $this->mpdf->cMarginT = 0; + $this->mpdf->cMarginR = 0; + $this->mpdf->cMarginB = 0; + $this->mpdf->default_font_size = $this->mpdf->original_default_font_size; + $this->mpdf->default_font = $this->mpdf->original_default_font; + $this->mpdf->SetFontSize($this->mpdf->default_font_size, false); + $this->mpdf->SetFont($this->mpdf->default_font, '', 0, false); + $this->mpdf->SetLineHeight(); + + if (isset($this->mpdf->blk[$this->mpdf->blklvl]['InlineProperties'])) { + $this->mpdf->restoreInlineProperties($this->mpdf->blk[$this->mpdf->blklvl]['InlineProperties']); + } + + if ($page_break_after) { + $save_blklvl = $this->mpdf->blklvl; + $save_blk = $this->mpdf->blk; + $save_silp = $this->mpdf->saveInlineProperties(); + $save_ilp = $this->mpdf->InlineProperties; + $save_bflp = $this->mpdf->InlineBDF; + $save_bflpc = $this->mpdf->InlineBDFctr; // mPDF 6 + // mPDF 6 pagebreaktype + $startpage = $this->mpdf->page; + $pagebreaktype = $this->mpdf->defaultPagebreakType; + if ($this->mpdf->ColActive) { + $pagebreaktype = 'cloneall'; + } + + // mPDF 6 pagebreaktype + $this->mpdf->_preForcedPagebreak($pagebreaktype); + + if ($page_break_after === 'RIGHT') { + $this->mpdf->AddPage($this->mpdf->CurOrientation, 'NEXT-ODD'); + } elseif ($page_break_after === 'LEFT') { + $this->mpdf->AddPage($this->mpdf->CurOrientation, 'NEXT-EVEN'); + } else { + $this->mpdf->AddPage($this->mpdf->CurOrientation); + } + + // mPDF 6 pagebreaktype + $this->mpdf->_postForcedPagebreak($pagebreaktype, $startpage, $save_blk, $save_blklvl); + + $this->mpdf->InlineProperties = $save_ilp; + $this->mpdf->InlineBDF = $save_bflp; + $this->mpdf->InlineBDFctr = $save_bflpc; // mPDF 6 + $this->mpdf->restoreInlineProperties($save_silp); + } + } + + /** + * This function determines the shrink factor when resizing tables + * val is the table_height / page_height_available + * returns a scaling factor used as $shrin_k to resize the table + * Overcompensating will be quicker but may unnecessarily shrink table too much + * Undercompensating means it will reiterate more times (taking more processing time) + */ + private function tbsqrt($val, $iteration = 3) + { + // Alters number of iterations until it returns $val itself - Must be > 2 + $k = 4; + + // Probably best guess and most accurate + if ($iteration === 1) { + return sqrt($val); + } + + // Faster than using sqrt (because it won't undercompensate), and gives reasonable results + // return 1 + (($val - 1) / 2); + $x = 2 - (($iteration - 2) / ($k - 2)); + + if ($x === 0) { + $ret = $val + 0.00001; + } elseif ($x < 0) { + $ret = 1 + ( pow(2, ($iteration - 2 - $k)) / 1000 ); + } else { + $ret = 1 + (($val - 1) / $x); + } + + return $ret; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tag.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tag.php new file mode 100644 index 0000000..9f7c52c --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tag.php @@ -0,0 +1,121 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Strict; + +use Mpdf\Cache; +use Mpdf\Color\ColorConverter; +use Mpdf\CssManager; +use Mpdf\Form; +use Mpdf\Image\ImageProcessor; +use Mpdf\Language\LanguageToFontInterface; +use Mpdf\Mpdf; +use Mpdf\Otl; +use Mpdf\SizeConverter; +use Mpdf\TableOfContents; + +abstract class Tag +{ + + use Strict; + + /** + * @var \Mpdf\Mpdf + */ + protected $mpdf; + + /** + * @var \Mpdf\Cache + */ + protected $cache; + + /** + * @var \Mpdf\CssManager + */ + protected $cssManager; + + /** + * @var \Mpdf\Form + */ + protected $form; + + /** + * @var \Mpdf\Otl + */ + protected $otl; + + /** + * @var \Mpdf\TableOfContents + */ + protected $tableOfContents; + + /** + * @var \Mpdf\SizeConverter + */ + protected $sizeConverter; + + /** + * @var \Mpdf\Color\ColorConverter + */ + protected $colorConverter; + + /** + * @var \Mpdf\Image\ImageProcessor + */ + protected $imageProcessor; + + /** + * @var \Mpdf\Language\LanguageToFontInterface + */ + protected $languageToFont; + + const ALIGN = [ + 'left' => 'L', + 'center' => 'C', + 'right' => 'R', + 'top' => 'T', + 'text-top' => 'TT', + 'middle' => 'M', + 'baseline' => 'BS', + 'bottom' => 'B', + 'text-bottom' => 'TB', + 'justify' => 'J' + ]; + + public function __construct( + Mpdf $mpdf, + Cache $cache, + CssManager $cssManager, + Form $form, + Otl $otl, + TableOfContents $tableOfContents, + SizeConverter $sizeConverter, + ColorConverter $colorConverter, + ImageProcessor $imageProcessor, + LanguageToFontInterface $languageToFont + ) { + + $this->mpdf = $mpdf; + $this->cache = $cache; + $this->cssManager = $cssManager; + $this->form = $form; + $this->otl = $otl; + $this->tableOfContents = $tableOfContents; + $this->sizeConverter = $sizeConverter; + $this->colorConverter = $colorConverter; + $this->imageProcessor = $imageProcessor; + $this->languageToFont = $languageToFont; + } + + public function getTagName() + { + $tag = get_class($this); + return strtoupper(str_replace('Mpdf\Tag\\', '', $tag)); + } + + abstract public function open($attr, &$ahtml, &$ihtml); + + abstract public function close(&$ahtml, &$ihtml); + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Td.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Td.php new file mode 100644 index 0000000..6708a67 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Td.php @@ -0,0 +1,469 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Css\Border; +use Mpdf\Css\TextVars; +use Mpdf\Utils\UtfString; + +class Td extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $tag = $this->getTagName(); + $this->mpdf->ignorefollowingspaces = true; + $this->mpdf->lastoptionaltag = $tag; // Save current HTML specified optional endtag + $this->cssManager->tbCSSlvl++; + $this->mpdf->InlineProperties = []; + $this->mpdf->InlineBDF = []; // mPDF 6 + $this->mpdf->InlineBDFctr = 0; // mPDF 6 + $this->mpdf->tdbegin = true; + $this->mpdf->col++; + while (isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col])) { + $this->mpdf->col++; + } + + //Update number column + if ($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nc'] < $this->mpdf->col + 1) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nc'] = $this->mpdf->col + 1; + } + + $table = &$this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]; + + $c = ['a' => false, + 'R' => false, + 'nowrap' => false, + 'bgcolor' => false, + 'padding' => ['L' => false, + 'R' => false, + 'T' => false, + 'B' => false + ] + ]; + + if ($this->mpdf->simpleTables && $this->mpdf->row == 0 && $this->mpdf->col == 0) { + $table['simple']['border'] = false; + $table['simple']['border_details']['R']['w'] = 0; + $table['simple']['border_details']['L']['w'] = 0; + $table['simple']['border_details']['T']['w'] = 0; + $table['simple']['border_details']['B']['w'] = 0; + $table['simple']['border_details']['R']['style'] = ''; + $table['simple']['border_details']['L']['style'] = ''; + $table['simple']['border_details']['T']['style'] = ''; + $table['simple']['border_details']['B']['style'] = ''; + } elseif (!$this->mpdf->simpleTables) { + $c['border'] = false; + $c['border_details']['R']['w'] = 0; + $c['border_details']['L']['w'] = 0; + $c['border_details']['T']['w'] = 0; + $c['border_details']['B']['w'] = 0; + $c['border_details']['mbw']['BL'] = 0; + $c['border_details']['mbw']['BR'] = 0; + $c['border_details']['mbw']['RT'] = 0; + $c['border_details']['mbw']['RB'] = 0; + $c['border_details']['mbw']['TL'] = 0; + $c['border_details']['mbw']['TR'] = 0; + $c['border_details']['mbw']['LT'] = 0; + $c['border_details']['mbw']['LB'] = 0; + $c['border_details']['R']['style'] = ''; + $c['border_details']['L']['style'] = ''; + $c['border_details']['T']['style'] = ''; + $c['border_details']['B']['style'] = ''; + $c['border_details']['R']['s'] = 0; + $c['border_details']['L']['s'] = 0; + $c['border_details']['T']['s'] = 0; + $c['border_details']['B']['s'] = 0; + $c['border_details']['R']['c'] = $this->colorConverter->convert(0, $this->mpdf->PDFAXwarnings); + $c['border_details']['L']['c'] = $this->colorConverter->convert(0, $this->mpdf->PDFAXwarnings); + $c['border_details']['T']['c'] = $this->colorConverter->convert(0, $this->mpdf->PDFAXwarnings); + $c['border_details']['B']['c'] = $this->colorConverter->convert(0, $this->mpdf->PDFAXwarnings); + $c['border_details']['R']['dom'] = 0; + $c['border_details']['L']['dom'] = 0; + $c['border_details']['T']['dom'] = 0; + $c['border_details']['B']['dom'] = 0; + $c['border_details']['cellposdom'] = 0; + } + + + if ($table['va']) { + $c['va'] = $table['va']; + } + if ($table['txta']) { + $c['a'] = $table['txta']; + } + if ($this->mpdf->table_border_attr_set && $table['border_details']) { + if (!$this->mpdf->simpleTables) { + $c['border_details']['R'] = $table['border_details']['R']; + $c['border_details']['L'] = $table['border_details']['L']; + $c['border_details']['T'] = $table['border_details']['T']; + $c['border_details']['B'] = $table['border_details']['B']; + $c['border'] = $table['border']; + $c['border_details']['L']['dom'] = 1; + $c['border_details']['R']['dom'] = 1; + $c['border_details']['T']['dom'] = 1; + $c['border_details']['B']['dom'] = 1; + } elseif ($this->mpdf->simpleTables && $this->mpdf->row == 0 && $this->mpdf->col == 0) { + $table['simple']['border_details']['R'] = $table['border_details']['R']; + $table['simple']['border_details']['L'] = $table['border_details']['L']; + $table['simple']['border_details']['T'] = $table['border_details']['T']; + $table['simple']['border_details']['B'] = $table['border_details']['B']; + $table['simple']['border'] = $table['border']; + } + } + // INHERITED THEAD CSS Properties + if ($this->mpdf->tablethead) { + if ($this->mpdf->thead_valign_default) { + $c['va'] = self::ALIGN[strtolower($this->mpdf->thead_valign_default)]; + } + if ($this->mpdf->thead_textalign_default) { + $c['a'] = self::ALIGN[strtolower($this->mpdf->thead_textalign_default)]; + } + if ($this->mpdf->thead_font_weight === 'B') { + $this->mpdf->SetStyle('B', true); + } + if ($this->mpdf->thead_font_style === 'I') { + $this->mpdf->SetStyle('I', true); + } + if ($this->mpdf->thead_font_smCaps === 'S') { + $this->mpdf->textvar |= TextVars::FC_SMALLCAPS; + } // mPDF 5.7.1 + } + + // INHERITED TFOOT CSS Properties + if ($this->mpdf->tabletfoot) { + if ($this->mpdf->tfoot_valign_default) { + $c['va'] = self::ALIGN[strtolower($this->mpdf->tfoot_valign_default)]; + } + if ($this->mpdf->tfoot_textalign_default) { + $c['a'] = self::ALIGN[strtolower($this->mpdf->tfoot_textalign_default)]; + } + if ($this->mpdf->tfoot_font_weight === 'B') { + $this->mpdf->SetStyle('B', true); + } + if ($this->mpdf->tfoot_font_style === 'I') { + $this->mpdf->SetStyle('I', true); + } + if ($this->mpdf->tfoot_font_style === 'S') { + $this->mpdf->textvar |= TextVars::FC_SMALLCAPS; + } // mPDF 5.7.1 + } + + + if ($this->mpdf->trow_text_rotate) { + $c['R'] = $this->mpdf->trow_text_rotate; + } + + $this->mpdf->cell_border_dominance_L = 0; + $this->mpdf->cell_border_dominance_R = 0; + $this->mpdf->cell_border_dominance_T = 0; + $this->mpdf->cell_border_dominance_B = 0; + + $properties = $this->cssManager->MergeCSS('TABLE', $tag, $attr); + + $properties = $this->cssManager->array_merge_recursive_unique($this->mpdf->base_table_properties, $properties); + + $this->mpdf->Reset(); // mPDF 6 ????????????????????? + + $this->mpdf->setCSS($properties, 'TABLECELL', $tag); + + $c['dfs'] = $this->mpdf->FontSize; // Default Font size + + + if (isset($properties['BACKGROUND-COLOR'])) { + $c['bgcolor'] = $properties['BACKGROUND-COLOR']; + } elseif (isset($properties['BACKGROUND'])) { + $c['bgcolor'] = $properties['BACKGROUND']; + } elseif (isset($attr['BGCOLOR'])) { + $c['bgcolor'] = $attr['BGCOLOR']; + } + + + + /* -- BACKGROUNDS -- */ + if (isset($properties['BACKGROUND-GRADIENT'])) { + $c['gradient'] = $properties['BACKGROUND-GRADIENT']; + } else { + $c['gradient'] = false; + } + + if (!empty($properties['BACKGROUND-IMAGE']) && !$this->mpdf->keep_block_together) { + $ret = $this->mpdf->SetBackground($properties, $this->mpdf->blk[$this->mpdf->blklvl]['inner_width']); + if ($ret) { + $c['background-image'] = $ret; + } + } + /* -- END BACKGROUNDS -- */ + if (isset($properties['VERTICAL-ALIGN'])) { + $c['va'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } elseif (isset($attr['VALIGN'])) { + $c['va'] = self::ALIGN[strtolower($attr['VALIGN'])]; + } + + + if (!empty($properties['TEXT-ALIGN'])) { + if (0 === strpos($properties['TEXT-ALIGN'], 'D')) { + $c['a'] = $properties['TEXT-ALIGN']; + } else { + $c['a'] = self::ALIGN[strtolower($properties['TEXT-ALIGN'])]; + } + } + if (!empty($attr['ALIGN'])) { + if (strtolower($attr['ALIGN']) === 'char') { + if (!empty($attr['CHAR'])) { + $char = html_entity_decode($attr['CHAR']); + $char = UtfString::strcode2utf($char); + $d = array_search($char, $this->mpdf->decimal_align); + if ($d !== false) { + $c['a'] = $d . 'R'; + } + } else { + $c['a'] = 'DPR'; + } + } else { + $c['a'] = self::ALIGN[strtolower($attr['ALIGN'])]; + } + } + + // mPDF 6 + $c['direction'] = $table['direction']; + if (isset($attr['DIR']) && $attr['DIR'] != '') { + $c['direction'] = strtolower($attr['DIR']); + } + if (isset($properties['DIRECTION'])) { + $c['direction'] = strtolower($properties['DIRECTION']); + } + + if (!$c['a']) { + if (isset($c['direction']) && $c['direction'] === 'rtl') { + $c['a'] = 'R'; + } else { + $c['a'] = 'L'; + } + } + + $c['cellLineHeight'] = $table['cellLineHeight']; + if (isset($properties['LINE-HEIGHT'])) { + $c['cellLineHeight'] = $this->mpdf->fixLineheight($properties['LINE-HEIGHT']); + } + + $c['cellLineStackingStrategy'] = $table['cellLineStackingStrategy']; + if (isset($properties['LINE-STACKING-STRATEGY'])) { + $c['cellLineStackingStrategy'] = strtolower($properties['LINE-STACKING-STRATEGY']); + } + + $c['cellLineStackingShift'] = $table['cellLineStackingShift']; + if (isset($properties['LINE-STACKING-SHIFT'])) { + $c['cellLineStackingShift'] = strtolower($properties['LINE-STACKING-SHIFT']); + } + + if (isset($properties['TEXT-ROTATE']) && ($properties['TEXT-ROTATE'] || $properties['TEXT-ROTATE'] === '0')) { + $c['R'] = $properties['TEXT-ROTATE']; + } + if (isset($properties['BORDER'])) { + $bord = $this->mpdf->border_details($properties['BORDER']); + if ($bord['s']) { + if (!$this->mpdf->simpleTables) { + $c['border'] = Border::ALL; + $c['border_details']['R'] = $bord; + $c['border_details']['L'] = $bord; + $c['border_details']['T'] = $bord; + $c['border_details']['B'] = $bord; + $c['border_details']['L']['dom'] = $this->mpdf->cell_border_dominance_L; + $c['border_details']['R']['dom'] = $this->mpdf->cell_border_dominance_R; + $c['border_details']['T']['dom'] = $this->mpdf->cell_border_dominance_T; + $c['border_details']['B']['dom'] = $this->mpdf->cell_border_dominance_B; + } elseif ($this->mpdf->simpleTables && $this->mpdf->row == 0 && $this->mpdf->col == 0) { + $table['simple']['border'] = Border::ALL; + $table['simple']['border_details']['R'] = $bord; + $table['simple']['border_details']['L'] = $bord; + $table['simple']['border_details']['T'] = $bord; + $table['simple']['border_details']['B'] = $bord; + } + } + } + if (!$this->mpdf->simpleTables) { + if (!empty($properties['BORDER-RIGHT'])) { + $c['border_details']['R'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); + $this->mpdf->setBorder($c['border'], Border::RIGHT, $c['border_details']['R']['s']); + $c['border_details']['R']['dom'] = $this->mpdf->cell_border_dominance_R; + } + if (!empty($properties['BORDER-LEFT'])) { + $c['border_details']['L'] = $this->mpdf->border_details($properties['BORDER-LEFT']); + $this->mpdf->setBorder($c['border'], Border::LEFT, $c['border_details']['L']['s']); + $c['border_details']['L']['dom'] = $this->mpdf->cell_border_dominance_L; + } + if (!empty($properties['BORDER-BOTTOM'])) { + $c['border_details']['B'] = $this->mpdf->border_details($properties['BORDER-BOTTOM']); + $this->mpdf->setBorder($c['border'], Border::BOTTOM, $c['border_details']['B']['s']); + $c['border_details']['B']['dom'] = $this->mpdf->cell_border_dominance_B; + } + if (!empty($properties['BORDER-TOP'])) { + $c['border_details']['T'] = $this->mpdf->border_details($properties['BORDER-TOP']); + $this->mpdf->setBorder($c['border'], Border::TOP, $c['border_details']['T']['s']); + $c['border_details']['T']['dom'] = $this->mpdf->cell_border_dominance_T; + } + } elseif ($this->mpdf->simpleTables && $this->mpdf->row == 0 && $this->mpdf->col == 0) { + if (!empty($properties['BORDER-LEFT'])) { + $bord = $this->mpdf->border_details($properties['BORDER-LEFT']); + if ($bord['s']) { + $table['simple']['border'] = Border::ALL; + } else { + $table['simple']['border'] = 0; + } + $table['simple']['border_details']['R'] = $bord; + $table['simple']['border_details']['L'] = $bord; + $table['simple']['border_details']['T'] = $bord; + $table['simple']['border_details']['B'] = $bord; + } + } + + if ($this->mpdf->simpleTables && $this->mpdf->row == 0 && $this->mpdf->col == 0 && !$table['borders_separate'] && $table['simple']['border']) { + $table['border_details'] = $table['simple']['border_details']; + $table['border'] = $table['simple']['border']; + } + + // Border set on TR (if collapsed only) + if (!$table['borders_separate'] && !$this->mpdf->simpleTables && isset($table['trborder-left'][$this->mpdf->row])) { + if ($this->mpdf->col == 0) { + $left = $this->mpdf->border_details($table['trborder-left'][$this->mpdf->row]); + $c['border_details']['L'] = $left; + $this->mpdf->setBorder($c['border'], Border::LEFT, $c['border_details']['L']['s']); + } + $c['border_details']['B'] = $this->mpdf->border_details($table['trborder-bottom'][$this->mpdf->row]); + $this->mpdf->setBorder($c['border'], Border::BOTTOM, $c['border_details']['B']['s']); + $c['border_details']['T'] = $this->mpdf->border_details($table['trborder-top'][$this->mpdf->row]); + $this->mpdf->setBorder($c['border'], Border::TOP, $c['border_details']['T']['s']); + } + + if ($this->mpdf->packTableData && !$this->mpdf->simpleTables) { + $c['borderbin'] = $this->mpdf->_packCellBorder($c); + unset($c['border'], $c['border_details']); + } + + if (isset($properties['PADDING-LEFT'])) { + $c['padding']['L'] = $this->sizeConverter->convert($properties['PADDING-LEFT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['PADDING-RIGHT'])) { + $c['padding']['R'] = $this->sizeConverter->convert($properties['PADDING-RIGHT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['PADDING-BOTTOM'])) { + $c['padding']['B'] = $this->sizeConverter->convert($properties['PADDING-BOTTOM'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + if (isset($properties['PADDING-TOP'])) { + $c['padding']['T'] = $this->sizeConverter->convert($properties['PADDING-TOP'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + + $w = ''; + if (isset($properties['WIDTH'])) { + $w = $properties['WIDTH']; + } elseif (isset($attr['WIDTH'])) { + $w = $attr['WIDTH']; + } + if ($w) { + if (strpos($w, '%') && !$this->mpdf->ignore_table_percents) { + $c['wpercent'] = (float) $w; + } // makes 80% -> 80 + elseif (!strpos($w, '%') && !$this->mpdf->ignore_table_widths) { + $c['w'] = $this->sizeConverter->convert($w, $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + } + + if (isset($properties['HEIGHT']) && !strpos($properties['HEIGHT'], '%')) { + $c['h'] = $this->sizeConverter->convert($properties['HEIGHT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } elseif (isset($attr['HEIGHT']) && !strpos($attr['HEIGHT'], '%')) { + $c['h'] = $this->sizeConverter->convert($attr['HEIGHT'], $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], $this->mpdf->FontSize, false); + } + + if (isset($properties['WHITE-SPACE'])) { + if (strtoupper($properties['WHITE-SPACE']) === 'NOWRAP') { + $c['nowrap'] = 1; + } + } + + if (isset($attr['TEXT-ROTATE'])) { + $c['R'] = $attr['TEXT-ROTATE']; + } + if (!empty($attr['NOWRAP'])) { + $c['nowrap'] = 1; + } + + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col] = $c; + unset($c); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] = 0; + + $cs = $rs = 1; + if (isset($attr['COLSPAN']) && $attr['COLSPAN'] > 1) { + $cs = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['colspan'] = $attr['COLSPAN']; + } + if ($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nc'] < $this->mpdf->col + $cs) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nc'] = $this->mpdf->col + $cs; + } // following code moved outside if... + for ($l = $this->mpdf->col; $l < $this->mpdf->col + $cs; $l++) { + if ($l - $this->mpdf->col) { + $this->mpdf->cell[$this->mpdf->row][$l] = 0; + } + } + if (isset($attr['ROWSPAN']) && $attr['ROWSPAN'] > 1) { + $rs = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['rowspan'] = $attr['ROWSPAN']; + } + for ($k = $this->mpdf->row; $k < $this->mpdf->row + $rs; $k++) { + for ($l = $this->mpdf->col; $l < $this->mpdf->col + $cs; $l++) { + if ($k - $this->mpdf->row || $l - $this->mpdf->col) { + $this->mpdf->cell[$k][$l] = 0; + } + } + } + unset($table); + } + + public function close(&$ahtml, &$ihtml) + { + if ($this->mpdf->tableLevel) { + $this->mpdf->lastoptionaltag = 'TR'; + unset($this->cssManager->tablecascadeCSS[$this->cssManager->tbCSSlvl]); + $this->cssManager->tbCSSlvl--; + if (!$this->mpdf->tdbegin) { + return; + } + $this->mpdf->tdbegin = false; + // Added for correct calculation of cell column width - otherwise misses the last line if not end </p> etc. + if (!isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'])) { + if (!is_array($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col])) { + throw new \Mpdf\MpdfException('You may have an error in your HTML code e.g. </td></td>'); + } + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } elseif ($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] < $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['maxs'] = $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s']; + } + + // Remove last <br> if at end of cell + $ntb = 0; + if (isset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'])) { + $ntb = count($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer']); + } + if ($ntb > 1 && $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][$ntb - 1][0] === "\n") { + unset($this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][$ntb - 1]); + } + + if ($this->mpdf->tablethead) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead'][$this->mpdf->row] = true; + if ($this->mpdf->tableLevel == 1) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['headernrows'] + = max($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['headernrows'], $this->mpdf->row + 1); + } + } + if ($this->mpdf->tabletfoot) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot'][$this->mpdf->row] = true; + if ($this->mpdf->tableLevel == 1) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['footernrows'] + = max( + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['footernrows'], + $this->mpdf->row + 1 - $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['headernrows'] + ); + } + } + $this->mpdf->Reset(); + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TextArea.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TextArea.php new file mode 100644 index 0000000..8685fab --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TextArea.php @@ -0,0 +1,162 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; + +class TextArea extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + if (isset($attr['DISABLED'])) { + $objattr['disabled'] = true; + } + if (isset($attr['READONLY'])) { + $objattr['readonly'] = true; + } + if (isset($attr['REQUIRED'])) { + $objattr['required'] = true; + } + if (isset($attr['SPELLCHECK']) && strtolower($attr['SPELLCHECK']) === 'true') { + $objattr['spellcheck'] = true; + } + if (isset($attr['TITLE'])) { + $objattr['title'] = $attr['TITLE']; + if ($this->mpdf->onlyCoreFonts) { + $objattr['title'] = mb_convert_encoding($objattr['title'], $this->mpdf->mb_enc, 'UTF-8'); + } + } + if ($this->mpdf->useActiveForms) { + if (isset($attr['NAME'])) { + $objattr['fieldname'] = $attr['NAME']; + } + $this->form->form_element_spacing['textarea']['outer']['v'] = 0; + $this->form->form_element_spacing['textarea']['inner']['v'] = 0; + if (isset($attr['ONCALCULATE'])) { + $objattr['onCalculate'] = $attr['ONCALCULATE']; + } elseif (isset($attr['ONCHANGE'])) { + $objattr['onCalculate'] = $attr['ONCHANGE']; + } + if (isset($attr['ONVALIDATE'])) { + $objattr['onValidate'] = $attr['ONVALIDATE']; + } + if (isset($attr['ONKEYSTROKE'])) { + $objattr['onKeystroke'] = $attr['ONKEYSTROKE']; + } + if (isset($attr['ONFORMAT'])) { + $objattr['onFormat'] = $attr['ONFORMAT']; + } + } + $this->mpdf->InlineProperties['TEXTAREA'] = $this->mpdf->saveInlineProperties(); + $properties = $this->cssManager->MergeCSS('', 'TEXTAREA', $attr); + if (isset($properties['FONT-FAMILY'])) { + $this->mpdf->SetFont($properties['FONT-FAMILY'], '', 0, false); + } + if (isset($properties['FONT-SIZE'])) { + $mmsize = $this->sizeConverter->convert($properties['FONT-SIZE'], $this->mpdf->default_font_size / Mpdf::SCALE); + $this->mpdf->SetFontSize($mmsize * Mpdf::SCALE, false); + } + if (isset($properties['COLOR'])) { + $objattr['color'] = $this->colorConverter->convert($properties['COLOR'], $this->mpdf->PDFAXwarnings); + } + $objattr['fontfamily'] = $this->mpdf->FontFamily; + $objattr['fontsize'] = $this->mpdf->FontSizePt; + if ($this->mpdf->useActiveForms) { + if (isset($properties['TEXT-ALIGN'])) { + $objattr['text_align'] = self::ALIGN[strtolower($properties['TEXT-ALIGN'])]; + } elseif (isset($attr['ALIGN'])) { + $objattr['text_align'] = self::ALIGN[strtolower($attr['ALIGN'])]; + } + if (isset($properties['OVERFLOW']) && strtolower($properties['OVERFLOW']) === 'hidden') { + $objattr['donotscroll'] = true; + } + if (isset($properties['BORDER-TOP-COLOR'])) { + $objattr['border-col'] = $this->colorConverter->convert($properties['BORDER-TOP-COLOR'], $this->mpdf->PDFAXwarnings); + } + if (isset($properties['BACKGROUND-COLOR'])) { + $objattr['background-col'] = $this->colorConverter->convert($properties['BACKGROUND-COLOR'], $this->mpdf->PDFAXwarnings); + } + } + $this->mpdf->SetLineHeight('', $this->form->textarea_lineheight); + + $w = 0; + $h = 0; + if (isset($properties['WIDTH'])) { + $w = $this->sizeConverter->convert( + $properties['WIDTH'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['HEIGHT'])) { + $h = $this->sizeConverter->convert( + $properties['HEIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['VERTICAL-ALIGN'])) { + $objattr['vertical-align'] = self::ALIGN[strtolower($properties['VERTICAL-ALIGN'])]; + } + + $colsize = 20; //HTML default value + $rowsize = 2; //HTML default value + if (isset($attr['COLS'])) { + $colsize = (int) $attr['COLS']; + } + if (isset($attr['ROWS'])) { + $rowsize = (int) $attr['ROWS']; + } + + $charsize = $this->mpdf->GetCharWidth('w', false); + if ($w) { + $colsize = round(($w - ($this->form->form_element_spacing['textarea']['outer']['h'] * 2) + - ($this->form->form_element_spacing['textarea']['inner']['h'] * 2)) / $charsize); + } + if ($h) { + $rowsize = round(($h - ($this->form->form_element_spacing['textarea']['outer']['v'] * 2) + - ($this->form->form_element_spacing['textarea']['inner']['v'] * 2)) / $this->mpdf->lineheight); + } + + $objattr['type'] = 'textarea'; + $objattr['width'] = ($colsize * $charsize) + ($this->form->form_element_spacing['textarea']['outer']['h'] * 2) + + ($this->form->form_element_spacing['textarea']['inner']['h'] * 2); + + $objattr['height'] = ($rowsize * $this->mpdf->lineheight) + + ($this->form->form_element_spacing['textarea']['outer']['v'] * 2) + + ($this->form->form_element_spacing['textarea']['inner']['v'] * 2); + + $objattr['rows'] = $rowsize; + $objattr['cols'] = $colsize; + + $this->mpdf->specialcontent = serialize($objattr); + + if ($this->mpdf->tableLevel) { // *TABLES* + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $objattr['width']; // *TABLES* + } // *TABLES* + } + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->ignorefollowingspaces = false; + $this->mpdf->specialcontent = ''; + if ($this->mpdf->InlineProperties['TEXTAREA']) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties['TEXTAREA']); + } + unset($this->mpdf->InlineProperties['TEXTAREA']); + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TextCircle.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TextCircle.php new file mode 100644 index 0000000..6bd5313 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TextCircle.php @@ -0,0 +1,250 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Mpdf; +use Mpdf\Utils\UtfString; + +class TextCircle extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $objattr = []; + $objattr['margin_top'] = 0; + $objattr['margin_bottom'] = 0; + $objattr['margin_left'] = 0; + $objattr['margin_right'] = 0; + $objattr['padding_top'] = 0; + $objattr['padding_bottom'] = 0; + $objattr['padding_left'] = 0; + $objattr['padding_right'] = 0; + $objattr['width'] = 0; + $objattr['height'] = 0; + $objattr['border_top']['w'] = 0; + $objattr['border_bottom']['w'] = 0; + $objattr['border_left']['w'] = 0; + $objattr['border_right']['w'] = 0; + $objattr['top-text'] = ''; + $objattr['bottom-text'] = ''; + $objattr['r'] = 20; // radius (default value here for safety) + $objattr['space-width'] = 120; + $objattr['char-width'] = 100; + + $this->mpdf->InlineProperties['TEXTCIRCLE'] = $this->mpdf->saveInlineProperties(); + $properties = $this->cssManager->MergeCSS('INLINE', 'TEXTCIRCLE', $attr); + + if (isset($properties ['DISPLAY']) && strtolower($properties ['DISPLAY']) === 'none') { + return; + } + if (isset($attr['R'])) { + $objattr['r'] = $this->sizeConverter->convert( + $attr['R'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($attr['TOP-TEXT'])) { + $objattr['top-text'] = UtfString::strcode2utf($attr['TOP-TEXT']); + $objattr['top-text'] = $this->mpdf->lesser_entity_decode($objattr['top-text']); + if ($this->mpdf->onlyCoreFonts) { + $objattr['top-text'] = mb_convert_encoding($objattr['top-text'], $this->mpdf->mb_enc, 'UTF-8'); + } + } + if (isset($attr['BOTTOM-TEXT'])) { + $objattr['bottom-text'] = UtfString::strcode2utf($attr['BOTTOM-TEXT']); + $objattr['bottom-text'] = $this->mpdf->lesser_entity_decode($objattr['bottom-text']); + if ($this->mpdf->onlyCoreFonts) { + $objattr['bottom-text'] = mb_convert_encoding($objattr['bottom-text'], $this->mpdf->mb_enc, 'UTF-8'); + } + } + if (!empty($attr['SPACE-WIDTH'])) { + $objattr['space-width'] = $attr['SPACE-WIDTH']; + } + if (!empty($attr['CHAR-WIDTH'])) { + $objattr['char-width'] = $attr['CHAR-WIDTH']; + } + + // VISIBILITY + $objattr['visibility'] = 'visible'; + if (isset($properties['VISIBILITY'])) { + $v = strtolower($properties['VISIBILITY']); + if (($v === 'hidden' || $v === 'printonly' || $v === 'screenonly') && $this->mpdf->visibility === 'visible') { + $objattr['visibility'] = $v; + } + } + if (isset($properties['FONT-SIZE'])) { + if (strtolower($properties['FONT-SIZE']) === 'auto') { + if ($objattr['top-text'] && $objattr['bottom-text']) { + $objattr['fontsize'] = -2; + } else { + $objattr['fontsize'] = -1; + } + } else { + $mmsize = $this->sizeConverter->convert($properties['FONT-SIZE'], $this->mpdf->default_font_size / Mpdf::SCALE); + $this->mpdf->SetFontSize($mmsize * Mpdf::SCALE, false); + $objattr['fontsize'] = $this->mpdf->FontSizePt; + } + } + if (isset($attr['DIVIDER'])) { + $objattr['divider'] = UtfString::strcode2utf($attr['DIVIDER']); + $objattr['divider'] = $this->mpdf->lesser_entity_decode($objattr['divider']); + if ($this->mpdf->onlyCoreFonts) { + $objattr['divider'] = mb_convert_encoding($objattr['divider'], $this->mpdf->mb_enc, 'UTF-8'); + } + } + + if (isset($properties['COLOR'])) { + $objattr['color'] = $this->colorConverter->convert($properties['COLOR'], $this->mpdf->PDFAXwarnings); + } + + $objattr['fontstyle'] = ''; + if (isset($properties['FONT-WEIGHT'])) { + if (strtoupper($properties['FONT-WEIGHT']) === 'BOLD') { + $objattr['fontstyle'] .= 'B'; + } + } + if (isset($properties['FONT-STYLE'])) { + if (strtoupper($properties['FONT-STYLE']) === 'ITALIC') { + $objattr['fontstyle'] .= 'I'; + } + } + + if (isset($properties['FONT-FAMILY'])) { + $this->mpdf->SetFont($properties['FONT-FAMILY'], $this->mpdf->FontStyle, 0, false); + } + $objattr['fontfamily'] = $this->mpdf->FontFamily; + + // VSPACE and HSPACE converted to margins in MergeCSS + if (isset($properties['MARGIN-TOP'])) { + $objattr['margin_top'] = $this->sizeConverter->convert( + $properties['MARGIN-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-BOTTOM'])) { + $objattr['margin_bottom'] = $this->sizeConverter->convert( + $properties['MARGIN-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-LEFT'])) { + $objattr['margin_left'] = $this->sizeConverter->convert( + $properties['MARGIN-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['MARGIN-RIGHT'])) { + $objattr['margin_right'] = $this->sizeConverter->convert( + $properties['MARGIN-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['PADDING-TOP'])) { + $objattr['padding_top'] = $this->sizeConverter->convert( + $properties['PADDING-TOP'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-BOTTOM'])) { + $objattr['padding_bottom'] = $this->sizeConverter->convert( + $properties['PADDING-BOTTOM'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-LEFT'])) { + $objattr['padding_left'] = $this->sizeConverter->convert( + $properties['PADDING-LEFT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + if (isset($properties['PADDING-RIGHT'])) { + $objattr['padding_right'] = $this->sizeConverter->convert( + $properties['PADDING-RIGHT'], + $this->mpdf->blk[$this->mpdf->blklvl]['inner_width'], + $this->mpdf->FontSize, + false + ); + } + + if (isset($properties['BORDER-TOP'])) { + $objattr['border_top'] = $this->mpdf->border_details($properties['BORDER-TOP']); + } + if (isset($properties['BORDER-BOTTOM'])) { + $objattr['border_bottom'] = $this->mpdf->border_details($properties['BORDER-BOTTOM']); + } + if (isset($properties['BORDER-LEFT'])) { + $objattr['border_left'] = $this->mpdf->border_details($properties['BORDER-LEFT']); + } + if (isset($properties['BORDER-RIGHT'])) { + $objattr['border_right'] = $this->mpdf->border_details($properties['BORDER-RIGHT']); + } + + if (isset($properties['OPACITY']) && $properties['OPACITY'] > 0 && $properties['OPACITY'] <= 1) { + $objattr['opacity'] = $properties['OPACITY']; + } + if (isset($properties['BACKGROUND-COLOR']) && $properties['BACKGROUND-COLOR'] != '') { + $objattr['bgcolor'] = $this->colorConverter->convert($properties['BACKGROUND-COLOR'], $this->mpdf->PDFAXwarnings); + } else { + $objattr['bgcolor'] = false; + } + if ($this->mpdf->HREF) { + if (strpos($this->mpdf->HREF, '.') === false && strpos($this->mpdf->HREF, '@') !== 0) { + $href = $this->mpdf->HREF; + while (array_key_exists($href, $this->mpdf->internallink)) { + $href = '#' . $href; + } + $this->mpdf->internallink[$href] = $this->mpdf->AddLink(); + $objattr['link'] = $this->mpdf->internallink[$href]; + } else { + $objattr['link'] = $this->mpdf->HREF; + } + } + $extraheight = $objattr['padding_top'] + $objattr['padding_bottom'] + $objattr['margin_top'] + $objattr['margin_bottom'] + $objattr['border_top']['w'] + $objattr['border_bottom']['w']; + $extrawidth = $objattr['padding_left'] + $objattr['padding_right'] + $objattr['margin_left'] + $objattr['margin_right'] + $objattr['border_left']['w'] + $objattr['border_right']['w']; + + + $w = $objattr['r'] * 2; + $h = $w; + $objattr['height'] = $h + $extraheight; + $objattr['width'] = $w + $extrawidth; + $objattr['type'] = 'textcircle'; + + $e = "\xbb\xa4\xactype=image,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + + /* -- TABLES -- */ + // Output it to buffers + if ($this->mpdf->tableLevel) { + $this->mpdf->_saveCellTextBuffer($e, $this->mpdf->HREF); + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['s'] += $objattr['width']; + } else { + /* -- END TABLES -- */ + $this->mpdf->_saveTextBuffer($e, $this->mpdf->HREF); + } // *TABLES* + + if ($this->mpdf->InlineProperties['TEXTCIRCLE']) { + $this->mpdf->restoreInlineProperties($this->mpdf->InlineProperties['TEXTCIRCLE']); + } + unset($this->mpdf->InlineProperties['TEXTCIRCLE']); + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Th.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Th.php new file mode 100644 index 0000000..c0df792 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Th.php @@ -0,0 +1,13 @@ +<?php + +namespace Mpdf\Tag; + +class Th extends Td +{ + + public function close(&$ahtml, &$ihtml) + { + $this->mpdf->SetStyle('B', false); + parent::close($ahtml, $ihtml); + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Time.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Time.php new file mode 100644 index 0000000..04ffb6e --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Time.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Time extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Toc.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Toc.php new file mode 100644 index 0000000..1afeae8 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Toc.php @@ -0,0 +1,17 @@ +<?php + +namespace Mpdf\Tag; + +class Toc extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + //added custom-tag - set Marker for insertion later of ToC + $this->tableOfContents->openTagTOC($attr); + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TocEntry.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TocEntry.php new file mode 100644 index 0000000..1b315ab --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TocEntry.php @@ -0,0 +1,38 @@ +<?php + +namespace Mpdf\Tag; + +class TocEntry extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + if (!empty($attr['CONTENT'])) { + $objattr = []; + $objattr['CONTENT'] = htmlspecialchars_decode($attr['CONTENT'], ENT_QUOTES); + $objattr['type'] = 'toc'; + $objattr['vertical-align'] = 'T'; + if (!empty($attr['LEVEL'])) { + $objattr['toclevel'] = $attr['LEVEL']; + } else { + $objattr['toclevel'] = 0; + } + if (!empty($attr['NAME'])) { + $objattr['toc_id'] = $attr['NAME']; + } else { + $objattr['toc_id'] = 0; + } + $e = "\xbb\xa4\xactype=toc,objattr=" . serialize($objattr) . "\xbb\xa4\xac"; + if ($this->mpdf->tableLevel) { + $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]['textbuffer'][] = [$e]; + } // *TABLES* + else { // *TABLES* + $this->mpdf->textbuffer[] = [$e]; + } // *TABLES* + } + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TocPageBreak.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TocPageBreak.php new file mode 100644 index 0000000..19c5167 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/TocPageBreak.php @@ -0,0 +1,16 @@ +<?php + +namespace Mpdf\Tag; + +class TocPageBreak extends FormFeed +{ + public function open($attr, &$ahtml, &$ihtml) + { + list($isbreak, $toc_id) = $this->tableOfContents->openTagTOCPAGEBREAK($attr); + $this->toc_id = $toc_id; + if ($isbreak) { + return; + } + parent::open($attr, $ahtml, $ihtml); + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tr.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tr.php new file mode 100644 index 0000000..f891df9 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tr.php @@ -0,0 +1,102 @@ +<?php + +namespace Mpdf\Tag; + +use Mpdf\Css\Border; + +class Tr extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + + $this->mpdf->lastoptionaltag = 'TR'; // Save current HTML specified optional endtag + $this->cssManager->tbCSSlvl++; + $this->mpdf->row++; + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['nr'] ++; + $this->mpdf->col = -1; + $properties = $this->cssManager->MergeCSS('TABLE', 'TR', $attr); + + if (!$this->mpdf->simpleTables && (!isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['borders_separate']) + || !$this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['borders_separate'])) { + if (!empty($properties['BORDER-LEFT'])) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trborder-left'][$this->mpdf->row] = $properties['BORDER-LEFT']; + } + if (!empty($properties['BORDER-RIGHT'])) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trborder-right'][$this->mpdf->row] = $properties['BORDER-RIGHT']; + } + if (!empty($properties['BORDER-TOP'])) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trborder-top'][$this->mpdf->row] = $properties['BORDER-TOP']; + } + if (!empty($properties['BORDER-BOTTOM'])) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trborder-bottom'][$this->mpdf->row] = $properties['BORDER-BOTTOM']; + } + } + + if (isset($properties['BACKGROUND-COLOR'])) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][$this->mpdf->row] = $properties['BACKGROUND-COLOR']; + } elseif (isset($attr['BGCOLOR'])) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['bgcolor'][$this->mpdf->row] = $attr['BGCOLOR']; + } + + /* -- BACKGROUNDS -- */ + if (isset($properties['BACKGROUND-GRADIENT']) && !$this->mpdf->kwt && !$this->mpdf->ColActive) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trgradients'][$this->mpdf->row] = $properties['BACKGROUND-GRADIENT']; + } + + // FIXME: undefined variable $currblk + if (!empty($properties['BACKGROUND-IMAGE']) && !$this->mpdf->kwt && !$this->mpdf->ColActive) { + $ret = $this->mpdf->SetBackground($properties, $currblk['inner_width']); + if ($ret) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trbackground-images'][$this->mpdf->row] = $ret; + } + } + /* -- END BACKGROUNDS -- */ + + if (isset($properties['TEXT-ROTATE'])) { + $this->mpdf->trow_text_rotate = $properties['TEXT-ROTATE']; + } + if (isset($attr['TEXT-ROTATE'])) { + $this->mpdf->trow_text_rotate = $attr['TEXT-ROTATE']; + } + + if ($this->mpdf->tablethead) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_thead'][$this->mpdf->row] = true; + } + if ($this->mpdf->tabletfoot) { + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['is_tfoot'][$this->mpdf->row] = true; + } + } + + public function close(&$ahtml, &$ihtml) + { + if ($this->mpdf->tableLevel) { + // If Border set on TR - Update right border + if (isset($this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trborder-left'][$this->mpdf->row])) { + $c = & $this->mpdf->cell[$this->mpdf->row][$this->mpdf->col]; + if ($c) { + if ($this->mpdf->packTableData) { + $cell = $this->mpdf->_unpackCellBorder($c['borderbin']); + } else { + $cell = $c; + } + $cell['border_details']['R'] = $this->mpdf->border_details( + $this->mpdf->table[$this->mpdf->tableLevel][$this->mpdf->tbctr[$this->mpdf->tableLevel]]['trborder-right'][$this->mpdf->row] + ); + $this->mpdf->setBorder($cell['border'], Border::RIGHT, $cell['border_details']['R']['s']); + if ($this->mpdf->packTableData) { + $c['borderbin'] = $this->mpdf->_packCellBorder($cell); + unset($c['border'], $c['border_details']); + } else { + $c = $cell; + } + } + } + $this->mpdf->lastoptionaltag = ''; + unset($this->cssManager->tablecascadeCSS[$this->cssManager->tbCSSlvl]); + $this->cssManager->tbCSSlvl--; + $this->mpdf->trow_text_rotate = ''; + $this->mpdf->tabletheadjustfinished = false; + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tt.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tt.php new file mode 100644 index 0000000..078277a --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tt.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Tt extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tta.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tta.php new file mode 100644 index 0000000..bac9fb5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tta.php @@ -0,0 +1,22 @@ +<?php + +namespace Mpdf\Tag; + +class Tta extends SubstituteTag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->tta = true; + $this->mpdf->InlineProperties['TTA'] = $this->mpdf->saveInlineProperties(); + + if (in_array($this->mpdf->FontFamily, $this->mpdf->mono_fonts)) { + $this->mpdf->setCSS(['FONT-FAMILY' => 'ccourier'], 'INLINE'); + } elseif (in_array($this->mpdf->FontFamily, $this->mpdf->serif_fonts)) { + $this->mpdf->setCSS(['FONT-FAMILY' => 'ctimes'], 'INLINE'); + } else { + $this->mpdf->setCSS(['FONT-FAMILY' => 'chelvetica'], 'INLINE'); + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tts.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tts.php new file mode 100644 index 0000000..f24d722 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Tts.php @@ -0,0 +1,15 @@ +<?php + +namespace Mpdf\Tag; + +class Tts extends SubstituteTag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->tts = true; + $this->mpdf->InlineProperties['TTS'] = $this->mpdf->saveInlineProperties(); + $this->mpdf->setCSS(['FONT-FAMILY' => 'csymbol', 'FONT-WEIGHT' => 'normal', 'FONT-STYLE' => 'normal'], 'INLINE'); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ttz.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ttz.php new file mode 100644 index 0000000..d76d4c5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ttz.php @@ -0,0 +1,15 @@ +<?php + +namespace Mpdf\Tag; + +class Ttz extends SubstituteTag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $this->mpdf->ttz = true; + $this->mpdf->InlineProperties['TTZ'] = $this->mpdf->saveInlineProperties(); + $this->mpdf->setCSS(['FONT-FAMILY' => 'czapfdingbats', 'FONT-WEIGHT' => 'normal', 'FONT-STYLE' => 'normal'], 'INLINE'); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/U.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/U.php new file mode 100644 index 0000000..513298d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/U.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class U extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ul.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ul.php new file mode 100644 index 0000000..6efa4d6 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/Ul.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class Ul extends BlockTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/VarTag.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/VarTag.php new file mode 100644 index 0000000..556dd40 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/VarTag.php @@ -0,0 +1,9 @@ +<?php + +namespace Mpdf\Tag; + +class VarTag extends InlineTag +{ + + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/WatermarkImage.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/WatermarkImage.php new file mode 100644 index 0000000..21f8c48 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/WatermarkImage.php @@ -0,0 +1,41 @@ +<?php + +namespace Mpdf\Tag; + +class WatermarkImage extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $src = ''; + if (isset($attr['SRC'])) { + $src = $attr['SRC']; + } + + $alpha = -1; + if (isset($attr['ALPHA']) && $attr['ALPHA'] > 0) { + $alpha = $attr['ALPHA']; + } + + $size = 'D'; + if (!empty($attr['SIZE'])) { + $size = $attr['SIZE']; + if (strpos($size, ',')) { + $size = explode(',', $size); + } + } + + $pos = 'P'; + if (!empty($attr['POSITION'])) { // mPDF 5.7.2 + $pos = $attr['POSITION']; + if (strpos($pos, ',')) { + $pos = explode(',', $pos); + } + } + $this->mpdf->SetWatermarkImage($src, $alpha, $size, $pos); + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Tag/WatermarkText.php b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/WatermarkText.php new file mode 100644 index 0000000..8d6dcc2 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Tag/WatermarkText.php @@ -0,0 +1,25 @@ +<?php + +namespace Mpdf\Tag; + +class WatermarkText extends Tag +{ + + public function open($attr, &$ahtml, &$ihtml) + { + $txt = ''; + if (!empty($attr['CONTENT'])) { + $txt = htmlspecialchars_decode($attr['CONTENT'], ENT_QUOTES); + } + + $alpha = -1; + if (isset($attr['ALPHA']) && $attr['ALPHA'] > 0) { + $alpha = $attr['ALPHA']; + } + $this->mpdf->SetWatermarkText($txt, $alpha); + } + + public function close(&$ahtml, &$ihtml) + { + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Ucdn.php b/lib/MPDF/vendor/mpdf/mpdf/src/Ucdn.php new file mode 100644 index 0000000..dbf7caa --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Ucdn.php @@ -0,0 +1,2921 @@ +<?php + +namespace Mpdf; + +class Ucdn +{ + + /* HarfBuzz ucdn/unicodedata_db.h */ + /* HarfBuzz ucdn/ucdn.c */ + /* HarfBuzz ucdn/ucdn.h */ + + const SCRIPT_COMMON = 0; + const SCRIPT_LATIN = 1; + const SCRIPT_GREEK = 2; + const SCRIPT_CYRILLIC = 3; + const SCRIPT_ARMENIAN = 4; + const SCRIPT_HEBREW = 5; + const SCRIPT_ARABIC = 6; + const SCRIPT_SYRIAC = 7; + const SCRIPT_THAANA = 8; + const SCRIPT_DEVANAGARI = 9; + const SCRIPT_BENGALI = 10; + const SCRIPT_GURMUKHI = 11; + const SCRIPT_GUJARATI = 12; + const SCRIPT_ORIYA = 13; + const SCRIPT_TAMIL = 14; + const SCRIPT_TELUGU = 15; + const SCRIPT_KANNADA = 16; + const SCRIPT_MALAYALAM = 17; + const SCRIPT_SINHALA = 18; + const SCRIPT_THAI = 19; + const SCRIPT_LAO = 20; + const SCRIPT_TIBETAN = 21; + const SCRIPT_MYANMAR = 22; + const SCRIPT_GEORGIAN = 23; + const SCRIPT_HANGUL = 24; + const SCRIPT_ETHIOPIC = 25; + const SCRIPT_CHEROKEE = 26; + const SCRIPT_CANADIAN_ABORIGINAL = 27; + const SCRIPT_OGHAM = 28; + const SCRIPT_RUNIC = 29; + const SCRIPT_KHMER = 30; + const SCRIPT_MONGOLIAN = 31; + const SCRIPT_HIRAGANA = 32; + const SCRIPT_KATAKANA = 33; + const SCRIPT_BOPOMOFO = 34; + const SCRIPT_HAN = 35; + const SCRIPT_YI = 36; + const SCRIPT_OLD_ITALIC = 37; + const SCRIPT_GOTHIC = 38; + const SCRIPT_DESERET = 39; + const SCRIPT_INHERITED = 40; + const SCRIPT_TAGALOG = 41; + const SCRIPT_HANUNOO = 42; + const SCRIPT_BUHID = 43; + const SCRIPT_TAGBANWA = 44; + const SCRIPT_LIMBU = 45; + const SCRIPT_TAI_LE = 46; + const SCRIPT_LINEAR_B = 47; + const SCRIPT_UGARITIC = 48; + const SCRIPT_SHAVIAN = 49; + const SCRIPT_OSMANYA = 50; + const SCRIPT_CYPRIOT = 51; + const SCRIPT_BRAILLE = 52; + const SCRIPT_BUGINESE = 53; + const SCRIPT_COPTIC = 54; + const SCRIPT_NEW_TAI_LUE = 55; + const SCRIPT_GLAGOLITIC = 56; + const SCRIPT_TIFINAGH = 57; + const SCRIPT_SYLOTI_NAGRI = 58; + const SCRIPT_OLD_PERSIAN = 59; + const SCRIPT_KHAROSHTHI = 60; + const SCRIPT_BALINESE = 61; + const SCRIPT_CUNEIFORM = 62; + const SCRIPT_PHOENICIAN = 63; + const SCRIPT_PHAGS_PA = 64; + const SCRIPT_NKO = 65; + const SCRIPT_SUNDANESE = 66; + const SCRIPT_LEPCHA = 67; + const SCRIPT_OL_CHIKI = 68; + const SCRIPT_VAI = 69; + const SCRIPT_SAURASHTRA = 70; + const SCRIPT_KAYAH_LI = 71; + const SCRIPT_REJANG = 72; + const SCRIPT_LYCIAN = 73; + const SCRIPT_CARIAN = 74; + const SCRIPT_LYDIAN = 75; + const SCRIPT_CHAM = 76; + const SCRIPT_TAI_THAM = 77; + const SCRIPT_TAI_VIET = 78; + const SCRIPT_AVESTAN = 79; + const SCRIPT_EGYPTIAN_HIEROGLYPHS = 80; + const SCRIPT_SAMARITAN = 81; + const SCRIPT_LISU = 82; + const SCRIPT_BAMUM = 83; + const SCRIPT_JAVANESE = 84; + const SCRIPT_MEETEI_MAYEK = 85; + const SCRIPT_IMPERIAL_ARAMAIC = 86; + const SCRIPT_OLD_SOUTH_ARABIAN = 87; + const SCRIPT_INSCRIPTIONAL_PARTHIAN = 88; + const SCRIPT_INSCRIPTIONAL_PAHLAVI = 89; + const SCRIPT_OLD_TURKIC = 90; + const SCRIPT_KAITHI = 91; + const SCRIPT_BATAK = 92; + const SCRIPT_BRAHMI = 93; + const SCRIPT_MANDAIC = 94; + const SCRIPT_CHAKMA = 95; + const SCRIPT_MEROITIC_CURSIVE = 96; + const SCRIPT_MEROITIC_HIEROGLYPHS = 97; + const SCRIPT_MIAO = 98; + const SCRIPT_SHARADA = 99; + const SCRIPT_SORA_SOMPENG = 100; + const SCRIPT_TAKRI = 101; + const SCRIPT_UNKNOWN = 102; + + public static function get_ucd_record($code) + { + if ($code >= 0x110000) { + $index = 0; + } else { + $index = self::$index0[$code >> (8)] << 5; + $offset = ($code >> 3) & ((1 << 5) - 1); + $index = self::$index1[$index + $offset] << 3; + $offset = $code & ((1 << 3) - 1); + $index = self::$index2[$index + $offset]; + } + return self::$ucd_records[$index]; + } + + public static function get_general_category($code) + { + $ucd_record = self::get_ucd_record($code); + return $ucd_record[0]; + } + + public static function get_combining_class($code) + { + $ucd_record = self::get_ucd_record($code); + return $ucd_record[1]; + } + + public static function get_bidi_class($code) + { + $ucd_record = self::get_ucd_record($code); + return $ucd_record[2]; + } + + public static function get_mirrored($code) + { + $ucd_record = self::get_ucd_record($code); + return $ucd_record[3]; + } + + public static function get_east_asian_width($code) + { + $ucd_record = self::get_ucd_record($code); + return $ucd_record[4]; + } + + public static function get_normalization_check($code) + { + $ucd_record = self::get_ucd_record($code); + return $ucd_record[5]; + } + + public static function get_script($code) + { + $ucd_record = self::get_ucd_record($code); + return $ucd_record[6]; + } + + // mPDF added + public static $uni_scriptblock = [ + /* SCRIPT_COMMON */ 0 => '', + /* SCRIPT_LATIN */ 1 => 'latn', + /* SCRIPT_GREEK */ 2 => 'grek', + /* SCRIPT_CYRILLIC */ 3 => 'cyrl', + /* SCRIPT_ARMENIAN */ 4 => 'armn', + /* SCRIPT_HEBREW */ 5 => 'hebr', + /* SCRIPT_ARABIC */ 6 => 'arab', + /* SCRIPT_SYRIAC */ 7 => 'syrc', + /* SCRIPT_THAANA */ 8 => 'thaa', + /* SCRIPT_DEVANAGARI */ 9 => 'dev2', + /* SCRIPT_BENGALI */ 10 => 'bng2', + /* SCRIPT_GURMUKHI */ 11 => 'gur2', + /* SCRIPT_GUJARATI */ 12 => 'gjr2', + /* SCRIPT_ORIYA */ 13 => 'ory2', + /* SCRIPT_TAMIL */ 14 => 'tml2', + /* SCRIPT_TELUGU */ 15 => 'tel2', + /* SCRIPT_KANNADA */ 16 => 'knd2', + /* SCRIPT_MALAYALAM */ 17 => 'mlm2', + /* SCRIPT_SINHALA */ 18 => 'sinh', + /* SCRIPT_THAI */ 19 => 'thai', + /* SCRIPT_LAO */ 20 => 'lao ', + /* SCRIPT_TIBETAN */ 21 => 'tibt', + /* SCRIPT_MYANMAR */ 22 => 'mym2', + /* SCRIPT_GEORGIAN */ 23 => 'geor', + /* SCRIPT_HANGUL */ 24 => 'jamo', /* there is also a hang tag, but we want to activate jamo features if present */ + /* SCRIPT_ETHIOPIC */ 25 => 'ethi', + /* SCRIPT_CHEROKEE */ 26 => 'cher', + /* SCRIPT_CANADIAN_ABORIGINAL */ 27 => 'cans', + /* SCRIPT_OGHAM */ 28 => 'ogam', + /* SCRIPT_RUNIC */ 29 => 'runr', + /* SCRIPT_KHMER */ 30 => 'khmr', + /* SCRIPT_MONGOLIAN */ 31 => 'mong', + /* SCRIPT_HIRAGANA */ 32 => 'kana', + /* SCRIPT_KATAKANA */ 33 => 'kana', + /* SCRIPT_BOPOMOFO */ 34 => 'bopo', + /* SCRIPT_HAN */ 35 => 'hani', + /* SCRIPT_YI */ 36 => 'yi ', + /* SCRIPT_OLD_ITALIC */ 37 => 'ital', + /* SCRIPT_GOTHIC */ 38 => 'goth', + /* SCRIPT_DESERET */ 39 => 'dsrt', + /* SCRIPT_INHERITED */ 40 => '', + /* SCRIPT_TAGALOG */ 41 => 'tglg', + /* SCRIPT_HANUNOO */ 42 => 'hano', + /* SCRIPT_BUHID */ 43 => 'buhd', + /* SCRIPT_TAGBANWA */ 44 => 'tagb', + /* SCRIPT_LIMBU */ 45 => 'limb', + /* SCRIPT_TAI_LE */ 46 => 'tale', + /* SCRIPT_LINEAR_B */ 47 => 'linb', + /* SCRIPT_UGARITIC */ 48 => 'ugar', + /* SCRIPT_SHAVIAN */ 49 => 'shaw', + /* SCRIPT_OSMANYA */ 50 => 'osma', + /* SCRIPT_CYPRIOT */ 51 => 'cprt', + /* SCRIPT_BRAILLE */ 52 => 'brai', + /* SCRIPT_BUGINESE */ 53 => 'bugi', + /* SCRIPT_COPTIC */ 54 => 'copt', + /* SCRIPT_NEW_TAI_LUE */ 55 => 'talu', + /* SCRIPT_GLAGOLITIC */ 56 => 'glag', + /* SCRIPT_TIFINAGH */ 57 => 'tfng', + /* SCRIPT_SYLOTI_NAGRI */ 58 => 'sylo', + /* SCRIPT_OLD_PERSIAN */ 59 => 'xpeo', + /* SCRIPT_KHAROSHTHI */ 60 => 'khar', + /* SCRIPT_BALINESE */ 61 => 'bali', + /* SCRIPT_CUNEIFORM */ 62 => 'xsux', + /* SCRIPT_PHOENICIAN */ 63 => 'phnx', + /* SCRIPT_PHAGS_PA */ 64 => 'phag', + /* SCRIPT_NKO */ 65 => 'nko ', + /* SCRIPT_SUNDANESE */ 66 => 'sund', + /* SCRIPT_LEPCHA */ 67 => 'lepc', + /* SCRIPT_OL_CHIKI */ 68 => 'olck', + /* SCRIPT_VAI */ 69 => 'vai ', + /* SCRIPT_SAURASHTRA */ 70 => 'saur', + /* SCRIPT_KAYAH_LI */ 71 => 'kali', + /* SCRIPT_REJANG */ 72 => 'rjng', + /* SCRIPT_LYCIAN */ 73 => 'lyci', + /* SCRIPT_CARIAN */ 74 => 'cari', + /* SCRIPT_LYDIAN */ 75 => 'lydi', + /* SCRIPT_CHAM */ 76 => 'cham', + /* SCRIPT_TAI_THAM */ 77 => 'lana', + /* SCRIPT_TAI_VIET */ 78 => 'tavt', + /* SCRIPT_AVESTAN */ 79 => 'avst', + /* SCRIPT_EGYPTIAN_HIEROGLYPHS */ 80 => 'egyp', + /* SCRIPT_SAMARITAN */ 81 => 'samr', + /* SCRIPT_LISU */ 82 => 'lisu', + /* SCRIPT_BAMUM */ 83 => 'bamu', + /* SCRIPT_JAVANESE */ 84 => 'java', + /* SCRIPT_MEETEI_MAYEK */ 85 => 'mtei', + /* SCRIPT_IMPERIAL_ARAMAIC */ 86 => 'armi', + /* SCRIPT_OLD_SOUTH_ARABIAN */ 87 => 'sarb', + /* SCRIPT_INSCRIPTIONAL_PARTHIAN */ 88 => 'prti', + /* SCRIPT_INSCRIPTIONAL_PAHLAVI */ 89 => 'phli', + /* SCRIPT_OLD_TURKIC */ 90 => 'orkh', + /* SCRIPT_KAITHI */ 91 => 'kthi', + /* SCRIPT_BATAK */ 92 => 'batk', + /* SCRIPT_BRAHMI */ 93 => 'brah', + /* SCRIPT_MANDAIC */ 94 => 'mand', + /* SCRIPT_CHAKMA */ 95 => 'cakm', + /* SCRIPT_MEROITIC_CURSIVE */ 96 => 'merc', + /* SCRIPT_MEROITIC_HIEROGLYPHS */ 97 => 'mero', + /* SCRIPT_MIAO */ 98 => 'plrd', + /* SCRIPT_SHARADA */ 99 => 'shrd', + /* SCRIPT_SORA_SOMPENG */ 100 => 'sora', + /* SCRIPT_TAKRI */ 101 => 'takr', + /* SCRIPT_UNKNOWN */ 102 => '', + ]; + + public static $ot_languages = [ + 'aa' => 'AFR ', /* Afar */ + 'ab' => 'ABK ', /* Abkhazian */ + 'abq' => 'ABA ', /* Abaza */ + 'ada' => 'DNG ', /* Dangme */ + 'ady' => 'ADY ', /* Adyghe */ + 'af' => 'AFK ', /* Afrikaans */ + 'aii' => 'SWA ', /* Swadaya Aramaic */ + 'aiw' => 'ARI ', /* Aari */ + 'alt' => 'ALT ', /* [Southern] Altai */ + 'am' => 'AMH ', /* Amharic */ + 'amf' => 'HBN ', /* Hammer-Banna */ + 'ar' => 'ARA ', /* Arabic */ + 'arn' => 'MAP ', /* Mapudungun */ + 'as' => 'ASM ', /* Assamese */ + 'ath' => 'ATH ', /* Athapaskan [family] */ + 'atv' => 'ALT ', /* [Northern] Altai */ + 'av' => 'AVR ', /* Avaric */ + 'awa' => 'AWA ', /* Awadhi */ + 'ay' => 'AYM ', /* Aymara */ + 'az' => 'AZE ', /* Azerbaijani */ + 'ba' => 'BSH ', /* Bashkir */ + 'bai' => 'BML ', /* Bamileke [family] */ + 'bal' => 'BLI ', /* Baluchi */ + 'bci' => 'BAU ', /* Baule */ + 'bcq' => 'BCH ', /* Bench */ + 'be' => 'BEL ', /* Belarussian */ + 'bem' => 'BEM ', /* Bemba (Zambia) */ + 'ber' => 'BER ', /* Berber [family] */ + 'bfq' => 'BAD ', /* Badaga */ + 'bft' => 'BLT ', /* Balti */ + 'bfy' => 'BAG ', /* Baghelkhandi */ + 'bg' => 'BGR ', /* Bulgarian */ + 'bhb' => 'BHI ', /* Bhili */ + 'bho' => 'BHO ', /* Bhojpuri */ + 'bik' => 'BIK ', /* Bikol */ + 'bin' => 'EDO ', /* Bini */ + 'bjt' => 'BLN ', /* Balanta-Ganja */ + 'bla' => 'BKF ', /* Blackfoot */ + 'ble' => 'BLN ', /* Balanta-Kentohe */ + 'bm' => 'BMB ', /* Bambara */ + 'bn' => 'BEN ', /* Bengali */ + 'bo' => 'TIB ', /* Tibetan */ + 'br' => 'BRE ', /* Breton */ + 'bra' => 'BRI ', /* Braj Bhasha */ + 'brh' => 'BRH ', /* Brahui */ + 'bs' => 'BOS ', /* Bosnian */ + 'btb' => 'BTI ', /* Beti (Cameroon) */ + 'bxr' => 'RBU ', /* Russian Buriat */ + 'byn' => 'BIL ', /* Bilen */ + 'ca' => 'CAT ', /* Catalan */ + 'ce' => 'CHE ', /* Chechen */ + 'ceb' => 'CEB ', /* Cebuano */ + 'chp' => 'CHP ', /* Chipewyan */ + 'chr' => 'CHR ', /* Cherokee */ + 'ckt' => 'CHK ', /* Chukchi */ + 'cop' => 'COP ', /* Coptic */ + 'cr' => 'CRE ', /* Cree */ + 'crh' => 'CRT ', /* Crimean Tatar */ + 'crj' => 'ECR ', /* [Southern] East Cree */ + 'crl' => 'ECR ', /* [Northern] East Cree */ + 'crm' => 'MCR ', /* Moose Cree */ + 'crx' => 'CRR ', /* Carrier */ + 'cs' => 'CSY ', /* Czech */ + 'cu' => 'CSL ', /* Church Slavic */ + 'cv' => 'CHU ', /* Chuvash */ + 'cwd' => 'DCR ', /* Woods Cree */ + 'cy' => 'WEL ', /* Welsh */ + 'da' => 'DAN ', /* Danish */ + 'dap' => 'NIS ', /* Nisi (India) */ + 'dar' => 'DAR ', /* Dargwa */ + 'de' => 'DEU ', /* German */ + 'din' => 'DNK ', /* Dinka */ + 'dje' => 'DJR ', /* Djerma */ + 'dng' => 'DUN ', /* Dungan */ + 'doi' => 'DGR ', /* Dogri */ + 'dsb' => 'LSB ', /* Lower Sorbian */ + 'dv' => 'DIV ', /* Dhivehi */ + 'dyu' => 'JUL ', /* Jula */ + 'dz' => 'DZN ', /* Dzongkha */ + 'ee' => 'EWE ', /* Ewe */ + 'efi' => 'EFI ', /* Efik */ + 'el' => 'ELL ', /* Modern Greek (1453-) */ + 'grc' => 'PGR ', /* Polytonic Greek */ + 'en' => 'ENG ', /* English */ + 'eo' => 'NTO ', /* Esperanto */ + 'eot' => 'BTI ', /* Beti (Côte d'Ivoire) */ + 'es' => 'ESP ', /* Spanish */ + 'et' => 'ETI ', /* Estonian */ + 'eu' => 'EUQ ', /* Basque */ + 'eve' => 'EVN ', /* Even */ + 'evn' => 'EVK ', /* Evenki */ + 'fa' => 'FAR ', /* Persian */ + 'ff' => 'FUL ', /* Fulah */ + 'fi' => 'FIN ', /* Finnish */ + 'fil' => 'PIL ', /* Filipino */ + 'fj' => 'FJI ', /* Fijian */ + 'fo' => 'FOS ', /* Faroese */ + 'fon' => 'FON ', /* Fon */ + 'fr' => 'FRA ', /* French */ + 'fur' => 'FRL ', /* Friulian */ + 'fy' => 'FRI ', /* Western Frisian */ + 'ga' => 'IRI ', /* Irish */ + 'gaa' => 'GAD ', /* Ga */ + 'gag' => 'GAG ', /* Gagauz */ + 'gbm' => 'GAW ', /* Garhwali */ + 'gd' => 'GAE ', /* Scottish Gaelic */ + 'gez' => 'GEZ ', /* Ge'ez */ + 'gl' => 'GAL ', /* Galician */ + 'gld' => 'NAN ', /* Nanai */ + 'gn' => 'GUA ', /* Guarani */ + 'gon' => 'GON ', /* Gondi */ + 'grt' => 'GRO ', /* Garo */ + 'gru' => 'SOG ', /* Sodo Gurage */ + 'gu' => 'GUJ ', /* Gujarati */ + 'guk' => 'GMZ ', /* Gumuz */ + 'gv' => 'MNX ', /* Manx Gaelic */ + 'ha' => 'HAU ', /* Hausa */ + 'har' => 'HRI ', /* Harari */ + 'haw' => 'HAW ', /* Hawaiin */ + 'he' => 'IWR ', /* Hebrew */ + 'hi' => 'HIN ', /* Hindi */ + 'hil' => 'HIL ', /* Hiligaynon */ + 'hnd' => 'HND ', /* [Southern] Hindko */ + 'hne' => 'CHH ', /* Chattisgarhi */ + 'hno' => 'HND ', /* [Northern] Hindko */ + 'hoc' => 'HO ', /* Ho */ + 'hoj' => 'HAR ', /* Harauti */ + 'hr' => 'HRV ', /* Croatian */ + 'hsb' => 'USB ', /* Upper Sorbian */ + 'ht' => 'HAI ', /* Haitian */ + 'hu' => 'HUN ', /* Hungarian */ + 'hy' => 'HYE ', /* Armenian */ + 'id' => 'IND ', /* Indonesian */ + 'ig' => 'IBO ', /* Igbo */ + 'igb' => 'EBI ', /* Ebira */ + 'ijo' => 'IJO ', /* Ijo [family] */ + 'ilo' => 'ILO ', /* Ilokano */ + 'inh' => 'ING ', /* Ingush */ + 'is' => 'ISL ', /* Icelandic */ + 'it' => 'ITA ', /* Italian */ + 'iu' => 'INU ', /* Inuktitut */ + 'ja' => 'JAN ', /* Japanese */ + 'jv' => 'JAV ', /* Javanese */ + 'ka' => 'KAT ', /* Georgian */ + 'kaa' => 'KRK ', /* Karakalpak */ + 'kam' => 'KMB ', /* Kamba (Kenya) */ + 'kar' => 'KRN ', /* Karen [family] */ + 'kbd' => 'KAB ', /* Kabardian */ + 'kdr' => 'KRM ', /* Karaim */ + 'kdt' => 'KUY ', /* Kuy */ + 'kex' => 'KKN ', /* Kokni */ + 'kfr' => 'KAC ', /* Kachchi */ + 'kfy' => 'KMN ', /* Kumaoni */ + 'kha' => 'KSI ', /* Khasi */ + 'khb' => 'XBD ', /* Tai Lue */ + 'khw' => 'KHW ', /* Khowar */ + 'ki' => 'KIK ', /* Kikuyu */ + 'kjh' => 'KHA ', /* Khakass */ + 'kk' => 'KAZ ', /* Kazakh */ + 'kl' => 'GRN ', /* Kalaallisut */ + 'kln' => 'KAL ', /* Kalenjin */ + 'km' => 'KHM ', /* Central Khmer */ + 'kmb' => 'MBN ', /* [North] Mbundu */ + 'kmw' => 'KMO ', /* Komo (Democratic Republic of Congo) */ + 'kn' => 'KAN ', /* Kannada */ + 'ko' => 'KOR ', /* Korean */ + 'koi' => 'KOP ', /* Komi-Permyak */ + 'kok' => 'KOK ', /* Konkani */ + 'kpe' => 'KPL ', /* Kpelle */ + 'kpv' => 'KOZ ', /* Komi-Zyrian */ + 'kpy' => 'KYK ', /* Koryak */ + 'kqy' => 'KRT ', /* Koorete */ + 'kr' => 'KNR ', /* Kanuri */ + 'kri' => 'KRI ', /* Krio */ + 'krl' => 'KRL ', /* Karelian */ + 'kru' => 'KUU ', /* Kurukh */ + 'ks' => 'KSH ', /* Kashmiri */ + 'ku' => 'KUR ', /* Kurdish */ + 'kum' => 'KUM ', /* Kumyk */ + 'kvd' => 'KUI ', /* Kui (Indonesia) */ + 'kxc' => 'KMS ', /* Komso */ + 'kxu' => 'KUI ', /* Kui (India) */ + 'ky' => 'KIR ', /* Kirghiz */ + 'la' => 'LAT ', /* Latin */ + 'lad' => 'JUD ', /* Ladino */ + 'lb' => 'LTZ ', /* Luxembourgish */ + 'lbe' => 'LAK ', /* Lak */ + 'lbj' => 'LDK ', /* Ladakhi */ + 'lez' => 'LEZ ', /* Lezgi */ + 'lg' => 'LUG ', /* Luganda */ + 'lif' => 'LMB ', /* Limbu */ + 'lld' => 'LAD ', /* Ladin */ + 'lmn' => 'LAM ', /* Lambani */ + 'ln' => 'LIN ', /* Lingala */ + 'lo' => 'LAO ', /* Lao */ + 'lt' => 'LTH ', /* Lithuanian */ + 'lu' => 'LUB ', /* Luba-Katanga */ + 'lua' => 'LUB ', /* Luba-Kasai */ + 'luo' => 'LUO ', /* Luo (Kenya and Tanzania) */ + 'lus' => 'MIZ ', /* Mizo */ + 'luy' => 'LUH ', /* Luhya [macrolanguage] */ + 'lv' => 'LVI ', /* Latvian */ + 'lzz' => 'LAZ ', /* Laz */ + 'mai' => 'MTH ', /* Maithili */ + 'mdc' => 'MLE ', /* Male (Papua New Guinea) */ + 'mdf' => 'MOK ', /* Moksha */ + 'mdy' => 'MLE ', /* Male (Ethiopia) */ + 'men' => 'MDE ', /* Mende (Sierra Leone) */ + 'mg' => 'MLG ', /* Malagasy */ + 'mhr' => 'LMA ', /* Low Mari */ + 'mi' => 'MRI ', /* Maori */ + 'mk' => 'MKD ', /* Macedonian */ + 'ml' => 'MLR ', /* Malayalam reformed (MAL is Malayalam Traditional) */ + 'mn' => 'MNG ', /* Mongolian */ + 'mnc' => 'MCH ', /* Manchu */ + 'mni' => 'MNI ', /* Manipuri */ + 'mnk' => 'MND ', /* Mandinka */ + 'mns' => 'MAN ', /* Mansi */ + 'mnw' => 'MON ', /* Mon */ + 'mo' => 'MOL ', /* Moldavian */ + 'moh' => 'MOH ', /* Mohawk */ + 'mpe' => 'MAJ ', /* Majang */ + 'mr' => 'MAR ', /* Marathi */ + 'mrj' => 'HMA ', /* High Mari */ + 'ms' => 'MLY ', /* Malay */ + 'mt' => 'MTS ', /* Maltese */ + 'mwr' => 'MAW ', /* Marwari */ + 'my' => 'BRM ', /* Burmese */ + 'mym' => 'MEN ', /* Me'en */ + 'myv' => 'ERZ ', /* Erzya */ + 'nag' => 'NAG ', /* Naga-Assamese */ + 'nb' => 'NOR ', /* Norwegian Bokmål */ + 'nco' => 'SIB ', /* Sibe */ + 'nd' => 'NDB ', /* [North] Ndebele */ + 'ne' => 'NEP ', /* Nepali */ + 'new' => 'NEW ', /* Newari */ + 'ng' => 'NDG ', /* Ndonga */ + 'ngl' => 'LMW ', /* Lomwe */ + 'niu' => 'NIU ', /* Niuean */ + 'niv' => 'GIL ', /* Gilyak */ + 'nl' => 'NLD ', /* Dutch */ + 'nn' => 'NYN ', /* Norwegian Nynorsk */ + 'no' => 'NOR ', /* Norwegian (deprecated) */ + 'nod' => 'NTA ', /* Northern Tai */ + 'nog' => 'NOG ', /* Nogai */ + 'nqo' => 'NKO ', /* N'Ko */ + 'nr' => 'NDB ', /* [South] Ndebele */ + 'nsk' => 'NAS ', /* Naskapi */ + 'nso' => 'SOT ', /* [Northern] Sotho */ + 'ny' => 'CHI ', /* Nyanja */ + 'nyn' => 'NKL ', /* Nkole */ + 'oc' => 'OCI ', /* Occitan (post 1500) */ + 'oj' => 'OJB ', /* Ojibwa */ + 'ojs' => 'OCR ', /* Oji-Cree */ + 'om' => 'ORO ', /* Oromo */ + 'or' => 'ORI ', /* Oriya */ + 'os' => 'OSS ', /* Ossetian */ + 'pa' => 'PAN ', /* Panjabi */ + 'pce' => 'PLG ', /* [Ruching] Palaung */ + 'pi' => 'PAL ', /* Pali */ + 'pl' => 'PLK ', /* Polish */ + 'pll' => 'PLG ', /* [Shwe] Palaung */ + 'plp' => 'PAP ', /* Palpa */ + 'prs' => 'DRI ', /* Dari */ + 'ps' => 'PAS ', /* Pushto */ + 'pt' => 'PTG ', /* Portuguese */ + 'raj' => 'RAJ ', /* Rajasthani */ + 'rbb' => 'PLG ', /* [Rumai] Palaung */ + 'ria' => 'RIA ', /* Riang (India) */ + 'ril' => 'RIA ', /* Riang (Myanmar) */ + 'rki' => 'ARK ', /* Arakanese */ + 'rm' => 'RMS ', /* Rhaeto-Romanic */ + 'ro' => 'ROM ', /* Romanian */ + 'rom' => 'ROY ', /* Romany */ + 'ru' => 'RUS ', /* Russian */ + 'rue' => 'RSY ', /* Rusyn */ + 'rw' => 'RUA ', /* Ruanda */ + 'sa' => 'SAN ', /* Sanskrit */ + 'sah' => 'YAK ', /* Yakut */ + 'sat' => 'SAT ', /* Santali */ + 'sck' => 'SAD ', /* Sadri */ + 'scs' => 'SLA ', /* [North] Slavey */ + 'sd' => 'SND ', /* Sindhi */ + 'se' => 'NSM ', /* Northern Sami */ + 'seh' => 'SNA ', /* Sena */ + 'sel' => 'SEL ', /* Selkup */ + 'sg' => 'SGO ', /* Sango */ + 'shn' => 'SHN ', /* Shan */ + 'si' => 'SNH ', /* Sinhala */ + 'sid' => 'SID ', /* Sidamo */ + 'sjd' => 'KSM ', /* Kildin Sami */ + 'sk' => 'SKY ', /* Slovak */ + 'skr' => 'SRK ', /* Seraiki */ + 'sl' => 'SLV ', /* Slovenian */ + 'sm' => 'SMO ', /* Samoan */ + 'sma' => 'SSM ', /* Southern Sami */ + 'smj' => 'LSM ', /* Lule Sami */ + 'smn' => 'ISM ', /* Inari Sami */ + 'sms' => 'SKS ', /* Skolt Sami */ + 'snk' => 'SNK ', /* Soninke */ + 'so' => 'SML ', /* Somali */ + 'sq' => 'SQI ', /* Albanian */ + 'sr' => 'SRB ', /* Serbian */ + 'srr' => 'SRR ', /* Serer */ + 'ss' => 'SWZ ', /* Swazi */ + 'st' => 'SOT ', /* [Southern] Sotho */ + 'suq' => 'SUR ', /* Suri */ + 'sv' => 'SVE ', /* Swedish */ + 'sva' => 'SVA ', /* Svan */ + 'sw' => 'SWK ', /* Swahili */ + 'swb' => 'CMR ', /* Comorian */ + 'syr' => 'SYR ', /* Syriac */ + 'ta' => 'TAM ', /* Tamil */ + 'tab' => 'TAB ', /* Tabasaran */ + 'tcy' => 'TUL ', /* Tulu */ + 'te' => 'TEL ', /* Telugu */ + 'tem' => 'TMN ', /* Temne */ + 'tg' => 'TAJ ', /* Tajik */ + 'th' => 'THA ', /* Thai */ + 'ti' => 'TGY ', /* Tigrinya */ + 'tig' => 'TGR ', /* Tigre */ + 'tk' => 'TKM ', /* Turkmen */ + 'tn' => 'TNA ', /* Tswana */ + 'to' => 'TGN ', /* Tonga (Tonga Islands) */ + 'tr' => 'TRK ', /* Turkish */ + 'tru' => 'TUA ', /* Turoyo Aramaic */ + 'ts' => 'TSG ', /* Tsonga */ + 'tt' => 'TAT ', /* Tatar */ + 'tw' => 'TWI ', /* Twi */ + 'ty' => 'THT ', /* Tahitian */ + 'tyv' => 'TUV ', /* Tuvin */ + 'udm' => 'UDM ', /* Udmurt */ + 'ug' => 'UYG ', /* Uighur */ + 'uk' => 'UKR ', /* Ukrainian */ + 'umb' => 'MBN ', /* [South] Mbundu */ + 'unr' => 'MUN ', /* Mundari */ + 'ur' => 'URD ', /* Urdu */ + 'uz' => 'UZB ', /* Uzbek */ + 've' => 'VEN ', /* Venda */ + 'vi' => 'VIT ', /* Vietnamese */ + 'vmw' => 'MAK ', /* Makua */ + 'wbm' => 'WA ', /* Wa */ + 'wbr' => 'WAG ', /* Wagdi */ + 'wo' => 'WLF ', /* Wolof */ + 'xal' => 'KLM ', /* Kalmyk */ + 'xh' => 'XHS ', /* Xhosa */ + 'xom' => 'KMO ', /* Komo (Sudan) */ + 'xsl' => 'SSL ', /* South Slavey */ + 'yi' => 'JII ', /* Yiddish */ + 'yid' => 'JII ', /* Yiddish */ + 'yo' => 'YBA ', /* Yoruba */ + 'yso' => 'NIS ', /* Nisi (China) */ + 'zne' => 'ZND ', /* Zande */ + 'zu' => 'ZUL ', /* Zulu */ + 'zh-cn' => 'ZHS ', /* Chinese (China) */ + 'zh-hk' => 'ZHH ', /* Chinese (Hong Kong) */ + 'zh-mo' => 'ZHT ', /* Chinese (Macao) */ + 'zh-sg' => 'ZHS ', /* Chinese (Singapore) */ + 'zh-tw' => 'ZHT ', /* Chinese (Taiwan) */ + ]; + + // hb-unicode.h + const UNICODE_GENERAL_CATEGORY_CONTROL = 0; /* Cc */ + const UNICODE_GENERAL_CATEGORY_FORMAT = 1; /* Cf */ + const UNICODE_GENERAL_CATEGORY_UNASSIGNED = 2; /* Cn */ + const UNICODE_GENERAL_CATEGORY_PRIVATE_USE = 3; /* Co */ + const UNICODE_GENERAL_CATEGORY_SURROGATE = 4; /* Cs */ + const UNICODE_GENERAL_CATEGORY_LOWERCASE_LETTER = 5; /* Ll */ + const UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER = 6; /* Lm */ + const UNICODE_GENERAL_CATEGORY_OTHER_LETTER = 7; /* Lo */ + const UNICODE_GENERAL_CATEGORY_TITLECASE_LETTER = 8; /* Lt */ + const UNICODE_GENERAL_CATEGORY_UPPERCASE_LETTER = 9; /* Lu */ + const UNICODE_GENERAL_CATEGORY_SPACING_MARK = 10; /* Mc */ + const UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK = 11; /* Me */ + const UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK = 12; /* Mn */ + const UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER = 13; /* Nd */ + const UNICODE_GENERAL_CATEGORY_LETTER_NUMBER = 14; /* Nl */ + const UNICODE_GENERAL_CATEGORY_OTHER_NUMBER = 15; /* No */ + const UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION = 16; /* Pc */ + const UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION = 17; /* Pd */ + const UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION = 18; /* Pe */ + const UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION = 19; /* Pf */ + const UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION = 20; /* Pi */ + const UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION = 21; /* Po */ + const UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION = 22; /* Ps */ + const UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL = 23; /* Sc */ + const UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL = 24; /* Sk */ + const UNICODE_GENERAL_CATEGORY_MATH_SYMBOL = 25; /* Sm */ + const UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL = 26; /* So */ + const UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR = 27; /* Zl */ + const UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR = 28; /* Zp */ + const UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR = 29; /* Zs */ + + function general_category_is_mark($gen_cat) + { + return $gen_cat == self::UNICODE_GENERAL_CATEGORY_SPACING_MARK || $gen_cat == self::UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK || + $gen_cat == self::UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK; + // define UNICODE_GENERAL_CATEGORY_IS_MARK(gen_cat) + //if (FLAG(gen_cat) & (FLAG(UNICODE_GENERAL_CATEGORY_SPACING_MARK) | FLAG(UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK) | FLAG(UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK))) { return true; } + } + + const BIDI_CLASS_L = 0; + const BIDI_CLASS_LRE = 1; + const BIDI_CLASS_LRO = 2; + const BIDI_CLASS_R = 3; + const BIDI_CLASS_AL = 4; + const BIDI_CLASS_RLE = 5; + const BIDI_CLASS_RLO = 6; + const BIDI_CLASS_PDF = 7; + const BIDI_CLASS_EN = 8; + const BIDI_CLASS_ES = 9; + const BIDI_CLASS_ET = 10; + const BIDI_CLASS_AN = 11; + const BIDI_CLASS_CS = 12; + const BIDI_CLASS_NSM = 13; + const BIDI_CLASS_BN = 14; + const BIDI_CLASS_B = 15; + const BIDI_CLASS_S = 16; + const BIDI_CLASS_WS = 17; + const BIDI_CLASS_ON = 18; + + // UNIDATA_VERSION 6.2.0 + /* a list of unique database records */ + /* struct { + category; + combining; + bidi_class; + mirrored; + east_asian_width; + normalization_check; + script; + } + */ + private static $ucd_records = [ + [2, 0, 18, 0, 5, 0, 102], + [0, 0, 14, 0, 5, 0, 0], + [0, 0, 16, 0, 5, 0, 0], + [0, 0, 15, 0, 5, 0, 0], + [0, 0, 17, 0, 5, 0, 0], + [29, 0, 17, 0, 3, 0, 0], + [21, 0, 18, 0, 3, 0, 0], + [21, 0, 10, 0, 3, 0, 0], + [23, 0, 10, 0, 3, 0, 0], + [22, 0, 18, 1, 3, 0, 0], + [18, 0, 18, 1, 3, 0, 0], + [25, 0, 9, 0, 3, 0, 0], + [21, 0, 12, 0, 3, 0, 0], + [17, 0, 9, 0, 3, 0, 0], + [13, 0, 8, 0, 3, 0, 0], + [25, 0, 18, 1, 3, 0, 0], + [25, 0, 18, 0, 3, 0, 0], + [9, 0, 0, 0, 3, 0, 1], + [24, 0, 18, 0, 3, 0, 0], + [16, 0, 18, 0, 3, 0, 0], + [5, 0, 0, 0, 3, 0, 1], + [29, 0, 12, 0, 5, 0, 0], + [21, 0, 18, 0, 4, 0, 0], + [23, 0, 10, 0, 4, 0, 0], + [26, 0, 18, 0, 3, 0, 0], + [24, 0, 18, 0, 4, 0, 0], + [26, 0, 18, 0, 5, 0, 0], + [7, 0, 0, 0, 4, 0, 1], + [20, 0, 18, 1, 5, 0, 0], + [1, 0, 14, 0, 4, 0, 0], + [26, 0, 18, 0, 4, 0, 0], + [26, 0, 10, 0, 4, 0, 0], + [25, 0, 10, 0, 4, 0, 0], + [15, 0, 8, 0, 4, 0, 0], + [5, 0, 0, 0, 5, 0, 0], + [19, 0, 18, 1, 5, 0, 0], + [15, 0, 18, 0, 4, 0, 0], + [9, 0, 0, 0, 5, 0, 1], + [9, 0, 0, 0, 4, 0, 1], + [25, 0, 18, 0, 4, 0, 0], + [5, 0, 0, 0, 4, 0, 1], + [5, 0, 0, 0, 5, 0, 1], + [7, 0, 0, 0, 5, 0, 1], + [8, 0, 0, 0, 5, 0, 1], + [6, 0, 0, 0, 5, 0, 1], + [6, 0, 18, 0, 5, 0, 0], + [6, 0, 0, 0, 5, 0, 0], + [24, 0, 18, 0, 5, 0, 0], + [6, 0, 18, 0, 4, 0, 0], + [6, 0, 0, 0, 4, 0, 0], + [24, 0, 18, 0, 5, 0, 34], + [12, 230, 13, 0, 4, 0, 40], + [12, 232, 13, 0, 4, 0, 40], + [12, 220, 13, 0, 4, 0, 40], + [12, 216, 13, 0, 4, 0, 40], + [12, 202, 13, 0, 4, 0, 40], + [12, 1, 13, 0, 4, 0, 40], + [12, 240, 13, 0, 4, 0, 40], + [12, 0, 13, 0, 4, 0, 40], + [12, 233, 13, 0, 4, 0, 40], + [12, 234, 13, 0, 4, 0, 40], + [9, 0, 0, 0, 5, 0, 2], + [5, 0, 0, 0, 5, 0, 2], + [24, 0, 18, 0, 5, 0, 2], + [2, 0, 18, 0, 5, 0, 102], + [6, 0, 0, 0, 5, 0, 2], + [21, 0, 18, 0, 5, 0, 0], + [9, 0, 0, 0, 4, 0, 2], + [5, 0, 0, 0, 4, 0, 2], + [9, 0, 0, 0, 5, 0, 54], + [5, 0, 0, 0, 5, 0, 54], + [25, 0, 18, 0, 5, 0, 2], + [9, 0, 0, 0, 5, 0, 3], + [9, 0, 0, 0, 4, 0, 3], + [5, 0, 0, 0, 4, 0, 3], + [5, 0, 0, 0, 5, 0, 3], + [26, 0, 0, 0, 5, 0, 3], + [12, 230, 13, 0, 5, 0, 3], + [12, 230, 13, 0, 5, 0, 40], + [11, 0, 13, 0, 5, 0, 3], + [9, 0, 0, 0, 5, 0, 4], + [6, 0, 0, 0, 5, 0, 4], + [21, 0, 0, 0, 5, 0, 4], + [5, 0, 0, 0, 5, 0, 4], + [21, 0, 0, 0, 5, 0, 0], + [17, 0, 18, 0, 5, 0, 4], + [23, 0, 10, 0, 5, 0, 4], + [12, 220, 13, 0, 5, 0, 5], + [12, 230, 13, 0, 5, 0, 5], + [12, 222, 13, 0, 5, 0, 5], + [12, 228, 13, 0, 5, 0, 5], + [12, 10, 13, 0, 5, 0, 5], + [12, 11, 13, 0, 5, 0, 5], + [12, 12, 13, 0, 5, 0, 5], + [12, 13, 13, 0, 5, 0, 5], + [12, 14, 13, 0, 5, 0, 5], + [12, 15, 13, 0, 5, 0, 5], + [12, 16, 13, 0, 5, 0, 5], + [12, 17, 13, 0, 5, 0, 5], + [12, 18, 13, 0, 5, 0, 5], + [12, 19, 13, 0, 5, 0, 5], + [12, 20, 13, 0, 5, 0, 5], + [12, 21, 13, 0, 5, 0, 5], + [12, 22, 13, 0, 5, 0, 5], + [17, 0, 3, 0, 5, 0, 5], + [12, 23, 13, 0, 5, 0, 5], + [21, 0, 3, 0, 5, 0, 5], + [12, 24, 13, 0, 5, 0, 5], + [12, 25, 13, 0, 5, 0, 5], + [7, 0, 3, 0, 5, 0, 5], + [1, 0, 11, 0, 5, 0, 6], + [25, 0, 18, 0, 5, 0, 6], + [25, 0, 4, 0, 5, 0, 6], + [21, 0, 10, 0, 5, 0, 6], + [23, 0, 4, 0, 5, 0, 6], + [21, 0, 12, 0, 5, 0, 0], + [21, 0, 4, 0, 5, 0, 6], + [26, 0, 18, 0, 5, 0, 6], + [12, 230, 13, 0, 5, 0, 6], + [12, 30, 13, 0, 5, 0, 6], + [12, 31, 13, 0, 5, 0, 6], + [12, 32, 13, 0, 5, 0, 6], + [21, 0, 4, 0, 5, 0, 0], + [7, 0, 4, 0, 5, 0, 6], + [6, 0, 4, 0, 5, 0, 0], + [12, 27, 13, 0, 5, 0, 40], + [12, 28, 13, 0, 5, 0, 40], + [12, 29, 13, 0, 5, 0, 40], + [12, 30, 13, 0, 5, 0, 40], + [12, 31, 13, 0, 5, 0, 40], + [12, 32, 13, 0, 5, 0, 40], + [12, 33, 13, 0, 5, 0, 40], + [12, 34, 13, 0, 5, 0, 40], + [12, 220, 13, 0, 5, 0, 40], + [12, 220, 13, 0, 5, 0, 6], + [13, 0, 11, 0, 5, 0, 0], + [21, 0, 11, 0, 5, 0, 6], + [12, 35, 13, 0, 5, 0, 40], + [1, 0, 11, 0, 5, 0, 0], + [6, 0, 4, 0, 5, 0, 6], + [13, 0, 8, 0, 5, 0, 6], + [26, 0, 4, 0, 5, 0, 6], + [21, 0, 4, 0, 5, 0, 7], + [1, 0, 4, 0, 5, 0, 7], + [7, 0, 4, 0, 5, 0, 7], + [12, 36, 13, 0, 5, 0, 7], + [12, 230, 13, 0, 5, 0, 7], + [12, 220, 13, 0, 5, 0, 7], + [7, 0, 4, 0, 5, 0, 8], + [12, 0, 13, 0, 5, 0, 8], + [13, 0, 3, 0, 5, 0, 65], + [7, 0, 3, 0, 5, 0, 65], + [12, 230, 13, 0, 5, 0, 65], + [12, 220, 13, 0, 5, 0, 65], + [6, 0, 3, 0, 5, 0, 65], + [26, 0, 18, 0, 5, 0, 65], + [21, 0, 18, 0, 5, 0, 65], + [7, 0, 3, 0, 5, 0, 81], + [12, 230, 13, 0, 5, 0, 81], + [6, 0, 3, 0, 5, 0, 81], + [21, 0, 3, 0, 5, 0, 81], + [7, 0, 3, 0, 5, 0, 94], + [12, 220, 13, 0, 5, 0, 94], + [21, 0, 3, 0, 5, 0, 94], + [12, 27, 13, 0, 5, 0, 6], + [12, 28, 13, 0, 5, 0, 6], + [12, 29, 13, 0, 5, 0, 6], + [12, 0, 13, 0, 5, 0, 9], + [10, 0, 0, 0, 5, 0, 9], + [7, 0, 0, 0, 5, 0, 9], + [12, 7, 13, 0, 5, 0, 9], + [12, 9, 13, 0, 5, 0, 9], + [12, 230, 13, 0, 5, 0, 9], + [13, 0, 0, 0, 5, 0, 9], + [21, 0, 0, 0, 5, 0, 9], + [6, 0, 0, 0, 5, 0, 9], + [12, 0, 13, 0, 5, 0, 10], + [10, 0, 0, 0, 5, 0, 10], + [7, 0, 0, 0, 5, 0, 10], + [12, 7, 13, 0, 5, 0, 10], + [12, 9, 13, 0, 5, 0, 10], + [13, 0, 0, 0, 5, 0, 10], + [23, 0, 10, 0, 5, 0, 10], + [15, 0, 0, 0, 5, 0, 10], + [26, 0, 0, 0, 5, 0, 10], + [12, 0, 13, 0, 5, 0, 11], + [10, 0, 0, 0, 5, 0, 11], + [7, 0, 0, 0, 5, 0, 11], + [12, 7, 13, 0, 5, 0, 11], + [12, 9, 13, 0, 5, 0, 11], + [13, 0, 0, 0, 5, 0, 11], + [12, 0, 13, 0, 5, 0, 12], + [10, 0, 0, 0, 5, 0, 12], + [7, 0, 0, 0, 5, 0, 12], + [12, 7, 13, 0, 5, 0, 12], + [12, 9, 13, 0, 5, 0, 12], + [13, 0, 0, 0, 5, 0, 12], + [21, 0, 0, 0, 5, 0, 12], + [23, 0, 10, 0, 5, 0, 12], + [12, 0, 13, 0, 5, 0, 13], + [10, 0, 0, 0, 5, 0, 13], + [7, 0, 0, 0, 5, 0, 13], + [12, 7, 13, 0, 5, 0, 13], + [12, 9, 13, 0, 5, 0, 13], + [13, 0, 0, 0, 5, 0, 13], + [26, 0, 0, 0, 5, 0, 13], + [15, 0, 0, 0, 5, 0, 13], + [12, 0, 13, 0, 5, 0, 14], + [7, 0, 0, 0, 5, 0, 14], + [10, 0, 0, 0, 5, 0, 14], + [12, 9, 13, 0, 5, 0, 14], + [13, 0, 0, 0, 5, 0, 14], + [15, 0, 0, 0, 5, 0, 14], + [26, 0, 18, 0, 5, 0, 14], + [23, 0, 10, 0, 5, 0, 14], + [10, 0, 0, 0, 5, 0, 15], + [7, 0, 0, 0, 5, 0, 15], + [12, 0, 13, 0, 5, 0, 15], + [12, 9, 13, 0, 5, 0, 15], + [12, 84, 13, 0, 5, 0, 15], + [12, 91, 13, 0, 5, 0, 15], + [13, 0, 0, 0, 5, 0, 15], + [15, 0, 18, 0, 5, 0, 15], + [26, 0, 0, 0, 5, 0, 15], + [10, 0, 0, 0, 5, 0, 16], + [7, 0, 0, 0, 5, 0, 16], + [12, 7, 13, 0, 5, 0, 16], + [12, 0, 0, 0, 5, 0, 16], + [12, 0, 13, 0, 5, 0, 16], + [12, 9, 13, 0, 5, 0, 16], + [13, 0, 0, 0, 5, 0, 16], + [10, 0, 0, 0, 5, 0, 17], + [7, 0, 0, 0, 5, 0, 17], + [12, 0, 13, 0, 5, 0, 17], + [12, 9, 13, 0, 5, 0, 17], + [13, 0, 0, 0, 5, 0, 17], + [15, 0, 0, 0, 5, 0, 17], + [26, 0, 0, 0, 5, 0, 17], + [10, 0, 0, 0, 5, 0, 18], + [7, 0, 0, 0, 5, 0, 18], + [12, 9, 13, 0, 5, 0, 18], + [12, 0, 13, 0, 5, 0, 18], + [21, 0, 0, 0, 5, 0, 18], + [7, 0, 0, 0, 5, 0, 19], + [12, 0, 13, 0, 5, 0, 19], + [12, 103, 13, 0, 5, 0, 19], + [12, 9, 13, 0, 5, 0, 19], + [23, 0, 10, 0, 5, 0, 0], + [6, 0, 0, 0, 5, 0, 19], + [12, 107, 13, 0, 5, 0, 19], + [21, 0, 0, 0, 5, 0, 19], + [13, 0, 0, 0, 5, 0, 19], + [7, 0, 0, 0, 5, 0, 20], + [12, 0, 13, 0, 5, 0, 20], + [12, 118, 13, 0, 5, 0, 20], + [6, 0, 0, 0, 5, 0, 20], + [12, 122, 13, 0, 5, 0, 20], + [13, 0, 0, 0, 5, 0, 20], + [7, 0, 0, 0, 5, 0, 21], + [26, 0, 0, 0, 5, 0, 21], + [21, 0, 0, 0, 5, 0, 21], + [12, 220, 13, 0, 5, 0, 21], + [13, 0, 0, 0, 5, 0, 21], + [15, 0, 0, 0, 5, 0, 21], + [12, 216, 13, 0, 5, 0, 21], + [22, 0, 18, 1, 5, 0, 21], + [18, 0, 18, 1, 5, 0, 21], + [10, 0, 0, 0, 5, 0, 21], + [12, 129, 13, 0, 5, 0, 21], + [12, 130, 13, 0, 5, 0, 21], + [12, 0, 13, 0, 5, 0, 21], + [12, 132, 13, 0, 5, 0, 21], + [12, 230, 13, 0, 5, 0, 21], + [12, 9, 13, 0, 5, 0, 21], + [26, 0, 0, 0, 5, 0, 0], + [7, 0, 0, 0, 5, 0, 22], + [10, 0, 0, 0, 5, 0, 22], + [12, 0, 13, 0, 5, 0, 22], + [12, 7, 13, 0, 5, 0, 22], + [12, 9, 13, 0, 5, 0, 22], + [13, 0, 0, 0, 5, 0, 22], + [21, 0, 0, 0, 5, 0, 22], + [12, 220, 13, 0, 5, 0, 22], + [26, 0, 0, 0, 5, 0, 22], + [9, 0, 0, 0, 5, 0, 23], + [7, 0, 0, 0, 5, 0, 23], + [6, 0, 0, 0, 5, 0, 23], + [7, 0, 0, 0, 2, 0, 24], + [7, 0, 0, 0, 5, 0, 24], + [7, 0, 0, 0, 5, 0, 25], + [12, 230, 13, 0, 5, 0, 25], + [21, 0, 0, 0, 5, 0, 25], + [15, 0, 0, 0, 5, 0, 25], + [26, 0, 18, 0, 5, 0, 25], + [7, 0, 0, 0, 5, 0, 26], + [17, 0, 18, 0, 5, 0, 27], + [7, 0, 0, 0, 5, 0, 27], + [21, 0, 0, 0, 5, 0, 27], + [29, 0, 17, 0, 5, 0, 28], + [7, 0, 0, 0, 5, 0, 28], + [22, 0, 18, 1, 5, 0, 28], + [18, 0, 18, 1, 5, 0, 28], + [7, 0, 0, 0, 5, 0, 29], + [14, 0, 0, 0, 5, 0, 29], + [7, 0, 0, 0, 5, 0, 41], + [12, 0, 13, 0, 5, 0, 41], + [12, 9, 13, 0, 5, 0, 41], + [7, 0, 0, 0, 5, 0, 42], + [12, 0, 13, 0, 5, 0, 42], + [12, 9, 13, 0, 5, 0, 42], + [7, 0, 0, 0, 5, 0, 43], + [12, 0, 13, 0, 5, 0, 43], + [7, 0, 0, 0, 5, 0, 44], + [12, 0, 13, 0, 5, 0, 44], + [7, 0, 0, 0, 5, 0, 30], + [12, 0, 13, 0, 5, 0, 30], + [10, 0, 0, 0, 5, 0, 30], + [12, 9, 13, 0, 5, 0, 30], + [21, 0, 0, 0, 5, 0, 30], + [6, 0, 0, 0, 5, 0, 30], + [23, 0, 10, 0, 5, 0, 30], + [12, 230, 13, 0, 5, 0, 30], + [13, 0, 0, 0, 5, 0, 30], + [15, 0, 18, 0, 5, 0, 30], + [21, 0, 18, 0, 5, 0, 31], + [17, 0, 18, 0, 5, 0, 31], + [12, 0, 13, 0, 5, 0, 31], + [29, 0, 17, 0, 5, 0, 31], + [13, 0, 0, 0, 5, 0, 31], + [7, 0, 0, 0, 5, 0, 31], + [6, 0, 0, 0, 5, 0, 31], + [12, 228, 13, 0, 5, 0, 31], + [7, 0, 0, 0, 5, 0, 45], + [12, 0, 13, 0, 5, 0, 45], + [10, 0, 0, 0, 5, 0, 45], + [12, 222, 13, 0, 5, 0, 45], + [12, 230, 13, 0, 5, 0, 45], + [12, 220, 13, 0, 5, 0, 45], + [26, 0, 18, 0, 5, 0, 45], + [21, 0, 18, 0, 5, 0, 45], + [13, 0, 0, 0, 5, 0, 45], + [7, 0, 0, 0, 5, 0, 46], + [7, 0, 0, 0, 5, 0, 55], + [10, 0, 0, 0, 5, 0, 55], + [13, 0, 0, 0, 5, 0, 55], + [15, 0, 0, 0, 5, 0, 55], + [26, 0, 18, 0, 5, 0, 55], + [26, 0, 18, 0, 5, 0, 30], + [7, 0, 0, 0, 5, 0, 53], + [12, 230, 13, 0, 5, 0, 53], + [12, 220, 13, 0, 5, 0, 53], + [10, 0, 0, 0, 5, 0, 53], + [21, 0, 0, 0, 5, 0, 53], + [7, 0, 0, 0, 5, 0, 77], + [10, 0, 0, 0, 5, 0, 77], + [12, 0, 13, 0, 5, 0, 77], + [12, 9, 13, 0, 5, 0, 77], + [12, 230, 13, 0, 5, 0, 77], + [12, 220, 13, 0, 5, 0, 77], + [13, 0, 0, 0, 5, 0, 77], + [21, 0, 0, 0, 5, 0, 77], + [6, 0, 0, 0, 5, 0, 77], + [12, 0, 13, 0, 5, 0, 61], + [10, 0, 0, 0, 5, 0, 61], + [7, 0, 0, 0, 5, 0, 61], + [12, 7, 13, 0, 5, 0, 61], + [10, 9, 0, 0, 5, 0, 61], + [13, 0, 0, 0, 5, 0, 61], + [21, 0, 0, 0, 5, 0, 61], + [26, 0, 0, 0, 5, 0, 61], + [12, 230, 13, 0, 5, 0, 61], + [12, 220, 13, 0, 5, 0, 61], + [12, 0, 13, 0, 5, 0, 66], + [10, 0, 0, 0, 5, 0, 66], + [7, 0, 0, 0, 5, 0, 66], + [10, 9, 0, 0, 5, 0, 66], + [12, 9, 13, 0, 5, 0, 66], + [13, 0, 0, 0, 5, 0, 66], + [7, 0, 0, 0, 5, 0, 92], + [12, 7, 13, 0, 5, 0, 92], + [10, 0, 0, 0, 5, 0, 92], + [12, 0, 13, 0, 5, 0, 92], + [10, 9, 0, 0, 5, 0, 92], + [21, 0, 0, 0, 5, 0, 92], + [7, 0, 0, 0, 5, 0, 67], + [10, 0, 0, 0, 5, 0, 67], + [12, 0, 13, 0, 5, 0, 67], + [12, 7, 13, 0, 5, 0, 67], + [21, 0, 0, 0, 5, 0, 67], + [13, 0, 0, 0, 5, 0, 67], + [13, 0, 0, 0, 5, 0, 68], + [7, 0, 0, 0, 5, 0, 68], + [6, 0, 0, 0, 5, 0, 68], + [21, 0, 0, 0, 5, 0, 68], + [21, 0, 0, 0, 5, 0, 66], + [12, 1, 13, 0, 5, 0, 40], + [10, 0, 0, 0, 5, 0, 0], + [7, 0, 0, 0, 5, 0, 0], + [6, 0, 0, 0, 5, 0, 3], + [12, 234, 13, 0, 5, 0, 40], + [12, 214, 13, 0, 5, 0, 40], + [12, 202, 13, 0, 5, 0, 40], + [12, 233, 13, 0, 5, 0, 40], + [8, 0, 0, 0, 5, 0, 2], + [29, 0, 17, 0, 5, 0, 0], + [1, 0, 14, 0, 5, 0, 0], + [1, 0, 14, 0, 5, 0, 40], + [1, 0, 0, 0, 5, 0, 0], + [1, 0, 3, 0, 5, 0, 0], + [17, 0, 18, 0, 4, 0, 0], + [17, 0, 18, 0, 5, 0, 0], + [20, 0, 18, 0, 4, 0, 0], + [19, 0, 18, 0, 4, 0, 0], + [22, 0, 18, 0, 5, 0, 0], + [20, 0, 18, 0, 5, 0, 0], + [27, 0, 17, 0, 5, 0, 0], + [28, 0, 15, 0, 5, 0, 0], + [1, 0, 1, 0, 5, 0, 0], + [1, 0, 5, 0, 5, 0, 0], + [1, 0, 7, 0, 5, 0, 0], + [1, 0, 2, 0, 5, 0, 0], + [1, 0, 6, 0, 5, 0, 0], + [21, 0, 10, 0, 4, 0, 0], + [21, 0, 10, 0, 5, 0, 0], + [16, 0, 18, 0, 5, 0, 0], + [25, 0, 12, 0, 5, 0, 0], + [22, 0, 18, 1, 5, 0, 0], + [18, 0, 18, 1, 5, 0, 0], + [25, 0, 18, 0, 5, 0, 0], + [15, 0, 8, 0, 5, 0, 0], + [25, 0, 9, 0, 5, 0, 0], + [6, 0, 0, 0, 4, 0, 1], + [23, 0, 10, 0, 1, 0, 0], + [11, 0, 13, 0, 5, 0, 40], + [9, 0, 0, 0, 5, 0, 0], + [5, 0, 0, 0, 4, 0, 0], + [26, 0, 10, 0, 5, 0, 0], + [25, 0, 18, 1, 5, 0, 0], + [15, 0, 18, 0, 5, 0, 0], + [14, 0, 0, 0, 4, 0, 1], + [14, 0, 0, 0, 5, 0, 1], + [25, 0, 18, 1, 4, 0, 0], + [25, 0, 10, 0, 5, 0, 0], + [22, 0, 18, 1, 2, 0, 0], + [18, 0, 18, 1, 2, 0, 0], + [26, 0, 0, 0, 4, 0, 0], + [26, 0, 0, 0, 5, 0, 52], + [9, 0, 0, 0, 5, 0, 56], + [5, 0, 0, 0, 5, 0, 56], + [26, 0, 18, 0, 5, 0, 54], + [12, 230, 13, 0, 5, 0, 54], + [21, 0, 18, 0, 5, 0, 54], + [15, 0, 18, 0, 5, 0, 54], + [5, 0, 0, 0, 5, 0, 23], + [7, 0, 0, 0, 5, 0, 57], + [6, 0, 0, 0, 5, 0, 57], + [21, 0, 0, 0, 5, 0, 57], + [12, 9, 13, 0, 5, 0, 57], + [26, 0, 18, 0, 2, 0, 35], + [26, 0, 18, 0, 2, 0, 0], + [29, 0, 17, 0, 0, 0, 0], + [21, 0, 18, 0, 2, 0, 0], + [6, 0, 0, 0, 2, 0, 35], + [7, 0, 0, 0, 2, 0, 0], + [14, 0, 0, 0, 2, 0, 35], + [17, 0, 18, 0, 2, 0, 0], + [22, 0, 18, 0, 2, 0, 0], + [18, 0, 18, 0, 2, 0, 0], + [12, 218, 13, 0, 2, 0, 40], + [12, 228, 13, 0, 2, 0, 40], + [12, 232, 13, 0, 2, 0, 40], + [12, 222, 13, 0, 2, 0, 40], + [10, 224, 0, 0, 2, 0, 24], + [6, 0, 0, 0, 2, 0, 0], + [7, 0, 0, 0, 2, 0, 32], + [12, 8, 13, 0, 2, 0, 40], + [24, 0, 18, 0, 2, 0, 0], + [6, 0, 0, 0, 2, 0, 32], + [7, 0, 0, 0, 2, 0, 33], + [6, 0, 0, 0, 2, 0, 33], + [7, 0, 0, 0, 2, 0, 34], + [26, 0, 0, 0, 2, 0, 0], + [15, 0, 0, 0, 2, 0, 0], + [26, 0, 0, 0, 2, 0, 24], + [26, 0, 18, 0, 2, 0, 24], + [15, 0, 0, 0, 4, 0, 0], + [15, 0, 18, 0, 2, 0, 0], + [26, 0, 0, 0, 2, 0, 33], + [7, 0, 0, 0, 2, 0, 35], + [2, 0, 18, 0, 2, 0, 35], + [2, 0, 18, 0, 2, 0, 102], + [7, 0, 0, 0, 2, 0, 36], + [6, 0, 0, 0, 2, 0, 36], + [26, 0, 18, 0, 2, 0, 36], + [7, 0, 0, 0, 5, 0, 82], + [6, 0, 0, 0, 5, 0, 82], + [21, 0, 0, 0, 5, 0, 82], + [7, 0, 0, 0, 5, 0, 69], + [6, 0, 0, 0, 5, 0, 69], + [21, 0, 18, 0, 5, 0, 69], + [13, 0, 0, 0, 5, 0, 69], + [7, 0, 0, 0, 5, 0, 3], + [21, 0, 18, 0, 5, 0, 3], + [6, 0, 18, 0, 5, 0, 3], + [7, 0, 0, 0, 5, 0, 83], + [14, 0, 0, 0, 5, 0, 83], + [12, 230, 13, 0, 5, 0, 83], + [21, 0, 0, 0, 5, 0, 83], + [24, 0, 0, 0, 5, 0, 0], + [7, 0, 0, 0, 5, 0, 58], + [12, 0, 13, 0, 5, 0, 58], + [12, 9, 13, 0, 5, 0, 58], + [10, 0, 0, 0, 5, 0, 58], + [26, 0, 18, 0, 5, 0, 58], + [15, 0, 0, 0, 5, 0, 0], + [7, 0, 0, 0, 5, 0, 64], + [21, 0, 18, 0, 5, 0, 64], + [10, 0, 0, 0, 5, 0, 70], + [7, 0, 0, 0, 5, 0, 70], + [12, 9, 13, 0, 5, 0, 70], + [21, 0, 0, 0, 5, 0, 70], + [13, 0, 0, 0, 5, 0, 70], + [13, 0, 0, 0, 5, 0, 71], + [7, 0, 0, 0, 5, 0, 71], + [12, 0, 13, 0, 5, 0, 71], + [12, 220, 13, 0, 5, 0, 71], + [21, 0, 0, 0, 5, 0, 71], + [7, 0, 0, 0, 5, 0, 72], + [12, 0, 13, 0, 5, 0, 72], + [10, 0, 0, 0, 5, 0, 72], + [10, 9, 0, 0, 5, 0, 72], + [21, 0, 0, 0, 5, 0, 72], + [12, 0, 13, 0, 5, 0, 84], + [10, 0, 0, 0, 5, 0, 84], + [7, 0, 0, 0, 5, 0, 84], + [12, 7, 13, 0, 5, 0, 84], + [10, 9, 0, 0, 5, 0, 84], + [21, 0, 0, 0, 5, 0, 84], + [6, 0, 0, 0, 5, 0, 84], + [13, 0, 0, 0, 5, 0, 84], + [7, 0, 0, 0, 5, 0, 76], + [12, 0, 13, 0, 5, 0, 76], + [10, 0, 0, 0, 5, 0, 76], + [13, 0, 0, 0, 5, 0, 76], + [21, 0, 0, 0, 5, 0, 76], + [6, 0, 0, 0, 5, 0, 22], + [7, 0, 0, 0, 5, 0, 78], + [12, 230, 13, 0, 5, 0, 78], + [12, 220, 13, 0, 5, 0, 78], + [6, 0, 0, 0, 5, 0, 78], + [21, 0, 0, 0, 5, 0, 78], + [7, 0, 0, 0, 5, 0, 85], + [10, 0, 0, 0, 5, 0, 85], + [12, 0, 13, 0, 5, 0, 85], + [21, 0, 0, 0, 5, 0, 85], + [6, 0, 0, 0, 5, 0, 85], + [12, 9, 13, 0, 5, 0, 85], + [13, 0, 0, 0, 5, 0, 85], + [2, 0, 18, 0, 2, 0, 24], + [4, 0, 0, 0, 5, 0, 102], + [3, 0, 0, 0, 4, 0, 102], + [2, 0, 18, 0, 4, 0, 102], + [12, 26, 13, 0, 5, 0, 5], + [25, 0, 9, 0, 5, 0, 5], + [24, 0, 4, 0, 5, 0, 6], + [18, 0, 18, 0, 5, 0, 0], + [16, 0, 18, 0, 2, 0, 0], + [21, 0, 12, 0, 2, 0, 0], + [21, 0, 10, 0, 2, 0, 0], + [25, 0, 9, 0, 2, 0, 0], + [17, 0, 9, 0, 2, 0, 0], + [25, 0, 18, 1, 2, 0, 0], + [25, 0, 18, 0, 2, 0, 0], + [23, 0, 10, 0, 2, 0, 0], + [21, 0, 18, 0, 0, 0, 0], + [21, 0, 10, 0, 0, 0, 0], + [23, 0, 10, 0, 0, 0, 0], + [22, 0, 18, 1, 0, 0, 0], + [18, 0, 18, 1, 0, 0, 0], + [25, 0, 9, 0, 0, 0, 0], + [21, 0, 12, 0, 0, 0, 0], + [17, 0, 9, 0, 0, 0, 0], + [13, 0, 8, 0, 0, 0, 0], + [25, 0, 18, 1, 0, 0, 0], + [25, 0, 18, 0, 0, 0, 0], + [9, 0, 0, 0, 0, 0, 1], + [24, 0, 18, 0, 0, 0, 0], + [16, 0, 18, 0, 0, 0, 0], + [5, 0, 0, 0, 0, 0, 1], + [21, 0, 18, 0, 1, 0, 0], + [22, 0, 18, 1, 1, 0, 0], + [18, 0, 18, 1, 1, 0, 0], + [7, 0, 0, 0, 1, 0, 33], + [6, 0, 0, 0, 1, 0, 0], + [7, 0, 0, 0, 1, 0, 24], + [26, 0, 18, 0, 0, 0, 0], + [26, 0, 18, 0, 1, 0, 0], + [25, 0, 18, 0, 1, 0, 0], + [1, 0, 18, 0, 5, 0, 0], + [7, 0, 0, 0, 5, 0, 47], + [14, 0, 18, 0, 5, 0, 2], + [15, 0, 18, 0, 5, 0, 2], + [26, 0, 18, 0, 5, 0, 2], + [7, 0, 0, 0, 5, 0, 73], + [7, 0, 0, 0, 5, 0, 74], + [7, 0, 0, 0, 5, 0, 37], + [15, 0, 0, 0, 5, 0, 37], + [7, 0, 0, 0, 5, 0, 38], + [14, 0, 0, 0, 5, 0, 38], + [7, 0, 0, 0, 5, 0, 48], + [21, 0, 0, 0, 5, 0, 48], + [7, 0, 0, 0, 5, 0, 59], + [21, 0, 0, 0, 5, 0, 59], + [14, 0, 0, 0, 5, 0, 59], + [9, 0, 0, 0, 5, 0, 39], + [5, 0, 0, 0, 5, 0, 39], + [7, 0, 0, 0, 5, 0, 49], + [7, 0, 0, 0, 5, 0, 50], + [13, 0, 0, 0, 5, 0, 50], + [7, 0, 3, 0, 5, 0, 51], + [7, 0, 3, 0, 5, 0, 86], + [21, 0, 3, 0, 5, 0, 86], + [15, 0, 3, 0, 5, 0, 86], + [7, 0, 3, 0, 5, 0, 63], + [15, 0, 3, 0, 5, 0, 63], + [21, 0, 18, 0, 5, 0, 63], + [7, 0, 3, 0, 5, 0, 75], + [21, 0, 3, 0, 5, 0, 75], + [7, 0, 3, 0, 5, 0, 97], + [7, 0, 3, 0, 5, 0, 96], + [7, 0, 3, 0, 5, 0, 60], + [12, 0, 13, 0, 5, 0, 60], + [12, 220, 13, 0, 5, 0, 60], + [12, 230, 13, 0, 5, 0, 60], + [12, 1, 13, 0, 5, 0, 60], + [12, 9, 13, 0, 5, 0, 60], + [15, 0, 3, 0, 5, 0, 60], + [21, 0, 3, 0, 5, 0, 60], + [7, 0, 3, 0, 5, 0, 87], + [15, 0, 3, 0, 5, 0, 87], + [21, 0, 3, 0, 5, 0, 87], + [7, 0, 3, 0, 5, 0, 79], + [21, 0, 18, 0, 5, 0, 79], + [7, 0, 3, 0, 5, 0, 88], + [15, 0, 3, 0, 5, 0, 88], + [7, 0, 3, 0, 5, 0, 89], + [15, 0, 3, 0, 5, 0, 89], + [7, 0, 3, 0, 5, 0, 90], + [15, 0, 11, 0, 5, 0, 6], + [10, 0, 0, 0, 5, 0, 93], + [12, 0, 13, 0, 5, 0, 93], + [7, 0, 0, 0, 5, 0, 93], + [12, 9, 13, 0, 5, 0, 93], + [21, 0, 0, 0, 5, 0, 93], + [15, 0, 18, 0, 5, 0, 93], + [13, 0, 0, 0, 5, 0, 93], + [12, 0, 13, 0, 5, 0, 91], + [10, 0, 0, 0, 5, 0, 91], + [7, 0, 0, 0, 5, 0, 91], + [12, 9, 13, 0, 5, 0, 91], + [12, 7, 13, 0, 5, 0, 91], + [21, 0, 0, 0, 5, 0, 91], + [1, 0, 0, 0, 5, 0, 91], + [7, 0, 0, 0, 5, 0, 100], + [13, 0, 0, 0, 5, 0, 100], + [12, 230, 13, 0, 5, 0, 95], + [7, 0, 0, 0, 5, 0, 95], + [12, 0, 13, 0, 5, 0, 95], + [10, 0, 0, 0, 5, 0, 95], + [12, 9, 13, 0, 5, 0, 95], + [13, 0, 0, 0, 5, 0, 95], + [21, 0, 0, 0, 5, 0, 95], + [12, 0, 13, 0, 5, 0, 99], + [10, 0, 0, 0, 5, 0, 99], + [7, 0, 0, 0, 5, 0, 99], + [10, 9, 0, 0, 5, 0, 99], + [21, 0, 0, 0, 5, 0, 99], + [13, 0, 0, 0, 5, 0, 99], + [7, 0, 0, 0, 5, 0, 101], + [12, 0, 13, 0, 5, 0, 101], + [10, 0, 0, 0, 5, 0, 101], + [10, 9, 0, 0, 5, 0, 101], + [12, 7, 13, 0, 5, 0, 101], + [13, 0, 0, 0, 5, 0, 101], + [7, 0, 0, 0, 5, 0, 62], + [14, 0, 0, 0, 5, 0, 62], + [21, 0, 0, 0, 5, 0, 62], + [7, 0, 0, 0, 5, 0, 80], + [7, 0, 0, 0, 5, 0, 98], + [10, 0, 0, 0, 5, 0, 98], + [12, 0, 13, 0, 5, 0, 98], + [6, 0, 0, 0, 5, 0, 98], + [10, 216, 0, 0, 5, 0, 0], + [10, 226, 0, 0, 5, 0, 0], + [12, 230, 13, 0, 5, 0, 2], + [25, 0, 0, 0, 5, 0, 0], + [13, 0, 8, 0, 5, 0, 0], + [26, 0, 0, 0, 2, 0, 32], + ]; + + /* Mirror unicode characters. Bidirectional Algorithm, at http://www.unicode.org/unicode/reports/tr9/ */ + + public static $mirror_pairs = [ + 40 => 41, + 41 => 40, + 60 => 62, + 62 => 60, + 91 => 93, + 93 => 91, + 123 => 125, + 125 => 123, + 171 => 187, + 187 => 171, + 3898 => 3899, + 3899 => 3898, + 3900 => 3901, + 3901 => 3900, + 5787 => 5788, + 5788 => 5787, + 8249 => 8250, + 8250 => 8249, + 8261 => 8262, + 8262 => 8261, + 8317 => 8318, + 8318 => 8317, + 8333 => 8334, + 8334 => 8333, + 8712 => 8715, + 8713 => 8716, + 8714 => 8717, + 8715 => 8712, + 8716 => 8713, + 8717 => 8714, + 8725 => 10741, + 8764 => 8765, + 8765 => 8764, + 8771 => 8909, + 8786 => 8787, + 8787 => 8786, + 8788 => 8789, + 8789 => 8788, + 8804 => 8805, + 8805 => 8804, + 8806 => 8807, + 8807 => 8806, + 8808 => 8809, + 8809 => 8808, + 8810 => 8811, + 8811 => 8810, + 8814 => 8815, + 8815 => 8814, + 8816 => 8817, + 8817 => 8816, + 8818 => 8819, + 8819 => 8818, + 8820 => 8821, + 8821 => 8820, + 8822 => 8823, + 8823 => 8822, + 8824 => 8825, + 8825 => 8824, + 8826 => 8827, + 8827 => 8826, + 8828 => 8829, + 8829 => 8828, + 8830 => 8831, + 8831 => 8830, + 8832 => 8833, + 8833 => 8832, + 8834 => 8835, + 8835 => 8834, + 8836 => 8837, + 8837 => 8836, + 8838 => 8839, + 8839 => 8838, + 8840 => 8841, + 8841 => 8840, + 8842 => 8843, + 8843 => 8842, + 8847 => 8848, + 8848 => 8847, + 8849 => 8850, + 8850 => 8849, + 8856 => 10680, + 8866 => 8867, + 8867 => 8866, + 8870 => 10974, + 8872 => 10980, + 8873 => 10979, + 8875 => 10981, + 8880 => 8881, + 8881 => 8880, + 8882 => 8883, + 8883 => 8882, + 8884 => 8885, + 8885 => 8884, + 8886 => 8887, + 8887 => 8886, + 8905 => 8906, + 8906 => 8905, + 8907 => 8908, + 8908 => 8907, + 8909 => 8771, + 8912 => 8913, + 8913 => 8912, + 8918 => 8919, + 8919 => 8918, + 8920 => 8921, + 8921 => 8920, + 8922 => 8923, + 8923 => 8922, + 8924 => 8925, + 8925 => 8924, + 8926 => 8927, + 8927 => 8926, + 8928 => 8929, + 8929 => 8928, + 8930 => 8931, + 8931 => 8930, + 8932 => 8933, + 8933 => 8932, + 8934 => 8935, + 8935 => 8934, + 8936 => 8937, + 8937 => 8936, + 8938 => 8939, + 8939 => 8938, + 8940 => 8941, + 8941 => 8940, + 8944 => 8945, + 8945 => 8944, + 8946 => 8954, + 8947 => 8955, + 8948 => 8956, + 8950 => 8957, + 8951 => 8958, + 8954 => 8946, + 8955 => 8947, + 8956 => 8948, + 8957 => 8950, + 8958 => 8951, + 8968 => 8969, + 8969 => 8968, + 8970 => 8971, + 8971 => 8970, + 9001 => 9002, + 9002 => 9001, + 10088 => 10089, + 10089 => 10088, + 10090 => 10091, + 10091 => 10090, + 10092 => 10093, + 10093 => 10092, + 10094 => 10095, + 10095 => 10094, + 10096 => 10097, + 10097 => 10096, + 10098 => 10099, + 10099 => 10098, + 10100 => 10101, + 10101 => 10100, + 10179 => 10180, + 10180 => 10179, + 10181 => 10182, + 10182 => 10181, + 10184 => 10185, + 10185 => 10184, + 10187 => 10189, + 10189 => 10187, + 10197 => 10198, + 10198 => 10197, + 10205 => 10206, + 10206 => 10205, + 10210 => 10211, + 10211 => 10210, + 10212 => 10213, + 10213 => 10212, + 10214 => 10215, + 10215 => 10214, + 10216 => 10217, + 10217 => 10216, + 10218 => 10219, + 10219 => 10218, + 10220 => 10221, + 10221 => 10220, + 10222 => 10223, + 10223 => 10222, + 10627 => 10628, + 10628 => 10627, + 10629 => 10630, + 10630 => 10629, + 10631 => 10632, + 10632 => 10631, + 10633 => 10634, + 10634 => 10633, + 10635 => 10636, + 10636 => 10635, + 10637 => 10640, + 10638 => 10639, + 10639 => 10638, + 10640 => 10637, + 10641 => 10642, + 10642 => 10641, + 10643 => 10644, + 10644 => 10643, + 10645 => 10646, + 10646 => 10645, + 10647 => 10648, + 10648 => 10647, + 10680 => 8856, + 10688 => 10689, + 10689 => 10688, + 10692 => 10693, + 10693 => 10692, + 10703 => 10704, + 10704 => 10703, + 10705 => 10706, + 10706 => 10705, + 10708 => 10709, + 10709 => 10708, + 10712 => 10713, + 10713 => 10712, + 10714 => 10715, + 10715 => 10714, + 10741 => 8725, + 10744 => 10745, + 10745 => 10744, + 10748 => 10749, + 10749 => 10748, + 10795 => 10796, + 10796 => 10795, + 10797 => 10798, + 10798 => 10797, + 10804 => 10805, + 10805 => 10804, + 10812 => 10813, + 10813 => 10812, + 10852 => 10853, + 10853 => 10852, + 10873 => 10874, + 10874 => 10873, + 10877 => 10878, + 10878 => 10877, + 10879 => 10880, + 10880 => 10879, + 10881 => 10882, + 10882 => 10881, + 10883 => 10884, + 10884 => 10883, + 10891 => 10892, + 10892 => 10891, + 10897 => 10898, + 10898 => 10897, + 10899 => 10900, + 10900 => 10899, + 10901 => 10902, + 10902 => 10901, + 10903 => 10904, + 10904 => 10903, + 10905 => 10906, + 10906 => 10905, + 10907 => 10908, + 10908 => 10907, + 10913 => 10914, + 10914 => 10913, + 10918 => 10919, + 10919 => 10918, + 10920 => 10921, + 10921 => 10920, + 10922 => 10923, + 10923 => 10922, + 10924 => 10925, + 10925 => 10924, + 10927 => 10928, + 10928 => 10927, + 10931 => 10932, + 10932 => 10931, + 10939 => 10940, + 10940 => 10939, + 10941 => 10942, + 10942 => 10941, + 10943 => 10944, + 10944 => 10943, + 10945 => 10946, + 10946 => 10945, + 10947 => 10948, + 10948 => 10947, + 10949 => 10950, + 10950 => 10949, + 10957 => 10958, + 10958 => 10957, + 10959 => 10960, + 10960 => 10959, + 10961 => 10962, + 10962 => 10961, + 10963 => 10964, + 10964 => 10963, + 10965 => 10966, + 10966 => 10965, + 10974 => 8870, + 10979 => 8873, + 10980 => 8872, + 10981 => 8875, + 10988 => 10989, + 10989 => 10988, + 10999 => 11000, + 11000 => 10999, + 11001 => 11002, + 11002 => 11001, + 11778 => 11779, + 11779 => 11778, + 11780 => 11781, + 11781 => 11780, + 11785 => 11786, + 11786 => 11785, + 11788 => 11789, + 11789 => 11788, + 11804 => 11805, + 11805 => 11804, + 11808 => 11809, + 11809 => 11808, + 11810 => 11811, + 11811 => 11810, + 11812 => 11813, + 11813 => 11812, + 11814 => 11815, + 11815 => 11814, + 11816 => 11817, + 11817 => 11816, + 12296 => 12297, + 12297 => 12296, + 12298 => 12299, + 12299 => 12298, + 12300 => 12301, + 12301 => 12300, + 12302 => 12303, + 12303 => 12302, + 12304 => 12305, + 12305 => 12304, + 12308 => 12309, + 12309 => 12308, + 12310 => 12311, + 12311 => 12310, + 12312 => 12313, + 12313 => 12312, + 12314 => 12315, + 12315 => 12314, + 65113 => 65114, + 65114 => 65113, + 65115 => 65116, + 65116 => 65115, + 65117 => 65118, + 65118 => 65117, + 65124 => 65125, + 65125 => 65124, + 65288 => 65289, + 65289 => 65288, + 65308 => 65310, + 65310 => 65308, + 65339 => 65341, + 65341 => 65339, + 65371 => 65373, + 65373 => 65371, + 65375 => 65376, + 65376 => 65375, + 65378 => 65379, + 65379 => 65378, + ]; + + + /* index tables for the database records */ + + private static $index0 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 54, 52, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 55, 56, 57, 57, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, + 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, + 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 68, 69, 70, 70, + 71, 69, 70, 70, 72, 73, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 75, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 70, 70, 70, 88, 89, 90, 91, 92, 70, 93, 70, 94, + 95, 70, 70, 70, 70, 96, 70, 70, 70, 70, 70, 70, 70, 70, 70, 97, 97, 97, + 98, 99, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 100, 100, 100, 100, + 101, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 102, 102, + 103, 70, 70, 70, 70, 104, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 105, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 106, 107, 108, 109, 110, + 111, 112, 113, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 114, 70, 115, 116, 117, 118, 119, 120, + 121, 122, 70, 70, 70, 70, 70, 70, 70, 70, 52, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 123, 52, 53, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 124, 125, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 76, 76, 127, 126, 126, 126, 126, 128, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 128, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 129, 130, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 73, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 131, 73, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, + 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 74, 131, + ]; + + private static $index1 = [ + 0, 1, 0, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 11, 12, 13, 0, 0, 0, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 29, 31, 32, + 33, 34, 35, 27, 30, 29, 27, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 27, 27, 49, 27, 27, 27, 27, 27, 27, 27, 50, 51, 52, 27, 53, 54, + 53, 54, 54, 54, 54, 54, 55, 54, 54, 54, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 64, 65, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 65, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, + 97, 97, 97, 97, 98, 98, 98, 98, 99, 100, 101, 101, 101, 101, 102, 103, + 101, 101, 101, 101, 101, 101, 104, 105, 101, 101, 101, 101, 101, 101, + 101, 101, 101, 101, 101, 106, 107, 108, 108, 108, 109, 110, 111, 112, + 112, 112, 112, 113, 114, 115, 116, 117, 118, 119, 120, 106, 121, 121, + 121, 122, 123, 106, 124, 125, 126, 127, 128, 128, 128, 128, 129, 130, + 131, 132, 133, 134, 135, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 145, 145, + 146, 147, 148, 149, 128, 128, 128, 128, 128, 128, 150, 150, 150, 150, + 151, 152, 153, 106, 154, 155, 156, 156, 156, 157, 158, 159, 160, 160, + 161, 162, 163, 164, 165, 166, 167, 167, 167, 168, 106, 106, 106, 106, + 106, 106, 106, 106, 169, 170, 106, 106, 106, 106, 106, 106, 171, 172, + 173, 174, 175, 176, 176, 176, 176, 176, 176, 177, 178, 179, 180, 176, + 181, 182, 183, 184, 185, 186, 187, 188, 188, 189, 190, 191, 192, 193, + 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 203, 204, 205, 206, + 207, 208, 209, 210, 211, 212, 213, 106, 214, 215, 216, 217, 217, 218, + 219, 220, 221, 222, 223, 106, 224, 225, 226, 106, 227, 228, 229, 230, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 106, 241, 242, + 243, 244, 245, 242, 246, 247, 248, 249, 250, 106, 251, 252, 253, 254, + 255, 256, 257, 258, 258, 257, 259, 260, 261, 262, 263, 264, 265, 266, + 106, 267, 268, 269, 270, 271, 271, 270, 272, 273, 274, 275, 276, 277, + 278, 279, 280, 106, 281, 282, 283, 284, 284, 284, 284, 285, 286, 287, + 288, 106, 289, 290, 291, 292, 293, 294, 295, 296, 294, 294, 297, 298, + 295, 299, 300, 301, 106, 106, 302, 106, 303, 304, 304, 304, 304, 304, + 305, 306, 307, 308, 309, 310, 106, 106, 106, 106, 311, 312, 313, 314, + 315, 316, 317, 318, 319, 320, 321, 322, 106, 106, 106, 106, 323, 324, + 325, 326, 327, 328, 329, 330, 331, 332, 331, 331, 331, 333, 334, 335, + 336, 337, 338, 339, 338, 338, 338, 340, 341, 342, 343, 344, 106, 106, + 106, 106, 345, 345, 345, 345, 345, 346, 347, 348, 349, 350, 351, 352, + 353, 354, 355, 345, 356, 357, 349, 358, 359, 359, 359, 359, 360, 361, + 362, 362, 362, 362, 362, 363, 364, 364, 364, 364, 364, 364, 364, 364, + 364, 364, 364, 364, 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, + 365, 365, 365, 365, 365, 365, 365, 365, 365, 365, 366, 366, 366, 366, + 366, 366, 366, 366, 366, 367, 368, 367, 366, 366, 366, 366, 366, 367, + 366, 366, 366, 366, 367, 368, 367, 366, 368, 366, 366, 366, 366, 366, + 366, 366, 367, 366, 366, 366, 366, 366, 366, 366, 366, 369, 370, 371, + 372, 373, 366, 366, 374, 375, 376, 376, 376, 376, 376, 376, 376, 376, + 376, 376, 377, 106, 378, 379, 379, 379, 379, 379, 379, 379, 379, 379, + 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, + 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, + 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, + 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, + 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 379, 380, 379, 379, + 381, 382, 382, 383, 384, 384, 384, 384, 384, 384, 384, 384, 384, 385, + 386, 106, 387, 388, 389, 106, 390, 390, 391, 106, 392, 392, 393, 106, + 394, 395, 396, 106, 397, 397, 397, 397, 397, 397, 398, 399, 400, 401, + 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 412, 412, 412, + 413, 412, 412, 412, 412, 412, 412, 106, 412, 412, 412, 412, 412, 414, + 379, 379, 379, 379, 379, 379, 379, 379, 415, 106, 416, 416, 416, 417, + 418, 419, 420, 421, 422, 423, 424, 424, 424, 425, 426, 106, 427, 427, + 427, 427, 427, 428, 429, 429, 430, 431, 432, 433, 434, 434, 434, 434, + 435, 435, 436, 437, 438, 438, 438, 438, 438, 438, 439, 440, 441, 442, + 443, 444, 445, 446, 445, 446, 447, 448, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 449, 450, 450, 450, 450, 450, 451, 452, 453, 454, + 455, 456, 457, 458, 459, 460, 461, 462, 462, 462, 463, 464, 465, 466, + 467, 467, 467, 467, 468, 469, 470, 471, 472, 472, 472, 472, 473, 474, + 475, 476, 477, 478, 479, 480, 481, 481, 481, 482, 106, 106, 106, 106, + 106, 106, 106, 106, 483, 106, 484, 485, 486, 487, 488, 106, 54, 54, 54, + 54, 489, 490, 56, 56, 56, 56, 56, 491, 492, 493, 54, 494, 54, 54, 54, + 495, 56, 56, 56, 496, 497, 498, 499, 500, 501, 106, 106, 502, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 503, 504, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 505, 506, 507, 508, 505, 506, + 505, 506, 507, 508, 505, 509, 505, 506, 505, 507, 505, 510, 505, 510, + 505, 510, 511, 512, 513, 514, 515, 516, 505, 517, 518, 519, 520, 521, + 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, + 536, 537, 56, 538, 539, 540, 539, 541, 106, 106, 542, 543, 544, 545, 546, + 106, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, + 560, 559, 561, 562, 563, 564, 565, 566, 567, 568, 569, 568, 570, 571, + 568, 572, 568, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, + 584, 585, 586, 587, 588, 583, 583, 589, 590, 591, 592, 593, 583, 583, + 594, 574, 595, 596, 583, 583, 597, 583, 583, 568, 598, 599, 568, 600, + 601, 602, 603, 603, 603, 603, 603, 603, 603, 603, 604, 568, 568, 605, + 606, 574, 574, 607, 568, 568, 568, 568, 573, 608, 568, 609, 106, 568, + 568, 568, 568, 610, 106, 106, 106, 568, 611, 106, 106, 612, 612, 612, + 612, 612, 613, 613, 614, 615, 615, 615, 615, 615, 615, 615, 615, 615, + 616, 612, 612, 617, 617, 617, 617, 617, 617, 617, 617, 617, 618, 617, + 617, 617, 617, 618, 568, 617, 617, 619, 568, 620, 569, 621, 622, 623, + 624, 569, 568, 619, 572, 568, 574, 625, 626, 622, 627, 568, 568, 568, + 568, 628, 568, 568, 568, 629, 630, 568, 568, 568, 568, 568, 631, 568, + 632, 568, 631, 633, 634, 617, 617, 635, 617, 617, 617, 636, 568, 568, + 568, 568, 568, 568, 637, 568, 568, 572, 568, 568, 638, 639, 612, 640, + 640, 641, 568, 568, 568, 568, 568, 642, 643, 644, 645, 646, 647, 574, + 574, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, + 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, + 648, 648, 648, 648, 648, 574, 574, 574, 574, 574, 574, 574, 574, 574, + 574, 574, 574, 574, 574, 574, 574, 649, 650, 650, 651, 583, 583, 574, + 652, 597, 653, 654, 655, 656, 657, 658, 659, 574, 660, 583, 661, 662, + 663, 664, 645, 574, 574, 586, 652, 664, 665, 666, 667, 583, 583, 583, + 583, 668, 669, 583, 583, 583, 583, 670, 671, 672, 645, 673, 674, 568, + 568, 568, 568, 568, 568, 574, 574, 675, 676, 677, 678, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 679, 679, 679, 679, 679, 680, 681, 681, 681, 681, 681, + 682, 683, 684, 685, 686, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, + 687, 688, 689, 690, 691, 691, 691, 691, 692, 693, 694, 694, 694, 694, + 694, 694, 694, 695, 696, 697, 366, 366, 368, 106, 368, 368, 368, 368, + 368, 368, 368, 368, 698, 698, 698, 698, 699, 700, 701, 702, 703, 704, + 529, 705, 106, 106, 106, 106, 106, 106, 106, 106, 706, 706, 706, 707, + 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 708, 106, 706, 706, + 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, + 706, 706, 706, 706, 706, 706, 706, 706, 706, 706, 709, 106, 106, 106, + 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 721, 721, + 721, 721, 721, 721, 721, 721, 722, 723, 724, 725, 725, 725, 725, 725, + 725, 725, 725, 725, 725, 726, 727, 728, 728, 728, 728, 729, 730, 364, + 364, 364, 364, 364, 364, 364, 364, 364, 364, 731, 732, 733, 728, 728, + 728, 734, 710, 710, 710, 710, 711, 106, 725, 725, 735, 735, 735, 736, + 737, 738, 733, 733, 733, 739, 740, 741, 735, 735, 735, 742, 737, 738, + 733, 733, 733, 733, 743, 741, 733, 744, 745, 745, 745, 745, 745, 746, + 745, 745, 745, 745, 745, 745, 745, 745, 745, 745, 745, 733, 733, 733, + 747, 748, 733, 733, 733, 733, 733, 733, 733, 733, 733, 733, 733, 749, + 733, 733, 733, 747, 750, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 752, 753, 568, 568, 568, 568, 568, 568, + 568, 568, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 754, + 753, 753, 753, 753, 753, 753, 755, 755, 756, 755, 755, 755, 755, 755, + 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, + 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, + 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, + 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, + 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, 755, + 755, 755, 755, 757, 758, 758, 758, 758, 758, 758, 759, 106, 760, 760, + 760, 760, 760, 761, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, + 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, 762, + 762, 762, 762, 762, 762, 762, 762, 762, 762, 763, 762, 762, 764, 765, + 106, 106, 101, 101, 101, 101, 101, 766, 767, 768, 101, 101, 101, 769, + 770, 770, 770, 770, 770, 770, 770, 770, 771, 772, 773, 106, 64, 64, 774, + 775, 776, 27, 777, 27, 27, 27, 27, 27, 27, 27, 778, 779, 27, 780, 781, + 106, 27, 782, 106, 106, 106, 106, 106, 106, 106, 106, 106, 783, 784, 785, + 786, 786, 787, 788, 789, 790, 791, 791, 791, 791, 791, 791, 792, 106, + 793, 794, 794, 794, 794, 794, 795, 796, 797, 798, 799, 800, 801, 801, + 802, 803, 804, 805, 806, 806, 807, 808, 809, 809, 810, 811, 812, 813, + 364, 364, 364, 814, 815, 816, 816, 816, 816, 816, 817, 818, 819, 820, + 821, 822, 106, 106, 106, 106, 823, 823, 823, 823, 823, 824, 825, 106, + 826, 827, 828, 829, 345, 345, 830, 831, 832, 832, 832, 832, 832, 832, + 833, 834, 835, 106, 106, 836, 837, 838, 839, 106, 840, 840, 840, 106, + 368, 368, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 837, 837, 837, 837, 841, 842, 843, 844, + 845, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, + 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, + 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, + 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, + 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, + 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, 846, + 847, 106, 365, 365, 848, 849, 365, 365, 365, 365, 365, 850, 851, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 852, 851, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 852, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 852, + 853, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 855, 856, 856, + 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, + 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, + 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, + 856, 857, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, 856, + 856, 858, 753, 753, 753, 753, 859, 106, 860, 861, 121, 862, 863, 864, + 865, 121, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 866, 867, 868, 106, 869, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 870, 106, 106, 128, 128, 128, 128, + 128, 128, 128, 128, 871, 128, 128, 128, 128, 128, 128, 106, 106, 106, + 106, 106, 128, 872, 873, 873, 874, 875, 501, 106, 876, 877, 878, 879, + 880, 881, 882, 883, 884, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 885, 886, 887, 888, 889, 890, 891, + 891, 892, 893, 894, 894, 895, 896, 897, 898, 897, 897, 897, 897, 899, + 900, 900, 900, 901, 902, 902, 902, 903, 904, 905, 106, 906, 907, 908, + 907, 907, 909, 907, 907, 910, 907, 911, 907, 911, 106, 106, 106, 106, + 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, 907, + 907, 912, 913, 914, 914, 914, 914, 914, 915, 603, 916, 916, 916, 916, + 916, 916, 917, 918, 919, 920, 568, 609, 106, 106, 106, 106, 106, 106, + 603, 603, 603, 603, 603, 921, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 922, 922, 922, 923, 924, 924, + 924, 924, 924, 924, 925, 106, 106, 106, 106, 106, 926, 926, 926, 927, + 928, 106, 929, 929, 930, 931, 106, 106, 106, 106, 106, 106, 932, 932, + 932, 933, 934, 934, 934, 934, 935, 934, 936, 106, 106, 106, 106, 106, + 937, 937, 937, 937, 937, 938, 938, 938, 938, 938, 939, 939, 939, 939, + 939, 939, 940, 940, 940, 941, 942, 943, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 944, 945, 946, 946, 946, 946, 947, 948, 949, 949, + 950, 951, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 952, 952, 953, 954, 955, 955, + 955, 956, 106, 106, 106, 106, 106, 106, 106, 106, 957, 957, 957, 957, + 958, 958, 958, 959, 106, 106, 106, 106, 106, 106, 106, 106, 960, 961, + 962, 963, 964, 964, 965, 966, 967, 106, 968, 969, 970, 970, 970, 971, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 972, 972, 972, 972, 972, 972, 973, 974, 975, 975, 976, 977, + 978, 978, 979, 980, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 981, 981, 981, 981, 981, 981, 981, 981, + 981, 982, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 983, 983, 983, 984, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 985, 986, 986, 986, 986, 986, 986, 987, 988, 989, 990, 991, 992, 993, + 106, 106, 994, 995, 995, 995, 995, 995, 996, 997, 998, 106, 999, 999, + 999, 1000, 1001, 1002, 1003, 1004, 1004, 1004, 1005, 1006, 1007, 1008, + 1009, 106, 106, 106, 106, 106, 106, 106, 1010, 1011, 1011, 1011, 1011, + 1011, 1012, 1013, 1014, 1015, 1016, 1017, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 1018, 1018, 1018, 1018, 1018, 1019, 1020, 106, 1021, 1022, 106, 106, 106, + 106, 106, 106, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, + 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, + 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, + 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1023, + 1024, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, + 1025, 1025, 1025, 1025, 1026, 106, 1027, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 1028, 1028, 1028, + 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, + 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, + 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1029, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 770, 770, 770, + 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, + 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, 770, + 770, 770, 770, 770, 770, 770, 770, 770, 1030, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 1031, 1031, 1031, 1031, 1031, 1031, 1031, 1031, + 1032, 106, 1033, 1034, 1034, 1034, 1034, 1035, 106, 1036, 1037, 1038, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 1039, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, + 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, + 603, 603, 603, 603, 1040, 106, 603, 603, 603, 603, 1041, 1042, 603, 603, + 603, 603, 603, 603, 1043, 1044, 1045, 1046, 1047, 1048, 603, 603, 603, + 1049, 603, 603, 603, 603, 603, 1040, 106, 106, 106, 106, 919, 919, 919, + 919, 919, 919, 919, 919, 1050, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 610, 106, 914, + 914, 1051, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 1052, 1052, 1052, 1053, 1054, 1054, 1055, 1052, + 1052, 1056, 1057, 1054, 1054, 1052, 1052, 1052, 1053, 1054, 1054, 1058, + 1059, 1060, 1056, 1061, 1062, 1054, 1052, 1052, 1052, 1053, 1054, 1054, + 1063, 1064, 1065, 1066, 1054, 1054, 1054, 1067, 1068, 1069, 1070, 1054, + 1054, 1055, 1052, 1052, 1056, 1054, 1054, 1054, 1052, 1052, 1052, 1053, + 1054, 1054, 1055, 1052, 1052, 1056, 1054, 1054, 1054, 1052, 1052, 1052, + 1053, 1054, 1054, 1055, 1052, 1052, 1056, 1054, 1054, 1054, 1052, 1052, + 1052, 1053, 1054, 1054, 1071, 1052, 1052, 1052, 1072, 1054, 1054, 1073, + 1074, 1052, 1052, 1075, 1054, 1054, 1076, 1055, 1052, 1052, 1077, 1054, + 1054, 1078, 1079, 1052, 1052, 1080, 1054, 1054, 1054, 1081, 1052, 1052, + 1052, 1072, 1054, 1054, 1073, 1082, 1083, 1083, 1083, 1083, 1083, 1083, + 1084, 128, 128, 128, 1085, 1086, 1087, 1088, 1089, 1090, 1085, 1091, + 1085, 1087, 1087, 1092, 128, 1093, 128, 1094, 1095, 1093, 128, 1094, 106, + 106, 106, 106, 106, 106, 1096, 106, 568, 568, 568, 568, 568, 609, 568, + 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 609, 106, 568, + 610, 636, 610, 636, 568, 636, 568, 106, 106, 106, 106, 613, 1097, 615, + 615, 615, 1098, 615, 615, 615, 615, 615, 615, 615, 1099, 615, 615, 615, + 615, 615, 1100, 106, 106, 106, 106, 106, 106, 106, 106, 1101, 603, 603, + 603, 1102, 106, 733, 733, 733, 733, 733, 1103, 733, 1104, 1105, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 568, 568, 568, 568, 1106, 106, 1107, 568, 568, + 568, 568, 568, 568, 568, 568, 1108, 568, 568, 609, 106, 568, 568, 568, + 568, 1109, 611, 106, 106, 568, 568, 1106, 106, 568, 568, 568, 568, 568, + 568, 568, 610, 1110, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, + 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 568, 1111, 568, + 568, 568, 568, 568, 568, 568, 1112, 609, 106, 568, 568, 568, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 106, 106, 1113, 568, 568, 568, 568, 568, 568, 568, 568, 1114, 568, 106, + 106, 106, 106, 106, 106, 568, 568, 568, 568, 568, 568, 568, 568, 1112, + 106, 106, 106, 106, 106, 106, 106, 568, 568, 568, 568, 568, 568, 568, + 568, 568, 568, 568, 568, 568, 568, 609, 106, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 1115, 753, 753, 753, 753, + 753, 751, 751, 751, 751, 751, 751, 754, 753, 750, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, 751, + 751, 751, 751, 751, 751, 751, 751, 751, 752, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 856, + 856, 856, 857, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, 753, + 753, 753, 753, 753, 753, 753, 1116, 1117, 106, 106, 106, 1118, 1118, + 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 106, 106, + 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, 106, + 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, + 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, 873, + 873, 873, 106, 106, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, 854, + 854, 854, 854, 854, 854, 854, 854, 1119, + ]; + + private static $index2 = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 4, 3, 1, 1, 1, 1, 1, 1, 3, 3, 3, 2, + 5, 6, 6, 7, 8, 7, 6, 6, 9, 10, 6, 11, 12, 13, 12, 12, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 12, 6, 15, 16, 15, 6, 6, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 9, 6, 10, 18, 19, 18, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 9, 16, + 10, 16, 1, 1, 1, 1, 1, 1, 3, 1, 1, 21, 22, 8, 8, 23, 8, 24, 22, 25, 26, + 27, 28, 16, 29, 30, 18, 31, 32, 33, 33, 25, 34, 22, 22, 25, 33, 27, 35, + 36, 36, 36, 22, 37, 37, 37, 37, 37, 37, 38, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 38, 37, 37, 37, 37, 37, 37, 39, 38, 37, 37, 37, 37, 37, 38, 40, + 40, 40, 41, 41, 41, 41, 40, 41, 40, 40, 40, 41, 40, 40, 41, 41, 40, 41, + 40, 40, 41, 41, 41, 39, 40, 40, 40, 41, 40, 41, 40, 41, 37, 40, 37, 41, + 37, 41, 37, 41, 37, 41, 37, 41, 37, 41, 37, 41, 37, 40, 37, 40, 37, 41, + 37, 41, 37, 41, 37, 40, 37, 41, 37, 41, 37, 41, 37, 41, 37, 41, 38, 40, + 37, 40, 38, 40, 37, 41, 37, 41, 40, 37, 41, 37, 41, 37, 41, 38, 40, 38, + 40, 37, 40, 37, 41, 37, 40, 40, 38, 40, 37, 40, 37, 41, 37, 41, 38, 40, + 37, 41, 37, 41, 37, 37, 41, 37, 41, 37, 41, 41, 41, 37, 37, 41, 37, 41, + 37, 37, 41, 37, 37, 37, 41, 41, 37, 37, 37, 37, 41, 37, 37, 41, 37, 37, + 37, 41, 41, 41, 37, 37, 41, 37, 37, 41, 37, 41, 37, 41, 37, 37, 41, 37, + 41, 41, 37, 41, 37, 37, 41, 37, 37, 37, 41, 37, 41, 37, 37, 41, 41, 42, + 37, 41, 41, 41, 42, 42, 42, 42, 37, 43, 41, 37, 43, 41, 37, 43, 41, 37, + 40, 37, 40, 37, 40, 37, 40, 37, 40, 37, 40, 37, 40, 37, 40, 41, 37, 41, + 41, 37, 43, 41, 37, 41, 37, 37, 37, 41, 37, 41, 41, 41, 41, 41, 41, 41, + 37, 37, 41, 37, 37, 41, 41, 37, 41, 37, 37, 37, 37, 41, 41, 40, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 42, 41, + 41, 41, 44, 44, 44, 44, 44, 44, 44, 44, 44, 45, 45, 46, 46, 46, 46, 46, + 46, 46, 47, 47, 25, 47, 45, 48, 45, 48, 48, 48, 45, 48, 45, 45, 49, 46, + 47, 47, 47, 47, 47, 47, 25, 25, 25, 25, 47, 25, 47, 25, 44, 44, 44, 44, + 44, 47, 47, 47, 47, 47, 50, 50, 45, 47, 46, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 53, 53, + 53, 53, 52, 54, 53, 53, 53, 53, 53, 55, 55, 53, 53, 53, 53, 55, 55, 53, + 53, 53, 53, 53, 53, 53, 53, 53, 53, 53, 56, 56, 56, 56, 56, 53, 53, 53, + 53, 51, 51, 51, 51, 51, 51, 51, 51, 57, 51, 53, 53, 53, 51, 51, 51, 53, + 53, 58, 51, 51, 51, 53, 53, 53, 53, 51, 52, 53, 53, 51, 59, 60, 60, 59, + 60, 60, 59, 51, 51, 51, 51, 51, 61, 62, 61, 62, 45, 63, 61, 62, 64, 64, + 65, 62, 62, 62, 66, 64, 64, 64, 64, 64, 63, 47, 61, 66, 61, 61, 61, 64, + 61, 64, 61, 61, 62, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, + 67, 67, 67, 67, 64, 67, 67, 67, 67, 67, 67, 67, 61, 61, 62, 62, 62, 62, + 62, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 62, 68, 68, 68, 68, 68, 68, 68, 62, 62, 62, 62, 62, 61, 62, 62, 61, 61, + 61, 62, 62, 62, 61, 62, 61, 62, 61, 62, 61, 62, 61, 62, 69, 70, 69, 70, + 69, 70, 69, 70, 69, 70, 69, 70, 69, 70, 62, 62, 62, 62, 61, 62, 71, 61, + 62, 61, 61, 62, 62, 61, 61, 61, 72, 73, 72, 72, 72, 72, 72, 72, 72, 72, + 72, 72, 72, 72, 72, 72, 73, 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, 74, + 74, 74, 74, 74, 75, 74, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, + 75, 75, 72, 75, 72, 75, 72, 75, 72, 75, 72, 75, 76, 77, 77, 78, 78, 77, + 79, 79, 72, 75, 72, 75, 72, 75, 72, 72, 75, 72, 75, 72, 75, 72, 75, 72, + 75, 72, 75, 72, 75, 75, 64, 64, 64, 64, 64, 64, 64, 64, 64, 80, 80, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 64, 64, 81, 82, 82, 82, 82, 82, 82, 64, 83, 83, 83, 83, 83, 83, 83, + 83, 83, 83, 83, 83, 83, 83, 83, 64, 84, 85, 64, 64, 64, 64, 86, 64, 87, + 88, 88, 88, 88, 87, 88, 88, 88, 89, 87, 88, 88, 88, 88, 88, 88, 87, 87, + 87, 87, 87, 87, 88, 88, 87, 88, 88, 89, 90, 88, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 100, 101, 102, 103, 104, 105, 106, 107, 108, 106, 88, + 87, 106, 99, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 64, + 64, 64, 64, 64, 109, 109, 109, 106, 106, 64, 64, 64, 110, 110, 110, 110, + 110, 64, 111, 111, 112, 113, 113, 114, 115, 116, 117, 117, 118, 118, 118, + 118, 118, 118, 118, 118, 119, 120, 121, 122, 64, 64, 116, 122, 123, 123, + 123, 123, 123, 123, 123, 123, 124, 123, 123, 123, 123, 123, 123, 123, + 123, 123, 123, 125, 126, 127, 128, 129, 130, 131, 132, 78, 78, 133, 134, + 118, 118, 118, 118, 118, 134, 118, 118, 134, 135, 135, 135, 135, 135, + 135, 135, 135, 135, 135, 113, 136, 136, 116, 123, 123, 137, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 123, 116, 123, 118, 118, 118, + 118, 118, 118, 118, 138, 117, 118, 118, 118, 118, 134, 118, 139, 139, + 118, 118, 117, 134, 118, 118, 134, 123, 123, 140, 140, 140, 140, 140, + 140, 140, 140, 140, 140, 123, 123, 123, 141, 141, 123, 142, 142, 142, + 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 64, 143, 144, 145, + 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, + 146, 147, 146, 146, 147, 146, 146, 147, 147, 147, 146, 147, 147, 146, + 147, 146, 146, 146, 147, 146, 147, 146, 147, 146, 147, 146, 146, 64, 64, + 144, 144, 144, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, + 148, 148, 148, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, + 148, 64, 64, 64, 64, 64, 64, 150, 150, 150, 150, 150, 150, 150, 150, 150, + 150, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, 151, + 151, 151, 151, 151, 152, 152, 152, 152, 152, 152, 152, 153, 152, 154, + 154, 155, 156, 156, 156, 154, 64, 64, 64, 64, 64, 157, 157, 157, 157, + 157, 157, 157, 157, 157, 157, 157, 157, 157, 157, 158, 158, 158, 158, + 159, 158, 158, 158, 158, 158, 158, 158, 158, 158, 159, 158, 158, 158, + 159, 158, 158, 158, 158, 158, 64, 64, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, 64, 161, 161, 161, 161, 161, 161, + 161, 161, 161, 162, 162, 162, 64, 64, 163, 64, 123, 64, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 123, 64, 64, 64, 64, 64, 64, 64, 118, + 118, 134, 118, 118, 134, 118, 118, 118, 134, 134, 134, 164, 165, 166, + 118, 118, 118, 134, 118, 118, 134, 134, 118, 118, 118, 118, 64, 167, 167, + 167, 168, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, + 169, 169, 167, 168, 170, 169, 168, 168, 168, 167, 167, 167, 167, 167, + 167, 167, 167, 168, 168, 168, 168, 171, 168, 168, 169, 78, 133, 172, 172, + 167, 167, 167, 169, 169, 167, 167, 84, 84, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 174, 175, 169, 169, 169, 169, 169, 169, 64, 169, 169, + 169, 169, 169, 169, 169, 64, 176, 177, 177, 64, 178, 178, 178, 178, 178, + 178, 178, 178, 64, 64, 178, 178, 64, 64, 178, 178, 178, 178, 178, 178, + 178, 178, 178, 178, 178, 178, 178, 178, 64, 178, 178, 178, 178, 178, 178, + 178, 64, 178, 64, 64, 64, 178, 178, 178, 178, 64, 64, 179, 178, 177, 177, + 177, 176, 176, 176, 176, 64, 64, 177, 177, 64, 64, 177, 177, 180, 178, + 64, 64, 64, 64, 64, 64, 64, 64, 177, 64, 64, 64, 64, 178, 178, 64, 178, + 178, 178, 176, 176, 64, 64, 181, 181, 181, 181, 181, 181, 181, 181, 181, + 181, 178, 178, 182, 182, 183, 183, 183, 183, 183, 183, 184, 182, 64, 64, + 64, 64, 64, 185, 185, 186, 64, 187, 187, 187, 187, 187, 187, 64, 64, 64, + 64, 187, 187, 64, 64, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + 187, 187, 187, 187, 64, 187, 187, 187, 187, 187, 187, 187, 64, 187, 187, + 64, 187, 187, 64, 187, 187, 64, 64, 188, 64, 186, 186, 186, 185, 185, 64, + 64, 64, 64, 185, 185, 64, 64, 185, 185, 189, 64, 64, 64, 185, 64, 64, 64, + 64, 64, 64, 64, 187, 187, 187, 187, 64, 187, 64, 64, 64, 64, 64, 64, 64, + 190, 190, 190, 190, 190, 190, 190, 190, 190, 190, 185, 185, 187, 187, + 187, 185, 64, 64, 64, 191, 191, 192, 64, 193, 193, 193, 193, 193, 193, + 193, 193, 193, 64, 193, 193, 193, 64, 193, 193, 193, 193, 193, 193, 193, + 193, 193, 193, 193, 193, 193, 193, 64, 193, 193, 193, 193, 193, 193, 193, + 64, 193, 193, 64, 193, 193, 193, 193, 193, 64, 64, 194, 193, 192, 192, + 192, 191, 191, 191, 191, 191, 64, 191, 191, 192, 64, 192, 192, 195, 64, + 64, 193, 64, 64, 64, 64, 64, 64, 64, 193, 193, 191, 191, 64, 64, 196, + 196, 196, 196, 196, 196, 196, 196, 196, 196, 197, 198, 64, 64, 64, 64, + 64, 64, 64, 199, 200, 200, 64, 201, 201, 201, 201, 201, 201, 201, 201, + 64, 64, 201, 201, 64, 64, 201, 201, 201, 201, 201, 201, 201, 201, 201, + 201, 201, 201, 201, 201, 64, 201, 201, 201, 201, 201, 201, 201, 64, 201, + 201, 64, 201, 201, 201, 201, 201, 64, 64, 202, 201, 200, 199, 200, 199, + 199, 199, 199, 64, 64, 200, 200, 64, 64, 200, 200, 203, 64, 64, 64, 64, + 64, 64, 64, 64, 199, 200, 64, 64, 64, 64, 201, 201, 64, 201, 201, 201, + 199, 199, 64, 64, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 205, + 201, 206, 206, 206, 206, 206, 206, 64, 64, 207, 208, 64, 208, 208, 208, + 208, 208, 208, 64, 64, 64, 208, 208, 208, 64, 208, 208, 208, 208, 64, 64, + 64, 208, 208, 64, 208, 64, 208, 208, 64, 64, 64, 208, 208, 64, 64, 64, + 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 64, 64, 64, 64, 209, + 209, 207, 209, 209, 64, 64, 64, 209, 209, 209, 64, 209, 209, 209, 210, + 64, 64, 208, 64, 64, 64, 64, 64, 64, 209, 64, 64, 64, 64, 64, 64, 211, + 211, 211, 211, 211, 211, 211, 211, 211, 211, 212, 212, 212, 213, 213, + 213, 213, 213, 213, 214, 213, 64, 64, 64, 64, 64, 64, 215, 215, 215, 64, + 216, 216, 216, 216, 216, 216, 216, 216, 64, 216, 216, 216, 64, 216, 216, + 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, 216, + 216, 216, 64, 216, 216, 216, 216, 216, 64, 64, 64, 216, 217, 217, 217, + 215, 215, 215, 215, 64, 217, 217, 217, 64, 217, 217, 217, 218, 64, 64, + 64, 64, 64, 64, 64, 219, 220, 64, 216, 216, 64, 64, 64, 64, 64, 64, 216, + 216, 217, 217, 64, 64, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, + 222, 222, 222, 222, 222, 222, 222, 223, 64, 64, 224, 224, 64, 225, 225, + 225, 225, 225, 225, 225, 225, 64, 225, 225, 225, 64, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 64, + 225, 225, 225, 225, 225, 64, 64, 226, 225, 224, 227, 224, 224, 224, 224, + 224, 64, 227, 224, 224, 64, 224, 224, 228, 229, 64, 64, 64, 64, 64, 64, + 64, 224, 224, 64, 64, 64, 64, 64, 64, 64, 225, 64, 225, 225, 228, 228, + 64, 64, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 64, 225, 225, + 64, 64, 64, 64, 64, 64, 64, 231, 231, 64, 232, 232, 232, 232, 232, 232, + 232, 232, 64, 232, 232, 232, 64, 232, 232, 232, 232, 232, 232, 232, 232, + 232, 232, 232, 232, 232, 232, 232, 232, 232, 64, 64, 232, 231, 231, 231, + 233, 233, 233, 233, 64, 231, 231, 231, 64, 231, 231, 231, 234, 232, 64, + 64, 64, 64, 64, 64, 64, 64, 231, 232, 232, 233, 233, 64, 64, 235, 235, + 235, 235, 235, 235, 235, 235, 235, 235, 236, 236, 236, 236, 236, 236, 64, + 64, 64, 237, 232, 232, 232, 232, 232, 232, 64, 64, 238, 238, 64, 239, + 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, 239, + 239, 239, 239, 64, 64, 64, 239, 239, 239, 239, 239, 239, 239, 239, 64, + 239, 239, 239, 239, 239, 239, 239, 239, 239, 64, 239, 64, 64, 64, 64, + 240, 64, 64, 64, 64, 238, 238, 238, 241, 241, 241, 64, 241, 64, 238, 238, + 238, 238, 238, 238, 238, 238, 64, 64, 238, 238, 242, 64, 64, 64, 64, 243, + 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, 243, + 243, 244, 243, 243, 244, 244, 244, 244, 245, 245, 246, 64, 64, 64, 64, + 247, 243, 243, 243, 243, 243, 243, 248, 244, 249, 249, 249, 249, 244, + 244, 244, 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 250, + 250, 64, 64, 64, 64, 64, 252, 252, 64, 252, 64, 64, 252, 252, 64, 252, + 64, 64, 252, 64, 64, 64, 64, 64, 64, 252, 252, 252, 252, 64, 252, 252, + 252, 252, 252, 252, 252, 64, 252, 252, 252, 64, 252, 64, 252, 64, 64, + 252, 252, 64, 252, 252, 252, 252, 253, 252, 252, 253, 253, 253, 253, 254, + 254, 64, 253, 253, 252, 64, 64, 252, 252, 252, 252, 252, 64, 255, 64, + 256, 256, 256, 256, 253, 253, 64, 64, 257, 257, 257, 257, 257, 257, 257, + 257, 257, 257, 64, 64, 252, 252, 252, 252, 258, 259, 259, 259, 260, 260, + 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 260, 259, + 260, 259, 259, 259, 261, 261, 259, 259, 259, 259, 259, 259, 262, 262, + 262, 262, 262, 262, 262, 262, 262, 262, 263, 263, 263, 263, 263, 263, + 263, 263, 263, 263, 259, 261, 259, 261, 259, 264, 265, 266, 265, 266, + 267, 267, 258, 258, 258, 258, 258, 258, 258, 258, 64, 258, 258, 258, 258, + 258, 258, 258, 258, 258, 258, 258, 258, 64, 64, 64, 64, 268, 269, 270, + 271, 270, 270, 270, 270, 270, 269, 269, 269, 269, 270, 267, 269, 270, + 272, 272, 273, 260, 272, 272, 258, 258, 258, 258, 258, 270, 270, 270, + 270, 270, 270, 270, 270, 270, 270, 270, 64, 270, 270, 270, 270, 270, 270, + 270, 270, 270, 270, 270, 270, 64, 259, 259, 259, 259, 259, 259, 259, 259, + 261, 259, 259, 259, 259, 259, 259, 64, 259, 259, 260, 260, 260, 260, 260, + 274, 274, 274, 274, 260, 260, 64, 64, 64, 64, 64, 275, 275, 275, 275, + 275, 275, 275, 275, 275, 275, 275, 276, 276, 277, 277, 277, 277, 276, + 277, 277, 277, 277, 277, 278, 276, 279, 279, 276, 276, 277, 277, 275, + 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, + 281, 281, 275, 275, 275, 275, 275, 275, 276, 276, 277, 277, 275, 275, + 275, 275, 277, 277, 277, 275, 276, 276, 276, 275, 275, 276, 276, 276, + 276, 276, 276, 276, 275, 275, 275, 277, 277, 277, 277, 275, 275, 275, + 275, 275, 277, 276, 276, 277, 277, 276, 276, 276, 276, 276, 276, 282, + 275, 276, 280, 280, 276, 276, 276, 277, 283, 283, 284, 284, 284, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 64, 284, 64, 64, 64, + 64, 64, 284, 64, 64, 285, 285, 285, 285, 285, 285, 285, 285, 285, 285, + 285, 84, 286, 285, 285, 285, 287, 287, 287, 287, 287, 287, 287, 287, 288, + 288, 288, 288, 288, 288, 288, 288, 289, 289, 289, 289, 289, 289, 289, + 289, 289, 64, 289, 289, 289, 289, 64, 64, 289, 289, 289, 289, 289, 289, + 289, 64, 289, 289, 289, 64, 64, 290, 290, 290, 291, 291, 291, 291, 291, + 291, 291, 291, 291, 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, + 292, 292, 292, 292, 292, 292, 292, 292, 292, 292, 64, 64, 64, 293, 293, + 293, 293, 293, 293, 293, 293, 293, 293, 64, 64, 64, 64, 64, 64, 294, 294, + 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 294, 64, 64, 64, 295, + 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, + 296, 296, 296, 296, 296, 296, 297, 297, 296, 298, 299, 299, 299, 299, + 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, 299, + 300, 301, 64, 64, 64, 302, 302, 302, 302, 302, 302, 302, 302, 302, 302, + 302, 84, 84, 84, 303, 303, 303, 64, 64, 64, 64, 64, 64, 64, 304, 304, + 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 304, 64, 304, 304, 304, + 304, 305, 305, 306, 64, 64, 64, 307, 307, 307, 307, 307, 307, 307, 307, + 307, 307, 308, 308, 309, 84, 84, 64, 310, 310, 310, 310, 310, 310, 310, + 310, 310, 310, 311, 311, 64, 64, 64, 64, 312, 312, 312, 312, 312, 312, + 312, 312, 312, 312, 312, 312, 312, 64, 312, 312, 312, 64, 313, 313, 64, + 64, 64, 64, 314, 314, 314, 314, 314, 314, 314, 314, 314, 314, 314, 314, + 315, 315, 316, 315, 315, 315, 315, 315, 315, 315, 316, 316, 316, 316, + 316, 316, 316, 316, 315, 316, 316, 315, 315, 315, 315, 315, 315, 315, + 315, 315, 317, 315, 318, 318, 318, 319, 318, 318, 318, 320, 314, 321, 64, + 64, 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, 64, 64, 64, 64, 64, + 64, 323, 323, 323, 323, 323, 323, 323, 323, 323, 323, 64, 64, 64, 64, 64, + 64, 324, 324, 66, 66, 324, 66, 325, 324, 324, 324, 324, 326, 326, 326, + 327, 64, 328, 328, 328, 328, 328, 328, 328, 328, 328, 328, 64, 64, 64, + 64, 64, 64, 329, 329, 329, 329, 329, 329, 329, 329, 329, 329, 329, 330, + 329, 329, 329, 329, 329, 331, 329, 64, 64, 64, 64, 64, 296, 296, 296, + 296, 296, 296, 64, 64, 332, 332, 332, 332, 332, 332, 332, 332, 332, 332, + 332, 332, 332, 64, 64, 64, 333, 333, 333, 334, 334, 334, 334, 333, 333, + 334, 334, 334, 64, 64, 64, 64, 334, 334, 333, 334, 334, 334, 334, 334, + 334, 335, 336, 337, 64, 64, 64, 64, 338, 64, 64, 64, 339, 339, 340, 340, + 340, 340, 340, 340, 340, 340, 340, 340, 341, 341, 341, 341, 341, 341, + 341, 341, 341, 341, 341, 341, 341, 341, 64, 64, 341, 341, 341, 341, 341, + 64, 64, 64, 342, 342, 342, 342, 342, 342, 342, 342, 342, 342, 342, 342, + 64, 64, 64, 64, 343, 343, 343, 343, 343, 343, 343, 343, 343, 342, 342, + 342, 342, 342, 342, 342, 343, 343, 64, 64, 64, 64, 64, 64, 344, 344, 344, + 344, 344, 344, 344, 344, 344, 344, 345, 64, 64, 64, 346, 346, 347, 347, + 347, 347, 347, 347, 347, 347, 348, 348, 348, 348, 348, 348, 348, 348, + 348, 348, 348, 348, 348, 348, 348, 349, 350, 351, 351, 351, 64, 64, 352, + 352, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353, 353, + 354, 355, 354, 355, 355, 355, 355, 355, 355, 355, 64, 356, 354, 355, 354, + 354, 355, 355, 355, 355, 355, 355, 355, 355, 354, 354, 354, 354, 354, + 354, 355, 355, 357, 357, 357, 357, 357, 357, 357, 357, 64, 64, 358, 359, + 359, 359, 359, 359, 359, 359, 359, 359, 359, 64, 64, 64, 64, 64, 64, 360, + 360, 360, 360, 360, 360, 360, 361, 360, 360, 360, 360, 360, 360, 64, 64, + 362, 362, 362, 362, 363, 364, 364, 364, 364, 364, 364, 364, 364, 364, + 364, 364, 364, 364, 364, 364, 365, 363, 362, 362, 362, 362, 362, 363, + 362, 363, 363, 363, 363, 363, 362, 363, 366, 364, 364, 364, 364, 364, + 364, 364, 64, 64, 64, 64, 367, 367, 367, 367, 367, 367, 367, 367, 367, + 367, 368, 368, 368, 368, 368, 368, 368, 369, 369, 369, 369, 369, 369, + 369, 369, 369, 369, 370, 371, 370, 370, 370, 370, 370, 370, 370, 369, + 369, 369, 369, 369, 369, 369, 369, 369, 64, 64, 64, 372, 372, 373, 374, + 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 374, 373, + 372, 372, 372, 372, 373, 373, 372, 372, 375, 376, 373, 373, 374, 374, + 377, 377, 377, 377, 377, 377, 377, 377, 377, 377, 374, 374, 374, 374, + 374, 374, 378, 378, 378, 378, 378, 378, 378, 378, 378, 378, 378, 378, + 378, 378, 379, 380, 381, 381, 380, 380, 380, 381, 380, 381, 381, 381, + 382, 382, 64, 64, 64, 64, 64, 64, 64, 64, 383, 383, 383, 383, 384, 384, + 384, 384, 384, 384, 384, 384, 384, 384, 384, 384, 385, 385, 385, 385, + 385, 385, 385, 385, 386, 386, 386, 386, 386, 386, 386, 386, 385, 385, + 386, 387, 64, 64, 64, 388, 388, 388, 388, 388, 389, 389, 389, 389, 389, + 389, 389, 389, 389, 389, 64, 64, 64, 384, 384, 384, 390, 390, 390, 390, + 390, 390, 390, 390, 390, 390, 391, 391, 391, 391, 391, 391, 391, 391, + 391, 391, 391, 391, 391, 391, 392, 392, 392, 392, 392, 392, 393, 393, + 394, 394, 394, 394, 394, 394, 394, 394, 78, 78, 78, 84, 395, 133, 133, + 133, 133, 133, 78, 78, 133, 133, 133, 133, 78, 396, 395, 395, 395, 395, + 395, 395, 395, 397, 397, 397, 397, 133, 397, 397, 397, 397, 396, 396, 78, + 397, 397, 64, 41, 41, 41, 41, 41, 41, 62, 62, 62, 62, 62, 75, 44, 44, 44, + 44, 44, 44, 44, 44, 44, 65, 65, 65, 65, 65, 44, 44, 44, 44, 65, 65, 65, + 65, 65, 41, 41, 41, 41, 41, 398, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, + 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 65, 78, 78, 133, 78, 78, + 78, 78, 78, 78, 78, 133, 78, 78, 399, 400, 133, 401, 78, 78, 78, 78, 78, + 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 78, 64, + 64, 64, 64, 64, 402, 133, 78, 133, 37, 41, 37, 41, 37, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 37, 41, 62, 62, 62, 62, 62, 62, 62, 62, 61, 61, 61, + 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 64, 64, 61, 61, 61, 61, 61, + 61, 64, 64, 64, 61, 64, 61, 64, 61, 64, 61, 403, 403, 403, 403, 403, 403, + 403, 403, 62, 62, 62, 62, 62, 64, 62, 62, 61, 61, 61, 61, 403, 63, 62, + 63, 63, 63, 62, 62, 62, 64, 62, 62, 61, 61, 61, 61, 403, 63, 63, 63, 62, + 62, 62, 62, 64, 64, 62, 62, 61, 61, 61, 61, 64, 63, 63, 63, 61, 61, 61, + 61, 61, 63, 63, 63, 64, 64, 62, 62, 62, 64, 62, 62, 61, 61, 61, 61, 403, + 63, 63, 64, 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, 404, 405, + 406, 406, 407, 408, 409, 410, 410, 409, 409, 409, 22, 66, 411, 412, 413, + 414, 411, 412, 413, 414, 22, 22, 22, 66, 22, 22, 22, 22, 415, 416, 417, + 418, 419, 420, 421, 21, 422, 423, 422, 422, 423, 22, 66, 66, 66, 28, 35, + 22, 66, 66, 22, 424, 424, 66, 66, 66, 425, 426, 427, 66, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 66, 428, 66, 424, 66, 66, 66, 66, 66, 66, 66, 66, 66, + 66, 404, 405, 405, 405, 405, 405, 64, 64, 64, 64, 64, 405, 405, 405, 405, + 405, 405, 429, 44, 64, 64, 33, 429, 429, 429, 429, 429, 430, 430, 428, + 426, 427, 431, 429, 33, 33, 33, 33, 429, 429, 429, 429, 429, 430, 430, + 428, 426, 427, 64, 44, 44, 44, 44, 44, 64, 64, 64, 247, 247, 247, 247, + 247, 247, 247, 247, 247, 432, 247, 247, 23, 247, 247, 247, 247, 247, 247, + 64, 64, 64, 64, 64, 78, 78, 395, 395, 78, 78, 78, 78, 395, 395, 395, 78, + 78, 433, 433, 433, 433, 78, 433, 433, 433, 395, 395, 78, 133, 78, 395, + 395, 133, 133, 133, 133, 78, 64, 64, 64, 64, 64, 64, 64, 26, 26, 434, 30, + 26, 30, 26, 434, 26, 30, 34, 434, 434, 434, 34, 34, 434, 434, 434, 435, + 26, 434, 30, 26, 428, 434, 434, 434, 434, 434, 26, 26, 26, 30, 30, 26, + 434, 26, 67, 26, 434, 26, 37, 38, 434, 434, 436, 34, 434, 434, 37, 434, + 34, 397, 397, 397, 397, 34, 26, 26, 34, 34, 434, 434, 437, 428, 428, 428, + 428, 434, 34, 34, 34, 34, 26, 428, 26, 26, 41, 274, 438, 438, 438, 36, + 36, 438, 438, 438, 438, 438, 438, 36, 36, 36, 36, 438, 439, 439, 439, + 439, 439, 439, 439, 439, 439, 439, 439, 439, 440, 440, 440, 440, 439, + 439, 440, 440, 440, 440, 440, 440, 440, 440, 440, 37, 41, 440, 440, 440, + 440, 36, 64, 64, 64, 64, 64, 64, 39, 39, 39, 39, 39, 30, 30, 30, 30, 30, + 428, 428, 26, 26, 26, 26, 428, 26, 26, 428, 26, 26, 428, 26, 26, 26, 26, + 26, 26, 26, 428, 26, 26, 26, 26, 26, 26, 26, 26, 26, 30, 30, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 428, 428, 26, 26, 39, 26, 39, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 30, 26, 26, 26, 26, 428, 428, 428, 428, + 428, 428, 428, 428, 428, 428, 428, 428, 39, 437, 441, 441, 437, 428, 428, + 39, 441, 437, 437, 441, 437, 437, 428, 39, 428, 441, 430, 442, 428, 441, + 437, 428, 428, 428, 441, 437, 437, 441, 39, 441, 441, 437, 437, 39, 437, + 39, 437, 39, 39, 39, 39, 441, 441, 437, 441, 437, 437, 437, 437, 437, 39, + 39, 39, 39, 428, 437, 428, 437, 441, 441, 437, 437, 437, 437, 437, 437, + 437, 437, 437, 437, 441, 437, 437, 437, 441, 428, 428, 428, 428, 428, + 441, 437, 437, 437, 428, 428, 428, 428, 428, 428, 428, 428, 428, 437, + 441, 39, 437, 428, 441, 441, 441, 441, 437, 437, 441, 441, 428, 428, 441, + 441, 437, 437, 441, 441, 437, 437, 441, 441, 437, 437, 437, 437, 437, + 428, 428, 437, 437, 437, 437, 428, 428, 39, 428, 428, 437, 39, 428, 428, + 428, 428, 428, 428, 428, 428, 437, 437, 428, 39, 437, 437, 437, 428, 428, + 428, 428, 428, 437, 441, 428, 437, 437, 437, 437, 437, 428, 428, 437, + 437, 428, 428, 428, 428, 437, 437, 437, 437, 437, 437, 437, 437, 428, + 428, 437, 437, 437, 437, 26, 26, 26, 26, 26, 26, 30, 26, 26, 26, 26, 26, + 437, 437, 26, 26, 26, 26, 26, 26, 26, 443, 444, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 274, 274, 274, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 26, 428, 26, 26, 26, 26, 26, 26, 26, 26, 274, 26, 26, 26, + 26, 26, 428, 428, 428, 428, 428, 428, 428, 428, 428, 26, 26, 26, 26, 428, + 428, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 64, 64, 64, 64, 26, 26, 26, + 26, 26, 26, 26, 64, 26, 26, 26, 64, 64, 64, 64, 64, 36, 36, 36, 36, 36, + 36, 36, 36, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 445, 445, + 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, 438, 36, 36, + 36, 36, 36, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 26, 26, 26, + 26, 26, 26, 30, 30, 30, 30, 26, 26, 30, 30, 26, 30, 30, 30, 30, 30, 26, + 26, 30, 30, 26, 26, 30, 39, 26, 26, 26, 26, 30, 30, 26, 26, 30, 39, 26, + 26, 26, 26, 30, 30, 30, 26, 26, 30, 26, 26, 30, 30, 26, 26, 26, 26, 26, + 30, 30, 26, 26, 30, 26, 26, 26, 26, 30, 30, 26, 26, 26, 26, 30, 26, 30, + 26, 30, 26, 30, 26, 26, 26, 26, 26, 30, 30, 26, 30, 30, 30, 26, 30, 30, + 30, 30, 26, 30, 30, 26, 39, 26, 26, 26, 26, 26, 26, 30, 30, 26, 26, 26, + 26, 274, 26, 26, 26, 26, 26, 26, 26, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 26, 30, 30, 30, 26, 30, 26, 26, 26, 26, 64, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 30, 26, 26, 426, 427, 426, 427, 426, 427, 426, + 427, 426, 427, 426, 427, 426, 427, 36, 36, 438, 438, 438, 438, 438, 438, + 438, 438, 438, 438, 438, 438, 26, 26, 26, 26, 437, 428, 428, 437, 437, + 426, 427, 428, 437, 437, 428, 437, 437, 437, 428, 428, 428, 428, 428, + 437, 437, 437, 437, 428, 428, 428, 428, 428, 437, 437, 437, 428, 428, + 428, 437, 437, 437, 437, 9, 10, 9, 10, 9, 10, 9, 10, 426, 427, 446, 446, + 446, 446, 446, 446, 446, 446, 428, 428, 428, 426, 427, 9, 10, 426, 427, + 426, 427, 426, 427, 426, 427, 426, 427, 428, 428, 437, 437, 437, 437, + 437, 437, 428, 428, 428, 428, 428, 428, 428, 428, 437, 428, 428, 428, + 428, 437, 437, 437, 437, 437, 428, 437, 437, 428, 428, 426, 427, 426, + 427, 437, 428, 428, 428, 428, 437, 428, 437, 437, 437, 428, 428, 437, + 437, 428, 428, 428, 428, 428, 428, 428, 428, 428, 428, 437, 437, 437, + 437, 437, 437, 428, 428, 426, 427, 428, 428, 428, 428, 437, 437, 437, + 437, 437, 437, 437, 437, 437, 437, 437, 428, 437, 437, 437, 437, 428, + 428, 437, 428, 437, 428, 428, 437, 428, 437, 437, 437, 437, 428, 428, + 428, 428, 428, 437, 437, 428, 428, 428, 428, 437, 437, 437, 437, 428, + 437, 437, 428, 428, 437, 437, 428, 428, 428, 428, 437, 437, 437, 437, + 437, 437, 437, 437, 437, 437, 437, 428, 428, 437, 437, 437, 437, 437, + 437, 437, 437, 428, 437, 437, 437, 437, 437, 437, 437, 437, 428, 428, + 428, 428, 428, 437, 428, 437, 428, 428, 428, 437, 437, 437, 437, 437, + 428, 428, 428, 428, 437, 428, 428, 428, 437, 437, 437, 437, 437, 428, + 437, 428, 428, 428, 428, 428, 428, 428, 26, 26, 428, 428, 428, 428, 428, + 428, 64, 64, 64, 26, 26, 26, 26, 26, 30, 30, 30, 30, 30, 64, 64, 64, 64, + 64, 64, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, 447, + 447, 447, 64, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, 448, + 448, 448, 448, 64, 37, 41, 37, 37, 37, 41, 41, 37, 41, 37, 41, 37, 41, + 37, 37, 37, 37, 41, 37, 41, 41, 37, 41, 41, 41, 41, 41, 41, 44, 44, 37, + 37, 69, 70, 69, 70, 70, 449, 449, 449, 449, 449, 449, 69, 70, 69, 70, + 450, 450, 450, 69, 70, 64, 64, 64, 64, 64, 451, 451, 451, 451, 452, 451, + 451, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, 453, + 453, 64, 453, 64, 64, 64, 64, 64, 453, 64, 64, 454, 454, 454, 454, 454, + 454, 454, 454, 64, 64, 64, 64, 64, 64, 64, 455, 456, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 457, 77, 77, 77, 77, 77, 77, 77, 77, + 66, 66, 28, 35, 28, 35, 66, 66, 66, 28, 35, 66, 28, 35, 66, 66, 66, 66, + 66, 66, 66, 66, 66, 410, 66, 66, 410, 66, 28, 35, 66, 66, 28, 35, 426, + 427, 426, 427, 426, 427, 426, 427, 66, 66, 66, 66, 66, 45, 66, 66, 410, + 410, 64, 64, 64, 64, 458, 458, 458, 458, 458, 458, 458, 458, 458, 458, + 64, 458, 458, 458, 458, 458, 458, 458, 458, 458, 64, 64, 64, 64, 458, + 458, 458, 458, 458, 458, 64, 64, 459, 459, 459, 459, 459, 459, 459, 459, + 459, 459, 459, 459, 64, 64, 64, 64, 460, 461, 461, 461, 459, 462, 463, + 464, 443, 444, 443, 444, 443, 444, 443, 444, 443, 444, 459, 459, 443, + 444, 443, 444, 443, 444, 443, 444, 465, 466, 467, 467, 459, 464, 464, + 464, 464, 464, 464, 464, 464, 464, 468, 469, 470, 471, 472, 472, 465, + 473, 473, 473, 473, 473, 459, 459, 464, 464, 464, 462, 463, 461, 459, 26, + 64, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, + 474, 474, 474, 474, 474, 474, 474, 474, 64, 64, 475, 475, 476, 476, 477, + 477, 474, 465, 478, 478, 478, 478, 478, 478, 478, 478, 478, 478, 478, + 478, 478, 478, 478, 478, 478, 478, 461, 473, 479, 479, 478, 64, 64, 64, + 64, 64, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480, 480, + 480, 480, 480, 480, 64, 64, 64, 287, 287, 287, 287, 287, 287, 287, 287, + 287, 287, 287, 287, 287, 287, 64, 481, 481, 482, 482, 482, 482, 481, 481, + 481, 481, 481, 481, 481, 481, 481, 481, 480, 480, 480, 64, 64, 64, 64, + 64, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 483, 484, + 484, 64, 482, 482, 482, 482, 482, 482, 482, 482, 482, 482, 481, 481, 481, + 481, 481, 481, 485, 485, 485, 485, 485, 485, 485, 485, 459, 486, 486, + 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 486, 483, + 483, 483, 483, 484, 484, 484, 481, 481, 486, 486, 486, 486, 486, 486, + 486, 481, 481, 481, 481, 459, 459, 459, 459, 487, 487, 487, 487, 487, + 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 64, 481, 481, 481, 481, + 481, 481, 481, 459, 459, 459, 459, 481, 481, 481, 481, 481, 481, 481, + 481, 481, 481, 481, 459, 459, 488, 489, 489, 489, 489, 489, 489, 489, + 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 489, 488, + 490, 490, 490, 490, 490, 490, 490, 490, 490, 490, 489, 489, 489, 489, + 488, 490, 490, 490, 491, 491, 491, 491, 491, 491, 491, 491, 491, 491, + 491, 491, 491, 492, 491, 491, 491, 491, 491, 491, 491, 64, 64, 64, 493, + 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 493, 64, + 494, 494, 494, 494, 494, 494, 494, 494, 495, 495, 495, 495, 495, 495, + 496, 496, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, 497, + 498, 499, 499, 499, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, + 497, 497, 64, 64, 64, 64, 72, 75, 72, 75, 72, 75, 501, 77, 79, 79, 79, + 502, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 502, 503, 64, 64, 64, 64, + 64, 64, 64, 77, 504, 504, 504, 504, 504, 504, 504, 504, 504, 504, 504, + 504, 504, 504, 505, 505, 505, 505, 505, 505, 505, 505, 505, 505, 506, + 506, 507, 507, 507, 507, 507, 507, 47, 47, 47, 47, 47, 47, 47, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 47, 47, 37, 41, 37, 41, 37, 41, 41, 41, 37, + 41, 37, 41, 37, 41, 44, 41, 41, 41, 41, 41, 41, 41, 41, 37, 41, 37, 41, + 37, 37, 41, 45, 508, 508, 37, 41, 37, 41, 64, 37, 41, 37, 41, 64, 64, 64, + 64, 37, 41, 37, 64, 64, 64, 64, 64, 44, 44, 41, 42, 42, 42, 42, 42, 509, + 509, 510, 509, 509, 509, 511, 509, 509, 509, 509, 510, 509, 509, 509, + 509, 509, 509, 509, 509, 509, 509, 509, 509, 509, 509, 509, 512, 512, + 510, 510, 512, 513, 513, 513, 513, 64, 64, 64, 64, 514, 514, 514, 514, + 514, 514, 274, 274, 247, 436, 64, 64, 64, 64, 64, 64, 515, 515, 515, 515, + 515, 515, 515, 515, 515, 515, 515, 515, 516, 516, 516, 516, 517, 517, + 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, 518, + 518, 518, 518, 518, 517, 517, 517, 517, 517, 517, 517, 517, 517, 517, + 517, 517, 517, 517, 517, 517, 519, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 520, 520, 521, 521, 521, 521, 521, 521, 521, 521, 521, 521, 64, 64, 64, + 64, 64, 64, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 169, 169, + 169, 169, 169, 169, 174, 174, 174, 169, 64, 64, 64, 64, 522, 522, 522, + 522, 522, 522, 522, 522, 522, 522, 523, 523, 523, 523, 523, 523, 523, + 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 523, 524, + 524, 524, 524, 524, 525, 525, 525, 526, 526, 527, 527, 527, 527, 527, + 527, 527, 527, 527, 527, 527, 527, 527, 527, 527, 528, 528, 528, 528, + 528, 528, 528, 528, 528, 528, 528, 529, 530, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 531, 287, 287, 287, 287, 287, 64, 64, 64, 532, 532, 532, + 533, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534, 534, + 534, 534, 535, 533, 533, 532, 532, 532, 532, 533, 533, 532, 533, 533, + 533, 536, 537, 537, 537, 537, 537, 537, 537, 537, 537, 537, 537, 537, + 537, 64, 538, 539, 539, 539, 539, 539, 539, 539, 539, 539, 539, 64, 64, + 64, 64, 537, 537, 540, 540, 540, 540, 540, 540, 540, 540, 540, 541, 541, + 541, 541, 541, 541, 542, 542, 541, 541, 542, 542, 541, 541, 64, 540, 540, + 540, 541, 540, 540, 540, 540, 540, 540, 540, 540, 541, 542, 64, 64, 543, + 543, 543, 543, 543, 543, 543, 543, 543, 543, 64, 64, 544, 544, 544, 544, + 545, 275, 275, 275, 275, 275, 275, 283, 283, 283, 275, 276, 64, 64, 64, + 64, 546, 546, 546, 546, 546, 546, 546, 546, 547, 546, 547, 547, 548, 546, + 546, 547, 547, 546, 546, 546, 546, 546, 547, 547, 546, 547, 546, 64, 64, + 64, 64, 64, 64, 64, 64, 546, 546, 549, 550, 550, 551, 551, 551, 551, 551, + 551, 551, 551, 551, 551, 551, 552, 553, 553, 552, 552, 554, 554, 551, + 555, 555, 552, 556, 64, 64, 289, 289, 289, 289, 289, 289, 64, 551, 551, + 551, 552, 552, 553, 552, 552, 553, 552, 552, 554, 552, 556, 64, 64, 557, + 557, 557, 557, 557, 557, 557, 557, 557, 557, 64, 64, 64, 64, 64, 64, 287, + 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, 558, + 558, 558, 558, 558, 287, 64, 64, 64, 64, 288, 288, 288, 288, 288, 288, + 288, 64, 64, 64, 64, 288, 288, 288, 288, 288, 288, 288, 288, 288, 64, 64, + 64, 64, 559, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 559, + 560, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561, 561, + 561, 561, 561, 561, 561, 561, 561, 561, 561, 560, 488, 488, 488, 488, + 488, 488, 488, 488, 488, 488, 488, 488, 488, 488, 490, 490, 488, 488, + 490, 490, 490, 490, 490, 490, 41, 41, 41, 41, 41, 41, 41, 64, 64, 64, 64, + 83, 83, 83, 83, 83, 64, 64, 64, 64, 64, 109, 562, 109, 109, 563, 109, + 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 64, 109, 109, + 109, 109, 109, 64, 109, 64, 109, 109, 64, 109, 109, 64, 109, 109, 123, + 123, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564, 564, + 564, 564, 564, 64, 64, 64, 64, 64, 64, 64, 64, 64, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 123, 413, 565, 64, 64, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 123, 114, 26, 64, 64, 58, 58, 58, 58, 58, 58, + 58, 58, 461, 461, 461, 461, 461, 461, 461, 466, 467, 461, 64, 64, 64, 64, + 64, 64, 461, 465, 465, 566, 566, 466, 467, 466, 467, 466, 467, 466, 467, + 466, 467, 466, 467, 466, 467, 466, 467, 461, 461, 466, 467, 461, 461, + 461, 461, 566, 566, 566, 567, 461, 567, 64, 461, 567, 461, 461, 465, 443, + 444, 443, 444, 443, 444, 568, 461, 461, 569, 570, 571, 571, 572, 64, 461, + 573, 568, 461, 64, 64, 64, 64, 123, 123, 123, 123, 123, 64, 123, 123, + 123, 123, 123, 123, 123, 64, 64, 405, 64, 574, 574, 575, 576, 575, 574, + 574, 577, 578, 574, 579, 580, 581, 580, 580, 582, 582, 582, 582, 582, + 582, 582, 582, 582, 582, 580, 574, 583, 584, 583, 574, 574, 585, 585, + 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, 585, + 585, 585, 577, 574, 578, 586, 587, 586, 588, 588, 588, 588, 588, 588, + 588, 588, 588, 588, 588, 588, 588, 588, 588, 588, 588, 588, 577, 584, + 578, 584, 577, 578, 589, 590, 591, 589, 589, 592, 592, 592, 592, 592, + 592, 592, 592, 592, 592, 593, 592, 592, 592, 592, 592, 592, 592, 592, + 592, 592, 592, 592, 592, 593, 593, 594, 594, 594, 594, 594, 594, 594, + 594, 594, 594, 594, 594, 594, 594, 594, 64, 64, 64, 594, 594, 594, 594, + 594, 594, 64, 64, 594, 594, 594, 64, 64, 64, 576, 576, 584, 586, 595, + 576, 576, 64, 596, 597, 597, 597, 597, 596, 596, 64, 64, 598, 598, 598, + 26, 30, 64, 64, 599, 599, 599, 599, 599, 599, 599, 599, 599, 599, 599, + 599, 64, 599, 599, 599, 599, 599, 599, 599, 599, 599, 599, 64, 599, 599, + 599, 64, 599, 599, 64, 599, 599, 599, 599, 599, 599, 599, 64, 64, 599, + 599, 599, 64, 64, 64, 64, 64, 84, 66, 84, 64, 64, 64, 64, 514, 514, 514, + 514, 514, 514, 514, 514, 514, 514, 514, 514, 514, 64, 64, 64, 274, 600, + 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 601, 601, + 601, 601, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, 602, + 602, 602, 602, 602, 602, 601, 64, 64, 64, 64, 64, 274, 274, 274, 274, + 274, 133, 64, 64, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, 603, + 603, 603, 64, 64, 64, 604, 604, 604, 604, 604, 604, 604, 604, 604, 64, + 64, 64, 64, 64, 64, 64, 605, 605, 605, 605, 605, 605, 605, 605, 605, 605, + 605, 605, 605, 605, 605, 64, 606, 606, 606, 606, 64, 64, 64, 64, 607, + 607, 607, 607, 607, 607, 607, 607, 607, 608, 607, 607, 607, 607, 607, + 607, 607, 607, 608, 64, 64, 64, 64, 64, 609, 609, 609, 609, 609, 609, + 609, 609, 609, 609, 609, 609, 609, 609, 64, 610, 611, 611, 611, 611, 611, + 611, 611, 611, 611, 611, 611, 611, 64, 64, 64, 64, 612, 613, 613, 613, + 613, 613, 64, 64, 614, 614, 614, 614, 614, 614, 614, 614, 615, 615, 615, + 615, 615, 615, 615, 615, 616, 616, 616, 616, 616, 616, 616, 616, 617, + 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 617, 64, 64, + 618, 618, 618, 618, 618, 618, 618, 618, 618, 618, 64, 64, 64, 64, 64, 64, + 619, 619, 619, 619, 619, 619, 64, 64, 619, 64, 619, 619, 619, 619, 619, + 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, 619, + 619, 64, 619, 619, 64, 64, 64, 619, 64, 64, 619, 620, 620, 620, 620, 620, + 620, 620, 620, 620, 620, 620, 620, 620, 620, 64, 621, 622, 622, 622, 622, + 622, 622, 622, 622, 623, 623, 623, 623, 623, 623, 623, 623, 623, 623, + 623, 623, 623, 623, 624, 624, 624, 624, 624, 624, 64, 64, 64, 625, 626, + 626, 626, 626, 626, 626, 626, 626, 626, 626, 64, 64, 64, 64, 64, 627, + 628, 628, 628, 628, 628, 628, 628, 628, 629, 629, 629, 629, 629, 629, + 629, 629, 64, 64, 64, 64, 64, 64, 629, 629, 630, 631, 631, 631, 64, 631, + 631, 64, 64, 64, 64, 64, 631, 632, 631, 633, 630, 630, 630, 630, 64, 630, + 630, 630, 64, 630, 630, 630, 630, 630, 630, 630, 630, 630, 630, 630, 630, + 630, 630, 630, 630, 630, 630, 630, 64, 64, 64, 64, 633, 634, 632, 64, 64, + 64, 64, 635, 636, 636, 636, 636, 636, 636, 636, 636, 637, 637, 637, 637, + 637, 637, 637, 637, 637, 64, 64, 64, 64, 64, 64, 64, 638, 638, 638, 638, + 638, 638, 638, 638, 638, 638, 638, 638, 638, 639, 639, 640, 641, 641, + 641, 641, 641, 641, 641, 641, 641, 641, 641, 641, 641, 641, 64, 64, 64, + 642, 642, 642, 642, 642, 642, 642, 643, 643, 643, 643, 643, 643, 643, + 643, 643, 643, 643, 643, 643, 643, 64, 64, 644, 644, 644, 644, 644, 644, + 644, 644, 645, 645, 645, 645, 645, 645, 645, 645, 645, 645, 645, 64, 64, + 64, 64, 64, 646, 646, 646, 646, 646, 646, 646, 646, 647, 647, 647, 647, + 647, 647, 647, 647, 647, 64, 64, 64, 64, 64, 64, 64, 648, 648, 648, 648, + 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 648, 64, 649, 650, 649, + 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 651, 650, + 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 650, 652, + 653, 653, 653, 653, 653, 653, 653, 64, 64, 64, 64, 654, 654, 654, 654, + 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, 654, + 654, 654, 655, 655, 655, 655, 655, 655, 655, 655, 655, 655, 656, 656, + 657, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, 658, + 657, 657, 657, 656, 656, 656, 656, 657, 657, 659, 660, 661, 661, 662, + 661, 661, 661, 661, 64, 64, 64, 64, 64, 64, 663, 663, 663, 663, 663, 663, + 663, 663, 663, 64, 64, 64, 64, 64, 64, 64, 664, 664, 664, 664, 664, 664, + 664, 664, 664, 664, 64, 64, 64, 64, 64, 64, 665, 665, 665, 666, 666, 666, + 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, 666, + 666, 666, 666, 667, 667, 667, 667, 667, 668, 667, 667, 667, 667, 667, + 667, 669, 669, 64, 670, 670, 670, 670, 670, 670, 670, 670, 670, 670, 671, + 671, 671, 671, 64, 64, 64, 64, 672, 672, 673, 674, 674, 674, 674, 674, + 674, 674, 674, 674, 674, 674, 674, 674, 674, 674, 674, 673, 673, 673, + 672, 672, 672, 672, 672, 672, 672, 672, 672, 673, 675, 674, 674, 674, + 674, 676, 676, 676, 676, 64, 64, 64, 64, 64, 64, 64, 677, 677, 677, 677, + 677, 677, 677, 677, 677, 677, 64, 64, 64, 64, 64, 64, 678, 678, 678, 678, + 678, 678, 678, 678, 678, 678, 678, 679, 680, 679, 680, 680, 679, 679, + 679, 679, 679, 679, 681, 682, 683, 683, 683, 683, 683, 683, 683, 683, + 683, 683, 64, 64, 64, 64, 64, 64, 684, 684, 684, 684, 684, 684, 684, 684, + 684, 684, 684, 684, 684, 684, 684, 64, 685, 685, 685, 685, 685, 685, 685, + 685, 685, 685, 685, 64, 64, 64, 64, 64, 686, 686, 686, 686, 64, 64, 64, + 64, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, 687, + 687, 64, 504, 64, 64, 64, 64, 64, 64, 64, 688, 688, 688, 688, 688, 688, + 688, 688, 688, 688, 688, 688, 688, 64, 64, 64, 688, 689, 689, 689, 689, + 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, 689, + 689, 689, 689, 689, 64, 64, 64, 64, 64, 64, 64, 64, 690, 690, 690, 690, + 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 691, 478, + 474, 64, 64, 64, 64, 64, 64, 274, 274, 274, 274, 274, 274, 64, 64, 274, + 274, 274, 274, 274, 274, 274, 64, 64, 274, 274, 274, 274, 274, 274, 274, + 274, 274, 274, 274, 274, 692, 692, 395, 395, 395, 274, 274, 274, 693, + 692, 692, 692, 692, 692, 405, 405, 405, 405, 405, 405, 405, 405, 133, + 133, 133, 133, 133, 133, 133, 133, 274, 274, 78, 78, 78, 78, 78, 133, + 133, 274, 274, 274, 274, 274, 274, 78, 78, 78, 78, 274, 274, 602, 602, + 694, 694, 694, 602, 64, 64, 514, 514, 64, 64, 64, 64, 64, 64, 434, 434, + 434, 434, 434, 434, 434, 434, 434, 434, 34, 34, 34, 34, 34, 34, 34, 34, + 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 434, 434, 434, 434, 434, 434, + 434, 434, 434, 434, 34, 34, 34, 34, 34, 34, 34, 64, 34, 34, 34, 34, 34, + 34, 434, 64, 434, 434, 64, 64, 434, 64, 64, 434, 434, 64, 64, 434, 434, + 434, 434, 64, 434, 434, 34, 34, 64, 34, 64, 34, 34, 34, 34, 34, 34, 34, + 64, 34, 34, 34, 34, 34, 34, 34, 434, 434, 64, 434, 434, 434, 434, 64, 64, + 434, 434, 434, 434, 434, 434, 434, 434, 64, 434, 434, 434, 434, 434, 434, + 434, 64, 34, 34, 434, 434, 64, 434, 434, 434, 434, 64, 434, 434, 434, + 434, 434, 64, 434, 64, 64, 64, 434, 434, 434, 434, 434, 434, 434, 64, 34, + 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 64, 64, 434, 695, 34, 34, 34, + 34, 34, 34, 34, 34, 34, 437, 34, 34, 34, 34, 34, 34, 434, 434, 434, 434, + 434, 434, 434, 434, 434, 695, 34, 34, 34, 34, 34, 34, 34, 34, 34, 437, + 34, 34, 434, 434, 434, 434, 434, 695, 34, 34, 34, 34, 34, 34, 34, 34, 34, + 437, 34, 34, 34, 34, 34, 34, 434, 434, 434, 434, 434, 434, 434, 434, 434, + 695, 34, 437, 34, 34, 34, 34, 34, 34, 34, 34, 434, 34, 64, 64, 696, 696, + 696, 696, 696, 696, 696, 696, 696, 696, 123, 123, 123, 123, 64, 123, 123, + 123, 64, 123, 123, 64, 123, 64, 64, 123, 64, 123, 123, 123, 123, 123, + 123, 123, 123, 123, 123, 64, 123, 123, 123, 123, 64, 123, 64, 123, 64, + 64, 64, 64, 64, 64, 123, 64, 64, 64, 64, 123, 64, 123, 64, 123, 64, 123, + 123, 123, 64, 123, 64, 123, 64, 123, 64, 123, 64, 123, 123, 123, 123, 64, + 123, 64, 123, 123, 64, 123, 123, 123, 123, 123, 123, 123, 123, 123, 64, + 64, 64, 64, 64, 123, 123, 123, 64, 123, 123, 123, 111, 111, 64, 64, 64, + 64, 64, 64, 33, 33, 33, 64, 64, 64, 64, 64, 445, 445, 445, 445, 445, 445, + 274, 64, 445, 445, 26, 26, 64, 64, 64, 64, 445, 445, 445, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 274, 274, 697, 481, 481, 64, 64, 64, 64, 64, + 481, 481, 481, 64, 64, 64, 64, 64, 481, 64, 64, 64, 64, 64, 64, 64, 481, + 481, 64, 64, 64, 64, 64, 64, 26, 64, 64, 64, 64, 64, 64, 64, 26, 26, 26, + 26, 26, 26, 64, 26, 26, 26, 26, 26, 26, 64, 64, 64, 26, 26, 26, 26, 26, + 64, 26, 26, 26, 64, 26, 26, 26, 26, 26, 26, 64, 26, 26, 26, 26, 64, 64, + 64, 26, 26, 26, 26, 26, 26, 64, 64, 64, 64, 64, 26, 26, 26, 26, 26, 26, + 64, 64, 64, 64, 26, 26, 26, 489, 489, 489, 489, 489, 489, 488, 490, 490, + 490, 490, 490, 490, 490, 64, 64, 64, 405, 64, 64, 64, 64, 64, 64, 405, + 405, 405, 405, 405, 405, 405, 405, 561, 561, 561, 561, 561, 560, 64, 64, + ]; +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Utils/Arrays.php b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/Arrays.php new file mode 100644 index 0000000..f27c83a --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/Arrays.php @@ -0,0 +1,92 @@ +<?php + +namespace Mpdf\Utils; + +class Arrays +{ + + public static function get($array, $key, $default = null) + { + if (is_array($array) && array_key_exists($key, $array)) { + return $array[$key]; + } + + if (func_num_args() < 3) { + throw new \InvalidArgumentException(sprintf('Array does not contain key "%s"', $key)); + } + + return $default; + } + + /** + * Returns an array of all k-combinations from an input array of n elements, where k equals 1..n. + * Elements will be sorted and unique in every combination. + * + * Example: array[one, two] will give: + * [ + * [one], + * [two], + * [one, two] + * ] + * @param array $array + * @return array + */ + public static function allUniqueSortedCombinations($array) + { + $input = array_unique($array); + if (count($input) <= 1) { + return [$input]; + } + + sort($input); + $combinations = []; + foreach ($input as $value) { + $combinations[] = [$value]; + } + + $n = count($input); + for ($k = 2; $k <= $n; $k++) { + $combinations = array_merge($combinations, self::combinations($input, $k)); + } + + return $combinations; + } + + /** + * Returns an array of unique k-combinations from an input array. + * + * Example: array=[one, two, three] and k=2 will give: + * [ + * [one, two], + * [one, three] + * ] + * @param array $array + * @param int $k + * @return array + */ + public static function combinations($array, $k) + { + $n = count($array); + $combinations = []; + $indexes = range(0, $k - 1); + $maxIndexes = range($n - $k, $n - 1); + do { + $combination = []; + foreach ($indexes as $index) { + $combination[] = $array[$index]; + } + $combinations[] = $combination; + + $anotherCombination = false; + for ($i = $k - 1; $i >= 0; $i--) { + if ($indexes[$i] < $maxIndexes[$i]) { + $indexes[$i]++; + $anotherCombination = true; + break; + } + } + } while ($anotherCombination); + + return $combinations; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Utils/NumericString.php b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/NumericString.php new file mode 100644 index 0000000..db10287 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/NumericString.php @@ -0,0 +1,18 @@ +<?php + +namespace Mpdf\Utils; + +class NumericString +{ + + public static function containsPercentChar($string) + { + return strstr($string, '%'); + } + + public static function removePercentChar($string) + { + return str_replace('%', '', $string); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Utils/PdfDate.php b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/PdfDate.php new file mode 100644 index 0000000..1fc3900 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/PdfDate.php @@ -0,0 +1,30 @@ +<?php + +namespace Mpdf\Utils; + +class PdfDate +{ + + /** + * PDF documents use the internal date format: (D:YYYYMMDDHHmmSSOHH'mm'). The date format has these parts: + * + * YYYY The full four-digit year. (For example, 2004) + * MM The month from 01 to 12. + * DD The day from 01 to 31. + * HH The hour from 00 to 23. + * mm The minute from 00 to 59. + * SS The seconds from 00 to 59. + * O The relationship of local time to Universal Time (UT), as denoted by one of the characters +, -, or Z. + * HH The absolute value of the offset from UT in hours specified as 00 to 23. + * mm The absolute value of the offset from UT in minutes specified as 00 to 59. + * + * @return string + */ + public static function format($date) + { + $z = date('O'); // +0200 + $offset = substr($z, 0, 3) . "'" . substr($z, 3, 2) . "'"; // +02'00' + return date('YmdHis', $date) . $offset; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Utils/UtfString.php b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/UtfString.php new file mode 100644 index 0000000..8b5c8c9 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Utils/UtfString.php @@ -0,0 +1,67 @@ +<?php + +namespace Mpdf\Utils; + +class UtfString +{ + + /** + * Converts all the &#nnn; and &#xhhh; in a string to Unicode + * + * @since mPDF 5.7 + * @param string $str + * @param bool $lo + * + * @return string + */ + public static function strcode2utf($str, $lo = true) + { + $str = preg_replace_callback('/\&\#(\d+)\;/m', function ($matches) use ($lo) { + return static::code2utf($matches[1], $lo ? 1 : 0); + }, $str); + $str = preg_replace_callback('/\&\#x([0-9a-fA-F]+)\;/m', function ($matches) use ($lo) { + return static::codeHex2utf($matches[1], $lo ? 1 : 0); + }, $str); + + return $str; + } + + /** + * @param int $num + * @param bool $lo + * + * @return string + */ + public static function code2utf($num, $lo = true) + { + // Returns the utf string corresponding to the unicode value + if ($num < 128) { + if ($lo) { + return chr($num); + } + return '&#' . $num . ';'; + } + if ($num < 2048) { + return chr(($num >> 6) + 192) . chr(($num & 63) + 128); + } + if ($num < 65536) { + return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); + } + if ($num < 2097152) { + return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128); + } + + return '?'; + } + + public static function codeHex2utf($hex, $lo = true) + { + $num = hexdec($hex); + if (($num < 128) && !$lo) { + return '&#x' . $hex . ';'; + } + + return static::code2utf($num, $lo); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BackgroundWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BackgroundWriter.php new file mode 100644 index 0000000..6dbce84 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BackgroundWriter.php @@ -0,0 +1,451 @@ +<?php + +namespace Mpdf\Writer; + +use Mpdf\Strict; +use Mpdf\Mpdf; + +final class BackgroundWriter +{ + + use Strict; + + /** + * @var \Mpdf\Mpdf + */ + private $mpdf; + + /** + * @var \Mpdf\Writer\BaseWriter + */ + private $writer; + + public function __construct(Mpdf $mpdf, BaseWriter $writer) + { + $this->mpdf = $mpdf; + $this->writer = $writer; + } + + public function writePatterns() // _putpatterns + { + $patternCount = count($this->mpdf->patterns); + + for ($i = 1; $i <= $patternCount; $i++) { + + $x = $this->mpdf->patterns[$i]['x']; + $y = $this->mpdf->patterns[$i]['y']; + $w = $this->mpdf->patterns[$i]['w']; + $h = $this->mpdf->patterns[$i]['h']; + $pgh = $this->mpdf->patterns[$i]['pgh']; + $orig_w = $this->mpdf->patterns[$i]['orig_w']; + $orig_h = $this->mpdf->patterns[$i]['orig_h']; + $image_id = $this->mpdf->patterns[$i]['image_id']; + $itype = $this->mpdf->patterns[$i]['itype']; + + if (isset($this->mpdf->patterns[$i]['bpa'])) { + $bpa = $this->mpdf->patterns[$i]['bpa']; + } else { + $bpa = []; // background positioning area + } + + if ($this->mpdf->patterns[$i]['x_repeat']) { + $x_repeat = true; + } else { + $x_repeat = false; + } + + if ($this->mpdf->patterns[$i]['y_repeat']) { + $y_repeat = true; + } else { + $y_repeat = false; + } + + $x_pos = $this->mpdf->patterns[$i]['x_pos']; + + if (false !== strpos($x_pos, '%')) { + $x_pos = (float) $x_pos; + $x_pos /= 100; + + if (isset($bpa['w']) && $bpa['w']) { + $x_pos = ($bpa['w'] * $x_pos) - ($orig_w / Mpdf::SCALE * $x_pos); + } else { + $x_pos = ($w * $x_pos) - ($orig_w / Mpdf::SCALE * $x_pos); + } + } + + $y_pos = $this->mpdf->patterns[$i]['y_pos']; + + if (false !== strpos($y_pos, '%')) { + $y_pos = (float) $y_pos; + $y_pos /= 100; + + if (isset($bpa['h']) && $bpa['h']) { + $y_pos = ($bpa['h'] * $y_pos) - ($orig_h / Mpdf::SCALE * $y_pos); + } else { + $y_pos = ($h * $y_pos) - ($orig_h / Mpdf::SCALE * $y_pos); + } + } + + if (isset($bpa['x']) && $bpa['x']) { + $adj_x = ($x_pos + $bpa['x']) * Mpdf::SCALE; + } else { + $adj_x = ($x_pos + $x) * Mpdf::SCALE; + } + + if (isset($bpa['y']) && $bpa['y']) { + $adj_y = (($pgh - $y_pos - $bpa['y']) * Mpdf::SCALE) - $orig_h; + } else { + $adj_y = (($pgh - $y_pos - $y) * Mpdf::SCALE) - $orig_h; + } + + $img_obj = false; + + if ($itype === 'svg' || $itype === 'wmf') { + foreach ($this->mpdf->formobjects as $fo) { + if ($fo['i'] == $image_id) { + $img_obj = $fo['n']; + $fo_w = $fo['w']; + $fo_h = -$fo['h']; + $wmf_x = $fo['x']; + $wmf_y = $fo['y']; + break; + } + } + } else { + foreach ($this->mpdf->images as $img) { + if ($img['i'] == $image_id) { + $img_obj = $img['n']; + break; + } + } + } + + if (!$img_obj) { + throw new \Mpdf\MpdfException('Problem: Image object not found for background pattern ' . $img['i']); + } + + $this->writer->object(); + $this->writer->write('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + + if ($itype === 'svg' || $itype === 'wmf') { + $this->writer->write('/XObject <</FO' . $image_id . ' ' . $img_obj . ' 0 R >>'); + + // ******* ADD ANY ExtGStates, Shading AND Fonts needed for the FormObject + // Set in classes/svg array['fo'] = true + // Required that _putshaders comes before _putpatterns in _putresources + // This adds any resources associated with any FormObject to every Formobject - overkill but works! + if (count($this->mpdf->extgstates)) { + $this->writer->write('/ExtGState <<'); + foreach ($this->mpdf->extgstates as $k => $extgstate) { + if (isset($extgstate['fo']) && $extgstate['fo']) { + if (isset($extgstate['trans'])) { + $this->writer->write('/' . $extgstate['trans'] . ' ' . $extgstate['n'] . ' 0 R'); + } else { + $this->writer->write('/GS' . $k . ' ' . $extgstate['n'] . ' 0 R'); + } + } + } + $this->writer->write('>>'); + } + + /* -- BACKGROUNDS -- */ + if (isset($this->mpdf->gradients) && ( count($this->mpdf->gradients) > 0)) { + $this->writer->write('/Shading <<'); + foreach ($this->mpdf->gradients as $id => $grad) { + if (isset($grad['fo']) && $grad['fo']) { + $this->writer->write('/Sh' . $id . ' ' . $grad['id'] . ' 0 R'); + } + } + $this->writer->write('>>'); + } + + /* -- END BACKGROUNDS -- */ + $this->writer->write('/Font <<'); + + foreach ($this->mpdf->fonts as $font) { + if (!$font['used'] && $font['type'] === 'TTF') { + continue; + } + if (isset($font['fo']) && $font['fo']) { + if ($font['type'] === 'TTF' && ($font['sip'] || $font['smp'])) { + foreach ($font['n'] as $k => $fid) { + $this->writer->write('/F' . $font['subsetfontids'][$k] . ' ' . $font['n'][$k] . ' 0 R'); + } + } else { + $this->writer->write('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); + } + } + } + $this->writer->write('>>'); + } else { + $this->writer->write('/XObject <</I' . $image_id . ' ' . $img_obj . ' 0 R >>'); + } + + $this->writer->write('>>'); + $this->writer->write('endobj'); + + $this->writer->object(); + $this->mpdf->patterns[$i]['n'] = $this->mpdf->n; + $this->writer->write('<< /Type /Pattern /PatternType 1 /PaintType 1 /TilingType 2'); + $this->writer->write('/Resources ' . ($this->mpdf->n - 1) . ' 0 R'); + + $this->writer->write(sprintf('/BBox [0 0 %.3F %.3F]', $orig_w, $orig_h)); + + if ($x_repeat) { + $this->writer->write(sprintf('/XStep %.3F', $orig_w)); + } else { + $this->writer->write(sprintf('/XStep %d', 99999)); + } + + if ($y_repeat) { + $this->writer->write(sprintf('/YStep %.3F', $orig_h)); + } else { + $this->writer->write(sprintf('/YStep %d', 99999)); + } + + if ($itype === 'svg' || $itype === 'wmf') { + $this->writer->write(sprintf('/Matrix [1 0 0 -1 %.3F %.3F]', $adj_x, $adj_y + $orig_h)); + $s = sprintf('q %.3F 0 0 %.3F %.3F %.3F cm /FO%d Do Q', $orig_w / $fo_w, -$orig_h / $fo_h, -($orig_w / $fo_w) * $wmf_x, ($orig_w / $fo_w) * $wmf_y, $image_id); + } else { + $this->writer->write(sprintf('/Matrix [1 0 0 1 %.3F %.3F]', $adj_x, $adj_y)); + $s = sprintf('q %.3F 0 0 %.3F 0 0 cm /I%d Do Q', $orig_w, $orig_h, $image_id); + } + + if ($this->mpdf->compress) { + $this->writer->write('/Filter /FlateDecode'); + $s = gzcompress($s); + } + $this->writer->write('/Length ' . strlen($s) . '>>'); + $this->writer->stream($s); + $this->writer->write('endobj'); + } + } + + public function writeShaders() // _putshaders + { + $maxid = count($this->mpdf->gradients); // index for transparency gradients + + foreach ($this->mpdf->gradients as $id => $grad) { + + if (empty($grad['is_mask']) && ($grad['type'] == 2 || $grad['type'] == 3)) { + + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/FunctionType 3'); + $this->writer->write('/Domain [0 1]'); + + $fn = []; + $bd = []; + $en = []; + + for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { + $fn[] = ($this->mpdf->n + 1 + $i) . ' 0 R'; + $en[] = '0 1'; + if ($i > 0) { + $bd[] = sprintf('%.3F', $grad['stops'][$i]['offset']); + } + } + + $this->writer->write('/Functions [' . implode(' ', $fn) . ']'); + $this->writer->write('/Bounds [' . implode(' ', $bd) . ']'); + $this->writer->write('/Encode [' . implode(' ', $en) . ']'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + $f1 = $this->mpdf->n; + + for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/FunctionType 2'); + $this->writer->write('/Domain [0 1]'); + $this->writer->write('/C0 [' . $grad['stops'][$i]['col'] . ']'); + $this->writer->write('/C1 [' . $grad['stops'][$i + 1]['col'] . ']'); + $this->writer->write('/N 1'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + } + + if ($grad['type'] == 2 || $grad['type'] == 3) { + + if (isset($grad['trans']) && $grad['trans']) { + + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/FunctionType 3'); + $this->writer->write('/Domain [0 1]'); + + $fn = []; + $bd = []; + $en = []; + + for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { + $fn[] = ($this->mpdf->n + 1 + $i) . ' 0 R'; + $en[] = '0 1'; + if ($i > 0) { + $bd[] = sprintf('%.3F', $grad['stops'][$i]['offset']); + } + } + + $this->writer->write('/Functions [' . implode(' ', $fn) . ']'); + $this->writer->write('/Bounds [' . implode(' ', $bd) . ']'); + $this->writer->write('/Encode [' . implode(' ', $en) . ']'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + $f2 = $this->mpdf->n; + + for ($i = 0; $i < (count($grad['stops']) - 1); $i++) { + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/FunctionType 2'); + $this->writer->write('/Domain [0 1]'); + $this->writer->write(sprintf('/C0 [%.3F]', $grad['stops'][$i]['opacity'])); + $this->writer->write(sprintf('/C1 [%.3F]', $grad['stops'][$i + 1]['opacity'])); + $this->writer->write('/N 1'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + } + } + + if (empty($grad['is_mask'])) { + + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/ShadingType ' . $grad['type']); + + if (isset($grad['colorspace'])) { + $this->writer->write('/ColorSpace /Device' . $grad['colorspace']); // Can use CMYK if all C0 and C1 above have 4 values + } else { + $this->writer->write('/ColorSpace /DeviceRGB'); + } + + if ($grad['type'] == 2) { + $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3])); + $this->writer->write('/Function ' . $f1 . ' 0 R'); + $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); + $this->writer->write('>>'); + } elseif ($grad['type'] == 3) { + // x0, y0, r0, x1, y1, r1 + // at this this time radius of inner circle is 0 + $ir = 0; + if (isset($grad['coords'][5]) && $grad['coords'][5]) { + $ir = $grad['coords'][5]; + } + $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $ir, $grad['coords'][2], $grad['coords'][3], $grad['coords'][4])); + $this->writer->write('/Function ' . $f1 . ' 0 R'); + $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); + $this->writer->write('>>'); + } elseif ($grad['type'] == 6) { + $this->writer->write('/BitsPerCoordinate 16'); + $this->writer->write('/BitsPerComponent 8'); + if ($grad['colorspace'] === 'CMYK') { + $this->writer->write('/Decode[0 1 0 1 0 1 0 1 0 1 0 1]'); + } elseif ($grad['colorspace'] === 'Gray') { + $this->writer->write('/Decode[0 1 0 1 0 1]'); + } else { + $this->writer->write('/Decode[0 1 0 1 0 1 0 1 0 1]'); + } + $this->writer->write('/BitsPerFlag 8'); + $this->writer->write('/Length ' . strlen($grad['stream'])); + $this->writer->write('>>'); + $this->writer->stream($grad['stream']); + } + + $this->writer->write('endobj'); + } + + $this->mpdf->gradients[$id]['id'] = $this->mpdf->n; + + // set pattern object + $this->writer->object(); + $out = '<< /Type /Pattern /PatternType 2'; + $out .= ' /Shading ' . $this->mpdf->gradients[$id]['id'] . ' 0 R'; + $out .= ' >>'; + $out .= "\n" . 'endobj'; + $this->writer->write($out); + + + $this->mpdf->gradients[$id]['pattern'] = $this->mpdf->n; + + if (isset($grad['trans']) && $grad['trans']) { + + // luminosity pattern + $transid = $id + $maxid; + + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/ShadingType ' . $grad['type']); + $this->writer->write('/ColorSpace /DeviceGray'); + + if ($grad['type'] == 2) { + $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3])); + $this->writer->write('/Function ' . $f2 . ' 0 R'); + $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); + $this->writer->write('>>'); + } elseif ($grad['type'] == 3) { + // x0, y0, r0, x1, y1, r1 + // at this this time radius of inner circle is 0 + $ir = 0; + if (isset($grad['coords'][5]) && $grad['coords'][5]) { + $ir = $grad['coords'][5]; + } + $this->writer->write(sprintf('/Coords [%.3F %.3F %.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $ir, $grad['coords'][2], $grad['coords'][3], $grad['coords'][4])); + $this->writer->write('/Function ' . $f2 . ' 0 R'); + $this->writer->write('/Extend [' . $grad['extend'][0] . ' ' . $grad['extend'][1] . '] '); + $this->writer->write('>>'); + } elseif ($grad['type'] == 6) { + $this->writer->write('/BitsPerCoordinate 16'); + $this->writer->write('/BitsPerComponent 8'); + $this->writer->write('/Decode[0 1 0 1 0 1]'); + $this->writer->write('/BitsPerFlag 8'); + $this->writer->write('/Length ' . strlen($grad['stream_trans'])); + $this->writer->write('>>'); + $this->writer->stream($grad['stream_trans']); + } + $this->writer->write('endobj'); + + $this->mpdf->gradients[$transid]['id'] = $this->mpdf->n; + + $this->writer->object(); + $this->writer->write('<< /Type /Pattern /PatternType 2'); + $this->writer->write('/Shading ' . $this->mpdf->gradients[$transid]['id'] . ' 0 R'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + $this->mpdf->gradients[$transid]['pattern'] = $this->mpdf->n; + $this->writer->object(); + + // Need to extend size of viewing box in case of transformations + $str = 'q /a0 gs /Pattern cs /p' . $transid . ' scn -' . ($this->mpdf->wPt / 2) . ' -' . ($this->mpdf->hPt / 2) . ' ' . (2 * $this->mpdf->wPt) . ' ' . (2 * $this->mpdf->hPt) . ' re f Q'; + $filter = ($this->mpdf->compress) ? '/Filter /FlateDecode ' : ''; + $p = ($this->mpdf->compress) ? gzcompress($str) : $str; + + $this->writer->write('<< /Type /XObject /Subtype /Form /FormType 1 ' . $filter); + $this->writer->write('/Length ' . strlen($p)); + $this->writer->write('/BBox [-' . ($this->mpdf->wPt / 2) . ' -' . ($this->mpdf->hPt / 2) . ' ' . (2 * $this->mpdf->wPt) . ' ' . (2 * $this->mpdf->hPt) . ']'); + $this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceGray >>'); + $this->writer->write('/Resources <<'); + $this->writer->write('/ExtGState << /a0 << /ca 1 /CA 1 >> >>'); + $this->writer->write('/Pattern << /p' . $transid . ' ' . $this->mpdf->gradients[$transid]['pattern'] . ' 0 R >>'); + $this->writer->write('>>'); + $this->writer->write('>>'); + $this->writer->stream($p); + $this->writer->write('endobj'); + $this->writer->object(); + $this->writer->write('<< /Type /Mask /S /Luminosity /G ' . ($this->mpdf->n - 1) . ' 0 R >>' . "\n" . 'endobj'); + $this->writer->object(); + $this->writer->write('<< /Type /ExtGState /SMask ' . ($this->mpdf->n - 1) . ' 0 R /AIS false >>' . "\n" . 'endobj'); + + if (isset($grad['fo']) && $grad['fo']) { + $this->mpdf->extgstates[] = ['n' => $this->mpdf->n, 'trans' => 'TGS' . $id, 'fo' => true]; + } else { + $this->mpdf->extgstates[] = ['n' => $this->mpdf->n, 'trans' => 'TGS' . $id]; + } + } + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BaseWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BaseWriter.php new file mode 100644 index 0000000..8abff91 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BaseWriter.php @@ -0,0 +1,250 @@ +<?php + +namespace Mpdf\Writer; + +use Mpdf\Strict; + +use Mpdf\Mpdf; +use Mpdf\Pdf\Protection; + +final class BaseWriter +{ + + use Strict; + + /** + * @var \Mpdf\Mpdf + */ + private $mpdf; + + /** + * @var \Mpdf\Pdf\Protection + */ + private $protection; + + public function __construct(Mpdf $mpdf, Protection $protection) + { + $this->mpdf = $mpdf; + $this->protection = $protection; + } + + public function write($s, $ln = true) + { + if ($this->mpdf->state === 2) { + $this->endPage($s, $ln); + } else { + $this->mpdf->buffer .= $s . ($ln ? "\n" : ''); + } + } + + public function string($s) + { + if ($this->mpdf->encrypted) { + $s = $this->protection->rc4($this->protection->objectKey($this->mpdf->currentObjectNumber), $s); + } + + return '(' . $this->escape($s) . ')'; + } + + public function object($obj_id = false, $onlynewobj = false) + { + if (!$obj_id) { + $obj_id = ++$this->mpdf->n; + } + + // Begin a new object + if (!$onlynewobj) { + $this->mpdf->offsets[$obj_id] = strlen($this->mpdf->buffer); + $this->write($obj_id . ' 0 obj'); + $this->mpdf->currentObjectNumber = $obj_id; // for later use with encryption + } + } + + public function stream($s) + { + if ($this->mpdf->encrypted) { + $s = $this->protection->rc4($this->protection->objectKey($this->mpdf->currentObjectNumber), $s); + } + + $this->write('stream'); + $this->write($s); + $this->write('endstream'); + } + + public function utf16BigEndianTextString($s) // _UTF16BEtextstring + { + $s = $this->utf8ToUtf16BigEndian($s, true); + if ($this->mpdf->encrypted) { + $s = $this->protection->rc4($this->protection->objectKey($this->mpdf->currentObjectNumber), $s); + } + + return '(' . $this->escape($s) . ')'; + } + + // Converts UTF-8 strings to UTF16-BE. + public function utf8ToUtf16BigEndian($str, $setbom = true) // UTF8ToUTF16BE + { + if ($this->mpdf->checkSIP && preg_match("/([\x{20000}-\x{2FFFF}])/u", $str)) { + if (!in_array($this->mpdf->currentfontfamily, ['gb', 'big5', 'sjis', 'uhc', 'gbB', 'big5B', 'sjisB', 'uhcB', 'gbI', 'big5I', 'sjisI', 'uhcI', + 'gbBI', 'big5BI', 'sjisBI', 'uhcBI'])) { + $str = preg_replace("/[\x{20000}-\x{2FFFF}]/u", chr(0), $str); + } + } + if ($this->mpdf->checkSMP && preg_match("/([\x{10000}-\x{1FFFF}])/u", $str)) { + $str = preg_replace("/[\x{10000}-\x{1FFFF}]/u", chr(0), $str); + } + + $outstr = ''; // string to be returned + if ($setbom) { + $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM) + } + + $outstr .= mb_convert_encoding($str, 'UTF-16BE', 'UTF-8'); + + return $outstr; + } + + public function escape($s) // _escape + { + return strtr($s, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']); + } + + public function escapeSlashes($s) // _escapeName + { + return strtr($s, ['/' => '#2F']); + } + + /** + * Un-escapes a PDF string + * + * @param string $s + * @return string + */ + public function unescape($s) + { + $out = ''; + for ($count = 0, $n = strlen($s); $count < $n; $count++) { + if ($count === $n - 1 || $s[$count] !== '\\') { + $out .= $s[$count]; + } else { + switch ($s[++$count]) { + case ')': + case '(': + case '\\': + $out .= $s[$count]; + break; + case 'f': + $out .= chr(0x0C); + break; + case 'b': + $out .= chr(0x08); + break; + case 't': + $out .= chr(0x09); + break; + case 'r': + $out .= chr(0x0D); + break; + case 'n': + $out .= chr(0x0A); + break; + case "\r": + if ($count !== $n - 1 && $s[$count + 1] === "\n") { + $count++; + } + break; + case "\n": + break; + default: + // Octal-Values + $ord = ord($s[$count]); + if ($ord >= ord('0') && $ord <= ord('9')) { + $oct = ''. $s[$count]; + $ord = ord($s[$count + 1]); + if ($ord >= ord('0') && $ord <= ord('9')) { + $oct .= $s[++$count]; + $ord = ord($s[$count + 1]); + if ($ord >= ord('0') && $ord <= ord('9')) { + $oct .= $s[++$count]; + } + } + $out .= chr(octdec($oct)); + } else { + $out .= $s[$count]; + } + } + } + } + + return $out; + } + + private function endPage($s, $ln) + { + if ($this->mpdf->bufferoutput) { + + $this->mpdf->headerbuffer.= $s . "\n"; + + } elseif ($this->mpdf->ColActive && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) { + + // Captures everything in buffer for columns; Almost everything is sent from fn. Cell() except: + // Images sent from Image() or + // later sent as write($textto) in printbuffer + // Line() + + if (preg_match('/q \d+\.\d\d+ 0 0 (\d+\.\d\d+) \d+\.\d\d+ \d+\.\d\d+ cm \/(I|FO)\d+ Do Q/', $s, $m)) { // Image data + + $h = ($m[1] / Mpdf::SCALE); + // Update/overwrite the lowest bottom of printing y value for a column + $this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'] = $this->mpdf->y + $h; + + } elseif ($this->mpdf->tableLevel > 0 && preg_match('/\d+\.\d\d+ \d+\.\d\d+ \d+\.\d\d+ ([\-]{0,1}\d+\.\d\d+) re/', $s, $m)) { // Rect in table + + $h = ($m[1] / Mpdf::SCALE); + // Update/overwrite the lowest bottom of printing y value for a column + $this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'] = max($this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'], $this->mpdf->y + $h); + + } elseif (isset($this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'])) { + + $h = $this->mpdf->ColDetails[$this->mpdf->CurrCol]['bottom_margin'] - $this->mpdf->y; + + } else { + + $h = 0; + + } + + if ($h < 0) { + $h = -$h; + } + + $this->mpdf->columnbuffer[] = [ + 's' => $s, // Text string to output + 'col' => $this->mpdf->CurrCol, // Column when printed + 'x' => $this->mpdf->x, // x when printed + 'y' => $this->mpdf->y, // this->y when printed (after column break) + 'h' => $h // actual y at bottom when printed = y+h + ]; + + } elseif ($this->mpdf->table_rotate && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) { + + // Captures eveything in buffer for rotated tables; + $this->mpdf->tablebuffer .= $s . "\n"; + + } elseif ($this->mpdf->kwt && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) { + + // Captures eveything in buffer for keep-with-table (h1-6); + $this->mpdf->kwt_buffer[] = [ + 's' => $s, // Text string to output + 'x' => $this->mpdf->x, // x when printed + 'y' => $this->mpdf->y, // y when printed + ]; + + } elseif ($this->mpdf->keep_block_together && !$this->mpdf->processingHeader && !$this->mpdf->processingFooter) { + // do nothing + } else { + $this->mpdf->pages[$this->mpdf->page] .= $s . ($ln ? "\n" : ''); + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BookmarkWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BookmarkWriter.php new file mode 100644 index 0000000..bbec0d7 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/BookmarkWriter.php @@ -0,0 +1,138 @@ +<?php + +namespace Mpdf\Writer; + +use Mpdf\Strict; +use Mpdf\Mpdf; + +final class BookmarkWriter +{ + + use Strict; + + /** + * @var \Mpdf\Mpdf + */ + private $mpdf; + + /** + * @var \Mpdf\Writer\BaseWriter + */ + private $writer; + + public function __construct(Mpdf $mpdf, BaseWriter $writer) + { + $this->mpdf = $mpdf; + $this->writer = $writer; + } + + public function writeBookmarks() // _putbookmarks + { + $nb = count($this->mpdf->BMoutlines); + if ($nb === 0) { + return; + } + + $bmo = $this->mpdf->BMoutlines; + $this->mpdf->BMoutlines = []; + $lastlevel = -1; + for ($i = 0; $i < count($bmo); $i++) { + if ($bmo[$i]['l'] > 0) { + while ($bmo[$i]['l'] - $lastlevel > 1) { // If jump down more than one level, insert a new entry + $new = $bmo[$i]; + $new['t'] = "[" . $new['t'] . "]"; // Put [] around text/title to highlight + $new['l'] = $lastlevel + 1; + $lastlevel++; + $this->mpdf->BMoutlines[] = $new; + } + } + $this->mpdf->BMoutlines[] = $bmo[$i]; + $lastlevel = $bmo[$i]['l']; + } + $nb = count($this->mpdf->BMoutlines); + + $lru = []; + $level = 0; + foreach ($this->mpdf->BMoutlines as $i => $o) { + if ($o['l'] > 0) { + $parent = $lru[$o['l'] - 1]; + // Set parent and last pointers + $this->mpdf->BMoutlines[$i]['parent'] = $parent; + $this->mpdf->BMoutlines[$parent]['last'] = $i; + if ($o['l'] > $level) { + // Level increasing: set first pointer + $this->mpdf->BMoutlines[$parent]['first'] = $i; + } + } else { + $this->mpdf->BMoutlines[$i]['parent'] = $nb; + } + if ($o['l'] <= $level and $i > 0) { + // Set prev and next pointers + $prev = $lru[$o['l']]; + $this->mpdf->BMoutlines[$prev]['next'] = $i; + $this->mpdf->BMoutlines[$i]['prev'] = $prev; + } + $lru[$o['l']] = $i; + $level = $o['l']; + } + + + // Outline items + $n = $this->mpdf->n + 1; + foreach ($this->mpdf->BMoutlines as $i => $o) { + $this->writer->object(); + $this->writer->write('<writer->utf16BigEndianTextString($o['t'])); + $this->writer->write('/Parent ' . ($n + $o['parent']) . ' 0 R'); + if (isset($o['prev'])) { + $this->writer->write('/Prev ' . ($n + $o['prev']) . ' 0 R'); + } + if (isset($o['next'])) { + $this->writer->write('/Next ' . ($n + $o['next']) . ' 0 R'); + } + if (isset($o['first'])) { + $this->writer->write('/First ' . ($n + $o['first']) . ' 0 R'); + } + if (isset($o['last'])) { + $this->writer->write('/Last ' . ($n + $o['last']) . ' 0 R'); + } + + + if (isset($this->mpdf->pageDim[$o['p']]['h'])) { + $h = $this->mpdf->pageDim[$o['p']]['h']; + } else { + $h = 0; + } + + $this->writer->write(sprintf('/Dest [%d 0 R /XYZ 0 %.3F null]', 1 + 2 * ($o['p']), ($h - $o['y']) * Mpdf::SCALE)); + if (isset($this->mpdf->bookmarkStyles) && isset($this->mpdf->bookmarkStyles[$o['l']])) { + // font style + $bms = $this->mpdf->bookmarkStyles[$o['l']]['style']; + $style = 0; + if (strpos($bms, 'B') !== false) { + $style += 2; + } + if (strpos($bms, 'I') !== false) { + $style += 1; + } + $this->writer->write(sprintf('/F %d', $style)); + // Colour + $col = $this->mpdf->bookmarkStyles[$o['l']]['color']; + if (isset($col) && is_array($col) && count($col) == 3) { + $this->writer->write(sprintf('/C [%.3F %.3F %.3F]', ($col[0] / 255), ($col[1] / 255), ($col[2] / 255))); + } + } + + $this->writer->write('/Count 0>>'); + $this->writer->write('endobj'); + } + // Outline root + $this->writer->object(); + + $this->mpdf->OutlineRoot = $this->mpdf->n; + + $this->writer->write('<writer->write('/Last ' . ($n + $lru[0]) . ' 0 R>>'); + $this->writer->write('endobj'); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ColorWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ColorWriter.php new file mode 100644 index 0000000..c6ece98 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ColorWriter.php @@ -0,0 +1,46 @@ +mpdf = $mpdf; + $this->writer = $writer; + } + + public function writeSpotColors() // _putspotcolors + { + foreach ($this->mpdf->spotColors as $name => $color) { + + $this->writer->object(); + + $this->writer->write('[/Separation /' . str_replace(' ', '#20', $name)); + $this->writer->write('/DeviceCMYK <<'); + $this->writer->write('/Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0] '); + $this->writer->write(sprintf('/C1 [%.3F %.3F %.3F %.3F] ', $color['c'] / 100, $color['m'] / 100, $color['y'] / 100, $color['k'] / 100)); + $this->writer->write('/FunctionType 2 /Domain [0 1] /N 1>>]'); + $this->writer->write('endobj'); + + $this->mpdf->spotColors[$name]['n'] = $this->mpdf->n; + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/FontWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/FontWriter.php new file mode 100644 index 0000000..8d6b2cf --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/FontWriter.php @@ -0,0 +1,680 @@ +mpdf = $mpdf; + $this->writer = $writer; + $this->fontCache = $fontCache; + $this->fontDescriptor = $fontDescriptor; + } + + public function writeFonts() + { + foreach ($this->mpdf->FontFiles as $fontkey => $info) { + // TrueType embedded + if (isset($info['type']) && $info['type'] === 'TTF' && !$info['sip'] && !$info['smp']) { + $used = true; + $asSubset = false; + foreach ($this->mpdf->fonts as $k => $f) { + if (isset($f['fontkey']) && $f['fontkey'] === $fontkey && $f['type'] === 'TTF') { + $used = $f['used']; + if ($used) { + $nChars = (ord($f['cw'][0]) << 8) + ord($f['cw'][1]); + $usage = (int) (count($f['subset']) * 100 / $nChars); + $fsize = $info['length1']; + // Always subset the very large TTF files + if ($fsize > ($this->mpdf->maxTTFFilesize * 1024)) { + $asSubset = true; + } elseif ($usage < $this->mpdf->percentSubset) { + $asSubset = true; + } + } + if ($this->mpdf->PDFA || $this->mpdf->PDFX) { + $asSubset = false; + } + $this->mpdf->fonts[$k]['asSubset'] = $asSubset; + break; + } + } + if ($used && !$asSubset) { + // Font file embedding + $this->writer->object(); + $this->mpdf->FontFiles[$fontkey]['n'] = $this->mpdf->n; + $originalsize = $info['length1']; + if ($this->mpdf->repackageTTF || $this->mpdf->fonts[$fontkey]['TTCfontID'] > 0 || $this->mpdf->fonts[$fontkey]['useOTL'] > 0) { // mPDF 5.7.1 + // First see if there is a cached compressed file + if ($this->fontCache->has($fontkey . '.ps.z') && $this->fontCache->jsonHas($fontkey . '.ps.json')) { + $font = $this->fontCache->load($fontkey . '.ps.z'); + $originalsize = $this->fontCache->jsonLoad($fontkey . '.ps.json'); // sets $originalsize (of repackaged font) + } else { + $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); + $font = $ttf->repackageTTF($this->mpdf->FontFiles[$fontkey]['ttffile'], $this->mpdf->fonts[$fontkey]['TTCfontID'], $this->mpdf->debugfonts, $this->mpdf->fonts[$fontkey]['useOTL']); // mPDF 5.7.1 + + $originalsize = strlen($font); + $font = gzcompress($font); + unset($ttf); + + $this->fontCache->binaryWrite($fontkey . '.ps.z', $font); + $this->fontCache->jsonWrite($fontkey . '.ps.json', $originalsize); + } + } elseif ($this->fontCache->has($fontkey . '.z')) { + $font = $this->fontCache->load($fontkey . '.z'); + } else { + $font = file_get_contents($this->mpdf->FontFiles[$fontkey]['ttffile']); + $font = gzcompress($font); + $this->fontCache->binaryWrite($fontkey . '.z', $font); + } + + $this->writer->write('<writer->write('/Filter /FlateDecode'); + $this->writer->write('/Length1 ' . $originalsize); + $this->writer->write('>>'); + $this->writer->stream($font); + $this->writer->write('endobj'); + } + } + } + + foreach ($this->mpdf->fonts as $k => $font) { + + // Font objects + $type = $font['type']; + $name = $font['name']; + + if ($type === 'TTF' && (!isset($font['used']) || !$font['used'])) { + continue; + } + + // @log Writing fonts + + if (isset($font['asSubset'])) { + $asSubset = $font['asSubset']; + } else { + $asSubset = ''; + } + + if ($type === 'Type0') { // Adobe CJK Fonts + + $this->mpdf->fonts[$k]['n'] = $this->mpdf->n + 1; + $this->writer->object(); + $this->writer->write('<writeType0($font); + + } elseif ($type === 'core') { + + // Standard font + $this->mpdf->fonts[$k]['n'] = $this->mpdf->n + 1; + + if ($this->mpdf->PDFA || $this->mpdf->PDFX) { + throw new \Mpdf\MpdfException('Core fonts are not allowed in PDF/A1-b or PDFX/1-a files (Times, Helvetica, Courier etc.)'); + } + + $this->writer->object(); + $this->writer->write('<writer->write('/BaseFont /' . $name); + $this->writer->write('/Subtype /Type1'); + + if ($name !== 'Symbol' && $name !== 'ZapfDingbats') { + $this->writer->write('/Encoding /WinAnsiEncoding'); + } + + $this->writer->write('>>'); + $this->writer->write('endobj'); + + } elseif ($type === 'TTF' && ($font['sip'] || $font['smp'])) { + + // TrueType embedded SUBSETS for SIP (CJK extB containing Supplementary Ideographic Plane 2) + // Or Unicode Plane 1 - Supplementary Multilingual Plane + + if (!$font['used']) { + continue; + } + + $ssfaid = 'AA'; + $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); + $subsetCount = count($font['subsetfontids']); + for ($sfid = 0; $sfid < $subsetCount; $sfid++) { + $this->mpdf->fonts[$k]['n'][$sfid] = $this->mpdf->n + 1; // NB an array for subset + $subsetname = 'MPDF' . $ssfaid . '+' . $font['name']; + $ssfaid++; + + /* For some strange reason a subset ($sfid > 0) containing less than 97 characters causes an error + so fill up the array */ + for ($j = count($font['subsets'][$sfid]); $j < 98; $j++) { + $font['subsets'][$sfid][$j] = 0; + } + + $subset = $font['subsets'][$sfid]; + unset($subset[0]); + $ttfontstream = $ttf->makeSubsetSIP($font['ttffile'], $subset, $font['TTCfontID'], $this->mpdf->debugfonts, $font['useOTL']); // mPDF 5.7.1 + $ttfontsize = strlen($ttfontstream); + $fontstream = gzcompress($ttfontstream); + $widthstring = ''; + $toUnistring = ''; + + foreach ($font['subsets'][$sfid] as $cp => $u) { + $w = $this->mpdf->_getCharWidth($font['cw'], $u); + if ($w !== false) { + $widthstring .= $w . ' '; + } else { + $widthstring .= round($ttf->defaultWidth) . ' '; + } + if ($u > 65535) { + $utf8 = chr(($u >> 18) + 240) . chr((($u >> 12) & 63) + 128) . chr((($u >> 6) & 63) + 128) . chr(($u & 63) + 128); + $utf16 = mb_convert_encoding($utf8, 'UTF-16BE', 'UTF-8'); + $l1 = ord($utf16[0]); + $h1 = ord($utf16[1]); + $l2 = ord($utf16[2]); + $h2 = ord($utf16[3]); + $toUnistring .= sprintf("<%02s> <%02s%02s%02s%02s>\n", strtoupper(dechex($cp)), strtoupper(dechex($l1)), strtoupper(dechex($h1)), strtoupper(dechex($l2)), strtoupper(dechex($h2))); + } else { + $toUnistring .= sprintf("<%02s> <%04s>\n", strtoupper(dechex($cp)), strtoupper(dechex($u))); + } + } + + // Additional Type1 or TrueType font + $this->writer->object(); + $this->writer->write('<writer->write('/BaseFont /' . $subsetname); + $this->writer->write('/Subtype /TrueType'); + $this->writer->write('/FirstChar 0 /LastChar ' . (count($font['subsets'][$sfid]) - 1)); + $this->writer->write('/Widths ' . ($this->mpdf->n + 1) . ' 0 R'); + $this->writer->write('/FontDescriptor ' . ($this->mpdf->n + 2) . ' 0 R'); + $this->writer->write('/ToUnicode ' . ($this->mpdf->n + 3) . ' 0 R'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + // Widths + $this->writer->object(); + $this->writer->write('[' . $widthstring . ']'); + $this->writer->write('endobj'); + + // Descriptor + $this->writer->object(); + $s = '< $v) { + if ($kd === 'Flags') { + $v |= 4; + $v &= ~32; + } // SYMBOLIC font flag + $s .= ' /' . $kd . ' ' . $v . "\n"; + } + $s .= '/FontFile2 ' . ($this->mpdf->n + 2) . ' 0 R'; + $this->writer->write($s . '>>'); + $this->writer->write('endobj'); + + // ToUnicode + $this->writer->object(); + $toUni = "/CIDInit /ProcSet findresource begin\n"; + $toUni .= "12 dict begin\n"; + $toUni .= "begincmap\n"; + $toUni .= "/CIDSystemInfo\n"; + $toUni .= "< <%02s>\n", strtoupper(dechex(count($font['subsets'][$sfid])-1))); + $toUni .= "endcodespacerange\n"; + $toUni .= count($font['subsets'][$sfid]) . " beginbfchar\n"; + $toUni .= $toUnistring; + $toUni .= "endbfchar\n"; + $toUni .= "endcmap\n"; + $toUni .= "CMapName currentdict /CMap defineresource pop\n"; + $toUni .= "end\n"; + $toUni .= "end\n"; + $this->writer->write('<>'); + $this->writer->stream($toUni); + $this->writer->write('endobj'); + + // Font file + $this->writer->object(); + $this->writer->write('<writer->write('/Filter /FlateDecode'); + $this->writer->write('/Length1 ' . $ttfontsize); + $this->writer->write('>>'); + $this->writer->stream($fontstream); + $this->writer->write('endobj'); + } // foreach subset + unset($ttf); + + } elseif ($type === 'TTF') { // TrueType embedded SUBSETS or FULL + + $this->mpdf->fonts[$k]['n'] = $this->mpdf->n + 1; + + if ($asSubset) { + $ssfaid = 'A'; + $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); + $fontname = 'MPDFA' . $ssfaid . '+' . $font['name']; + $subset = $font['subset']; + unset($subset[0]); + $ttfontstream = $ttf->makeSubset($font['ttffile'], $subset, $font['TTCfontID'], $this->mpdf->debugfonts, $font['useOTL']); + $ttfontsize = strlen($ttfontstream); + $fontstream = gzcompress($ttfontstream); + $codeToGlyph = $ttf->codeToGlyph; + unset($codeToGlyph[0]); + } else { + $fontname = $font['name']; + } + + // Type0 Font + // A composite font - a font composed of other fonts, organized hierarchically + $this->writer->object(); + $this->writer->write('<writer->write('/Subtype /Type0'); + $this->writer->write('/BaseFont /' . $fontname . ''); + $this->writer->write('/Encoding /Identity-H'); + $this->writer->write('/DescendantFonts [' . ($this->mpdf->n + 1) . ' 0 R]'); + $this->writer->write('/ToUnicode ' . ($this->mpdf->n + 2) . ' 0 R'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + // CIDFontType2 + // A CIDFont whose glyph descriptions are based on TrueType font technology + $this->writer->object(); + $this->writer->write('<writer->write('/Subtype /CIDFontType2'); + $this->writer->write('/BaseFont /' . $fontname . ''); + $this->writer->write('/CIDSystemInfo ' . ($this->mpdf->n + 2) . ' 0 R'); + $this->writer->write('/FontDescriptor ' . ($this->mpdf->n + 3) . ' 0 R'); + + if (isset($font['desc']['MissingWidth'])) { + $this->writer->write('/DW ' . $font['desc']['MissingWidth'] . ''); + } + + if (!$asSubset && $this->fontCache->has($font['fontkey'] . '.cw')) { + $w = $this->fontCache->load($font['fontkey'] . '.cw'); + $this->writer->write($w); + } else { + $this->writeTTFontWidths($font, $asSubset, ($asSubset ? $ttf->maxUni : 0)); + } + + $this->writer->write('/CIDToGIDMap ' . ($this->mpdf->n + 4) . ' 0 R'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + // ToUnicode + $this->writer->object(); + $toUni = "/CIDInit /ProcSet findresource begin\n"; + $toUni .= "12 dict begin\n"; + $toUni .= "begincmap\n"; + $toUni .= "/CIDSystemInfo\n"; + $toUni .= "<writer->write('<>'); + $this->writer->stream($toUni); + $this->writer->write('endobj'); + + // CIDSystemInfo dictionary + $this->writer->object(); + $this->writer->write('<writer->write('/Ordering (UCS)'); + $this->writer->write('/Supplement 0'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + // Font descriptor + $this->writer->object(); + $this->writer->write('<writer->write('/FontName /' . $fontname); + + foreach ($font['desc'] as $kd => $v) { + if ($asSubset && $kd === 'Flags') { + $v |= 4; + $v &= ~32; + } // SYMBOLIC font flag + $this->writer->write(' /' . $kd . ' ' . $v); + } + + if ($font['panose']) { + $this->writer->write(' /Style << /Panose <' . $font['panose'] . '> >>'); + } + + if ($asSubset) { + $this->writer->write('/FontFile2 ' . ($this->mpdf->n + 2) . ' 0 R'); + } elseif ($font['fontkey']) { + // obj ID of a stream containing a TrueType font program + $this->writer->write('/FontFile2 ' . $this->mpdf->FontFiles[$font['fontkey']]['n'] . ' 0 R'); + } + + $this->writer->write('>>'); + $this->writer->write('endobj'); + + // Embed CIDToGIDMap + // A specification of the mapping from CIDs to glyph indices + if ($asSubset) { + $cidtogidmap = str_pad('', 256 * 256 * 2, "\x00"); + foreach ($codeToGlyph as $cc => $glyph) { + $cidtogidmap[$cc * 2] = chr($glyph >> 8); + $cidtogidmap[$cc * 2 + 1] = chr($glyph & 0xFF); + } + $cidtogidmap = gzcompress($cidtogidmap); + } else { + // First see if there is a cached CIDToGIDMapfile + if ($this->fontCache->has($font['fontkey'] . '.cgm')) { + $cidtogidmap = $this->fontCache->load($font['fontkey'] . '.cgm'); + } else { + $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); + $charToGlyph = $ttf->getCTG($font['ttffile'], $font['TTCfontID'], $this->mpdf->debugfonts, $font['useOTL']); + $cidtogidmap = str_pad('', 256 * 256 * 2, "\x00"); + foreach ($charToGlyph as $cc => $glyph) { + $cidtogidmap[$cc * 2] = chr($glyph >> 8); + $cidtogidmap[$cc * 2 + 1] = chr($glyph & 0xFF); + } + unset($ttf); + $cidtogidmap = gzcompress($cidtogidmap); + $this->fontCache->binaryWrite($font['fontkey'] . '.cgm', $cidtogidmap); + } + } + $this->writer->object(); + $this->writer->write('<writer->write('/Filter /FlateDecode'); + $this->writer->write('>>'); + $this->writer->stream($cidtogidmap); + $this->writer->write('endobj'); + + // Font file + if ($asSubset) { + $this->writer->object(); + $this->writer->write('<writer->write('/Filter /FlateDecode'); + $this->writer->write('/Length1 ' . $ttfontsize); + $this->writer->write('>>'); + $this->writer->stream($fontstream); + $this->writer->write('endobj'); + unset($ttf); + } + } else { + throw new \Mpdf\MpdfException(sprintf('Unsupported font type: %s (%s)', $type, $name)); + } + } + } + + private function writeTTFontWidths(&$font, $asSubset, $maxUni) // _putTTfontwidths + { + $character = [ + 'startcid' => 1, + 'rangeid' => 0, + 'prevcid' => -2, + 'prevwidth' => -1, + 'interval' => false, + 'range' => [], + ]; + + $fontCacheFilename = $font['fontkey'] . '.cw127.json'; + if ($asSubset && $this->fontCache->jsonHas($fontCacheFilename)) { + $character = $this->fontCache->jsonLoad($fontCacheFilename); + $character['startcid'] = 128; + } + + // for each character + $cwlen = ($asSubset) ? $maxUni + 1 : (strlen($font['cw']) / 2); + for ($cid = $character['startcid']; $cid < $cwlen; $cid++) { + if ($cid == 128 && $asSubset && (!$this->fontCache->has($fontCacheFilename))) { + $character = [ + 'rangeid' => $character['rangeid'], + 'prevcid' => $character['prevcid'], + 'prevwidth' => $character['prevwidth'], + 'interval' => $character['interval'], + 'range' => $character['range'], + ]; + + $this->fontCache->jsonWrite($fontCacheFilename, $character); + } + + $character1 = isset($font['cw'][$cid * 2]) ? $font['cw'][$cid * 2] : ''; + $character2 = isset($font['cw'][$cid * 2 + 1]) ? $font['cw'][$cid * 2 + 1] : ''; + + if ($character1 === "\00" && $character2 === "\00") { + continue; + } + + $width = (ord($character1) << 8) + ord($character2); + + if ($width === 65535) { + $width = 0; + } + + if ($asSubset && $cid > 255 && (!isset($font['subset'][$cid]) || !$font['subset'][$cid])) { + continue; + } + + if ($asSubset && $cid > 0xFFFF) { + continue; + } // mPDF 6 + + if (!isset($font['dw']) || (isset($font['dw']) && $width != $font['dw'])) { + if ($cid === ($character['prevcid'] + 1)) { + // consecutive CID + if ($width === $character['prevwidth']) { + if (isset($character['range'][$character['rangeid']][0]) && $width === $character['range'][$character['rangeid']][0]) { + $character['range'][$character['rangeid']][] = $width; + } else { + array_pop($character['range'][$character['rangeid']]); + // new range + $character['rangeid'] = $character['prevcid']; + $character['range'][$character['rangeid']] = []; + $character['range'][$character['rangeid']][] = $character['prevwidth']; + $character['range'][$character['rangeid']][] = $width; + } + $character['interval'] = true; + $character['range'][$character['rangeid']]['interval'] = true; + } else { + if ($character['interval']) { + // new range + $character['rangeid'] = $cid; + $character['range'][$character['rangeid']] = []; + $character['range'][$character['rangeid']][] = $width; + } else { + $character['range'][$character['rangeid']][] = $width; + } + $character['interval'] = false; + } + } else { + // new range + $character['rangeid'] = $cid; + $character['range'][$character['rangeid']] = []; + $character['range'][$character['rangeid']][] = $width; + $character['interval'] = false; + } + $character['prevcid'] = $cid; + $character['prevwidth'] = $width; + } + } + $w = $this->writeFontRanges($character['range']); + $this->writer->write($w); + if (!$asSubset) { + $this->fontCache->binaryWrite($font['fontkey'] . '.cw', $w); + } + } + + private function writeFontRanges(&$range) // _putfontranges + { + // optimize ranges + $prevk = -1; + $nextk = -1; + $prevint = false; + foreach ($range as $k => $ws) { + $cws = count($ws); + if (($k == $nextk) and ( !$prevint) and ( (!isset($ws['interval'])) or ( $cws < 4))) { + if (isset($range[$k]['interval'])) { + unset($range[$k]['interval']); + } + $range[$prevk] = array_merge($range[$prevk], $range[$k]); + unset($range[$k]); + } else { + $prevk = $k; + } + $nextk = $k + $cws; + if (isset($ws['interval'])) { + if ($cws > 3) { + $prevint = true; + } else { + $prevint = false; + } + unset($range[$k]['interval']); + --$nextk; + } else { + $prevint = false; + } + } + // output data + $w = ''; + foreach ($range as $k => $ws) { + if (count(array_count_values($ws)) === 1) { + // interval mode is more compact + $w .= ' ' . $k . ' ' . ($k + count($ws) - 1) . ' ' . $ws[0]; + } else { + // range mode + $w .= ' ' . $k . ' [ ' . implode(' ', $ws) . ' ]' . "\n"; + } + } + return '/W [' . $w . ' ]'; + } + + private function writeFontWidths(&$font, $cidoffset = 0) // _putfontwidths + { + ksort($font['cw']); + unset($font['cw'][65535]); + $rangeid = 0; + $range = []; + $prevcid = -2; + $prevwidth = -1; + $interval = false; + // for each character + foreach ($font['cw'] as $cid => $width) { + $cid -= $cidoffset; + if (!isset($font['dw']) || (isset($font['dw']) && $width != $font['dw'])) { + if ($cid === ($prevcid + 1)) { + // consecutive CID + if ($width === $prevwidth) { + if ($width === $range[$rangeid][0]) { + $range[$rangeid][] = $width; + } else { + array_pop($range[$rangeid]); + // new range + $rangeid = $prevcid; + $range[$rangeid] = []; + $range[$rangeid][] = $prevwidth; + $range[$rangeid][] = $width; + } + $interval = true; + $range[$rangeid]['interval'] = true; + } else { + if ($interval) { + // new range + $rangeid = $cid; + $range[$rangeid] = []; + $range[$rangeid][] = $width; + } else { + $range[$rangeid][] = $width; + } + $interval = false; + } + } else { + // new range + $rangeid = $cid; + $range[$rangeid] = []; + $range[$rangeid][] = $width; + $interval = false; + } + $prevcid = $cid; + $prevwidth = $width; + } + } + $this->writer->write($this->writeFontRanges($range)); + } + + // from class PDF_Chinese CJK EXTENSIONS + public function writeType0(&$font) // _putType0 + { + // Type0 + $this->writer->write('/Subtype /Type0'); + $this->writer->write('/BaseFont /' . $font['name'] . '-' . $font['CMap']); + $this->writer->write('/Encoding /' . $font['CMap']); + $this->writer->write('/DescendantFonts [' . ($this->mpdf->n + 1) . ' 0 R]'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + // CIDFont + $this->writer->object(); + $this->writer->write('<writer->write('/Subtype /CIDFontType0'); + $this->writer->write('/BaseFont /' . $font['name']); + + $cidinfo = '/Registry ' . $this->writer->string('Adobe'); + $cidinfo .= ' /Ordering ' . $this->writer->string($font['registry']['ordering']); + $cidinfo .= ' /Supplement ' . $font['registry']['supplement']; + $this->writer->write('/CIDSystemInfo <<' . $cidinfo . '>>'); + + $this->writer->write('/FontDescriptor ' . ($this->mpdf->n + 1) . ' 0 R'); + if (isset($font['MissingWidth'])) { + $this->writer->write('/DW ' . $font['MissingWidth'] . ''); + } + $this->writeFontWidths($font, 31); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + // Font descriptor + $this->writer->object(); + $s = '< $v) { + if ($k !== 'Style') { + $s .= ' /' . $k . ' ' . $v . ''; + } + } + $this->writer->write($s . '>>'); + $this->writer->write('endobj'); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/FormWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/FormWriter.php new file mode 100644 index 0000000..609aac5 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/FormWriter.php @@ -0,0 +1,63 @@ +mpdf = $mpdf; + $this->writer = $writer; + } + + public function writeFormObjects() // _putformobjects + { + foreach ($this->mpdf->formobjects as $file => $info) { + + $this->writer->object(); + + $this->mpdf->formobjects[$file]['n'] = $this->mpdf->n; + + $this->writer->write('<writer->write('/Subtype /Form'); + $this->writer->write('/Group ' . ($this->mpdf->n + 1) . ' 0 R'); + $this->writer->write('/BBox [' . $info['x'] . ' ' . $info['y'] . ' ' . ($info['w'] + $info['x']) . ' ' . ($info['h'] + $info['y']) . ']'); + + if ($this->mpdf->compress) { + $this->writer->write('/Filter /FlateDecode'); + } + + $data = $this->mpdf->compress ? gzcompress($info['data']) : $info['data']; + $this->writer->write('/Length ' . strlen($data) . '>>'); + $this->writer->stream($data); + + unset($this->mpdf->formobjects[$file]['data']); + + $this->writer->write('endobj'); + + // Required for SVG transparency (opacity) to work + $this->writer->object(); + $this->writer->write('<writer->write('/S /Transparency'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ImageWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ImageWriter.php new file mode 100644 index 0000000..b4d3ac8 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ImageWriter.php @@ -0,0 +1,119 @@ +mpdf = $mpdf; + $this->writer = $writer; + } + + public function writeImages() + { + $filter = $this->mpdf->compress ? '/Filter /FlateDecode ' : ''; + + foreach ($this->mpdf->images as $file => $info) { + + $this->writer->object(); + + $this->mpdf->images[$file]['n'] = $this->mpdf->n; + + $this->writer->write('<writer->write('/Subtype /Image'); + $this->writer->write('/Width ' . $info['w']); + $this->writer->write('/Height ' . $info['h']); + + if (isset($info['interpolation']) && $info['interpolation']) { + $this->writer->write('/Interpolate true'); // mPDF 6 - image interpolation shall be performed by a conforming reader + } + + if (isset($info['masked'])) { + $this->writer->write('/SMask ' . ($this->mpdf->n - 1) . ' 0 R'); + } + + // set color space + $icc = false; + if (isset($info['icc']) && ( $info['icc'] !== false)) { + // ICC Colour Space + $icc = true; + $this->writer->write('/ColorSpace [/ICCBased ' . ($this->mpdf->n + 1) . ' 0 R]'); + } elseif ($info['cs'] === 'Indexed') { + if ($this->mpdf->PDFX || ($this->mpdf->PDFA && $this->mpdf->restrictColorSpace === 3)) { + throw new \Mpdf\MpdfException('PDFA1-b and PDFX/1-a files do not permit using mixed colour space (' . $file . ').'); + } + $this->writer->write('/ColorSpace [/Indexed /DeviceRGB ' . (strlen($info['pal']) / 3 - 1) . ' ' . ($this->mpdf->n + 1) . ' 0 R]'); + } else { + $this->writer->write('/ColorSpace /' . $info['cs']); + if ($info['cs'] === 'DeviceCMYK') { + if ($this->mpdf->PDFA && $this->mpdf->restrictColorSpace !== 3) { + throw new \Mpdf\MpdfException('PDFA1-b does not permit Images using mixed colour space (' . $file . ').'); + } + if ($info['type'] === 'jpg') { + $this->writer->write('/Decode [1 0 1 0 1 0 1 0]'); + } + } elseif (($this->mpdf->PDFX || ($this->mpdf->PDFA && $this->mpdf->restrictColorSpace === 3)) && $info['cs'] === 'DeviceRGB') { + throw new \Mpdf\MpdfException('PDFA1-b and PDFX/1-a files do not permit using mixed colour space (' . $file . ').'); + } + } + + $this->writer->write('/BitsPerComponent ' . $info['bpc']); + + if (isset($info['f']) && $info['f']) { + $this->writer->write('/Filter /' . $info['f']); + } + + if (isset($info['parms'])) { + $this->writer->write($info['parms']); + } + + if (isset($info['trns']) && is_array($info['trns'])) { + $trns = ''; + $maskCount = count($info['trns']); + for ($i = 0; $i < $maskCount; $i++) { + $trns .= $info['trns'][$i] . ' ' . $info['trns'][$i] . ' '; + } + $this->writer->write('/Mask [' . $trns . ']'); + } + + $this->writer->write('/Length ' . strlen($info['data']) . '>>'); + $this->writer->stream($info['data']); + + unset($this->mpdf->images[$file]['data']); + + $this->writer->write('endobj'); + + if ($icc) { // ICC colour profile + $this->writer->object(); + $icc = $this->mpdf->compress ? gzcompress($info['icc']) : $info['icc']; + $this->writer->write('<>'); + $this->writer->stream($icc); + $this->writer->write('endobj'); + } elseif ($info['cs'] === 'Indexed') { // Palette + $this->writer->object(); + $pal = $this->mpdf->compress ? gzcompress($info['pal']) : $info['pal']; + $this->writer->write('<<' . $filter . '/Length ' . strlen($pal) . '>>'); + $this->writer->stream($pal); + $this->writer->write('endobj'); + } + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/JavaScriptWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/JavaScriptWriter.php new file mode 100644 index 0000000..f3bb290 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/JavaScriptWriter.php @@ -0,0 +1,46 @@ +mpdf = $mpdf; + $this->writer = $writer; + } + + public function writeJavascript() // _putjavascript + { + $this->writer->object(); + $this->mpdf->n_js = $this->mpdf->n; + $this->writer->write('<<'); + $this->writer->write('/Names [(EmbeddedJS) ' . (1 + $this->mpdf->n) . ' 0 R ]'); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + $this->writer->object(); + $this->writer->write('<<'); + $this->writer->write('/S /JavaScript'); + $this->writer->write('/JS ' . $this->writer->string($this->mpdf->js)); + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/MetadataWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/MetadataWriter.php new file mode 100644 index 0000000..568776e --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/MetadataWriter.php @@ -0,0 +1,821 @@ +mpdf = $mpdf; + $this->writer = $writer; + $this->form = $form; + $this->protection = $protection; + $this->logger = $logger; + } + + public function writeMetadata() // _putmetadata + { + $this->writer->object(); + $this->mpdf->MetadataRoot = $this->mpdf->n; + $Producer = 'mPDF ' . Mpdf::VERSION; + $z = date('O'); // +0200 + $offset = substr($z, 0, 3) . ':' . substr($z, 3, 2); + $CreationDate = date('Y-m-d\TH:i:s') . $offset; // 2006-03-10T10:47:26-05:00 2006-06-19T09:05:17Z + $uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0x0fff) | 0x4000, random_int(0, 0x3fff) | 0x8000, random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff)); + + + $m = '' . "\n"; // begin = FEFF BOM + $m .= ' ' . "\n"; + $m .= ' ' . "\n"; + $m .= ' ' . "\n"; + $m .= ' ' . $Producer . '' . "\n"; + if (!empty($this->mpdf->keywords)) { + $m .= ' ' . $this->mpdf->keywords . '' . "\n"; + } + $m .= ' ' . "\n"; + + $m .= ' ' . "\n"; + $m .= ' ' . $CreationDate . '' . "\n"; + $m .= ' ' . $CreationDate . '' . "\n"; + $m .= ' ' . $CreationDate . '' . "\n"; + if (!empty($this->mpdf->creator)) { + $m .= ' ' . $this->mpdf->creator . '' . "\n"; + } + $m .= ' ' . "\n"; + + // DC elements + $m .= ' ' . "\n"; + $m .= ' application/pdf' . "\n"; + if (!empty($this->mpdf->title)) { + $m .= ' + + ' . $this->mpdf->title . ' + + ' . "\n"; + } + if (!empty($this->mpdf->keywords)) { + $m .= ' + + ' . $this->mpdf->keywords . ' + + ' . "\n"; + } + if (!empty($this->mpdf->subject)) { + $m .= ' + + ' . $this->mpdf->subject . ' + + ' . "\n"; + } + if (!empty($this->mpdf->author)) { + $m .= ' + + ' . $this->mpdf->author . ' + + ' . "\n"; + } + $m .= ' ' . "\n"; + + if (!empty($this->mpdf->additionalXmpRdf)) { + $m .= $this->mpdf->additionalXmpRdf; + } + + // This bit is specific to PDFX-1a + if ($this->mpdf->PDFX) { + $m .= ' ' . "\n"; + } // This bit is specific to PDFA-1b + elseif ($this->mpdf->PDFA) { + + if (strpos($this->mpdf->PDFAversion, '-') === false) { + throw new \Mpdf\MpdfException(sprintf('PDFA version (%s) is not valid. (Use: 1-B, 3-B, etc.)', $this->mpdf->PDFAversion)); + } + + list($part, $conformance) = explode('-', strtoupper($this->mpdf->PDFAversion)); + $m .= ' ' . "\n"; + $m .= ' ' . $part . '' . "\n"; + $m .= ' ' . $conformance . '' . "\n"; + if ($part === '1' && $conformance === 'B') { + $m .= ' 2005' . "\n"; + } + $m .= ' ' . "\n"; + } + + $m .= ' ' . "\n"; + $m .= ' uuid:' . $uuid . '' . "\n"; + $m .= ' ' . "\n"; + $m .= ' ' . "\n"; + $m .= ' ' . "\n"; + $m .= str_repeat(str_repeat(' ', 100) . "\n", 20); // 2-4kB whitespace padding required + $m .= ''; // "r" read only + $this->writer->write('<>'); + $this->writer->stream($m); + $this->writer->write('endobj'); + } + + public function writeInfo() // _putinfo + { + $this->writer->write('/Producer ' . $this->writer->utf16BigEndianTextString('mPDF ' . $this->getVersionString())); + + if (!empty($this->mpdf->title)) { + $this->writer->write('/Title ' . $this->writer->utf16BigEndianTextString($this->mpdf->title)); + } + + if (!empty($this->mpdf->subject)) { + $this->writer->write('/Subject ' . $this->writer->utf16BigEndianTextString($this->mpdf->subject)); + } + + if (!empty($this->mpdf->author)) { + $this->writer->write('/Author ' . $this->writer->utf16BigEndianTextString($this->mpdf->author)); + } + + if (!empty($this->mpdf->keywords)) { + $this->writer->write('/Keywords ' . $this->writer->utf16BigEndianTextString($this->mpdf->keywords)); + } + + if (!empty($this->mpdf->creator)) { + $this->writer->write('/Creator ' . $this->writer->utf16BigEndianTextString($this->mpdf->creator)); + } + + foreach ($this->mpdf->customProperties as $key => $value) { + $this->writer->write('/' . $key . ' ' . $this->writer->utf16BigEndianTextString($value)); + } + + $now = PdfDate::format(time()); + $this->writer->write('/CreationDate ' . $this->writer->string('D:' . $now)); + $this->writer->write('/ModDate ' . $this->writer->string('D:' . $now)); + if ($this->mpdf->PDFX) { + $this->writer->write('/Trapped/False'); + $this->writer->write('/GTS_PDFXVersion(PDF/X-1a:2003)'); + } + } + + public function writeOutputIntent() // _putoutputintent + { + $this->writer->object(); + $this->mpdf->OutputIntentRoot = $this->mpdf->n; + $this->writer->write('<mpdf->ICCProfile, '.icc')); + + if ($this->mpdf->PDFA) { + $this->writer->write('/S /GTS_PDFA1'); + if ($this->mpdf->ICCProfile) { + $this->writer->write('/Info (' . $ICCProfile . ')'); + $this->writer->write('/OutputConditionIdentifier (Custom)'); + $this->writer->write('/OutputCondition ()'); + } else { + $this->writer->write('/Info (sRGB IEC61966-2.1)'); + $this->writer->write('/OutputConditionIdentifier (sRGB IEC61966-2.1)'); + $this->writer->write('/OutputCondition ()'); + } + $this->writer->write('/DestOutputProfile ' . ($this->mpdf->n + 1) . ' 0 R'); + } elseif ($this->mpdf->PDFX) { // always a CMYK profile + $this->writer->write('/S /GTS_PDFX'); + if ($this->mpdf->ICCProfile) { + $this->writer->write('/Info (' . $ICCProfile . ')'); + $this->writer->write('/OutputConditionIdentifier (Custom)'); + $this->writer->write('/OutputCondition ()'); + $this->writer->write('/DestOutputProfile ' . ($this->mpdf->n + 1) . ' 0 R'); + } else { + $this->writer->write('/Info (CGATS TR 001)'); + $this->writer->write('/OutputConditionIdentifier (CGATS TR 001)'); + $this->writer->write('/OutputCondition (CGATS TR 001 (SWOP))'); + $this->writer->write('/RegistryName (http://www.color.org)'); + } + } + $this->writer->write('>>'); + $this->writer->write('endobj'); + + if ($this->mpdf->PDFX && !$this->mpdf->ICCProfile) { + return; + } + + $this->writer->object(); + + if ($this->mpdf->ICCProfile) { + if (!file_exists($this->mpdf->ICCProfile)) { + throw new \Mpdf\MpdfException(sprintf('Unable to find ICC profile "%s"', $this->mpdf->ICCProfile)); + } + $s = file_get_contents($this->mpdf->ICCProfile); + } else { + $s = file_get_contents(__DIR__ . '/../../data/iccprofiles/sRGB_IEC61966-2-1.icc'); + } + + if ($this->mpdf->compress) { + $s = gzcompress($s); + } + + $this->writer->write('<<'); + + if ($this->mpdf->PDFX || ($this->mpdf->PDFA && $this->mpdf->restrictColorSpace === 3)) { + $this->writer->write('/N 4'); + } else { + $this->writer->write('/N 3'); + } + + if ($this->mpdf->compress) { + $this->writer->write('/Filter /FlateDecode '); + } + + $this->writer->write('/Length ' . strlen($s) . '>>'); + $this->writer->stream($s); + $this->writer->write('endobj'); + } + + public function writeAssociatedFiles() // _putAssociatedFiles + { + if (!function_exists('gzcompress')) { + throw new \Mpdf\MpdfException('ext-zlib is required for compression of associated files'); + } + + // for each file, we create the spec object + the stream object + foreach ($this->mpdf->associatedFiles as $k => $file) { + // spec + $this->writer->object(); + $this->mpdf->associatedFiles[$k]['_root'] = $this->mpdf->n; // we store the root ref of object for future reference (e.g. /EmbeddedFiles catalog) + $this->writer->write('<writer->string($file['name'])); + if ($file['description']) { + $this->writer->write('/Desc ' . $this->writer->string($file['description'])); + } + $this->writer->write('/Type /Filespec'); + $this->writer->write('/EF <<'); + $this->writer->write('/F ' . ($this->mpdf->n + 1) . ' 0 R'); + $this->writer->write('/UF ' . ($this->mpdf->n + 1) . ' 0 R'); + $this->writer->write('>>'); + if ($file['AFRelationship']) { + $this->writer->write('/AFRelationship /' . $file['AFRelationship']); + } + $this->writer->write('/UF ' . $this->writer->string($file['name'])); + $this->writer->write('>>'); + $this->writer->write('endobj'); + + $fileContent = null; + if (isset($file['path'])) { + $fileContent = @file_get_contents($file['path']); + } elseif (isset($file['content'])) { + $fileContent = $file['content']; + } + + if (!$fileContent) { + throw new \Mpdf\MpdfException(sprintf('Cannot access associated file - %s', $file['path'])); + } + + $filestream = gzcompress($fileContent); + $this->writer->object(); + $this->writer->write('<writer->write('/Subtype /' . $this->writer->escapeSlashes($file['mime'])); + } + $this->writer->write('/Length '.strlen($filestream)); + $this->writer->write('/Filter /FlateDecode'); + if (isset($file['path'])) { + $this->writer->write('/Params <writer->string('D:' . PdfDate::format(filemtime($file['path']))).' >>'); + } else { + $this->writer->write('/Params <writer->string('D:' . PdfDate::format(time())).' >>'); + } + + $this->writer->write('>>'); + $this->writer->stream($filestream); + $this->writer->write('endobj'); + } + + // AF array + $this->writer->object(); + $refs = []; + foreach ($this->mpdf->associatedFiles as $file) { + $refs[] = '' . $file['_root'] . ' 0 R'; + } + $this->writer->write('[' . implode(' ', $refs) . ']'); + $this->writer->write('endobj'); + + $this->mpdf->associatedFilesRoot = $this->mpdf->n; + } + + public function writeCatalog() //_putcatalog + { + $this->writer->write('/Type /Catalog'); + $this->writer->write('/Pages 1 0 R'); + + if ($this->mpdf->ZoomMode === 'fullpage') { + $this->writer->write('/OpenAction [3 0 R /Fit]'); + } elseif ($this->mpdf->ZoomMode === 'fullwidth') { + $this->writer->write('/OpenAction [3 0 R /FitH null]'); + } elseif ($this->mpdf->ZoomMode === 'real') { + $this->writer->write('/OpenAction [3 0 R /XYZ null null 1]'); + } elseif (!is_string($this->mpdf->ZoomMode)) { + $this->writer->write('/OpenAction [3 0 R /XYZ null null ' . ($this->mpdf->ZoomMode / 100) . ']'); + } elseif ($this->mpdf->ZoomMode === 'none') { + // do not write any zoom mode / OpenAction + } else { + $this->writer->write('/OpenAction [3 0 R /XYZ null null null]'); + } + + if ($this->mpdf->LayoutMode === 'single') { + $this->writer->write('/PageLayout /SinglePage'); + } elseif ($this->mpdf->LayoutMode === 'continuous') { + $this->writer->write('/PageLayout /OneColumn'); + } elseif ($this->mpdf->LayoutMode === 'twoleft') { + $this->writer->write('/PageLayout /TwoColumnLeft'); + } elseif ($this->mpdf->LayoutMode === 'tworight') { + $this->writer->write('/PageLayout /TwoColumnRight'); + } elseif ($this->mpdf->LayoutMode === 'two') { + if ($this->mpdf->mirrorMargins) { + $this->writer->write('/PageLayout /TwoColumnRight'); + } else { + $this->writer->write('/PageLayout /TwoColumnLeft'); + } + } + + // Bookmarks + if (count($this->mpdf->BMoutlines) > 0) { + $this->writer->write('/Outlines ' . $this->mpdf->OutlineRoot . ' 0 R'); + $this->writer->write('/PageMode /UseOutlines'); + } + + // Fullscreen + if (is_int(strpos($this->mpdf->DisplayPreferences, 'FullScreen'))) { + $this->writer->write('/PageMode /FullScreen'); + } + + // Metadata + if ($this->mpdf->PDFA || $this->mpdf->PDFX) { + $this->writer->write('/Metadata ' . $this->mpdf->MetadataRoot . ' 0 R'); + } + + // OutputIntents + if ($this->mpdf->PDFA || $this->mpdf->PDFX || $this->mpdf->ICCProfile) { + $this->writer->write('/OutputIntents [' . $this->mpdf->OutputIntentRoot . ' 0 R]'); + } + + // Associated files + if ($this->mpdf->associatedFilesRoot) { + $this->writer->write('/AF '. $this->mpdf->associatedFilesRoot .' 0 R'); + + $names = []; + foreach ($this->mpdf->associatedFiles as $file) { + $names[] = $this->writer->string($file['name']) . ' ' . $file['_root'] . ' 0 R'; + } + $this->writer->write('/Names << /EmbeddedFiles << /Names [' . implode(' ', $names) . '] >> >>'); + } + + // Forms + if (count($this->form->forms) > 0) { + $this->form->_putFormsCatalog(); + } + + if ($this->mpdf->js !== null) { + $this->writer->write('/Names << /JavaScript ' . $this->mpdf->n_js . ' 0 R >> '); + } + + if ($this->mpdf->DisplayPreferences || $this->mpdf->directionality === 'rtl' || $this->mpdf->mirrorMargins) { + + $this->writer->write('/ViewerPreferences<<'); + + if (is_int(strpos($this->mpdf->DisplayPreferences, 'HideMenubar'))) { + $this->writer->write('/HideMenubar true'); + } + + if (is_int(strpos($this->mpdf->DisplayPreferences, 'HideToolbar'))) { + $this->writer->write('/HideToolbar true'); + } + + if (is_int(strpos($this->mpdf->DisplayPreferences, 'HideWindowUI'))) { + $this->writer->write('/HideWindowUI true'); + } + + if (is_int(strpos($this->mpdf->DisplayPreferences, 'DisplayDocTitle'))) { + $this->writer->write('/DisplayDocTitle true'); + } + + if (is_int(strpos($this->mpdf->DisplayPreferences, 'CenterWindow'))) { + $this->writer->write('/CenterWindow true'); + } + + if (is_int(strpos($this->mpdf->DisplayPreferences, 'FitWindow'))) { + $this->writer->write('/FitWindow true'); + } + + // PrintScaling is PDF 1.6 spec. + if (!$this->mpdf->PDFA && !$this->mpdf->PDFX && is_int(strpos($this->mpdf->DisplayPreferences, 'NoPrintScaling'))) { + $this->writer->write('/PrintScaling /None'); + } + + if ($this->mpdf->directionality === 'rtl') { + $this->writer->write('/Direction /R2L'); + } + + // Duplex is PDF 1.7 spec. + if ($this->mpdf->mirrorMargins && !$this->mpdf->PDFA && !$this->mpdf->PDFX) { + // if ($this->mpdf->DefOrientation=='P') $this->writer->write('/Duplex /DuplexFlipShortEdge'); + $this->writer->write('/Duplex /DuplexFlipLongEdge'); // PDF v1.7+ + } + + $this->writer->write('>>'); + } + + if ($this->mpdf->open_layer_pane && ($this->mpdf->hasOC || count($this->mpdf->layers))) { + $this->writer->write('/PageMode /UseOC'); + } + + if ($this->mpdf->hasOC || count($this->mpdf->layers)) { + $p = $v = $h = $l = $loff = $lall = $as = ''; + if ($this->mpdf->hasOC) { + if (($this->mpdf->hasOC & 1) === 1) { + $p = $this->mpdf->n_ocg_print . ' 0 R'; + } + if (($this->mpdf->hasOC & 2) === 2) { + $v = $this->mpdf->n_ocg_view . ' 0 R'; + } + if (($this->mpdf->hasOC & 4) === 4) { + $h = $this->mpdf->n_ocg_hidden . ' 0 R'; + } + $as = "<> <>"; + } + + if (count($this->mpdf->layers)) { + foreach ($this->mpdf->layers as $k => $layer) { + if (strtolower($this->mpdf->layerDetails[$k]['state']) === 'hidden') { + $loff .= $layer['n'] . ' 0 R '; + } else { + $l .= $layer['n'] . ' 0 R '; + } + $lall .= $layer['n'] . ' 0 R '; + } + } + $this->writer->write("/OCProperties <writer->write("/Order [$v $p $h $lall] "); + if ($as) { + $this->writer->write("/AS [$as] "); + } + $this->writer->write('>>>>'); + } + } + + /** + * @since 5.7.2 + */ + public function writeAnnotations() // _putannots + { + $nb = $this->mpdf->page; + + for ($n = 1; $n <= $nb; $n++) { + + if (isset($this->mpdf->PageLinks[$n]) || isset($this->mpdf->PageAnnots[$n]) || count($this->form->forms) > 0) { + + $wPt = $this->mpdf->pageDim[$n]['w'] * Mpdf::SCALE; + $hPt = $this->mpdf->pageDim[$n]['h'] * Mpdf::SCALE; + + // Links + if (isset($this->mpdf->PageLinks[$n])) { + + foreach ($this->mpdf->PageLinks[$n] as $key => $pl) { + + $this->writer->object(); + $annot = ''; + + $rect = sprintf('%.3F %.3F %.3F %.3F', $pl[0], $pl[1], $pl[0] + $pl[2], $pl[1] - $pl[3]); + + $annot .= '<writer->utf16BigEndianTextString($pl[4]); + $annot .= ' /NM ' . $this->writer->string(sprintf('%04u-%04u', $n, $key)); + $annot .= ' /M ' . $this->writer->string('D:' . date('YmdHis')); + + $annot .= ' /Border [0 0 0]'; + + // Use this (instead of /Border) to specify border around link + + // $annot .= ' /BS <mpdf->PDFA || $this->mpdf->PDFX) { + $annot .= ' /F 28'; + } + + if (strpos($pl[4], '@') === 0) { + + $p = substr($pl[4], 1); + // $h=isset($this->mpdf->OrientationChanges[$p]) ? $wPt : $hPt; + $htarg = $this->mpdf->pageDim[$p]['h'] * Mpdf::SCALE; + $annot .= sprintf(' /Dest [%d 0 R /XYZ 0 %.3F null]>>', 1 + 2 * $p, $htarg); + + } elseif (is_string($pl[4])) { + + $annot .= ' /A <writer->string($pl[4]) . '>> >>'; + + } else { + + $l = $this->mpdf->links[$pl[4]]; + // may not be set if #link points to non-existent target + if (isset($this->mpdf->pageDim[$l[0]]['h'])) { + $htarg = $this->mpdf->pageDim[$l[0]]['h'] * Mpdf::SCALE; + } else { + $htarg = $this->mpdf->h * Mpdf::SCALE; + } // doesn't really matter + + $annot .= sprintf(' /Dest [%d 0 R /XYZ 0 %.3F null]>>', 1 + 2 * $l[0], $htarg - $l[1] * Mpdf::SCALE); + } + + $this->writer->write($annot); + $this->writer->write('endobj'); + + } + } + + /* -- ANNOTATIONS -- */ + if (isset($this->mpdf->PageAnnots[$n])) { + + foreach ($this->mpdf->PageAnnots[$n] as $key => $pl) { + + $fileAttachment = (bool) $pl['opt']['file']; + + if ($fileAttachment && !$this->mpdf->allowAnnotationFiles) { + $this->logger->warning('Embedded files for annotations have to be allowed explicitly with "allowAnnotationFiles" config key'); + $fileAttachment = false; + } + + $this->writer->object(); + + $annot = ''; + $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER); + $x = $pl['x']; + + if ($this->mpdf->annotMargin != 0 || $x == 0 || $x < 0) { // Odd page, intentional non-strict comparison + $x = ($wPt / Mpdf::SCALE) - $this->mpdf->annotMargin; + } + + $w = $h = 0; + $a = $x * Mpdf::SCALE; + $b = $hPt - ($pl['y'] * Mpdf::SCALE); + + $annot .= '<mpdf->n + 1) . ' 0 R>>'; + $annot .= '>>'; + + } else { + $annot .= '/Subtype /Text'; + $w = 20; + $h = 20; // mPDF 6 + } + + $rect = sprintf('%.3F %.3F %.3F %.3F', $a, $b - $h, $a + $w, $b); + $annot .= ' /Rect [' . $rect . ']'; + + // contents = description of file in free text + $annot .= ' /Contents ' . $this->writer->utf16BigEndianTextString($pl['txt']); + + $annot .= ' /NM ' . $this->writer->string(sprintf('%04u-%04u', $n, 2000 + $key)); + $annot .= ' /M ' . $this->writer->string('D:' . date('YmdHis')); + $annot .= ' /CreationDate ' . $this->writer->string('D:' . date('YmdHis')); + $annot .= ' /Border [0 0 0]'; + + if ($this->mpdf->PDFA || $this->mpdf->PDFX) { + $annot .= ' /F 28'; + $annot .= ' /CA 1'; + } elseif ($pl['opt']['ca'] > 0) { + $annot .= ' /CA ' . $pl['opt']['ca']; + } + + $annotcolor = ' /C ['; + if (isset($pl['opt']['c']) && $pl['opt']['c']) { + $col = $pl['opt']['c']; + if ($col[0] == 3 || $col[0] == 5) { + $annotcolor .= sprintf('%.3F %.3F %.3F', ord($col[1]) / 255, ord($col[2]) / 255, ord($col[3]) / 255); + } elseif ($col[0] == 1) { + $annotcolor .= sprintf('%.3F', ord($col[1]) / 255); + } elseif ($col[0] == 4 || $col[0] == 6) { + $annotcolor .= sprintf('%.3F %.3F %.3F %.3F', ord($col[1]) / 100, ord($col[2]) / 100, ord($col[3]) / 100, ord($col[4]) / 100); + } else { + $annotcolor .= '1 1 0'; + } + } else { + $annotcolor .= '1 1 0'; + } + $annotcolor .= ']'; + $annot .= $annotcolor; + + // Usually Author + // Use as Title for fileattachment + if (isset($pl['opt']['t']) && is_string($pl['opt']['t'])) { + $annot .= ' /T ' . $this->writer->utf16BigEndianTextString($pl['opt']['t']); + } + + if ($fileAttachment) { + $iconsapp = ['Paperclip', 'Graph', 'PushPin', 'Tag']; + } else { + $iconsapp = ['Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph']; + } + + if (isset($pl['opt']['icon']) && in_array($pl['opt']['icon'], $iconsapp)) { + $annot .= ' /Name /' . $pl['opt']['icon']; + } elseif ($fileAttachment) { + $annot .= ' /Name /PushPin'; + } else { + $annot .= ' /Name /Note'; + } + + if (!$fileAttachment) { + // Subj is PDF 1.5 spec. + if (!$this->mpdf->PDFA && !$this->mpdf->PDFX && isset($pl['opt']['subj'])) { + $annot .= ' /Subj ' . $this->writer->utf16BigEndianTextString($pl['opt']['subj']); + } + if (!empty($pl['opt']['popup'])) { + $annot .= ' /Open true'; + $annot .= ' /Popup ' . ($this->mpdf->n + 1) . ' 0 R'; + } else { + $annot .= ' /Open false'; + } + } + + $annot .= ' /P ' . $pl['pageobj'] . ' 0 R'; + $annot .= '>>'; + $this->writer->write($annot); + $this->writer->write('endobj'); + + if ($fileAttachment) { + + $file = @file_get_contents($pl['opt']['file']); + if (!$file) { + throw new \Mpdf\MpdfException('mPDF Error: Cannot access file attachment - ' . $pl['opt']['file']); + } + + $filestream = gzcompress($file); + $this->writer->object(); + $this->writer->write('<writer->write('/Length ' . strlen($filestream)); + $this->writer->write('/Filter /FlateDecode'); + $this->writer->write('>>'); + $this->writer->stream($filestream); + $this->writer->write('endobj'); + + } elseif (!empty($pl['opt']['popup'])) { + $this->writer->object(); + $annot = ''; + if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][0])) { + $x = $pl['opt']['popup'][0] * Mpdf::SCALE; + } else { + $x = $pl['x'] * Mpdf::SCALE; + } + if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][1])) { + $y = $hPt - ($pl['opt']['popup'][1] * Mpdf::SCALE); + } else { + $y = $hPt - ($pl['y'] * Mpdf::SCALE); + } + if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][2])) { + $w = $pl['opt']['popup'][2] * Mpdf::SCALE; + } else { + $w = 180; + } + if (is_array($pl['opt']['popup']) && isset($pl['opt']['popup'][3])) { + $h = $pl['opt']['popup'][3] * Mpdf::SCALE; + } else { + $h = 120; + } + $rect = sprintf('%.3F %.3F %.3F %.3F', $x, $y - $h, $x + $w, $y); + $annot .= '<writer->string('D:' . date('YmdHis')); + if ($this->mpdf->PDFA || $this->mpdf->PDFX) { + $annot .= ' /F 28'; + } + $annot .= ' /Parent ' . ($this->mpdf->n - 1) . ' 0 R'; + $annot .= '>>'; + $this->writer->write($annot); + $this->writer->write('endobj'); + } + } + } + + // Active Forms + if (count($this->form->forms) > 0) { + $this->form->_putFormItems($n, $hPt); + } + } + } + + // Active Forms - Radio Button Group entries + // Output Radio Button Group form entries (radio_on_obj_id already determined) + if (count($this->form->form_radio_groups)) { + $this->form->_putRadioItems($n); + } + } + + public function writeEncryption() // _putencryption + { + $this->writer->write('/Filter /Standard'); + if ($this->protection->getUseRC128Encryption()) { + $this->writer->write('/V 2'); + $this->writer->write('/R 3'); + $this->writer->write('/Length 128'); + } else { + $this->writer->write('/V 1'); + $this->writer->write('/R 2'); + } + $this->writer->write('/O (' . $this->writer->escape($this->protection->getOValue()) . ')'); + $this->writer->write('/U (' . $this->writer->escape($this->protection->getUValue()) . ')'); + $this->writer->write('/P ' . $this->protection->getPValue()); + } + + public function writeTrailer() // _puttrailer + { + $this->writer->write('/Size ' . ($this->mpdf->n + 1)); + $this->writer->write('/Root ' . $this->mpdf->n . ' 0 R'); + $this->writer->write('/Info ' . $this->mpdf->InfoRoot . ' 0 R'); + + if ($this->mpdf->encrypted) { + $this->writer->write('/Encrypt ' . $this->mpdf->enc_obj_id . ' 0 R'); + $this->writer->write('/ID [<' . $this->protection->getUniqid() . '> <' . $this->protection->getUniqid() . '>]'); + } else { + $uniqid = md5(time() . $this->mpdf->buffer); + $this->writer->write('/ID [<' . $uniqid . '> <' . $uniqid . '>]'); + } + } + + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + private function getVersionString() + { + $return = Mpdf::VERSION; + $headFile = __DIR__ . '/../../.git/HEAD'; + if (file_exists($headFile)) { + $ref = file($headFile); + $path = explode('/', $ref[0], 3); + $branch = isset($path[2]) ? trim($path[2]) : ''; + $revFile = __DIR__ . '/../../.git/refs/heads/' . $branch; + if ($branch && file_exists($revFile)) { + $rev = file($revFile); + $rev = substr($rev[0], 0, 7); + $return .= ' (' . $rev . ')'; + } + } + + return $return; + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ObjectWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ObjectWriter.php new file mode 100644 index 0000000..2e0ad7d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ObjectWriter.php @@ -0,0 +1,64 @@ +mpdf = $mpdf; + $this->writer = $writer; + } + + public function writeImportedObjects() + { + if (is_array($this->mpdf->parsers) && count($this->mpdf->parsers) > 0) { + + foreach ($this->mpdf->parsers as $filename => $p) { + + $this->mpdf->current_parser = $this->mpdf->parsers[$filename]; + + if (is_array($this->mpdf->_obj_stack[$filename])) { + + while ($n = key($this->mpdf->_obj_stack[$filename])) { + + $nObj = $this->mpdf->current_parser->resolveObject($this->mpdf->_obj_stack[$filename][$n][1]); + $this->writer->object($this->mpdf->_obj_stack[$filename][$n][0]); + + if ($nObj[0] == pdf_parser::TYPE_STREAM) { + $this->mpdf->pdf_write_value($nObj); + } else { + $this->mpdf->pdf_write_value($nObj[1]); + } + + $this->writer->write('endobj'); + + $this->mpdf->_obj_stack[$filename][$n] = null; // free memory + + unset($this->mpdf->_obj_stack[$filename][$n]); + + reset($this->mpdf->_obj_stack[$filename]); + } + } + } + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/OptionalContentWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/OptionalContentWriter.php new file mode 100644 index 0000000..499a77f --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/OptionalContentWriter.php @@ -0,0 +1,71 @@ +mpdf = $mpdf; + $this->writer = $writer; + } + + public function writeOptionalContentGroups() // _putocg Optional Content Groups + { + if ($this->mpdf->hasOC) { + + $this->writer->object(); + $this->mpdf->n_ocg_print = $this->mpdf->n; + $this->writer->write('<writer->string('Print only')); + $this->writer->write('/Usage <> /View <>>>>>'); + $this->writer->write('endobj'); + + $this->writer->object(); + $this->mpdf->n_ocg_view = $this->mpdf->n; + $this->writer->write('<writer->string('Screen only')); + $this->writer->write('/Usage <> /View <>>>>>'); + $this->writer->write('endobj'); + + $this->writer->object(); + $this->mpdf->n_ocg_hidden = $this->mpdf->n; + $this->writer->write('<writer->string('Hidden')); + $this->writer->write('/Usage <> /View <>>>>>'); + $this->writer->write('endobj'); + } + + if (count($this->mpdf->layers)) { + + ksort($this->mpdf->layers); + foreach ($this->mpdf->layers as $id => $layer) { + $this->writer->object(); + $this->mpdf->layers[$id]['n'] = $this->mpdf->n; + + if (isset($this->mpdf->layerDetails[$id]['name']) && $this->mpdf->layerDetails[$id]['name']) { + $name = $this->mpdf->layerDetails[$id]['name']; + } else { + $name = $layer['name']; + } + + $this->writer->write('<writer->utf16BigEndianTextString($name) . '>>'); + $this->writer->write('endobj'); + } + } + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/PageWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/PageWriter.php new file mode 100644 index 0000000..7e4e21d --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/PageWriter.php @@ -0,0 +1,285 @@ +mpdf = $mpdf; + $this->form = $form; + $this->writer = $writer; + $this->metadataWriter = $metadataWriter; + } + + public function writePages() // _putpages + { + $nb = $this->mpdf->page; + $filter = $this->mpdf->compress ? '/Filter /FlateDecode ' : ''; + + if ($this->mpdf->DefOrientation === 'P') { + $defwPt = $this->mpdf->fwPt; + $defhPt = $this->mpdf->fhPt; + } else { + $defwPt = $this->mpdf->fhPt; + $defhPt = $this->mpdf->fwPt; + } + + $annotid = (3 + 2 * $nb); + + // Active Forms + $totaladdnum = 0; + for ($n = 1; $n <= $nb; $n++) { + if (isset($this->mpdf->PageLinks[$n])) { + $totaladdnum += count($this->mpdf->PageLinks[$n]); + } + + /* -- ANNOTATIONS -- */ + if (isset($this->mpdf->PageAnnots[$n])) { + foreach ($this->mpdf->PageAnnots[$n] as $k => $pl) { + if (!empty($pl['opt']['popup']) || !empty($pl['opt']['file'])) { + $totaladdnum += 2; + } else { + $totaladdnum++; + } + } + } + /* -- END ANNOTATIONS -- */ + + /* -- FORMS -- */ + if (count($this->form->forms) > 0) { + $this->form->countPageForms($n, $totaladdnum); + } + /* -- END FORMS -- */ + } + + /* -- FORMS -- */ + // Make a note in the radio button group of the obj_id it will have + $ctr = 0; + if (count($this->form->form_radio_groups)) { + foreach ($this->form->form_radio_groups as $name => $frg) { + $this->form->form_radio_groups[$name]['obj_id'] = $annotid + $totaladdnum + $ctr; + $ctr++; + } + } + /* -- END FORMS -- */ + + // Select unused fonts (usually default font) + $unused = []; + foreach ($this->mpdf->fonts as $fk => $font) { + if (isset($font['type']) && $font['type'] === 'TTF' && !$font['used']) { + $unused[] = $fk; + } + } + + for ($n = 1; $n <= $nb; $n++) { + + $thispage = $this->mpdf->pages[$n]; + + if (isset($this->mpdf->OrientationChanges[$n])) { + $hPt = $this->mpdf->pageDim[$n]['w'] * Mpdf::SCALE; + $wPt = $this->mpdf->pageDim[$n]['h'] * Mpdf::SCALE; + $owidthPt_LR = $this->mpdf->pageDim[$n]['outer_width_TB'] * Mpdf::SCALE; + $owidthPt_TB = $this->mpdf->pageDim[$n]['outer_width_LR'] * Mpdf::SCALE; + } else { + $wPt = $this->mpdf->pageDim[$n]['w'] * Mpdf::SCALE; + $hPt = $this->mpdf->pageDim[$n]['h'] * Mpdf::SCALE; + $owidthPt_LR = $this->mpdf->pageDim[$n]['outer_width_LR'] * Mpdf::SCALE; + $owidthPt_TB = $this->mpdf->pageDim[$n]['outer_width_TB'] * Mpdf::SCALE; + } + + // Remove references to unused fonts (usually default font) + foreach ($unused as $fk) { + if ($this->mpdf->fonts[$fk]['sip'] || $this->mpdf->fonts[$fk]['smp']) { + foreach ($this->mpdf->fonts[$fk]['subsetfontids'] as $k => $fid) { + $thispage = preg_replace('/\s\/F' . $fid . ' \d[\d.]* Tf\s/is', ' ', $thispage); + } + } else { + $thispage = preg_replace('/\s\/F' . $this->mpdf->fonts[$fk]['i'] . ' \d[\d.]* Tf\s/is', ' ', $thispage); + } + } + + // Clean up repeated /GS1 gs statements + // For some reason using + for repetition instead of {2,20} crashes PHP Script Interpreter ??? + $thispage = preg_replace('/(\/GS1 gs\n){2,20}/', "/GS1 gs\n", $thispage); + + $thispage = preg_replace('/(\s*___BACKGROUND___PATTERNS' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage); + $thispage = preg_replace('/(\s*___HEADER___MARKER' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage); + $thispage = preg_replace('/(\s*___PAGE___START' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage); + $thispage = preg_replace('/(\s*___TABLE___BACKGROUNDS' . $this->mpdf->uniqstr . '\s*)/', ' ', $thispage); + + // mPDF 5.7.3 TRANSFORMS + while (preg_match('/(\% BTR(.*?)\% ETR)/is', $thispage, $m)) { + $thispage = preg_replace('/(\% BTR.*?\% ETR)/is', '', $thispage, 1) . "\n" . $m[2]; + } + + // Page + $this->writer->object(); + $this->writer->write('<writer->write('/Parent 1 0 R'); + + if (isset($this->mpdf->OrientationChanges[$n])) { + + $this->writer->write(sprintf('/MediaBox [0 0 %.3F %.3F]', $hPt, $wPt)); + + // If BleedBox is defined, it must be larger than the TrimBox, but smaller than the MediaBox + $bleedMargin = $this->mpdf->pageDim[$n]['bleedMargin'] * Mpdf::SCALE; + + if ($bleedMargin && ($owidthPt_TB || $owidthPt_LR)) { + $x0 = $owidthPt_TB - $bleedMargin; + $y0 = $owidthPt_LR - $bleedMargin; + $x1 = $hPt - $owidthPt_TB + $bleedMargin; + $y1 = $wPt - $owidthPt_LR + $bleedMargin; + $this->writer->write(sprintf('/BleedBox [%.3F %.3F %.3F %.3F]', $x0, $y0, $x1, $y1)); + } + + $this->writer->write(sprintf('/TrimBox [%.3F %.3F %.3F %.3F]', $owidthPt_TB, $owidthPt_LR, $hPt - $owidthPt_TB, $wPt - $owidthPt_LR)); + + if ($this->mpdf->displayDefaultOrientation) { + if ($this->mpdf->DefOrientation === 'P') { + $this->writer->write('/Rotate 270'); + } else { + $this->writer->write('/Rotate 90'); + } + } + + } else { // elseif($wPt != $defwPt || $hPt != $defhPt) { + + $this->writer->write(sprintf('/MediaBox [0 0 %.3F %.3F]', $wPt, $hPt)); + $bleedMargin = $this->mpdf->pageDim[$n]['bleedMargin'] * Mpdf::SCALE; + + if ($bleedMargin && ($owidthPt_TB || $owidthPt_LR)) { + $x0 = $owidthPt_LR - $bleedMargin; + $y0 = $owidthPt_TB - $bleedMargin; + $x1 = $wPt - $owidthPt_LR + $bleedMargin; + $y1 = $hPt - $owidthPt_TB + $bleedMargin; + $this->writer->write(sprintf('/BleedBox [%.3F %.3F %.3F %.3F]', $x0, $y0, $x1, $y1)); + } + + $this->writer->write(sprintf('/TrimBox [%.3F %.3F %.3F %.3F]', $owidthPt_LR, $owidthPt_TB, $wPt - $owidthPt_LR, $hPt - $owidthPt_TB)); + } + $this->writer->write('/Resources 2 0 R'); + + // Important to keep in RGB colorSpace when using transparency + if (!$this->mpdf->PDFA && !$this->mpdf->PDFX) { + if ($this->mpdf->restrictColorSpace === 3) { + $this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceCMYK >> '); + } elseif ($this->mpdf->restrictColorSpace === 1) { + $this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceGray >> '); + } else { + $this->writer->write('/Group << /Type /Group /S /Transparency /CS /DeviceRGB >> '); + } + } + + $annotsnum = 0; + $embeddedfiles = []; // mPDF 5.7.2 /EmbeddedFiles + + if (isset($this->mpdf->PageLinks[$n])) { + $annotsnum += count($this->mpdf->PageLinks[$n]); + } + + if (isset($this->mpdf->PageAnnots[$n])) { + foreach ($this->mpdf->PageAnnots[$n] as $k => $pl) { + if (!empty($pl['opt']['file'])) { + $embeddedfiles[$annotsnum + 1] = true; + } // mPDF 5.7.2 /EmbeddedFiles + if (!empty($pl['opt']['popup']) || !empty($pl['opt']['file'])) { + $annotsnum += 2; + } else { + $annotsnum++; + } + $this->mpdf->PageAnnots[$n][$k]['pageobj'] = $this->mpdf->n; + } + } + + // Active Forms + $formsnum = 0; + if (count($this->form->forms) > 0) { + foreach ($this->form->forms as $val) { + if ($val['page'] == $n) { + $formsnum++; + } + } + } + + if ($annotsnum || $formsnum) { + + $s = '/Annots [ '; + + for ($i = 0; $i < $annotsnum; $i++) { + if (!isset($embeddedfiles[$i])) { + $s .= ($annotid + $i) . ' 0 R '; + } // mPDF 5.7.2 /EmbeddedFiles + } + + $annotid += $annotsnum; + + /* -- FORMS -- */ + if (count($this->form->forms) > 0) { + $this->form->addFormIds($n, $s, $annotid); + } + /* -- END FORMS -- */ + + $s .= '] '; + $this->writer->write($s); + } + + $this->writer->write('/Contents ' . ($this->mpdf->n + 1) . ' 0 R>>'); + $this->writer->write('endobj'); + + // Page content + $this->writer->object(); + $p = $this->mpdf->compress ? gzcompress($thispage) : $thispage; + $this->writer->write('<<' . $filter . '/Length ' . strlen($p) . '>>'); + $this->writer->stream($p); + $this->writer->write('endobj'); + } + + $this->metadataWriter->writeAnnotations(); // mPDF 5.7.2 + + // Pages root + $this->mpdf->offsets[1] = strlen($this->mpdf->buffer); + $this->writer->write('1 0 obj'); + $this->writer->write('<writer->write($kids . ']'); + $this->writer->write('/Count ' . $nb); + $this->writer->write(sprintf('/MediaBox [0 0 %.3F %.3F]', $defwPt, $defhPt)); + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ResourceWriter.php b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ResourceWriter.php new file mode 100644 index 0000000..996b882 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/Writer/ResourceWriter.php @@ -0,0 +1,256 @@ +mpdf = $mpdf; + $this->writer = $writer; + $this->colorWriter = $colorWriter; + $this->fontWriter = $fontWriter; + $this->imageWriter = $imageWriter; + $this->formWriter = $formWriter; + $this->optionalContentWriter = $optionalContentWriter; + $this->backgroundWriter = $backgroundWriter; + $this->bookmarkWriter = $bookmarkWriter; + $this->metadataWriter = $metadataWriter; + $this->javaScriptWriter = $javaScriptWriter; + $this->logger = $logger; + } + + public function writeResources() // _putresources + { + if ($this->mpdf->hasOC || count($this->mpdf->layers)) { + $this->optionalContentWriter->writeOptionalContentGroups(); + } + + $this->mpdf->_putextgstates(); + $this->colorWriter->writeSpotColors(); + + // @log Compiling Fonts + + $this->fontWriter->writeFonts(); + + // @log Compiling Images + + $this->imageWriter->writeImages(); + + $this->formWriter->writeFormObjects(); + + $this->mpdf->writeImportedPagesAndResolvedObjects(); + + $this->backgroundWriter->writeShaders(); + $this->backgroundWriter->writePatterns(); + + // Resource dictionary + $this->mpdf->offsets[2] = strlen($this->mpdf->buffer); + $this->writer->write('2 0 obj'); + $this->writer->write('<writer->write('/Font <<'); + foreach ($this->mpdf->fonts as $font) { + if (isset($font['type']) && $font['type'] === 'TTF' && !$font['used']) { + continue; + } + if (isset($font['type']) && $font['type'] === 'TTF' && ($font['sip'] || $font['smp'])) { + foreach ($font['n'] as $k => $fid) { + $this->writer->write('/F' . $font['subsetfontids'][$k] . ' ' . $font['n'][$k] . ' 0 R'); + } + } else { + $this->writer->write('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); + } + } + $this->writer->write('>>'); + + if (count($this->mpdf->spotColors)) { + $this->writer->write('/ColorSpace <<'); + foreach ($this->mpdf->spotColors as $color) { + $this->writer->write('/CS' . $color['i'] . ' ' . $color['n'] . ' 0 R'); + } + $this->writer->write('>>'); + } + + if (count($this->mpdf->extgstates)) { + $this->writer->write('/ExtGState <<'); + foreach ($this->mpdf->extgstates as $k => $extgstate) { + if (isset($extgstate['trans'])) { + $this->writer->write('/' . $extgstate['trans'] . ' ' . $extgstate['n'] . ' 0 R'); + } else { + $this->writer->write('/GS' . $k . ' ' . $extgstate['n'] . ' 0 R'); + } + } + $this->writer->write('>>'); + } + + /* -- BACKGROUNDS -- */ + if (($this->mpdf->gradients !== null && (count($this->mpdf->gradients) > 0))) { // mPDF 5.7.3 + + $this->writer->write('/Shading <<'); + + foreach ($this->mpdf->gradients as $id => $grad) { + $this->writer->write('/Sh' . $id . ' ' . $grad['id'] . ' 0 R'); + } + + $this->writer->write('>>'); + + /* + // ??? Not needed !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + $this->writer->write('/Pattern <<'); + foreach ($this->mpdf->gradients as $id => $grad) { + $this->writer->write('/P'.$id.' '.$grad['pattern'].' 0 R'); + } + $this->writer->write('>>'); + */ + } + /* -- END BACKGROUNDS -- */ + + if (count($this->mpdf->images) || count($this->mpdf->formobjects) || count($this->mpdf->getImportedPages())) { + $this->writer->write('/XObject <<'); + foreach ($this->mpdf->images as $image) { + $this->writer->write('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R'); + } + foreach ($this->mpdf->formobjects as $formobject) { + $this->writer->write('/FO' . $formobject['i'] . ' ' . $formobject['n'] . ' 0 R'); + } + /* -- IMPORTS -- */ + foreach ($this->mpdf->getImportedPages() as $pageData) { + $this->writer->write('/' . $pageData['id'] . ' ' . $pageData['objectNumber'] . ' 0 R'); + } + /* -- END IMPORTS -- */ + $this->writer->write('>>'); + } + + /* -- BACKGROUNDS -- */ + + if (count($this->mpdf->patterns)) { + $this->writer->write('/Pattern <<'); + foreach ($this->mpdf->patterns as $k => $patterns) { + $this->writer->write('/P' . $k . ' ' . $patterns['n'] . ' 0 R'); + } + $this->writer->write('>>'); + } + /* -- END BACKGROUNDS -- */ + + if ($this->mpdf->hasOC || count($this->mpdf->layers)) { + $this->writer->write('/Properties <<'); + if ($this->mpdf->hasOC) { + $this->writer->write('/OC1 ' . $this->mpdf->n_ocg_print . ' 0 R /OC2 ' . $this->mpdf->n_ocg_view . ' 0 R /OC3 ' . $this->mpdf->n_ocg_hidden . ' 0 R '); + } + if (count($this->mpdf->layers)) { + foreach ($this->mpdf->layers as $id => $layer) { + $this->writer->write('/ZI' . $id . ' ' . $layer['n'] . ' 0 R'); + } + } + $this->writer->write('>>'); + } + + $this->writer->write('>>'); + $this->writer->write('endobj'); // end resource dictionary + + $this->bookmarkWriter->writeBookmarks(); + + if (!empty($this->mpdf->js)) { + $this->javaScriptWriter->writeJavascript(); + } + + if ($this->mpdf->encrypted) { + $this->writer->object(); + $this->mpdf->enc_obj_id = $this->mpdf->n; + $this->writer->write('<<'); + $this->metadataWriter->writeEncryption(); + $this->writer->write('>>'); + $this->writer->write('endobj'); + } + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * + * @return void + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } +} diff --git a/lib/MPDF/vendor/mpdf/mpdf/src/functions-dev.php b/lib/MPDF/vendor/mpdf/mpdf/src/functions-dev.php new file mode 100644 index 0000000..355f3e3 --- /dev/null +++ b/lib/MPDF/vendor/mpdf/mpdf/src/functions-dev.php @@ -0,0 +1,13 @@ +copy($myObject); +``` + + +## Why? + +- How do you create copies of your objects? + +```php +$myCopy = clone $myObject; +``` + +- How do you create **deep** copies of your objects (i.e. copying also all the objects referenced in the properties)? + +You use [`__clone()`](http://www.php.net/manual/en/language.oop5.cloning.php#object.clone) and implement the behavior +yourself. + +- But how do you handle **cycles** in the association graph? + +Now you're in for a big mess :( + +![association graph](doc/graph.png) + + +### Using simply `clone` + +![Using clone](doc/clone.png) + + +### Overridding `__clone()` + +![Overridding __clone](doc/deep-clone.png) + + +### With `DeepCopy` + +![With DeepCopy](doc/deep-copy.png) + + +## How it works + +DeepCopy recursively traverses all the object's properties and clones them. To avoid cloning the same object twice it +keeps a hash map of all instances and thus preserves the object graph. + +To use it: + +```php +use function DeepCopy\deep_copy; + +$copy = deep_copy($var); +``` + +Alternatively, you can create your own `DeepCopy` instance to configure it differently for example: + +```php +use DeepCopy\DeepCopy; + +$copier = new DeepCopy(true); + +$copy = $copier->copy($var); +``` + +You may want to roll your own deep copy function: + +```php +namespace Acme; + +use DeepCopy\DeepCopy; + +function deep_copy($var) +{ + static $copier = null; + + if (null === $copier) { + $copier = new DeepCopy(true); + } + + return $copier->copy($var); +} +``` + + +## Going further + +You can add filters to customize the copy process. + +The method to add a filter is `DeepCopy\DeepCopy::addFilter($filter, $matcher)`, +with `$filter` implementing `DeepCopy\Filter\Filter` +and `$matcher` implementing `DeepCopy\Matcher\Matcher`. + +We provide some generic filters and matchers. + + +### Matchers + + - `DeepCopy\Matcher` applies on a object attribute. + - `DeepCopy\TypeMatcher` applies on any element found in graph, including array elements. + + +#### Property name + +The `PropertyNameMatcher` will match a property by its name: + +```php +use DeepCopy\Matcher\PropertyNameMatcher; + +// Will apply a filter to any property of any objects named "id" +$matcher = new PropertyNameMatcher('id'); +``` + + +#### Specific property + +The `PropertyMatcher` will match a specific property of a specific class: + +```php +use DeepCopy\Matcher\PropertyMatcher; + +// Will apply a filter to the property "id" of any objects of the class "MyClass" +$matcher = new PropertyMatcher('MyClass', 'id'); +``` + + +#### Type + +The `TypeMatcher` will match any element by its type (instance of a class or any value that could be parameter of +[gettype()](http://php.net/manual/en/function.gettype.php) function): + +```php +use DeepCopy\TypeMatcher\TypeMatcher; + +// Will apply a filter to any object that is an instance of Doctrine\Common\Collections\Collection +$matcher = new TypeMatcher('Doctrine\Common\Collections\Collection'); +``` + + +### Filters + +- `DeepCopy\Filter` applies a transformation to the object attribute matched by `DeepCopy\Matcher` +- `DeepCopy\TypeFilter` applies a transformation to any element matched by `DeepCopy\TypeMatcher` + + +#### `SetNullFilter` (filter) + +Let's say for example that you are copying a database record (or a Doctrine entity), so you want the copy not to have +any ID: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\SetNullFilter; +use DeepCopy\Matcher\PropertyNameMatcher; + +$object = MyClass::load(123); +echo $object->id; // 123 + +$copier = new DeepCopy(); +$copier->addFilter(new SetNullFilter(), new PropertyNameMatcher('id')); + +$copy = $copier->copy($object); + +echo $copy->id; // null +``` + + +#### `KeepFilter` (filter) + +If you want a property to remain untouched (for example, an association to an object): + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\KeepFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new KeepFilter(), new PropertyMatcher('MyClass', 'category')); + +$copy = $copier->copy($object); +// $copy->category has not been touched +``` + + +#### `DoctrineCollectionFilter` (filter) + +If you use Doctrine and want to copy an entity, you will need to use the `DoctrineCollectionFilter`: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter; +use DeepCopy\Matcher\PropertyTypeMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection')); + +$copy = $copier->copy($object); +``` + + +#### `DoctrineEmptyCollectionFilter` (filter) + +If you use Doctrine and want to copy an entity who contains a `Collection` that you want to be reset, you can use the +`DoctrineEmptyCollectionFilter` + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineEmptyCollectionFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineEmptyCollectionFilter(), new PropertyMatcher('MyClass', 'myProperty')); + +$copy = $copier->copy($object); + +// $copy->myProperty will return an empty collection +``` + + +#### `DoctrineProxyFilter` (filter) + +If you use Doctrine and use cloning on lazy loaded entities, you might encounter errors mentioning missing fields on a +Doctrine proxy class (...\\\_\_CG\_\_\Proxy). +You can use the `DoctrineProxyFilter` to load the actual entity behind the Doctrine proxy class. +**Make sure, though, to put this as one of your very first filters in the filter chain so that the entity is loaded +before other filters are applied!** + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\Doctrine\DoctrineProxyFilter; +use DeepCopy\Matcher\Doctrine\DoctrineProxyMatcher; + +$copier = new DeepCopy(); +$copier->addFilter(new DoctrineProxyFilter(), new DoctrineProxyMatcher()); + +$copy = $copier->copy($object); + +// $copy should now contain a clone of all entities, including those that were not yet fully loaded. +``` + + +#### `ReplaceFilter` (type filter) + +1. If you want to replace the value of a property: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\Filter\ReplaceFilter; +use DeepCopy\Matcher\PropertyMatcher; + +$copier = new DeepCopy(); +$callback = function ($currentValue) { + return $currentValue . ' (copy)' +}; +$copier->addFilter(new ReplaceFilter($callback), new PropertyMatcher('MyClass', 'title')); + +$copy = $copier->copy($object); + +// $copy->title will contain the data returned by the callback, e.g. 'The title (copy)' +``` + +2. If you want to replace whole element: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\TypeFilter\ReplaceFilter; +use DeepCopy\TypeMatcher\TypeMatcher; + +$copier = new DeepCopy(); +$callback = function (MyClass $myClass) { + return get_class($myClass); +}; +$copier->addTypeFilter(new ReplaceFilter($callback), new TypeMatcher('MyClass')); + +$copy = $copier->copy([new MyClass, 'some string', new MyClass]); + +// $copy will contain ['MyClass', 'some string', 'MyClass'] +``` + + +The `$callback` parameter of the `ReplaceFilter` constructor accepts any PHP callable. + + +#### `ShallowCopyFilter` (type filter) + +Stop *DeepCopy* from recursively copying element, using standard `clone` instead: + +```php +use DeepCopy\DeepCopy; +use DeepCopy\TypeFilter\ShallowCopyFilter; +use DeepCopy\TypeMatcher\TypeMatcher; +use Mockery as m; + +$this->deepCopy = new DeepCopy(); +$this->deepCopy->addTypeFilter( + new ShallowCopyFilter, + new TypeMatcher(m\MockInterface::class) +); + +$myServiceWithMocks = new MyService(m::mock(MyDependency1::class), m::mock(MyDependency2::class)); +// All mocks will be just cloned, not deep copied +``` + + +## Edge cases + +The following structures cannot be deep-copied with PHP Reflection. As a result they are shallow cloned and filters are +not applied. There is two ways for you to handle them: + +- Implement your own `__clone()` method +- Use a filter with a type matcher + + +## Contributing + +DeepCopy is distributed under the MIT license. + + +### Tests + +Running the tests is simple: + +```php +vendor/bin/phpunit +``` diff --git a/lib/MPDF/vendor/myclabs/deep-copy/composer.json b/lib/MPDF/vendor/myclabs/deep-copy/composer.json new file mode 100644 index 0000000..4108a23 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/composer.json @@ -0,0 +1,38 @@ +{ + "name": "myclabs/deep-copy", + "type": "library", + "description": "Create deep copies (clones) of your objects", + "keywords": ["clone", "copy", "duplicate", "object", "object graph"], + "license": "MIT", + + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "autoload-dev": { + "psr-4": { + "DeepCopy\\": "fixtures/", + "DeepCopyTest\\": "tests/DeepCopyTest/" + } + }, + + "require": { + "php": "^7.1" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + + "config": { + "sort-packages": true + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/doc/clone.png b/lib/MPDF/vendor/myclabs/deep-copy/doc/clone.png new file mode 100644 index 0000000..376afd4 Binary files /dev/null and b/lib/MPDF/vendor/myclabs/deep-copy/doc/clone.png differ diff --git a/lib/MPDF/vendor/myclabs/deep-copy/doc/deep-clone.png b/lib/MPDF/vendor/myclabs/deep-copy/doc/deep-clone.png new file mode 100644 index 0000000..2b37a6d Binary files /dev/null and b/lib/MPDF/vendor/myclabs/deep-copy/doc/deep-clone.png differ diff --git a/lib/MPDF/vendor/myclabs/deep-copy/doc/deep-copy.png b/lib/MPDF/vendor/myclabs/deep-copy/doc/deep-copy.png new file mode 100644 index 0000000..68c508a Binary files /dev/null and b/lib/MPDF/vendor/myclabs/deep-copy/doc/deep-copy.png differ diff --git a/lib/MPDF/vendor/myclabs/deep-copy/doc/graph.png b/lib/MPDF/vendor/myclabs/deep-copy/doc/graph.png new file mode 100644 index 0000000..4d5c942 Binary files /dev/null and b/lib/MPDF/vendor/myclabs/deep-copy/doc/graph.png differ diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php new file mode 100644 index 0000000..2e53cf3 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/DeepCopy.php @@ -0,0 +1,289 @@ + Filter, 'matcher' => Matcher] pairs. + */ + private $filters = []; + + /** + * Type Filters to apply. + * + * @var array Array of ['filter' => Filter, 'matcher' => Matcher] pairs. + */ + private $typeFilters = []; + + /** + * @var bool + */ + private $skipUncloneable = false; + + /** + * @var bool + */ + private $useCloneMethod; + + /** + * @param bool $useCloneMethod If set to true, when an object implements the __clone() function, it will be used + * instead of the regular deep cloning. + */ + public function __construct($useCloneMethod = false) + { + $this->useCloneMethod = $useCloneMethod; + + $this->addTypeFilter(new DateIntervalFilter(), new TypeMatcher(DateInterval::class)); + $this->addTypeFilter(new SplDoublyLinkedListFilter($this), new TypeMatcher(SplDoublyLinkedList::class)); + } + + /** + * If enabled, will not throw an exception when coming across an uncloneable property. + * + * @param $skipUncloneable + * + * @return $this + */ + public function skipUncloneable($skipUncloneable = true) + { + $this->skipUncloneable = $skipUncloneable; + + return $this; + } + + /** + * Deep copies the given object. + * + * @param mixed $object + * + * @return mixed + */ + public function copy($object) + { + $this->hashMap = []; + + return $this->recursiveCopy($object); + } + + public function addFilter(Filter $filter, Matcher $matcher) + { + $this->filters[] = [ + 'matcher' => $matcher, + 'filter' => $filter, + ]; + } + + public function prependFilter(Filter $filter, Matcher $matcher) + { + array_unshift($this->filters, [ + 'matcher' => $matcher, + 'filter' => $filter, + ]); + } + + public function addTypeFilter(TypeFilter $filter, TypeMatcher $matcher) + { + $this->typeFilters[] = [ + 'matcher' => $matcher, + 'filter' => $filter, + ]; + } + + private function recursiveCopy($var) + { + // Matches Type Filter + if ($filter = $this->getFirstMatchedTypeFilter($this->typeFilters, $var)) { + return $filter->apply($var); + } + + // Resource + if (is_resource($var)) { + return $var; + } + + // Array + if (is_array($var)) { + return $this->copyArray($var); + } + + // Scalar + if (! is_object($var)) { + return $var; + } + + // Object + return $this->copyObject($var); + } + + /** + * Copy an array + * @param array $array + * @return array + */ + private function copyArray(array $array) + { + foreach ($array as $key => $value) { + $array[$key] = $this->recursiveCopy($value); + } + + return $array; + } + + /** + * Copies an object. + * + * @param object $object + * + * @throws CloneException + * + * @return object + */ + private function copyObject($object) + { + $objectHash = spl_object_hash($object); + + if (isset($this->hashMap[$objectHash])) { + return $this->hashMap[$objectHash]; + } + + $reflectedObject = new ReflectionObject($object); + $isCloneable = $reflectedObject->isCloneable(); + + if (false === $isCloneable) { + if ($this->skipUncloneable) { + $this->hashMap[$objectHash] = $object; + + return $object; + } + + throw new CloneException( + sprintf( + 'The class "%s" is not cloneable.', + $reflectedObject->getName() + ) + ); + } + + $newObject = clone $object; + $this->hashMap[$objectHash] = $newObject; + + if ($this->useCloneMethod && $reflectedObject->hasMethod('__clone')) { + return $newObject; + } + + if ($newObject instanceof DateTimeInterface || $newObject instanceof DateTimeZone) { + return $newObject; + } + + foreach (ReflectionHelper::getProperties($reflectedObject) as $property) { + $this->copyObjectProperty($newObject, $property); + } + + return $newObject; + } + + private function copyObjectProperty($object, ReflectionProperty $property) + { + // Ignore static properties + if ($property->isStatic()) { + return; + } + + // Apply the filters + foreach ($this->filters as $item) { + /** @var Matcher $matcher */ + $matcher = $item['matcher']; + /** @var Filter $filter */ + $filter = $item['filter']; + + if ($matcher->matches($object, $property->getName())) { + $filter->apply( + $object, + $property->getName(), + function ($object) { + return $this->recursiveCopy($object); + } + ); + + // If a filter matches, we stop processing this property + return; + } + } + + $property->setAccessible(true); + $propertyValue = $property->getValue($object); + + // Copy the property + $property->setValue($object, $this->recursiveCopy($propertyValue)); + } + + /** + * Returns first filter that matches variable, `null` if no such filter found. + * + * @param array $filterRecords Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and + * 'matcher' with value of type {@see TypeMatcher} + * @param mixed $var + * + * @return TypeFilter|null + */ + private function getFirstMatchedTypeFilter(array $filterRecords, $var) + { + $matched = $this->first( + $filterRecords, + function (array $record) use ($var) { + /* @var TypeMatcher $matcher */ + $matcher = $record['matcher']; + + return $matcher->matches($var); + } + ); + + return isset($matched) ? $matched['filter'] : null; + } + + /** + * Returns first element that matches predicate, `null` if no such element found. + * + * @param array $elements Array of ['filter' => Filter, 'matcher' => Matcher] pairs. + * @param callable $predicate Predicate arguments are: element. + * + * @return array|null Associative array with 2 members: 'filter' with value of type {@see TypeFilter} and 'matcher' + * with value of type {@see TypeMatcher} or `null`. + */ + private function first(array $elements, callable $predicate) + { + foreach ($elements as $element) { + if (call_user_func($predicate, $element)) { + return $element; + } + } + + return null; + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php new file mode 100644 index 0000000..c046706 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Exception/CloneException.php @@ -0,0 +1,9 @@ +setAccessible(true); + $oldCollection = $reflectionProperty->getValue($object); + + $newCollection = $oldCollection->map( + function ($item) use ($objectCopier) { + return $objectCopier($item); + } + ); + + $reflectionProperty->setValue($object, $newCollection); + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php new file mode 100644 index 0000000..7b33fd5 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineEmptyCollectionFilter.php @@ -0,0 +1,28 @@ +setAccessible(true); + + $reflectionProperty->setValue($object, new ArrayCollection()); + } +} \ No newline at end of file diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php new file mode 100644 index 0000000..8bee8f7 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Doctrine/DoctrineProxyFilter.php @@ -0,0 +1,22 @@ +__load(); + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php new file mode 100644 index 0000000..85ba18c --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/Filter.php @@ -0,0 +1,18 @@ +callback = $callable; + } + + /** + * Replaces the object property by the result of the callback called with the object property. + * + * {@inheritdoc} + */ + public function apply($object, $property, $objectCopier) + { + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + $reflectionProperty->setAccessible(true); + + $value = call_user_func($this->callback, $reflectionProperty->getValue($object)); + + $reflectionProperty->setValue($object, $value); + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php new file mode 100644 index 0000000..bea86b8 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Filter/SetNullFilter.php @@ -0,0 +1,24 @@ +setAccessible(true); + $reflectionProperty->setValue($object, null); + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php new file mode 100644 index 0000000..ec8856f --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/Doctrine/DoctrineProxyMatcher.php @@ -0,0 +1,22 @@ +class = $class; + $this->property = $property; + } + + /** + * Matches a specific property of a specific class. + * + * {@inheritdoc} + */ + public function matches($object, $property) + { + return ($object instanceof $this->class) && $property == $this->property; + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php new file mode 100644 index 0000000..c8ec0d2 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyNameMatcher.php @@ -0,0 +1,32 @@ +property = $property; + } + + /** + * Matches a property by its name. + * + * {@inheritdoc} + */ + public function matches($object, $property) + { + return $property == $this->property; + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php new file mode 100644 index 0000000..a6b0c0b --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Matcher/PropertyTypeMatcher.php @@ -0,0 +1,46 @@ +propertyType = $propertyType; + } + + /** + * {@inheritdoc} + */ + public function matches($object, $property) + { + try { + $reflectionProperty = ReflectionHelper::getProperty($object, $property); + } catch (ReflectionException $exception) { + return false; + } + + $reflectionProperty->setAccessible(true); + + return $reflectionProperty->getValue($object) instanceof $this->propertyType; + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php new file mode 100644 index 0000000..742410c --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/Reflection/ReflectionHelper.php @@ -0,0 +1,78 @@ +getProperties() does not return private properties from ancestor classes. + * + * @author muratyaman@gmail.com + * @see http://php.net/manual/en/reflectionclass.getproperties.php + * + * @param ReflectionClass $ref + * + * @return ReflectionProperty[] + */ + public static function getProperties(ReflectionClass $ref) + { + $props = $ref->getProperties(); + $propsArr = array(); + + foreach ($props as $prop) { + $propertyName = $prop->getName(); + $propsArr[$propertyName] = $prop; + } + + if ($parentClass = $ref->getParentClass()) { + $parentPropsArr = self::getProperties($parentClass); + foreach ($propsArr as $key => $property) { + $parentPropsArr[$key] = $property; + } + + return $parentPropsArr; + } + + return $propsArr; + } + + /** + * Retrieves property by name from object and all its ancestors. + * + * @param object|string $object + * @param string $name + * + * @throws PropertyException + * @throws ReflectionException + * + * @return ReflectionProperty + */ + public static function getProperty($object, $name) + { + $reflection = is_object($object) ? new ReflectionObject($object) : new ReflectionClass($object); + + if ($reflection->hasProperty($name)) { + return $reflection->getProperty($name); + } + + if ($parentClass = $reflection->getParentClass()) { + return self::getProperty($parentClass->getName(), $name); + } + + throw new PropertyException( + sprintf( + 'The class "%s" doesn\'t have a property with the given name: "%s".', + is_object($object) ? get_class($object) : $object, + $name + ) + ); + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php new file mode 100644 index 0000000..becd1cf --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/Date/DateIntervalFilter.php @@ -0,0 +1,33 @@ + $propertyValue) { + $copy->{$propertyName} = $propertyValue; + } + + return $copy; + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php new file mode 100644 index 0000000..164f8b8 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ReplaceFilter.php @@ -0,0 +1,30 @@ +callback = $callable; + } + + /** + * {@inheritdoc} + */ + public function apply($element) + { + return call_user_func($this->callback, $element); + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php new file mode 100644 index 0000000..a5fbd7a --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/ShallowCopyFilter.php @@ -0,0 +1,17 @@ +copier = $copier; + } + + /** + * {@inheritdoc} + */ + public function apply($element) + { + $newElement = clone $element; + + $copy = $this->createCopyClosure(); + + return $copy($newElement); + } + + private function createCopyClosure() + { + $copier = $this->copier; + + $copy = function (SplDoublyLinkedList $list) use ($copier) { + // Replace each element in the list with a deep copy of itself + for ($i = 1; $i <= $list->count(); $i++) { + $copy = $copier->recursiveCopy($list->shift()); + + $list->push($copy); + } + + return $list; + }; + + return Closure::bind($copy, null, DeepCopy::class); + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php new file mode 100644 index 0000000..5785a7d --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/TypeFilter/TypeFilter.php @@ -0,0 +1,13 @@ +type = $type; + } + + /** + * @param mixed $element + * + * @return boolean + */ + public function matches($element) + { + return is_object($element) ? is_a($element, $this->type) : gettype($element) === $this->type; + } +} diff --git a/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php new file mode 100644 index 0000000..55dcc92 --- /dev/null +++ b/lib/MPDF/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php @@ -0,0 +1,20 @@ +copy($value); + } +} diff --git a/lib/MPDF/vendor/paragonie/random_compat/LICENSE b/lib/MPDF/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 0000000..45c7017 --- /dev/null +++ b/lib/MPDF/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lib/MPDF/vendor/paragonie/random_compat/build-phar.sh b/lib/MPDF/vendor/paragonie/random_compat/build-phar.sh new file mode 100755 index 0000000..b4a5ba3 --- /dev/null +++ b/lib/MPDF/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/lib/MPDF/vendor/paragonie/random_compat/composer.json b/lib/MPDF/vendor/paragonie/random_compat/composer.json new file mode 100644 index 0000000..1fa8de9 --- /dev/null +++ b/lib/MPDF/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,34 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "polyfill", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": "^7" + }, + "require-dev": { + "vimeo/psalm": "^1", + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + } +} diff --git a/lib/MPDF/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/lib/MPDF/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000..eb50ebf --- /dev/null +++ b/lib/MPDF/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/lib/MPDF/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/lib/MPDF/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000..6a1d7f3 --- /dev/null +++ b/lib/MPDF/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/lib/MPDF/vendor/paragonie/random_compat/lib/random.php b/lib/MPDF/vendor/paragonie/random_compat/lib/random.php new file mode 100644 index 0000000..c7731a5 --- /dev/null +++ b/lib/MPDF/vendor/paragonie/random_compat/lib/random.php @@ -0,0 +1,32 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/lib/MPDF/vendor/paragonie/random_compat/psalm-autoload.php b/lib/MPDF/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000..d71d1b8 --- /dev/null +++ b/lib/MPDF/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + diff --git a/lib/MPDF/vendor/psr/log/.gitignore b/lib/MPDF/vendor/psr/log/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/lib/MPDF/vendor/psr/log/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/lib/MPDF/vendor/psr/log/LICENSE b/lib/MPDF/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/lib/MPDF/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/MPDF/vendor/psr/log/Psr/Log/AbstractLogger.php b/lib/MPDF/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..90e721a --- /dev/null +++ b/lib/MPDF/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/lib/MPDF/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/lib/MPDF/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/lib/MPDF/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/lib/MPDF/vendor/psr/log/Psr/Log/LoggerInterface.php b/lib/MPDF/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..e695046 --- /dev/null +++ b/lib/MPDF/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/lib/MPDF/vendor/psr/log/Psr/Log/NullLogger.php b/lib/MPDF/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..c8f7293 --- /dev/null +++ b/lib/MPDF/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/lib/MPDF/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/lib/MPDF/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 0000000..9ecb6c4 --- /dev/null +++ b/lib/MPDF/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @@ -0,0 +1,146 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} + +class DummyTest +{ + public function __toString() + { + return 'DummyTest'; + } +} diff --git a/lib/MPDF/vendor/psr/log/Psr/Log/Test/TestLogger.php b/lib/MPDF/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000..1be3230 --- /dev/null +++ b/lib/MPDF/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/lib/MPDF/vendor/psr/log/README.md b/lib/MPDF/vendor/psr/log/README.md new file mode 100644 index 0000000..a9f20c4 --- /dev/null +++ b/lib/MPDF/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/lib/MPDF/vendor/psr/log/composer.json b/lib/MPDF/vendor/psr/log/composer.json new file mode 100644 index 0000000..3f6d4ee --- /dev/null +++ b/lib/MPDF/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/LICENSE.txt b/lib/MPDF/vendor/setasign/fpdi/LICENSE.txt new file mode 100644 index 0000000..7e75850 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Setasign - Jan Slabon, https://www.setasign.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/lib/MPDF/vendor/setasign/fpdi/README.md b/lib/MPDF/vendor/setasign/fpdi/README.md new file mode 100644 index 0000000..42856df --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/README.md @@ -0,0 +1,168 @@ +FPDI - Free PDF Document Importer +================================= + +[![Latest Stable Version](https://poser.pugx.org/setasign/fpdi/v/stable.svg)](https://packagist.org/packages/setasign/fpdi) +[![Total Downloads](https://poser.pugx.org/setasign/fpdi/downloads.svg)](https://packagist.org/packages/setasign/fpdi) +[![Latest Unstable Version](https://poser.pugx.org/setasign/fpdi/v/unstable.svg)](https://packagist.org/packages/setasign/fpdi) +[![License](https://poser.pugx.org/setasign/fpdi/license.svg)](https://packagist.org/packages/setasign/fpdi) +[![Build Status](https://travis-ci.org/Setasign/FPDI.svg?branch=development)](https://travis-ci.org/Setasign/FPDI) + +:heavy_exclamation_mark: This document refers to FPDI 2. Version 1 is deprecated and development is discontinued. :heavy_exclamation_mark: + +FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF +documents and use them as templates in [FPDF](http://www.fpdf.org), which was developed by Olivier Plathey. Apart +from a copy of [FPDF](http://www.fpdf.org), FPDI does not require any special PHP extensions. + +FPDI can also be used as an extension for [TCPDF](https://github.com/tecnickcom/TCPDF) or +[tFPDF](http://fpdf.org/en/script/script92.php), too. + +## Installation with [Composer](https://packagist.org/packages/setasign/fpdi) + +Because FPDI can be used with FPDF, TCPDF or tFPDF we didn't added a fixed dependency in the main +composer.json file but we added metadata packages for +[FPDF](https://github.com/Setasign/FPDI-FPDF), +[TCPDF](https://github.com/Setasign/FPDI-TCPDF) and +[tFPDF](https://github.com/Setasign/FPDI-tFPDF). + +### Evaluate Dependencies Automatically + +For FPDF add following [package](https://github.com/Setasign/FPDI-FPDF) to your composer.json: +```json +{ + "require": { + "setasign/fpdi-fpdf": "^2.0" + } +} +``` + +For TCPDF add following [package](https://github.com/Setasign/FPDI-TCPDF) to your composer.json: +```json +{ + "require": { + "setasign/fpdi-tcpdf": "^2.0" + } +} +``` + +For tFPDF add following [package](https://github.com/Setasign/FPDI-tFPDF) to your composer.json: +```json +{ + "require": { + "setasign/fpdi-tfpdf": "^2.1" + } +} +``` + +### Manual Dependencies + +If you don't want to use the metadata packages, it is up to you to add the dependencies to your +composer.json file. + +To use FPDI with FPDF include following in your composer.json file: + +```json +{ + "require": { + "setasign/fpdf": "^1.8", + "setasign/fpdi": "^2.0" + } +} +``` + +If you want to use TCPDF, your have to update your composer.json respectively to: + +```json +{ + "require": { + "tecnickcom/tcpdf": "^6.2", + "setasign/fpdi": "^2.0" + } +} +``` + +If you want to use tFPDF, your have to update your composer.json respectively to: + +```json +{ + "require": { + "tecnickcom/tfpdf": "1.25", + "setasign/fpdi": "^2.1" + } +} +``` + +## Manual Installation + +If you do not use composer, just require the autoload.php in the /src folder: + +```php +require_once('src/autoload.php'); +``` + +If you have a PSR-4 autoloader implemented, just register the src path as follows: +```php +$loader = new \Example\Psr4AutoloaderClass; +$loader->register(); +$loader->addNamespace('setasign\Fpdi', 'path/to/src/'); +``` + +## Changes to Version 1 + +Version 2 is a complete rewrite from scratch of FPDI which comes with: +- Namespaced code +- Clean and up-to-date code base and style +- PSR-4 compatible autoloading +- Performance improvements by up to 100% +- Less memory consumption +- Native support for reading PDFs from strings or stream-resources +- Support for documents with "invalid" data before their file-header +- Optimized page tree resolving +- Usage of individual exceptions +- Several test types (unit, functional and visual tests) + +We tried to keep the main methods and logical workflow the same as in version 1 but please +notice that there were incompatible changes which you should consider when updating to +version 2: +- You need to load the code using the `src/autoload.php` file instead of `classes/FPDI.php`. +- The classes and traits are namespaced now: `setasign\Fpdi` +- Page boundaries beginning with a slash, such as `/MediaBox`, are not supported anymore. Remove + the slash or use a constant of `PdfReader\PageBoundaries`. +- The parameters $x, $y, $width and $height of the `useTemplate()` or `getTemplateSize()` + method have more logical correct default values now. Passing `0` as width or height will + result in an `InvalidArgumentException` now. +- The return value of `getTemplateSize()` had changed to an array with more speaking keys + and reusability: Use `width` instead of `w` and `height` instead of `h`. +- If you want to use **FPDI with TCPDF** you need to refactor your code to use the class `Tcpdf\Fpdi` +(since 2.1; before it was `TcpdfFpdi`) instead of `FPDI`. + +## Example and Documentation + +A simple example, that imports a single page and places this onto a new created page: + +```php +AddPage(); +// set the source file +$pdf->setSourceFile("Fantastic-Speaker.pdf"); +// import page 1 +$tplId = $pdf->importPage(1); +// use the imported page and place it at point 10,10 with a width of 100 mm +$pdf->useTemplate($tplId, 10, 10, 100); + +$pdf->Output(); +``` + +A full end-user documentation and API reference is available [here](https://manuals.setasign.com/fpdi-manual/). \ No newline at end of file diff --git a/lib/MPDF/vendor/setasign/fpdi/composer.json b/lib/MPDF/vendor/setasign/fpdi/composer.json new file mode 100644 index 0000000..58f7931 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/composer.json @@ -0,0 +1,50 @@ +{ + "name": "setasign/fpdi", + "homepage": "https://www.setasign.com/fpdi", + "description": "FPDI is a collection of PHP classes facilitating developers to read pages from existing PDF documents and use them as templates in FPDF. Because it is also possible to use FPDI with TCPDF, there are no fixed dependencies defined. Please see suggestions for packages which evaluates the dependencies automatically.", + "type": "library", + "keywords": [ + "pdf", + "fpdi", + "fpdf" + ], + "license": "MIT", + "autoload": { + "psr-4": { + "setasign\\Fpdi\\": "src/" + } + }, + "require": { + "php": "^5.6 || ^7.0", + "ext-zlib": "*" + }, + "authors": [ + { + "name": "Jan Slabon", + "email": "jan.slabon@setasign.com", + "homepage": "https://www.setasign.com" + }, + { + "name": "Maximilian Kresse", + "email": "maximilian.kresse@setasign.com", + "homepage": "https://www.setasign.com" + } + ], + "suggest": { + "setasign/fpdf": "FPDI will extend this class but as it is also possible to use TCPDF or tFPDF as an alternative. There's no fixed dependency configured.", + "setasign/fpdi-fpdf": "Use this package to automatically evaluate dependencies to FPDF.", + "setasign/fpdi-tcpdf": "Use this package to automatically evaluate dependencies to TCPDF.", + "setasign/fpdi-tfpdf": "Use this package to automatically evaluate dependencies to tFPDF." + }, + "require-dev": { + "phpunit/phpunit": "~5.7", + "setasign/fpdf": "~1.8", + "tecnickcom/tcpdf": "~6.2", + "setasign/tfpdf": "1.25" + }, + "autoload-dev": { + "psr-4": { + "setasign\\Fpdi\\": "tests/" + } + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/FpdfTpl.php b/lib/MPDF/vendor/setasign/fpdi/src/FpdfTpl.php new file mode 100644 index 0000000..fba7c6a --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/FpdfTpl.php @@ -0,0 +1,22 @@ +currentTemplateId !== null) { + throw new \BadMethodCallException('The page format cannot be changed when writing to a template.'); + } + + if (!\in_array($orientation, ['P', 'L'], true)) { + throw new \InvalidArgumentException(\sprintf( + 'Invalid page orientation "%s"! Only "P" and "L" are allowed!', + $orientation + )); + } + + $size = $this->_getpagesize($size); + + if ($orientation != $this->CurOrientation + || $size[0] != $this->CurPageSize[0] + || $size[1] != $this->CurPageSize[1] + ) { + // New size or orientation + if ($orientation === 'P') { + $this->w = $size[0]; + $this->h = $size[1]; + } else { + $this->w = $size[1]; + $this->h = $size[0]; + } + $this->wPt = $this->w * $this->k; + $this->hPt = $this->h * $this->k; + $this->PageBreakTrigger = $this->h - $this->bMargin; + $this->CurOrientation = $orientation; + $this->CurPageSize = $size; + + $this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt); + } + } + + /** + * Draws a template onto the page or another template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param array|float|int $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size + * @see FpdfTplTrait::getTemplateSize() + */ + public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + if (!isset($this->templates[$tpl])) { + throw new \InvalidArgumentException('Template does not exist!'); + } + + if (\is_array($x)) { + unset($x['tpl']); + \extract($x, EXTR_IF_EXISTS); + /** @noinspection NotOptimalIfConditionsInspection */ + /** @noinspection CallableParameterUseCaseInTypeContextInspection */ + if (\is_array($x)) { + $x = 0; + } + } + + $template = $this->templates[$tpl]; + + $originalSize = $this->getTemplateSize($tpl); + $newSize = $this->getTemplateSize($tpl, $width, $height); + if ($adjustPageSize) { + $this->setPageFormat($newSize, $newSize['orientation']); + } + + $this->_out( + // reset standard values, translate and scale + \sprintf( + 'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q', + ($newSize['width'] / $originalSize['width']), + ($newSize['height'] / $originalSize['height']), + $x * $this->k, + ($this->h - $y - $newSize['height']) * $this->k, + $template['id'] + ) + ); + + return $newSize; + } + + /** + * Get the size of a template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getTemplateSize($tpl, $width = null, $height = null) + { + if (!isset($this->templates[$tpl])) { + return false; + } + + if ($width === null && $height === null) { + $width = $this->templates[$tpl]['width']; + $height = $this->templates[$tpl]['height']; + } elseif ($width === null) { + $width = $height * $this->templates[$tpl]['width'] / $this->templates[$tpl]['height']; + } + + if ($height === null) { + $height = $width * $this->templates[$tpl]['height'] / $this->templates[$tpl]['width']; + } + + if ($height <= 0. || $width <= 0.) { + throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.'); + } + + return [ + 'width' => $width, + 'height' => $height, + 0 => $width, + 1 => $height, + 'orientation' => $width > $height ? 'L' : 'P' + ]; + } + + /** + * Begins a new template. + * + * @param float|int|null $width The width of the template. If null, the current page width is used. + * @param float|int|null $height The height of the template. If null, the current page height is used. + * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used). + * @return int A template identifier. + */ + public function beginTemplate($width = null, $height = null, $groupXObject = false) + { + if ($width === null) { + $width = $this->w; + } + + if ($height === null) { + $height = $this->h; + } + + $templateId = $this->getNextTemplateId(); + + // initiate buffer with current state of FPDF + $buffer = "2 J\n" + . \sprintf('%.2F w', $this->LineWidth * $this->k) . "\n"; + + if ($this->FontFamily) { + $buffer .= \sprintf("BT /F%d %.2F Tf ET\n", $this->CurrentFont['i'], $this->FontSizePt); + } + + if ($this->DrawColor !== '0 G') { + $buffer .= $this->DrawColor . "\n"; + } + if ($this->FillColor !== '0 g') { + $buffer .= $this->FillColor . "\n"; + } + + if ($groupXObject && \version_compare('1.4', $this->PDFVersion, '>')) { + $this->PDFVersion = '1.4'; + } + + $this->templates[$templateId] = [ + 'objectNumber' => null, + 'id' => 'TPL' . $templateId, + 'buffer' => $buffer, + 'width' => $width, + 'height' => $height, + 'groupXObject' => $groupXObject, + 'state' => [ + 'x' => $this->x, + 'y' => $this->y, + 'AutoPageBreak' => $this->AutoPageBreak, + 'bMargin' => $this->bMargin, + 'tMargin' => $this->tMargin, + 'lMargin' => $this->lMargin, + 'rMargin' => $this->rMargin, + 'h' => $this->h, + 'w' => $this->w, + 'FontFamily' => $this->FontFamily, + 'FontStyle' => $this->FontStyle, + 'FontSizePt' => $this->FontSizePt, + 'FontSize' => $this->FontSize, + 'underline' => $this->underline, + 'TextColor' => $this->TextColor, + 'DrawColor' => $this->DrawColor, + 'FillColor' => $this->FillColor, + 'ColorFlag' => $this->ColorFlag + ] + ]; + + $this->SetAutoPageBreak(false); + $this->currentTemplateId = $templateId; + + $this->h = $height; + $this->w = $width; + + $this->SetXY($this->lMargin, $this->tMargin); + $this->SetRightMargin($this->w - $width + $this->rMargin); + + return $templateId; + } + + /** + * Ends a template. + * + * @return bool|int|null A template identifier. + */ + public function endTemplate() + { + if (null === $this->currentTemplateId) { + return false; + } + + $templateId = $this->currentTemplateId; + $template = $this->templates[$templateId]; + + $state = $template['state']; + $this->SetXY($state['x'], $state['y']); + $this->tMargin = $state['tMargin']; + $this->lMargin = $state['lMargin']; + $this->rMargin = $state['rMargin']; + $this->h = $state['h']; + $this->w = $state['w']; + $this->SetAutoPageBreak($state['AutoPageBreak'], $state['bMargin']); + + $this->FontFamily = $state['FontFamily']; + $this->FontStyle = $state['FontStyle']; + $this->FontSizePt = $state['FontSizePt']; + $this->FontSize = $state['FontSize']; + + $this->TextColor = $state['TextColor']; + $this->DrawColor = $state['DrawColor']; + $this->FillColor = $state['FillColor']; + $this->ColorFlag = $state['ColorFlag']; + + $this->underline = $state['underline']; + + $fontKey = $this->FontFamily . $this->FontStyle; + if ($fontKey) { + $this->CurrentFont =& $this->fonts[$fontKey]; + } else { + unset($this->CurrentFont); + } + + $this->currentTemplateId = null; + + return $templateId; + } + + /** + * Get the next template id. + * + * @return int + */ + protected function getNextTemplateId() + { + return $this->templateId++; + } + + /* overwritten FPDF methods: */ + + /** + * @inheritdoc + */ + public function AddPage($orientation = '', $size = '', $rotation = 0) + { + if ($this->currentTemplateId !== null) { + throw new \BadMethodCallException('Pages cannot be added when writing to a template.'); + } + parent::AddPage($orientation, $size, $rotation); + } + + /** + * @inheritdoc + */ + public function Link($x, $y, $w, $h, $link) + { + if ($this->currentTemplateId !== null) { + throw new \BadMethodCallException('Links cannot be set when writing to a template.'); + } + parent::Link($x, $y, $w, $h, $link); + } + + /** + * @inheritdoc + */ + public function SetLink($link, $y = 0, $page = -1) + { + if ($this->currentTemplateId !== null) { + throw new \BadMethodCallException('Links cannot be set when writing to a template.'); + } + return parent::SetLink($link, $y, $page); + } + + /** + * @inheritdoc + */ + public function SetDrawColor($r, $g = null, $b = null) + { + parent::SetDrawColor($r, $g, $b); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out($this->DrawColor); + } + } + + /** + * @inheritdoc + */ + public function SetFillColor($r, $g = null, $b = null) + { + parent::SetFillColor($r, $g, $b); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out($this->FillColor); + } + } + + /** + * @inheritdoc + */ + public function SetLineWidth($width) + { + parent::SetLineWidth($width); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out(\sprintf('%.2F w', $width * $this->k)); + } + } + + /** + * @inheritdoc + */ + public function SetFont($family, $style = '', $size = 0) + { + parent::SetFont($family, $style, $size); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out(\sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); + } + } + + /** + * @inheritdoc + */ + public function SetFontSize($size) + { + parent::SetFontSize($size); + if ($this->page === 0 && $this->currentTemplateId !== null) { + $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); + } + } + + /** + * @inheritdoc + */ + protected function _putimages() + { + parent::_putimages(); + + foreach ($this->templates as $key => $template) { + $this->_newobj(); + $this->templates[$key]['objectNumber'] = $this->n; + + $this->_put('<_put(\sprintf('/BBox[0 0 %.2F %.2F]', $template['width'] * $this->k, $template['height'] * $this->k)); + $this->_put('/Resources 2 0 R'); // default resources dictionary of FPDF + + if ($this->compress) { + $buffer = \gzcompress($template['buffer']); + $this->_put('/Filter/FlateDecode'); + } else { + $buffer = $template['buffer']; + } + + $this->_put('/Length ' . \strlen($buffer)); + + if ($template['groupXObject']) { + $this->_put('/Group <>'); + } + + $this->_put('>>'); + $this->_putstream($buffer); + $this->_put('endobj'); + } + } + + /** + * @inheritdoc + */ + protected function _putxobjectdict() + { + foreach ($this->templates as $key => $template) { + $this->_put('/' . $template['id'] . ' ' . $template['objectNumber'] . ' 0 R'); + } + + parent::_putxobjectdict(); + } + + /** + * @inheritdoc + */ + public function _out($s) + { + if ($this->currentTemplateId !== null) { + $this->templates[$this->currentTemplateId]['buffer'] .= $s . "\n"; + } else { + parent::_out($s); + } + } +} \ No newline at end of file diff --git a/lib/MPDF/vendor/setasign/fpdi/src/Fpdi.php b/lib/MPDF/vendor/setasign/fpdi/src/Fpdi.php new file mode 100644 index 0000000..b49c103 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/Fpdi.php @@ -0,0 +1,155 @@ +cleanUp(); + } + + /** + * Draws an imported page or a template onto the page or another template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size + * @see Fpdi::getTemplateSize() + */ + public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + if (isset($this->importedPages[$tpl])) { + $size = $this->useImportedPage($tpl, $x, $y, $width, $height, $adjustPageSize); + if ($this->currentTemplateId !== null) { + $this->templates[$this->currentTemplateId]['resources']['templates']['importedPages'][$tpl] = $tpl; + } + return $size; + } + + return parent::useTemplate($tpl, $x, $y, $width, $height, $adjustPageSize); + } + + /** + * Get the size of an imported page or template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getTemplateSize($tpl, $width = null, $height = null) + { + $size = parent::getTemplateSize($tpl, $width, $height); + if ($size === false) { + return $this->getImportedPageSize($tpl, $width, $height); + } + + return $size; + } + + /** + * @inheritdoc + * @throws CrossReferenceException + * @throws PdfParserException + */ + protected function _putimages() + { + $this->currentReaderId = null; + parent::_putimages(); + + foreach ($this->importedPages as $key => $pageData) { + $this->_newobj(); + $this->importedPages[$key]['objectNumber'] = $this->n; + $this->currentReaderId = $pageData['readerId']; + $this->writePdfType($pageData['stream']); + $this->_put('endobj'); + } + + foreach (\array_keys($this->readers) as $readerId) { + $parser = $this->getPdfReader($readerId)->getParser(); + $this->currentReaderId = $readerId; + + while (($objectNumber = \array_pop($this->objectsToCopy[$readerId])) !== null) { + try { + $object = $parser->getIndirectObject($objectNumber); + + } catch (CrossReferenceException $e) { + if ($e->getCode() === CrossReferenceException::OBJECT_NOT_FOUND) { + $object = PdfIndirectObject::create($objectNumber, 0, new PdfNull()); + } else { + throw $e; + } + } + + $this->writePdfType($object); + } + } + + $this->currentReaderId = null; + } + + /** + * @inheritdoc + */ + protected function _putxobjectdict() + { + foreach ($this->importedPages as $key => $pageData) { + $this->_put('/' . $pageData['id'] . ' ' . $pageData['objectNumber'] . ' 0 R'); + } + + parent::_putxobjectdict(); + } + + /** + * @inheritdoc + */ + protected function _put($s, $newLine = true) + { + if ($newLine) { + $this->buffer .= $s . "\n"; + } else { + $this->buffer .= $s; + } + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/FpdiException.php b/lib/MPDF/vendor/setasign/fpdi/src/FpdiException.php new file mode 100644 index 0000000..d126c14 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/FpdiException.php @@ -0,0 +1,19 @@ +readers) : $this->createdReaders; + foreach ($readers as $id) { + $this->readers[$id]->getParser()->getStreamReader()->cleanUp(); + unset($this->readers[$id]); + } + + $this->createdReaders= []; + } + + /** + * Set the minimal PDF version. + * + * @param string $pdfVersion + */ + protected function setMinPdfVersion($pdfVersion) + { + if (\version_compare($pdfVersion, $this->PDFVersion, '>')) { + $this->PDFVersion = $pdfVersion; + } + } + + /** @noinspection PhpUndefinedClassInspection */ + /** + * Get a new pdf parser instance. + * + * @param StreamReader $streamReader + * @return PdfParser|FpdiPdfParser + */ + protected function getPdfParserInstance(StreamReader $streamReader) + { + /** @noinspection PhpUndefinedClassInspection */ + if (\class_exists(FpdiPdfParser::class)) { + /** @noinspection PhpUndefinedClassInspection */ + return new FpdiPdfParser($streamReader); + } + + return new PdfParser($streamReader); + } + + /** + * Get an unique reader id by the $file parameter. + * + * @param string|resource|PdfReader|StreamReader $file An open file descriptor, a path to a file, a PdfReader + * instance or a StreamReader instance. + * @return string + */ + protected function getPdfReaderId($file) + { + if (\is_resource($file)) { + $id = (string) $file; + } elseif (\is_string($file)) { + $id = \realpath($file); + if ($id === false) { + $id = $file; + } + } elseif (\is_object($file)) { + $id = \spl_object_hash($file); + } else { + throw new \InvalidArgumentException( + \sprintf('Invalid type in $file parameter (%s)', \gettype($file)) + ); + } + + /** @noinspection OffsetOperationsInspection */ + if (isset($this->readers[$id])) { + return $id; + } + + if (\is_resource($file)) { + $streamReader = new StreamReader($file); + } elseif (\is_string($file)) { + $streamReader = StreamReader::createByFile($file); + $this->createdReaders[] = $id; + } else { + $streamReader = $file; + } + + $reader = new PdfReader($this->getPdfParserInstance($streamReader)); + /** @noinspection OffsetOperationsInspection */ + $this->readers[$id] = $reader; + + return $id; + } + + /** + * Get a pdf reader instance by its id. + * + * @param string $id + * @return PdfReader + */ + protected function getPdfReader($id) + { + if (isset($this->readers[$id])) { + return $this->readers[$id]; + } + + throw new \InvalidArgumentException( + \sprintf('No pdf reader with the given id (%s) exists.', $id) + ); + } + + /** + * Set the source PDF file. + * + * @param string|resource|StreamReader $file Path to the file or a stream resource or a StreamReader instance. + * @return int The page count of the PDF document. + * @throws PdfParserException + */ + public function setSourceFile($file) + { + $this->currentReaderId = $this->getPdfReaderId($file); + $this->objectsToCopy[$this->currentReaderId] = []; + + $reader = $this->getPdfReader($this->currentReaderId); + $this->setMinPdfVersion($reader->getPdfVersion()); + + return $reader->getPageCount(); + } + + /** + * Imports a page. + * + * @param int $pageNumber The page number. + * @param string $box The page boundary to import. Default set to PageBoundaries::CROP_BOX. + * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used). + * @return string A unique string identifying the imported page. + * @throws CrossReferenceException + * @throws FilterException + * @throws PdfParserException + * @throws PdfTypeException + * @throws PdfReaderException + * @see PageBoundaries + */ + public function importPage($pageNumber, $box = PageBoundaries::CROP_BOX, $groupXObject = true) + { + if (null === $this->currentReaderId) { + throw new \BadMethodCallException('No reader initiated. Call setSourceFile() first.'); + } + + $pageId = $this->currentReaderId; + + $pageNumber = (int)$pageNumber; + $pageId .= '|' . $pageNumber . '|' . ($groupXObject ? '1' : '0'); + + // for backwards compatibility with FPDI 1 + $box = \ltrim($box, '/'); + if (!PageBoundaries::isValidName($box)) { + throw new \InvalidArgumentException( + \sprintf('Box name is invalid: "%s"', $box) + ); + } + + $pageId .= '|' . $box; + + if (isset($this->importedPages[$pageId])) { + return $pageId; + } + + $reader = $this->getPdfReader($this->currentReaderId); + $page = $reader->getPage($pageNumber); + + $bbox = $page->getBoundary($box); + if ($bbox === false) { + throw new PdfReaderException( + \sprintf("Page doesn't have a boundary box (%s).", $box), + PdfReaderException::MISSING_DATA + ); + } + + $dict = new PdfDictionary(); + $dict->value['Type'] = PdfName::create('XObject'); + $dict->value['Subtype'] = PdfName::create('Form'); + $dict->value['FormType'] = PdfNumeric::create(1); + $dict->value['BBox'] = $bbox->toPdfArray(); + + if ($groupXObject) { + $this->setMinPdfVersion('1.4'); + $dict->value['Group'] = PdfDictionary::create([ + 'Type' => PdfName::create('Group'), + 'S' => PdfName::create('Transparency') + ]); + } + + $resources = $page->getAttribute('Resources'); + if ($resources !== null) { + $dict->value['Resources'] = $resources; + } + + list($width, $height) = $page->getWidthAndHeight($box); + + $a = 1; + $b = 0; + $c = 0; + $d = 1; + $e = -$bbox->getLlx(); + $f = -$bbox->getLly(); + + $rotation = $page->getRotation(); + + if ($rotation !== 0) { + $rotation *= -1; + $angle = $rotation * M_PI/180; + $a = \cos($angle); + $b = \sin($angle); + $c = -$b; + $d = $a; + + switch ($rotation) { + case -90: + $e = -$bbox->getLly(); + $f = $bbox->getUrx(); + break; + case -180: + $e = $bbox->getUrx(); + $f = $bbox->getUry(); + break; + case -270: + $e = $bbox->getUry(); + $f = -$bbox->getLlx(); + break; + } + } + + // we need to rotate/translate + if ($a != 1 || $b != 0 || $c != 0 || $d != 1 || $e != 0 || $f != 0) { + $dict->value['Matrix'] = PdfArray::create([ + PdfNumeric::create($a), PdfNumeric::create($b), PdfNumeric::create($c), + PdfNumeric::create($d), PdfNumeric::create($e), PdfNumeric::create($f) + ]); + } + + // try to use the existing content stream + $pageDict = $page->getPageDictionary(); + + $contentsObject = PdfType::resolve(PdfDictionary::get($pageDict, 'Contents'), $reader->getParser(), true); + $contents = PdfType::resolve($contentsObject, $reader->getParser()); + + // just copy the stream reference if it is only a single stream + if (($contentsIsStream = ($contents instanceof PdfStream)) + || ($contents instanceof PdfArray && \count($contents->value) === 1) + ) { + if ($contentsIsStream) { + /** + * @var PdfIndirectObject $contentsObject + */ + $stream = $contents; + } else { + $stream = PdfType::resolve($contents->value[0], $reader->getParser()); + } + + $filter = PdfDictionary::get($stream->value, 'Filter'); + if (!$filter instanceof PdfNull) { + $dict->value['Filter'] = $filter; + } + $length = PdfType::resolve(PdfDictionary::get($stream->value, 'Length'), $reader->getParser()); + $dict->value['Length'] = $length; + $stream->value = $dict; + + // otherwise extract it from the array and re-compress the whole stream + } else { + $streamContent = $this->compress + ? \gzcompress($page->getContentStream()) + : $page->getContentStream(); + + $dict->value['Length'] = PdfNumeric::create(\strlen($streamContent)); + if ($this->compress) { + $dict->value['Filter'] = PdfName::create('FlateDecode'); + } + + $stream = PdfStream::create($dict, $streamContent); + } + + $this->importedPages[$pageId] = [ + 'objectNumber' => null, + 'readerId' => $this->currentReaderId, + 'id' => 'TPL' . $this->getNextTemplateId(), + 'width' => $width / $this->k, + 'height' => $height / $this->k, + 'stream' => $stream + ]; + + return $pageId; + } + + /** + * Draws an imported page onto the page. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $pageId The page id + * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size. + * @see Fpdi::getTemplateSize() + */ + public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + if (\is_array($x)) { + /** @noinspection OffsetOperationsInspection */ + unset($x['pageId']); + \extract($x, EXTR_IF_EXISTS); + /** @noinspection NotOptimalIfConditionsInspection */ + if (\is_array($x)) { + $x = 0; + } + } + + if (!isset($this->importedPages[$pageId])) { + throw new \InvalidArgumentException('Imported page does not exist!'); + } + + $importedPage = $this->importedPages[$pageId]; + + $originalSize = $this->getTemplateSize($pageId); + $newSize = $this->getTemplateSize($pageId, $width, $height); + if ($adjustPageSize) { + $this->setPageFormat($newSize, $newSize['orientation']); + } + + $this->_out( + // reset standard values, translate and scale + \sprintf( + 'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q', + ($newSize['width'] / $originalSize['width']), + ($newSize['height'] / $originalSize['height']), + $x * $this->k, + ($this->h - $y - $newSize['height']) * $this->k, + $importedPage['id'] + ) + ); + + return $newSize; + } + + /** + * Get the size of an imported page. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getImportedPageSize($tpl, $width = null, $height = null) + { + if (isset($this->importedPages[$tpl])) { + $importedPage = $this->importedPages[$tpl]; + + if ($width === null && $height === null) { + $width = $importedPage['width']; + $height = $importedPage['height']; + } elseif ($width === null) { + $width = $height * $importedPage['width'] / $importedPage['height']; + } + + if ($height === null) { + $height = $width * $importedPage['height'] / $importedPage['width']; + } + + if ($height <= 0. || $width <= 0.) { + throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.'); + } + + return [ + 'width' => $width, + 'height' => $height, + 0 => $width, + 1 => $height, + 'orientation' => $width > $height ? 'L' : 'P' + ]; + } + + return false; + } + + /** + * Writes a PdfType object to the resulting buffer. + * + * @param PdfType $value + * @throws PdfTypeException + */ + protected function writePdfType(PdfType $value) + { + if ($value instanceof PdfNumeric) { + if (\is_int($value->value)) { + $this->_put($value->value . ' ', false); + } else { + $this->_put(\rtrim(\rtrim(\sprintf('%.5F', $value->value), '0'), '.') . ' ', false); + } + + } elseif ($value instanceof PdfName) { + $this->_put('/' . $value->value . ' ', false); + + } elseif ($value instanceof PdfString) { + $this->_put('(' . $value->value . ')', false); + + } elseif ($value instanceof PdfHexString) { + $this->_put('<' . $value->value . '>'); + + } elseif ($value instanceof PdfBoolean) { + $this->_put($value->value ? 'true ' : 'false ', false); + + } elseif ($value instanceof PdfArray) { + $this->_put('[', false); + foreach ($value->value as $entry) { + $this->writePdfType($entry); + } + $this->_put(']'); + + } elseif ($value instanceof PdfDictionary) { + $this->_put('<<', false); + foreach ($value->value as $name => $entry) { + $this->_put('/' . $name . ' ', false); + $this->writePdfType($entry); + } + $this->_put('>>'); + + } elseif ($value instanceof PdfToken) { + $this->_put($value->value); + + } elseif ($value instanceof PdfNull) { + $this->_put('null '); + + } elseif ($value instanceof PdfStream) { + /** + * @var $value PdfStream + */ + $this->writePdfType($value->value); + $this->_put('stream'); + $this->_put($value->getStream()); + $this->_put('endstream'); + + } elseif ($value instanceof PdfIndirectObjectReference) { + if (!isset($this->objectMap[$this->currentReaderId])) { + $this->objectMap[$this->currentReaderId] = []; + } + + if (!isset($this->objectMap[$this->currentReaderId][$value->value])) { + $this->objectMap[$this->currentReaderId][$value->value] = ++$this->n; + $this->objectsToCopy[$this->currentReaderId][] = $value->value; + } + + $this->_put($this->objectMap[$this->currentReaderId][$value->value] . ' 0 R ', false); + + } elseif ($value instanceof PdfIndirectObject) { + /** + * @var $value PdfIndirectObject + */ + $n = $this->objectMap[$this->currentReaderId][$value->objectNumber]; + $this->_newobj($n); + $this->writePdfType($value->value); + $this->_put('endobj'); + } + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/AbstractReader.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/AbstractReader.php new file mode 100644 index 0000000..1d5c579 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/AbstractReader.php @@ -0,0 +1,96 @@ +parser = $parser; + $this->readTrailer(); + } + + /** + * Get the trailer dictionary. + * + * @return PdfDictionary + */ + public function getTrailer() + { + return $this->trailer; + } + + /** + * Read the trailer dictionary. + * + * @throws CrossReferenceException + * @throws PdfTypeException + */ + protected function readTrailer() + { + try { + $trailerKeyword = $this->parser->readValue(null, PdfToken::class); + if ($trailerKeyword->value !== 'trailer') { + throw new CrossReferenceException( + \sprintf( + 'Unexpected end of cross reference. "trailer"-keyword expected, got: %s.', + $trailerKeyword->value + ), + CrossReferenceException::UNEXPECTED_END + ); + } + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + 'Unexpected end of cross reference. "trailer"-keyword expected, got an invalid object type.', + CrossReferenceException::UNEXPECTED_END, + $e + ); + } + + try { + $trailer = $this->parser->readValue(null, PdfDictionary::class); + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + 'Unexpected end of cross reference. Trailer not found.', + CrossReferenceException::UNEXPECTED_END, + $e + ); + } + + $this->trailer = $trailer; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/CrossReference.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/CrossReference.php new file mode 100644 index 0000000..95674ce --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/CrossReference.php @@ -0,0 +1,320 @@ +parser = $parser; + $this->fileHeaderOffset = $fileHeaderOffset; + + $offset = $this->findStartXref(); + $reader = null; + /** @noinspection TypeUnsafeComparisonInspection */ + while ($offset != false) { // By doing an unsafe comparsion we ignore faulty references to byte offset 0 + try { + $reader = $this->readXref($offset + $this->fileHeaderOffset); + } catch (CrossReferenceException $e) { + // sometimes the file header offset is part of the byte offsets, so let's retry by resetting it to zero. + if ($e->getCode() === CrossReferenceException::INVALID_DATA && $this->fileHeaderOffset !== 0) { + $this->fileHeaderOffset = 0; + $reader = $this->readXref($offset + $this->fileHeaderOffset); + } else { + throw $e; + } + } + + $trailer = $reader->getTrailer(); + $this->checkForEncryption($trailer); + $this->readers[] = $reader; + + if (isset($trailer->value['Prev'])) { + $offset = $trailer->value['Prev']->value; + } else { + $offset = false; + } + } + + // fix faulty sub-section header + if ($reader instanceof FixedReader) { + /** + * @var FixedReader $reader + */ + $reader->fixFaultySubSectionShift(); + } + + if ($reader === null) { + throw new CrossReferenceException('No cross-reference found.', CrossReferenceException::NO_XREF_FOUND); + } + } + + /** + * Get the size of the cross reference. + * + * @return integer + */ + public function getSize() + { + return $this->getTrailer()->value['Size']->value; + } + + /** + * Get the trailer dictionary. + * + * @return PdfDictionary + */ + public function getTrailer() + { + return $this->readers[0]->getTrailer(); + } + + /** + * Get the cross reference readser instances. + * + * @return ReaderInterface[] + */ + public function getReaders() + { + return $this->readers; + } + + /** + * Get the offset by an object number. + * + * @param int $objectNumber + * @return integer|bool + */ + public function getOffsetFor($objectNumber) + { + foreach ($this->getReaders() as $reader) { + $offset = $reader->getOffsetFor($objectNumber); + if ($offset !== false) { + return $offset; + } + } + + return false; + } + + /** + * Get an indirect object by its object number. + * + * @param int $objectNumber + * @return PdfIndirectObject + * @throws CrossReferenceException + */ + public function getIndirectObject($objectNumber) + { + $offset = $this->getOffsetFor($objectNumber); + if ($offset === false) { + throw new CrossReferenceException( + \sprintf('Object (id:%s) not found.', $objectNumber), + CrossReferenceException::OBJECT_NOT_FOUND + ); + } + + $parser = $this->parser; + + $parser->getTokenizer()->clearStack(); + $parser->getStreamReader()->reset($offset + $this->fileHeaderOffset); + + try { + /** @var PdfIndirectObject $object */ + $object = $parser->readValue(null, PdfIndirectObject::class); + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + \sprintf('Object (id:%s) not found at location (%s).', $objectNumber, $offset), + CrossReferenceException::OBJECT_NOT_FOUND, + $e + ); + } + + if ($object->objectNumber !== $objectNumber) { + throw new CrossReferenceException( + \sprintf('Wrong object found, got %s while %s was expected.', $object->objectNumber, $objectNumber), + CrossReferenceException::OBJECT_NOT_FOUND + ); + } + + return $object; + } + + /** + * Read the cross-reference table at a given offset. + * + * Internally the method will try to evaluate the best reader for this cross-reference. + * + * @param int $offset + * @return ReaderInterface + * @throws CrossReferenceException + * @throws PdfTypeException + */ + protected function readXref($offset) + { + $this->parser->getStreamReader()->reset($offset); + $this->parser->getTokenizer()->clearStack(); + $initValue = $this->parser->readValue(); + + return $this->initReaderInstance($initValue); + } + + /** + * Get a cross-reference reader instance. + * + * @param PdfToken|PdfIndirectObject $initValue + * @return ReaderInterface|bool + * @throws CrossReferenceException + * @throws PdfTypeException + */ + protected function initReaderInstance($initValue) + { + $position = $this->parser->getStreamReader()->getPosition() + + $this->parser->getStreamReader()->getOffset() + $this->fileHeaderOffset; + + if ($initValue instanceof PdfToken && $initValue->value === 'xref') { + try { + return new FixedReader($this->parser); + } catch (CrossReferenceException $e) { + $this->parser->getStreamReader()->reset($position); + $this->parser->getTokenizer()->clearStack(); + + return new LineReader($this->parser); + } + } + + if ($initValue instanceof PdfIndirectObject) { + // check for encryption + $stream = PdfStream::ensure($initValue->value); + + $type = PdfDictionary::get($stream->value, 'Type'); + if ($type->value !== 'XRef') { + throw new CrossReferenceException( + 'The xref position points to an incorrect object type.', + CrossReferenceException::INVALID_DATA + ); + } + + $this->checkForEncryption($stream->value); + + throw new CrossReferenceException( + 'This PDF document probably uses a compression technique which is not supported by the ' . + 'free parser shipped with FPDI. (See https://www.setasign.com/fpdi-pdf-parser for more details)', + CrossReferenceException::COMPRESSED_XREF + ); + } + + throw new CrossReferenceException( + 'The xref position points to an incorrect object type.', + CrossReferenceException::INVALID_DATA + ); + } + + /** + * Check for encryption. + * + * @param PdfDictionary $dictionary + * @throws CrossReferenceException + */ + protected function checkForEncryption(PdfDictionary $dictionary) + { + if (isset($dictionary->value['Encrypt'])) { + throw new CrossReferenceException( + 'This PDF document is encrypted and cannot be processed with FPDI.', + CrossReferenceException::ENCRYPTED + ); + } + } + + /** + * Find the start position for the first cross-reference. + * + * @return int The byte-offset position of the first cross-reference. + * @throws CrossReferenceException + */ + protected function findStartXref() + { + $reader = $this->parser->getStreamReader(); + $reader->reset(-self::$trailerSearchLength, self::$trailerSearchLength); + + $buffer = $reader->getBuffer(false); + $pos = \strrpos($buffer, 'startxref'); + $addOffset = 9; + if ($pos === false) { + // Some corrupted documents uses startref, instead of startxref + $pos = \strrpos($buffer, 'startref'); + if ($pos === false) { + throw new CrossReferenceException( + 'Unable to find pointer to xref table', + CrossReferenceException::NO_STARTXREF_FOUND + ); + } + $addOffset = 8; + } + + $reader->setOffset($pos + $addOffset); + + try { + $value = $this->parser->readValue(null, PdfNumeric::class); + } catch (PdfTypeException $e) { + throw new CrossReferenceException( + 'Invalid data after startxref keyword.', + CrossReferenceException::INVALID_DATA, + $e + ); + } + + return $value->value; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/CrossReferenceException.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/CrossReferenceException.php new file mode 100644 index 0000000..3fa9b33 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/CrossReferenceException.php @@ -0,0 +1,80 @@ +reader = $parser->getStreamReader(); + $this->read(); + parent::__construct($parser); + } + + /** + * Get all subsection data. + * + * @return array + */ + public function getSubSections() + { + return $this->subSections; + } + + /** + * @inheritdoc + */ + public function getOffsetFor($objectNumber) + { + foreach ($this->subSections as $offset => list($startObject, $objectCount)) { + if ($objectNumber >= $startObject && $objectNumber < ($startObject + $objectCount)) { + $position = $offset + 20 * ($objectNumber - $startObject); + $this->reader->ensure($position, 20); + $line = $this->reader->readBytes(20); + if ($line[17] === 'f') { + return false; + } + + return (int) \substr($line, 0, 10); + } + } + + return false; + } + + /** + * Read the cross-reference. + * + * This reader will only read the subsections in this method. The offsets were resolved individually by this + * information. + * + * @throws CrossReferenceException + */ + protected function read() + { + $subSections = []; + + $startObject = $entryCount = $lastLineStart = null; + $validityChecked = false; + while (($line = $this->reader->readLine(20)) !== false) { + if (\strpos($line, 'trailer') !== false) { + $this->reader->reset($lastLineStart); + break; + } + + // jump over if line content doesn't match the expected string + if (\sscanf($line, '%d %d', $startObject, $entryCount) !== 2) { + continue; + } + + $oldPosition = $this->reader->getPosition(); + $position = $oldPosition + $this->reader->getOffset(); + + if (!$validityChecked && $entryCount > 0) { + $nextLine = $this->reader->readBytes(21); + /* Check the next line for maximum of 20 bytes and not longer + * By catching 21 bytes and trimming the length should be still 21. + */ + if (\strlen(\trim($nextLine)) !== 21) { + throw new CrossReferenceException( + 'Cross-reference entries are larger than 20 bytes.', + CrossReferenceException::ENTRIES_TOO_LARGE + ); + } + + /* Check for less than 20 bytes: cut the line to 20 bytes and trim; have to result in exactly 18 bytes. + * If it would have less bytes the substring would get the first bytes of the next line which would + * evaluate to a 20 bytes long string after trimming. + */ + if (\strlen(\trim(\substr($nextLine, 0, 20))) !== 18) { + throw new CrossReferenceException( + 'Cross-reference entries are less than 20 bytes.', + CrossReferenceException::ENTRIES_TOO_SHORT + ); + } + + $validityChecked = true; + } + + $subSections[$position] = [$startObject, $entryCount]; + + $lastLineStart = $position + $entryCount * 20; + $this->reader->reset($lastLineStart); + } + + // reset after the last correct parsed line + $this->reader->reset($lastLineStart); + + if (\count($subSections) === 0) { + throw new CrossReferenceException( + 'No entries found in cross-reference.', + CrossReferenceException::NO_ENTRIES + ); + } + + $this->subSections = $subSections; + } + + /** + * Fixes an invalid object number shift. + * + * This method can be used to repair documents with an invalid subsection header: + * + * + * xref + * 1 7 + * 0000000000 65535 f + * 0000000009 00000 n + * 0000412075 00000 n + * 0000412172 00000 n + * 0000412359 00000 n + * 0000412417 00000 n + * 0000412468 00000 n + * + * + * It shall only be called on the first table. + * + * @return bool + */ + public function fixFaultySubSectionShift() + { + $subSections = $this->getSubSections(); + if (\count($subSections) > 1) { + return false; + } + + $subSection = \current($subSections); + if ($subSection[0] != 1) { + return false; + } + + if ($this->getOffsetFor(1) === false) { + foreach ($subSections as $offset => list($startObject, $objectCount)) { + $this->subSections[$offset] = [$startObject - 1, $objectCount]; + } + return true; + } + + return false; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/LineReader.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/LineReader.php new file mode 100644 index 0000000..31a96be --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/LineReader.php @@ -0,0 +1,173 @@ + 20 bytes). + * + * @package setasign\Fpdi\PdfParser\CrossReference + */ +class LineReader extends AbstractReader implements ReaderInterface +{ + /** + * The object offsets. + * + * @var array + */ + protected $offsets; + + /** + * LineReader constructor. + * + * @param PdfParser $parser + * @throws CrossReferenceException + */ + public function __construct(PdfParser $parser) + { + $this->read($this->extract($parser->getStreamReader())); + parent::__construct($parser); + } + + /** + * @inheritdoc + */ + public function getOffsetFor($objectNumber) + { + if (isset($this->offsets[$objectNumber])) { + return $this->offsets[$objectNumber][0]; + } + + return false; + } + + /** + * Get all found offsets. + * + * @return array + */ + public function getOffsets() + { + return $this->offsets; + } + + /** + * Extracts the cross reference data from the stream reader. + * + * @param StreamReader $reader + * @return string + * @throws CrossReferenceException + */ + protected function extract(StreamReader $reader) + { + $cycles = -1; + $bytesPerCycle = 100; + + $reader->reset(null, $bytesPerCycle); + + while ( + ($trailerPos = \strpos($reader->getBuffer(false), 'trailer', \max($bytesPerCycle * $cycles++, 0))) === false + ) { + if ($reader->increaseLength($bytesPerCycle) === false) { + break; + } + } + + if ($trailerPos === false) { + throw new CrossReferenceException( + 'Unexpected end of cross reference. "trailer"-keyword not found.', + CrossReferenceException::NO_TRAILER_FOUND + ); + } + + $xrefContent = \substr($reader->getBuffer(false), 0, $trailerPos); + $reader->reset($reader->getPosition() + $trailerPos); + + return $xrefContent; + } + + /** + * Read the cross-reference entries. + * + * @param string $xrefContent + * @throws CrossReferenceException + */ + protected function read($xrefContent) + { + // get eol markers in the first 100 bytes + \preg_match_all("/(\r\n|\n|\r)/", \substr($xrefContent, 0, 100), $m); + + if (\count($m[0]) === 0) { + throw new CrossReferenceException( + 'No data found in cross-reference.', + CrossReferenceException::INVALID_DATA + ); + } + + // count(array_count_values()) is faster then count(array_unique()) + // @see https://github.com/symfony/symfony/pull/23731 + // can be reverted for php7.2 + $differentLineEndings = \count(\array_count_values($m[0])); + if ($differentLineEndings > 1) { + $lines = \preg_split("/(\r\n|\n|\r)/", $xrefContent, -1, PREG_SPLIT_NO_EMPTY); + } else { + $lines = \explode($m[0][0], $xrefContent); + } + + unset($differentLineEndings, $m); + $linesCount = \count($lines); + $start = null; + $entryCount = 0; + + $offsets = []; + + /** @noinspection ForeachInvariantsInspection */ + for ($i = 0; $i < $linesCount; $i++) { + $line = \trim($lines[$i]); + if ($line) { + $pieces = \explode(' ', $line); + + $c = \count($pieces); + switch ($c) { + case 2: + $start = (int) $pieces[0]; + $entryCount += (int) $pieces[1]; + break; + + /** @noinspection PhpMissingBreakStatementInspection */ + case 3: + switch ($pieces[2]) { + case 'n': + $offsets[$start] = [(int) $pieces[0], (int) $pieces[1]]; + $start++; + break 2; + case 'f': + $start++; + break 2; + } + // fall through if pieces doesn't match + + default: + throw new CrossReferenceException( + \sprintf('Unexpected data in xref table (%s)', \implode(' ', $pieces)), + CrossReferenceException::INVALID_DATA + ); + } + } + } + + $this->offsets = $offsets; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/ReaderInterface.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/ReaderInterface.php new file mode 100644 index 0000000..84b2267 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/CrossReference/ReaderInterface.php @@ -0,0 +1,35 @@ + + if ($ch === 126 && isset($data[$k + 1]) && (\ord($data[$k + 1]) & 0xFF) === 62) { + break; + } + if (\preg_match('/^\s$/', \chr($ch))) { + continue; + } + if ($ch === 122 /* z */ && $state === 0) { + $out .= \chr(0) . \chr(0) . \chr(0) . \chr(0); + continue; + } + if ($ch < 33 /* ! */ || $ch > 117 /* u */) { + throw new Ascii85Exception( + 'Illegal character found while ASCII85 decode.', + Ascii85Exception::ILLEGAL_CHAR_FOUND + ); + } + + $chn[$state] = $ch - 33;/* ! */ + $state++; + + if ($state === 5) { + $state = 0; + $r = 0; + for ($j = 0; $j < 5; ++$j) { + /** @noinspection UnnecessaryCastingInspection */ + $r = (int)($r * 85 + $chn[$j]); + } + + $out .= \chr($r >> 24) + . \chr($r >> 16) + . \chr($r >> 8) + . \chr($r); + } + } + + if ($state === 1) { + throw new Ascii85Exception( + 'Illegal length while ASCII85 decode.', + Ascii85Exception::ILLEGAL_LENGTH + ); + } + + if ($state === 2) { + $r = $chn[0] * 85 * 85 * 85 * 85 + ($chn[1] + 1) * 85 * 85 * 85; + $out .= \chr($r >> 24); + + } elseif ($state === 3) { + $r = $chn[0] * 85 * 85 * 85 * 85 + $chn[1] * 85 * 85 * 85 + ($chn[2] + 1) * 85 * 85; + $out .= \chr($r >> 24); + $out .= \chr($r >> 16); + + } elseif ($state === 4) { + $r = $chn[0] * 85 * 85 * 85 * 85 + $chn[1] * 85 * 85 * 85 + $chn[2] * 85 * 85 + ($chn[3] + 1) * 85; + $out .= \chr($r >> 24); + $out .= \chr($r >> 16); + $out .= \chr($r >> 8); + } + + return $out; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/Ascii85Exception.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/Ascii85Exception.php new file mode 100644 index 0000000..898276c --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/Ascii85Exception.php @@ -0,0 +1,28 @@ +')); + if ((\strlen($data) % 2) === 1) { + $data .= '0'; + } + + return \pack('H*', $data); + } + + /** + * Converts a string into ASCII hexadecimal representation. + * + * @param string $data The input string + * @param boolean $leaveEOD + * @return string + */ + public function encode($data, $leaveEOD = false) + { + $t = \unpack('H*', $data); + return \current($t) + . ($leaveEOD ? '' : '>'); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/FilterException.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/FilterException.php new file mode 100644 index 0000000..a41ce69 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/FilterException.php @@ -0,0 +1,24 @@ +extensionLoaded()) { + $oData = $data; + $data = @((\strlen($data) > 0) ? \gzuncompress($data) : ''); + if ($data === false) { + // Try this fallback + $tries = 1; + while ($tries < 10 && ($data === false || \strlen($data) < (\strlen($oData) - $tries - 1))) { + $data = @(\gzinflate(\substr($oData, $tries))); + $tries++; + } + + if ($data === false) { + // let's try if the checksum is CRC32 + $fh = fopen('php://temp', 'w+b'); + \fwrite($fh, "\x1f\x8b\x08\x00\x00\x00\x00\x00" . $oData); + \stream_filter_append($fh, 'zlib.inflate', \STREAM_FILTER_READ, ['window' => 30]); + \fseek($fh, 0); + $data = \stream_get_contents($fh); + \fclose($fh); + } + + if (!$data) { + throw new FlateException( + 'Error while decompressing stream.', + FlateException::DECOMPRESS_ERROR + ); + } + } + } else { + throw new FlateException( + 'To handle FlateDecode filter, enable zlib support in PHP.', + FlateException::NO_ZLIB + ); + } + + return $data; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/FlateException.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/FlateException.php new file mode 100644 index 0000000..d44281d --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/FlateException.php @@ -0,0 +1,28 @@ +initsTable(); + + $this->data = $data; + $this->dataLength = \strlen($data); + + // Initialize pointers + $this->bytePointer = 0; + + $this->nextData = 0; + $this->nextBits = 0; + + $oldCode = 0; + + $uncompData = ''; + + while (($code = $this->getNextCode()) !== 257) { + if ($code === 256) { + $this->initsTable(); + $code = $this->getNextCode(); + + if ($code === 257) { + break; + } + + $uncompData .= $this->sTable[$code]; + $oldCode = $code; + + } else { + if ($code < $this->tIdx) { + $string = $this->sTable[$code]; + $uncompData .= $string; + + $this->addStringToTable($this->sTable[$oldCode], $string[0]); + $oldCode = $code; + } else { + $string = $this->sTable[$oldCode]; + $string .= $string[0]; + $uncompData .= $string; + + $this->addStringToTable($string); + $oldCode = $code; + } + } + } + + return $uncompData; + } + + /** + * Initialize the string table. + */ + protected function initsTable() + { + $this->sTable = []; + + for ($i = 0; $i < 256; $i++) { + $this->sTable[$i] = \chr($i); + } + + $this->tIdx = 258; + $this->bitsToGet = 9; + } + + /** + * Add a new string to the string table. + * + * @param string $oldString + * @param string $newString + */ + protected function addStringToTable($oldString, $newString = '') + { + $string = $oldString . $newString; + + // Add this new String to the table + $this->sTable[$this->tIdx++] = $string; + + if ($this->tIdx === 511) { + $this->bitsToGet = 10; + } elseif ($this->tIdx === 1023) { + $this->bitsToGet = 11; + } elseif ($this->tIdx === 2047) { + $this->bitsToGet = 12; + } + } + + /** + * Returns the next 9, 10, 11 or 12 bits. + * + * @return integer + */ + protected function getNextCode() + { + if ($this->bytePointer === $this->dataLength) { + return 257; + } + + $this->nextData = ($this->nextData << 8) | (\ord($this->data[$this->bytePointer++]) & 0xff); + $this->nextBits += 8; + + if ($this->nextBits < $this->bitsToGet) { + $this->nextData = ($this->nextData << 8) | (\ord($this->data[$this->bytePointer++]) & 0xff); + $this->nextBits += 8; + } + + $code = ($this->nextData >> ($this->nextBits - $this->bitsToGet)) & $this->andTable[$this->bitsToGet - 9]; + $this->nextBits -= $this->bitsToGet; + + return $code; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/LzwException.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/LzwException.php new file mode 100644 index 0000000..e822bc9 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Filter/LzwException.php @@ -0,0 +1,23 @@ +streamReader = $streamReader; + $this->tokenizer = new Tokenizer($streamReader); + } + + /** + * Removes cycled references. + * + * @internal + */ + public function cleanUp() + { + $this->xref = null; + } + + /** + * Get the stream reader instance. + * + * @return StreamReader + */ + public function getStreamReader() + { + return $this->streamReader; + } + + /** + * Get the tokenizer instance. + * + * @return Tokenizer + */ + public function getTokenizer() + { + return $this->tokenizer; + } + + /** + * Resolves the file header. + * + * @throws PdfParserException + * @return int + */ + protected function resolveFileHeader() + { + if ($this->fileHeader) { + return $this->fileHeaderOffset; + } + + $this->streamReader->reset(0); + $offset = false; + $maxIterations = 1000; + while (true) { + $buffer = $this->streamReader->getBuffer(false); + $offset = \strpos($buffer, '%PDF-'); + if ($offset === false) { + if (!$this->streamReader->increaseLength(100) || (--$maxIterations === 0)) { + throw new PdfParserException( + 'Unable to find PDF file header.', + PdfParserException::FILE_HEADER_NOT_FOUND + ); + } + continue; + } + break; + } + + $this->fileHeaderOffset = $offset; + $this->streamReader->setOffset($offset); + + $this->fileHeader = \trim($this->streamReader->readLine()); + return $this->fileHeaderOffset; + } + + /** + * Get the cross reference instance. + * + * @return CrossReference + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getCrossReference() + { + if ($this->xref === null) { + $this->xref = new CrossReference($this, $this->resolveFileHeader()); + } + + return $this->xref; + } + + /** + * Get the PDF version. + * + * @return int[] An array of major and minor version. + * @throws PdfParserException + */ + public function getPdfVersion() + { + $this->resolveFileHeader(); + + if (\preg_match('/%PDF-(\d)\.(\d)/', $this->fileHeader, $result) === 0) { + throw new PdfParserException( + 'Unable to extract PDF version from file header.', + PdfParserException::PDF_VERSION_NOT_FOUND + ); + } + list(, $major, $minor) = $result; + + $catalog = $this->getCatalog(); + if (isset($catalog->value['Version'])) { + $versionParts = \explode('.', PdfName::unescape(PdfType::resolve($catalog->value['Version'], $this)->value)); + if (count($versionParts) === 2) { + list($major, $minor) = $versionParts; + } + } + + return [(int) $major, (int) $minor]; + } + + /** + * Get the catalog dictionary. + * + * @return PdfDictionary + * @throws Type\PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getCatalog() + { + $xref = $this->getCrossReference(); + $trailer = $xref->getTrailer(); + + $catalog = PdfType::resolve(PdfDictionary::get($trailer, 'Root'), $this); + + return PdfDictionary::ensure($catalog); + } + + /** + * Get an indirect object by its object number. + * + * @param int $objectNumber + * @param bool $cache + * @return PdfIndirectObject + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getIndirectObject($objectNumber, $cache = false) + { + $objectNumber = (int) $objectNumber; + if (isset($this->objects[$objectNumber])) { + return $this->objects[$objectNumber]; + } + + $xref = $this->getCrossReference(); + $object = $xref->getIndirectObject($objectNumber); + + if ($cache) { + $this->objects[$objectNumber] = $object; + } + + return $object; + } + + /** + * Read a PDF value. + * + * @param null|bool|string $token + * @param null|string $expectedType + * @return bool|PdfArray|PdfBoolean|PdfHexString|PdfName|PdfNull|PdfNumeric|PdfString|PdfToken|PdfIndirectObjectReference + * @throws Type\PdfTypeException + */ + public function readValue($token = null, $expectedType = null) + { + if ($token === null) { + $token = $this->tokenizer->getNextToken(); + } + + if ($token === false) { + if ($expectedType !== null) { + throw new Type\PdfTypeException('Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE); + } + return false; + } + + switch ($token) { + case '(': + $this->ensureExpectedType($token, $expectedType); + return PdfString::parse($this->streamReader); + + case '<': + if ($this->streamReader->getByte() === '<') { + $this->ensureExpectedType('<<', $expectedType); + $this->streamReader->addOffset(1); + return PdfDictionary::parse($this->tokenizer, $this->streamReader, $this); + } + + $this->ensureExpectedType($token, $expectedType); + return PdfHexString::parse($this->streamReader); + + case '/': + $this->ensureExpectedType($token, $expectedType); + return PdfName::parse($this->tokenizer, $this->streamReader); + + case '[': + $this->ensureExpectedType($token, $expectedType); + return PdfArray::parse($this->tokenizer, $this); + + default: + if (\is_numeric($token)) { + if (($token2 = $this->tokenizer->getNextToken()) !== false) { + if (\is_numeric($token2)) { + if (($token3 = $this->tokenizer->getNextToken()) !== false) { + switch ($token3) { + case 'obj': + if ($expectedType !== null && $expectedType !== PdfIndirectObject::class) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + + return PdfIndirectObject::parse( + $token, + $token2, + $this, + $this->tokenizer, + $this->streamReader + ); + case 'R': + if ($expectedType !== null && + $expectedType !== PdfIndirectObjectReference::class + ) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + + return PdfIndirectObjectReference::create($token, $token2); + } + + $this->tokenizer->pushStack($token3); + } + } + + $this->tokenizer->pushStack($token2); + } + + if ($expectedType !== null && $expectedType !== PdfNumeric::class) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + return PdfNumeric::create($token); + } + + if ($token === 'true' || $token === 'false') { + $this->ensureExpectedType($token, $expectedType); + return PdfBoolean::create($token === 'true'); + } + + if ($token === 'null') { + $this->ensureExpectedType($token, $expectedType); + return new PdfNull(); + } + + if ($expectedType !== null && $expectedType !== PdfToken::class) { + throw new Type\PdfTypeException( + 'Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE + ); + } + + $v = new PdfToken(); + $v->value = $token; + + return $v; + } + } + + /** + * Ensures that the token will evaluate to an expected object type (or not). + * + * @param string $token + * @param string|null $expectedType + * @return bool + * @throws Type\PdfTypeException + */ + private function ensureExpectedType($token, $expectedType) + { + static $mapping = [ + '(' => PdfString::class, + '<' => PdfHexString::class, + '<<' => PdfDictionary::class, + '/' => PdfName::class, + '[' => PdfArray::class, + 'true' => PdfBoolean::class, + 'false' => PdfBoolean::class, + 'null' => PdfNull::class + ]; + + if ($expectedType === null || $mapping[$token] === $expectedType) { + return true; + } + + throw new Type\PdfTypeException('Got unexpected token type.', Type\PdfTypeException::INVALID_DATA_TYPE); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/PdfParserException.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/PdfParserException.php new file mode 100644 index 0000000..476bccd --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/PdfParserException.php @@ -0,0 +1,50 @@ +stream = $stream; + $this->closeStream = $closeStream; + $this->reset(); + } + + /** + * The destructor. + */ + public function __destruct() + { + $this->cleanUp(); + } + + /** + * Closes the file handle. + */ + public function cleanUp() + { + if ($this->closeStream && is_resource($this->stream)) { + \fclose($this->stream); + } + } + + /** + * Returns the byte length of the buffer. + * + * @param bool $atOffset + * @return int + */ + public function getBufferLength($atOffset = false) + { + if ($atOffset === false) { + return $this->bufferLength; + } + + return $this->bufferLength - $this->offset; + } + + /** + * Get the current position in the stream. + * + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * Returns the current buffer. + * + * @param bool $atOffset + * @return string + */ + public function getBuffer($atOffset = true) + { + if ($atOffset === false) { + return $this->buffer; + } + + $string = \substr($this->buffer, $this->offset); + + return (string) $string; + } + + /** + * Gets a byte at a specific position in the buffer. + * + * If the position is invalid the method will return false. + * + * If the $position parameter is set to null the value of $this->offset will be used. + * + * @param int|null $position + * @return string|bool + */ + public function getByte($position = null) + { + $position = (int) ($position !== null ? $position : $this->offset); + if ($position >= $this->bufferLength && + (!$this->increaseLength() || $position >= $this->bufferLength) + ) { + return false; + } + + return $this->buffer[$position]; + } + + /** + * Returns a byte at a specific position, and set the offset to the next byte position. + * + * If the position is invalid the method will return false. + * + * If the $position parameter is set to null the value of $this->offset will be used. + * + * @param int|null $position + * @return string|bool + */ + public function readByte($position = null) + { + if ($position !== null) { + $position = (int) $position; + // check if needed bytes are available in the current buffer + if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) { + $this->reset($position); + $offset = $this->offset; + } else { + $offset = $position - $this->position; + } + } else { + $offset = $this->offset; + } + + if ($offset >= $this->bufferLength && + ((!$this->increaseLength()) || $offset >= $this->bufferLength) + ) { + return false; + } + + $this->offset = $offset + 1; + return $this->buffer[$offset]; + } + + /** + * Read bytes from the current or a specific offset position and set the internal pointer to the next byte. + * + * If the position is invalid the method will return false. + * + * If the $position parameter is set to null the value of $this->offset will be used. + * + * @param int $length + * @param int|null $position + * @return string + */ + public function readBytes($length, $position = null) + { + $length = (int) $length; + if ($position !== null) { + // check if needed bytes are available in the current buffer + if (!($position >= $this->position && $position < $this->position + $this->bufferLength)) { + $this->reset($position, $length); + $offset = $this->offset; + } else { + $offset = $position - $this->position; + } + } else { + $offset = $this->offset; + } + + if (($offset + $length) > $this->bufferLength && + ((!$this->increaseLength($length)) || ($offset + $length) > $this->bufferLength) + ) { + return false; + } + + $bytes = \substr($this->buffer, $offset, $length); + $this->offset = $offset + $length; + + return $bytes; + } + + /** + * Read a line from the current position. + * + * @param int $length + * @return string|bool + */ + public function readLine($length = 1024) + { + if ($this->ensureContent() === false) { + return false; + } + + $line = ''; + while ($this->ensureContent()) { + $char = $this->readByte(); + + if ($char === "\n") { + break; + } + + if ($char === "\r") { + if ($this->getByte() === "\n") { + $this->addOffset(1); + } + break; + } + + $line .= $char; + + if (\strlen($line) >= $length) { + break; + } + } + + return $line; + } + + /** + * Set the offset position in the current buffer. + * + * @param int $offset + */ + public function setOffset($offset) + { + if ($offset > $this->bufferLength || $offset < 0) { + throw new \OutOfRangeException( + \sprintf('Offset (%s) out of range (length: %s)', $offset, $this->bufferLength) + ); + } + + $this->offset = (int) $offset; + } + + /** + * Returns the current offset in the current buffer. + * + * @return int + */ + public function getOffset() + { + return $this->offset; + } + + /** + * Add an offset to the current offset. + * + * @param int $offset + */ + public function addOffset($offset) + { + $this->setOffset($this->offset + $offset); + } + + /** + * Make sure that there is at least one character beyond the current offset in the buffer. + * + * @return bool + */ + public function ensureContent() + { + while ($this->offset >= $this->bufferLength) { + if (!$this->increaseLength()) { + return false; + } + } + return true; + } + + /** + * Returns the stream. + * + * @return resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * Gets the total available length. + * + * @return int + */ + public function getTotalLength() + { + if ($this->totalLength === null) { + $stat = \fstat($this->stream); + $this->totalLength = $stat['size']; + } + + return $this->totalLength; + } + + /** + * Resets the buffer to a position and re-read the buffer with the given length. + * + * If the $pos parameter is negative the start buffer position will be the $pos'th position from + * the end of the file. + * + * If the $pos parameter is negative and the absolute value is bigger then the totalLength of + * the file $pos will set to zero. + * + * @param int|null $pos Start position of the new buffer + * @param int $length Length of the new buffer. Mustn't be negative + */ + public function reset($pos = 0, $length = 200) + { + if ($pos === null) { + $pos = $this->position + $this->offset; + } elseif ($pos < 0) { + $pos = \max(0, $this->getTotalLength() + $pos); + } + + \fseek($this->stream, $pos); + + $this->position = $pos; + $this->buffer = $length > 0 ? \fread($this->stream, $length) : ''; + $this->bufferLength = \strlen($this->buffer); + $this->offset = 0; + + // If a stream wrapper is in use it is possible that + // length values > 8096 will be ignored, so use the + // increaseLength()-method to correct that behavior + if ($this->bufferLength < $length && $this->increaseLength($length - $this->bufferLength)) { + // increaseLength parameter is $minLength, so cut to have only the required bytes in the buffer + $this->buffer = \substr($this->buffer, 0, $length); + $this->bufferLength = \strlen($this->buffer); + } + } + + /** + * Ensures bytes in the buffer with a specific length and location in the file. + * + * @param int $pos + * @param int $length + * @see reset() + */ + public function ensure($pos, $length) + { + if ($pos >= $this->position + && $pos < ($this->position + $this->bufferLength) + && ($this->position + $this->bufferLength) >= ($pos + $length) + ) { + $this->offset = $pos - $this->position; + } else { + $this->reset($pos, $length); + } + } + + /** + * Forcefully read more data into the buffer. + * + * @param int $minLength + * @return bool Returns false if the stream reaches the end + */ + public function increaseLength($minLength = 100) + { + $length = \max($minLength, 100); + + if (\feof($this->stream) || $this->getTotalLength() === $this->position + $this->bufferLength) { + return false; + } + + $newLength = $this->bufferLength + $length; + do { + $this->buffer .= \fread($this->stream, $newLength - $this->bufferLength); + $this->bufferLength = \strlen($this->buffer); + } while (($this->bufferLength !== $newLength) && !\feof($this->stream)); + + return true; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Tokenizer.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Tokenizer.php new file mode 100644 index 0000000..ebf0d0b --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Tokenizer.php @@ -0,0 +1,161 @@ +streamReader = $streamReader; + } + + /** + * Get the stream reader instance. + * + * @return StreamReader + */ + public function getStreamReader() + { + return $this->streamReader; + } + + /** + * Clear the token stack. + */ + public function clearStack() + { + $this->stack = []; + } + + /** + * Push a token onto the stack. + * + * @param string $token + */ + public function pushStack($token) + { + $this->stack[] = $token; + } + + /** + * Get next token. + * + * @return bool|string + */ + public function getNextToken() + { + $token = \array_pop($this->stack); + if ($token !== null) { + return $token; + } + + if (($byte = $this->streamReader->readByte()) === false) { + return false; + } + + if ($byte === "\x20" || + $byte === "\x0A" || + $byte === "\x0D" || + $byte === "\x0C" || + $byte === "\x09" || + $byte === "\x00" + ) { + if ($this->leapWhiteSpaces() === false) { + return false; + } + $byte = $this->streamReader->readByte(); + } + + switch ($byte) { + case '/': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case '<': + case '>': + return $byte; + case '%': + $this->streamReader->readLine(); + return $this->getNextToken(); + } + + /* This way is faster than checking single bytes. + */ + $bufferOffset = $this->streamReader->getOffset(); + do { + $lastBuffer = $this->streamReader->getBuffer(false); + $pos = \strcspn( + $lastBuffer, + "\x00\x09\x0A\x0C\x0D\x20()<>[]{}/%", + $bufferOffset + ); + } while ( + // Break the loop if a delimiter or white space char is matched + // in the current buffer or increase the buffers length + $lastBuffer !== false && + ( + $bufferOffset + $pos === \strlen($lastBuffer) && + $this->streamReader->increaseLength() + ) + ); + + $result = \substr($lastBuffer, $bufferOffset - 1, $pos + 1); + $this->streamReader->setOffset($bufferOffset + $pos); + + return $result; + } + + /** + * Leap white spaces. + * + * @return boolean + */ + public function leapWhiteSpaces() + { + do { + if (!$this->streamReader->ensureContent()) { + return false; + } + + $buffer = $this->streamReader->getBuffer(false); + $matches = \strspn($buffer, "\x20\x0A\x0C\x0D\x09\x00", $this->streamReader->getOffset()); + if ($matches > 0) { + $this->streamReader->addOffset($matches); + } + } while ($this->streamReader->getOffset() >= $this->streamReader->getBufferLength()); + + return true; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfArray.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfArray.php new file mode 100644 index 0000000..9cf449a --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfArray.php @@ -0,0 +1,85 @@ +getNextToken()) !== ']') { + if ($token === false || ($value = $parser->readValue($token)) === false) { + return false; + } + + $result[] = $value; + } + + $v = new self; + $v->value = $result; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param PdfType[] $values + * @return self + */ + public static function create(array $values = []) + { + $v = new self; + $v->value = $values; + + return $v; + } + + /** + * Ensures that the passed array is a PdfArray instance with a (optional) specific size. + * + * @param mixed $array + * @param null|int $size + * @return self + * @throws PdfTypeException + */ + public static function ensure($array, $size = null) + { + $result = PdfType::ensureType(self::class, $array, 'Array value expected.'); + + if ($size !== null && \count($array->value) !== $size) { + throw new PdfTypeException( + \sprintf('Array with %s entries expected.', $size), + PdfTypeException::INVALID_DATA_SIZE + ); + } + + return $result; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfBoolean.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfBoolean.php new file mode 100644 index 0000000..5f746b7 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfBoolean.php @@ -0,0 +1,43 @@ +value = (boolean) $value; + return $v; + } + + /** + * Ensures that the passed value is a PdfBoolean instance. + * + * @param mixed $value + * @return self + * @throws PdfTypeException + */ + public static function ensure($value) + { + return PdfType::ensureType(self::class, $value, 'Boolean value expected.'); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfDictionary.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfDictionary.php new file mode 100644 index 0000000..46b669d --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfDictionary.php @@ -0,0 +1,135 @@ +getNextToken(); + if ($token === '>' && $streamReader->getByte() === '>') { + $streamReader->addOffset(1); + break; + } + + $key = $parser->readValue($token); + if ($key === false) { + return false; + } + + // ensure the first value to be a Name object + if (!($key instanceof PdfName)) { + $lastToken = null; + // ignore all other entries and search for the closing brackets + while (($token = $tokenizer->getNextToken()) !== '>' && $token !== false && $lastToken !== '>') { + $lastToken = $token; + } + + if ($token === false) { + return false; + } + + break; + } + + + $value = $parser->readValue(); + if ($value === false) { + return false; + } + + if ($value instanceof PdfNull) { + continue; + } + + // catch missing value + if ($value instanceof PdfToken && $value->value === '>' && $streamReader->getByte() === '>') { + $streamReader->addOffset(1); + break; + } + + $entries[$key->value] = $value; + } + + $v = new self; + $v->value = $entries; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param PdfType[] $entries The keys are the name entries of the dictionary. + * @return self + */ + public static function create(array $entries = []) + { + $v = new self; + $v->value = $entries; + + return $v; + } + + /** + * Get a value by its key from a dictionary or a default value. + * + * @param mixed $dictionary + * @param string $key + * @param PdfType|mixed|null $default + * @return PdfNull|PdfType + * @throws PdfTypeException + */ + public static function get($dictionary, $key, PdfType $default = null) + { + $dictionary = self::ensure($dictionary); + + if (isset($dictionary->value[$key])) { + return $dictionary->value[$key]; + } + + return $default === null + ? new PdfNull() + : $default; + } + + /** + * Ensures that the passed value is a PdfDictionary instance. + * + * @param mixed $dictionary + * @return self + * @throws PdfTypeException + */ + public static function ensure($dictionary) + { + return PdfType::ensureType(self::class, $dictionary, 'Dictionary value expected.'); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfHexString.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfHexString.php new file mode 100644 index 0000000..793fa43 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfHexString.php @@ -0,0 +1,82 @@ +getOffset(); + + /** + * @var string $buffer + * @var int $pos + */ + while (true) { + $buffer = $streamReader->getBuffer(false); + $pos = \strpos($buffer, '>', $bufferOffset); + if ($pos === false) { + if (!$streamReader->increaseLength()) { + return false; + } + continue; + } + + break; + } + + $result = \substr($buffer, $bufferOffset, $pos - $bufferOffset); + $streamReader->setOffset($pos + 1); + + $v = new self; + $v->value = $result; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param string $string The hex encoded string. + * @return self + */ + public static function create($string) + { + $v = new self; + $v->value = $string; + + return $v; + } + + /** + * Ensures that the passed value is a PdfHexString instance. + * + * @param mixed $hexString + * @return self + * @throws PdfTypeException + */ + public static function ensure($hexString) + { + return PdfType::ensureType(self::class, $hexString, 'Hex string value expected.'); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfIndirectObject.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfIndirectObject.php new file mode 100644 index 0000000..a5bd2ac --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfIndirectObject.php @@ -0,0 +1,104 @@ +readValue(); + if ($value === false) { + return false; + } + + $nextToken = $tokenizer->getNextToken(); + if ($nextToken === 'stream') { + $value = PdfStream::parse($value, $reader, $parser); + } elseif ($nextToken !== false) { + $tokenizer->pushStack($nextToken); + } + + $v = new self; + $v->objectNumber = (int) $objectNumberToken; + $v->generationNumber = (int) $objectGenerationNumberToken; + $v->value = $value; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param int $objectNumber + * @param int $generationNumber + * @param PdfType $value + * @return self + */ + public static function create($objectNumber, $generationNumber, PdfType $value) + { + $v = new self; + $v->objectNumber = (int) $objectNumber; + $v->generationNumber = (int) $generationNumber; + $v->value = $value; + + return $v; + } + + /** + * Ensures that the passed value is a PdfIndirectObject instance. + * + * @param mixed $indirectObject + * @return self + * @throws PdfTypeException + */ + public static function ensure($indirectObject) + { + return PdfType::ensureType(self::class, $indirectObject, 'Indirect object expected.'); + } + + /** + * The object number. + * + * @var int + */ + public $objectNumber; + + /** + * The generation number. + * + * @var int + */ + public $generationNumber; +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfIndirectObjectReference.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfIndirectObjectReference.php new file mode 100644 index 0000000..c6a67a0 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfIndirectObjectReference.php @@ -0,0 +1,53 @@ +value = (int) $objectNumber; + $v->generationNumber = (int) $generationNumber; + + return $v; + } + + /** + * Ensures that the passed value is a PdfIndirectObject instance. + * + * @param mixed $value + * @return self + * @throws PdfTypeException + */ + public static function ensure($value) + { + return PdfType::ensureType(self::class, $value, 'Indirect reference value expected.'); + } + + /** + * The generation number. + * + * @var int + */ + public $generationNumber; +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfName.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfName.php new file mode 100644 index 0000000..6be7d0e --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfName.php @@ -0,0 +1,82 @@ +getByte(), "\x00\x09\x0A\x0C\x0D\x20()<>[]{}/%") === 0) { + $v->value = (string) $tokenizer->getNextToken(); + return $v; + } + + $v->value = ''; + return $v; + } + + /** + * Unescapes a name string. + * + * @param string $value + * @return string + */ + static public function unescape($value) + { + if (strpos($value, '#') === false) + return $value; + + return preg_replace_callback('/#[a-fA-F\d]{2}/', function($matches) { + return chr(hexdec($matches[0])); + }, $value); + } + + /** + * Helper method to create an instance. + * + * @param string $string + * @return self + */ + public static function create($string) + { + $v = new self; + $v->value = $string; + + return $v; + } + + /** + * Ensures that the passed value is a PdfName instance. + * + * @param mixed $name + * @return self + * @throws PdfTypeException + */ + public static function ensure($name) + { + return PdfType::ensureType(self::class, $name, 'Name value expected.'); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfNull.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfNull.php new file mode 100644 index 0000000..3dbe37c --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfNull.php @@ -0,0 +1,20 @@ +value = $value + 0; + + return $v; + } + + /** + * Ensures that the passed value is a PdfNumeric instance. + * + * @param mixed $value + * @return self + * @throws PdfTypeException + */ + public static function ensure($value) + { + return PdfType::ensureType(self::class, $value, 'Numeric value expected.'); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfStream.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfStream.php new file mode 100644 index 0000000..4b9300b --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfStream.php @@ -0,0 +1,320 @@ +value = $dictionary; + $v->reader = $reader; + $v->parser = $parser; + + $offset = $reader->getOffset(); + + // Find the first "newline" + while (($firstByte = $reader->getByte($offset)) !== false) { + if ($firstByte !== "\n" && $firstByte !== "\r") { + $offset++; + } else { + break; + } + } + + if (false === $firstByte) { + throw new PdfTypeException( + 'Unable to parse stream data. No newline after the stream keyword found.', + PdfTypeException::NO_NEWLINE_AFTER_STREAM_KEYWORD + ); + } + + $sndByte = $reader->getByte($offset + 1); + if ($firstByte === "\n" || $firstByte === "\r") { + $offset++; + } + + if ($sndByte === "\n" && $firstByte !== "\n") { + $offset++; + } + + $reader->setOffset($offset); + // let's only save the byte-offset and read the stream only when needed + $v->stream = $reader->getPosition() + $reader->getOffset(); + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param PdfDictionary $dictionary + * @param string $stream + * @return self + */ + public static function create(PdfDictionary $dictionary, $stream) + { + $v = new self; + $v->value = $dictionary; + $v->stream = (string) $stream; + + return $v; + } + + /** + * Ensures that the passed value is a PdfStream instance. + * + * @param mixed $stream + * @return self + * @throws PdfTypeException + */ + public static function ensure($stream) + { + return PdfType::ensureType(self::class, $stream, 'Stream value expected.'); + } + + /** + * The stream or its byte-offset position. + * + * @var int|string + */ + protected $stream; + + /** + * The stream reader instance. + * + * @var StreamReader + */ + protected $reader; + + /** + * The PDF parser instance. + * + * @var PdfParser + */ + protected $parser; + + /** + * Get the stream data. + * + * @param bool $cache Whether cache the stream data or not. + * @return bool|string + * @throws PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getStream($cache = false) + { + if (\is_int($this->stream)) { + $length = PdfDictionary::get($this->value, 'Length'); + if ($this->parser !== null) { + $length = PdfType::resolve($length, $this->parser); + } + + if (!($length instanceof PdfNumeric) || $length->value === 0) { + $this->reader->reset($this->stream, 100000); + $buffer = $this->extractStream(); + } else { + $this->reader->reset($this->stream, $length->value); + $buffer = $this->reader->getBuffer(false); + if ($this->parser !== null) { + $this->reader->reset($this->stream + strlen($buffer)); + $this->parser->getTokenizer()->clearStack(); + $token = $this->parser->readValue(); + if ($token === false || !($token instanceof PdfToken) || $token->value !== 'endstream') { + $this->reader->reset($this->stream, 100000); + $buffer = $this->extractStream(); + $this->reader->reset($this->stream + strlen($buffer)); + } + } + } + + if ($cache === false) { + return $buffer; + } + + $this->stream = $buffer; + $this->reader = null; + } + + return $this->stream; + } + + /** + * Extract the stream "manually". + * + * @return string + * @throws PdfTypeException + */ + protected function extractStream() + { + while (true) { + $buffer = $this->reader->getBuffer(false); + $length = \strpos($buffer, 'endstream'); + if ($length === false) { + if (!$this->reader->increaseLength(100000)) { + throw new PdfTypeException('Cannot extract stream.'); + } + continue; + } + break; + } + + $buffer = \substr($buffer, 0, $length); + $lastByte = \substr($buffer, -1); + + /* Check for EOL marker = + * CARRIAGE RETURN (\r) and a LINE FEED (\n) or just a LINE FEED (\n}, + * and not by a CARRIAGE RETURN (\r) alone + */ + if ($lastByte === "\n") { + $buffer = \substr($buffer, 0, -1); + + $lastByte = \substr($buffer, -1); + if ($lastByte === "\r") { + $buffer = \substr($buffer, 0, -1); + } + } + + return $buffer; + } + + /** + * Get the unfiltered stream data. + * + * @return string + * @throws FilterException + * @throws PdfParserException + */ + public function getUnfilteredStream() + { + $stream = $this->getStream(); + $filters = PdfDictionary::get($this->value, 'Filter'); + if ($filters instanceof PdfNull) { + return $stream; + } + + if ($filters instanceof PdfArray) { + $filters = $filters->value; + } else { + $filters = [$filters]; + } + + $decodeParams = PdfDictionary::get($this->value, 'DecodeParms'); + if ($decodeParams instanceof PdfArray) { + $decodeParams = $decodeParams->value; + } else { + $decodeParams = [$decodeParams]; + } + + foreach ($filters as $key => $filter) { + if (!($filter instanceof PdfName)) { + continue; + } + + $decodeParam = null; + if (isset($decodeParams[$key])) { + $decodeParam = ($decodeParams[$key] instanceof PdfDictionary ? $decodeParams[$key] : null); + } + + switch ($filter->value) { + case 'FlateDecode': + case 'Fl': + case 'LZWDecode': + case 'LZW': + if (\strpos($filter->value, 'LZW') === 0) { + $filterObject = new Lzw(); + } else { + $filterObject = new Flate(); + } + + $stream = $filterObject->decode($stream); + + if ($decodeParam instanceof PdfDictionary) { + $predictor = PdfDictionary::get($decodeParam, 'Predictor', PdfNumeric::create(1)); + if ($predictor->value !== 1) { + if (!\class_exists(Predictor::class)) { + throw new PdfParserException( + 'This PDF document makes use of features which are only implemented in the ' . + 'commercial "FPDI PDF-Parser" add-on (see https://www.setasign.com/fpdi-pdf-' . + 'parser).', + PdfParserException::IMPLEMENTED_IN_FPDI_PDF_PARSER + ); + } + + $colors = PdfDictionary::get($decodeParam, 'Colors', PdfNumeric::create(1)); + $bitsPerComponent = PdfDictionary::get( + $decodeParam, + 'BitsPerComponent', + PdfNumeric::create(8) + ); + + $columns = PdfDictionary::get($decodeParam, 'Columns', PdfNumeric::create(1)); + + $filterObject = new Predictor( + $predictor->value, + $colors->value, + $bitsPerComponent->value, + $columns->value + ); + + $stream = $filterObject->decode($stream); + } + } + + break; + case 'ASCII85Decode': + case 'A85': + $filterObject = new Ascii85(); + $stream = $filterObject->decode($stream); + break; + + case 'ASCIIHexDecode': + case 'AHx': + $filterObject = new AsciiHex(); + $stream = $filterObject->decode($stream); + break; + + default: + throw new FilterException( + \sprintf('Unsupported filter "%s".', $filter->value), + FilterException::UNSUPPORTED_FILTER + ); + } + } + + return $stream; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfString.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfString.php new file mode 100644 index 0000000..f933b1a --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfString.php @@ -0,0 +1,172 @@ +getOffset(); + $openBrackets = 1; + do { + $buffer = $streamReader->getBuffer(false); + for ($length = \strlen($buffer); $openBrackets !== 0 && $pos < $length; $pos++) { + switch ($buffer[$pos]) { + case '(': + $openBrackets++; + break; + case ')': + $openBrackets--; + break; + case '\\': + $pos++; + } + } + } while ($openBrackets !== 0 && $streamReader->increaseLength()); + + $result = \substr($buffer, $startPos, $openBrackets + $pos - $startPos - 1); + $streamReader->setOffset($pos); + + $v = new self; + $v->value = $result; + + return $v; + } + + /** + * Helper method to create an instance. + * + * @param string $value The string needs to be escaped accordingly. + * @return self + */ + public static function create($value) + { + $v = new self; + $v->value = $value; + + return $v; + } + + /** + * Ensures that the passed value is a PdfString instance. + * + * @param mixed $string + * @return self + * @throws PdfTypeException + */ + public static function ensure($string) + { + return PdfType::ensureType(self::class, $string, 'String value expected.'); + } + + /** + * Unescapes escaped sequences in a PDF string according to the PDF specification. + * + * @param string $s + * @return string + */ + public static function unescape($s) + { + $out = ''; + /** @noinspection ForeachInvariantsInspection */ + for ($count = 0, $n = \strlen($s); $count < $n; $count++) { + if ($s[$count] !== '\\') { + $out .= $s[$count]; + } else { + // A backslash at the end of the string - ignore it + if ($count === ($n - 1)) { + break; + } + + switch ($s[++$count]) { + case ')': + case '(': + case '\\': + $out .= $s[$count]; + break; + + case 'f': + $out .= "\x0C"; + break; + + case 'b': + $out .= "\x08"; + break; + + case 't': + $out .= "\x09"; + break; + + case 'r': + $out .= "\x0D"; + break; + + case 'n': + $out .= "\x0A"; + break; + + case "\r": + if ($count !== $n - 1 && $s[$count + 1] === "\n") { + $count++; + } + break; + + case "\n": + break; + + default: + $actualChar = \ord($s[$count]); + // ascii 48 = number 0 + // ascii 57 = number 9 + if ($actualChar >= 48 && + $actualChar <= 57) { + $oct = '' . $s[$count]; + + /** @noinspection NotOptimalIfConditionsInspection */ + if ($count + 1 < $n && + \ord($s[$count + 1]) >= 48 && + \ord($s[$count + 1]) <= 57 + ) { + $count++; + $oct .= $s[$count]; + + /** @noinspection NotOptimalIfConditionsInspection */ + if ($count + 1 < $n && + \ord($s[$count + 1]) >= 48 && + \ord($s[$count + 1]) <= 57 + ) { + $oct .= $s[++$count]; + } + } + + $out .= \chr(\octdec($oct)); + } else { + // If the character is not one of those defined, the backslash is ignored + $out .= $s[$count]; + } + } + } + } + return $out; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfToken.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfToken.php new file mode 100644 index 0000000..4abda09 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfToken.php @@ -0,0 +1,44 @@ +value = $token; + + return $v; + } + + /** + * Ensures that the passed value is a PdfToken instance. + * + * @param mixed $token + * @return self + * @throws PdfTypeException + */ + public static function ensure($token) + { + return PdfType::ensureType(self::class, $token, 'Token value expected.'); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfType.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfType.php new file mode 100644 index 0000000..6ce3c8c --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfType.php @@ -0,0 +1,79 @@ +value, $parser, $stopAtIndirectObject); + } + + if ($value instanceof PdfIndirectObjectReference) { + return self::resolve($parser->getIndirectObject($value->value), $parser, $stopAtIndirectObject); + } + + return $value; + } + + /** + * Ensure that a value is an instance of a specific PDF type. + * + * @param string $type + * @param PdfType $value + * @param string $errorMessage + * @return mixed + * @throws PdfTypeException + */ + protected static function ensureType($type, $value, $errorMessage) + { + if (!($value instanceof $type)) { + throw new PdfTypeException( + $errorMessage, + PdfTypeException::INVALID_DATA_TYPE + ); + } + + return $value; + } + + /** + * The value of the PDF type. + * + * @var mixed + */ + public $value; +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfTypeException.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfTypeException.php new file mode 100644 index 0000000..8327220 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfParser/Type/PdfTypeException.php @@ -0,0 +1,25 @@ +value; + $ax = PdfNumeric::ensure(PdfType::resolve($array[0], $parser))->value; + $ay = PdfNumeric::ensure(PdfType::resolve($array[1], $parser))->value; + $bx = PdfNumeric::ensure(PdfType::resolve($array[2], $parser))->value; + $by = PdfNumeric::ensure(PdfType::resolve($array[3], $parser))->value; + + return new self($ax, $ay, $bx, $by); + } + + /** + * Rectangle constructor. + * + * @param float|int $ax + * @param float|int $ay + * @param float|int $bx + * @param float|int $by + */ + public function __construct($ax, $ay, $bx, $by) + { + $this->llx = \min($ax, $bx); + $this->lly = \min($ay, $by); + $this->urx = \max($ax, $bx); + $this->ury = \max($ay, $by); + } + + /** + * Get the width of the rectangle. + * + * @return float|int + */ + public function getWidth() + { + return $this->urx - $this->llx; + } + + /** + * Get the height of the rectangle. + * + * @return float|int + */ + public function getHeight() + { + return $this->ury - $this->lly; + } + + /** + * Get the lower left abscissa. + * + * @return float|int + */ + public function getLlx() + { + return $this->llx; + } + + /** + * Get the lower left ordinate. + * + * @return float|int + */ + public function getLly() + { + return $this->lly; + } + + /** + * Get the upper right abscissa. + * + * @return float|int + */ + public function getUrx() + { + return $this->urx; + } + + /** + * Get the upper right ordinate. + * + * @return float|int + */ + public function getUry() + { + return $this->ury; + } + + /** + * Get the rectangle as an array. + * + * @return array + */ + public function toArray() + { + return [ + $this->llx, + $this->lly, + $this->urx, + $this->ury + ]; + } + + /** + * Get the rectangle as a PdfArray. + * + * @return PdfArray + */ + public function toPdfArray() + { + $array = new PdfArray(); + $array->value[] = PdfNumeric::create($this->llx); + $array->value[] = PdfNumeric::create($this->lly); + $array->value[] = PdfNumeric::create($this->urx); + $array->value[] = PdfNumeric::create($this->ury); + + return $array; + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/Page.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/Page.php new file mode 100644 index 0000000..0c2fb56 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/Page.php @@ -0,0 +1,272 @@ +pageObject = $page; + $this->parser = $parser; + } + + /** + * Get the indirect object of this page. + * + * @return PdfIndirectObject + */ + public function getPageObject() + { + return $this->pageObject; + } + + /** + * Get the dictionary of this page. + * + * @return PdfDictionary + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getPageDictionary() + { + if (null === $this->pageDictionary) { + $this->pageDictionary = PdfDictionary::ensure(PdfType::resolve($this->getPageObject(), $this->parser)); + } + + return $this->pageDictionary; + } + + /** + * Get a page attribute. + * + * @param string $name + * @param bool $inherited + * @return PdfType|null + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getAttribute($name, $inherited = true) + { + $dict = $this->getPageDictionary(); + + if (isset($dict->value[$name])) { + return $dict->value[$name]; + } + + $inheritedKeys = ['Resources', 'MediaBox', 'CropBox', 'Rotate']; + if ($inherited && \in_array($name, $inheritedKeys, true)) { + if ($this->inheritedAttributes === null) { + $this->inheritedAttributes = []; + $inheritedKeys = \array_filter($inheritedKeys, function ($key) use ($dict) { + return !isset($dict->value[$key]); + }); + + if (\count($inheritedKeys) > 0) { + $parentDict = PdfType::resolve(PdfDictionary::get($dict, 'Parent'), $this->parser); + while ($parentDict instanceof PdfDictionary) { + foreach ($inheritedKeys as $index => $key) { + if (isset($parentDict->value[$key])) { + $this->inheritedAttributes[$key] = $parentDict->value[$key]; + unset($inheritedKeys[$index]); + } + } + + /** @noinspection NotOptimalIfConditionsInspection */ + if (isset($parentDict->value['Parent']) && \count($inheritedKeys) > 0) { + $parentDict = PdfType::resolve(PdfDictionary::get($parentDict, 'Parent'), $this->parser); + } else { + break; + } + } + } + } + + if (isset($this->inheritedAttributes[$name])) { + return $this->inheritedAttributes[$name]; + } + } + + return null; + } + + /** + * Get the rotation value. + * + * @return int + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getRotation() + { + $rotation = $this->getAttribute('Rotate'); + if (null === $rotation) { + return 0; + } + + $rotation = PdfNumeric::ensure(PdfType::resolve($rotation, $this->parser))->value % 360; + + if ($rotation < 0) { + $rotation += 360; + } + + return $rotation; + } + + /** + * Get a boundary of this page. + * + * @param string $box + * @param bool $fallback + * @return bool|Rectangle + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + * @see PageBoundaries + */ + public function getBoundary($box = PageBoundaries::CROP_BOX, $fallback = true) + { + $value = $this->getAttribute($box); + + if ($value !== null) { + return Rectangle::byPdfArray($value, $this->parser); + } + + if ($fallback === false) { + return false; + } + + switch ($box) { + case PageBoundaries::BLEED_BOX: + case PageBoundaries::TRIM_BOX: + case PageBoundaries::ART_BOX: + return $this->getBoundary(PageBoundaries::CROP_BOX, true); + case PageBoundaries::CROP_BOX: + return $this->getBoundary(PageBoundaries::MEDIA_BOX, true); + } + + return false; + } + + /** + * Get the width and height of this page. + * + * @param string $box + * @param bool $fallback + * @return array|bool + * @throws PdfParserException + * @throws PdfTypeException + * @throws CrossReferenceException + */ + public function getWidthAndHeight($box = PageBoundaries::CROP_BOX, $fallback = true) + { + $boundary = $this->getBoundary($box, $fallback); + if ($boundary === false) { + return false; + } + + $rotation = $this->getRotation(); + $interchange = ($rotation / 90) % 2; + + return [ + $interchange ? $boundary->getHeight() : $boundary->getWidth(), + $interchange ? $boundary->getWidth() : $boundary->getHeight() + ]; + } + + /** + * Get the raw content stream. + * + * @return string + * @throws PdfReaderException + * @throws PdfTypeException + * @throws FilterException + * @throws PdfParserException + */ + public function getContentStream() + { + $dict = $this->getPageDictionary(); + $contents = PdfType::resolve(PdfDictionary::get($dict, 'Contents'), $this->parser); + if ($contents instanceof PdfNull) { + return ''; + } + + if ($contents instanceof PdfArray) { + $result = []; + foreach ($contents->value as $content) { + $content = PdfType::resolve($content, $this->parser); + if (!($content instanceof PdfStream)) { + continue; + } + $result[] = $content->getUnfilteredStream(); + } + + return \implode("\n", $result); + } + + if ($contents instanceof PdfStream) { + return $contents->getUnfilteredStream(); + } + + throw new PdfReaderException( + 'Array or stream expected.', + PdfReaderException::UNEXPECTED_DATA_TYPE + ); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/PageBoundaries.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/PageBoundaries.php new file mode 100644 index 0000000..aaeaf91 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/PageBoundaries.php @@ -0,0 +1,95 @@ +parser = $parser; + } + + /** + * PdfReader destructor. + */ + public function __destruct() + { + if ($this->parser !== null) { + /** @noinspection PhpInternalEntityUsedInspection */ + $this->parser->cleanUp(); + } + } + + /** + * Get the pdf parser instance. + * + * @return PdfParser + */ + public function getParser() + { + return $this->parser; + } + + /** + * Get the PDF version. + * + * @return string + * @throws PdfParserException + */ + public function getPdfVersion() + { + return \implode('.', $this->parser->getPdfVersion()); + } + + /** + * Get the page count. + * + * @return int + * @throws PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function getPageCount() + { + if ($this->pageCount === null) { + $catalog = $this->parser->getCatalog(); + + $pages = PdfType::resolve(PdfDictionary::get($catalog, 'Pages'), $this->parser); + $count = PdfType::resolve(PdfDictionary::get($pages, 'Count'), $this->parser); + + $this->pageCount = PdfNumeric::ensure($count)->value; + } + + return $this->pageCount; + } + + /** + * Get a page instance. + * + * @param int $pageNumber + * @return Page + * @throws PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + * @throws \InvalidArgumentException + */ + public function getPage($pageNumber) + { + if (!\is_numeric($pageNumber)) { + throw new \InvalidArgumentException( + 'Page number needs to be a number.' + ); + } + + if ($pageNumber < 1 || $pageNumber > $this->getPageCount()) { + throw new \InvalidArgumentException( + \sprintf( + 'Page number "%s" out of available page range (1 - %s)', + $pageNumber, + $this->getPageCount() + ) + ); + } + + $this->readPages(); + + $page = $this->pages[$pageNumber - 1]; + + if ($page instanceof PdfIndirectObjectReference) { + $readPages = function ($kids) use (&$readPages) { + $kids = PdfArray::ensure($kids); + + /** @noinspection LoopWhichDoesNotLoopInspection */ + foreach ($kids->value as $reference) { + $reference = PdfIndirectObjectReference::ensure($reference); + $object = $this->parser->getIndirectObject($reference->value); + $type = PdfDictionary::get($object->value, 'Type'); + + if ($type->value === 'Pages') { + return $readPages(PdfDictionary::get($object->value, 'Kids')); + } + + return $object; + } + + throw new PdfReaderException( + 'Kids array cannot be empty.', + PdfReaderException::KIDS_EMPTY + ); + }; + + $page = $this->parser->getIndirectObject($page->value); + $dict = PdfType::resolve($page, $this->parser); + $type = PdfDictionary::get($dict, 'Type'); + if ($type->value === 'Pages') { + $kids = PdfType::resolve(PdfDictionary::get($dict, 'Kids'), $this->parser); + $page = $this->pages[$pageNumber - 1] = $readPages($kids); + } else { + $this->pages[$pageNumber - 1] = $page; + } + } + + return new Page($page, $this->parser); + } + + /** + * Walk the page tree and resolve all indirect objects of all pages. + * + * @throws PdfTypeException + * @throws CrossReferenceException + * @throws PdfParserException + */ + protected function readPages() + { + if (\count($this->pages) > 0) { + return; + } + + $readPages = function ($kids, $count) use (&$readPages) { + $kids = PdfArray::ensure($kids); + $isLeaf = $count->value === \count($kids->value); + + foreach ($kids->value as $reference) { + $reference = PdfIndirectObjectReference::ensure($reference); + + if ($isLeaf) { + $this->pages[] = $reference; + continue; + } + + $object = $this->parser->getIndirectObject($reference->value); + $type = PdfDictionary::get($object->value, 'Type'); + + if ($type->value === 'Pages') { + $readPages(PdfDictionary::get($object->value, 'Kids'), PdfDictionary::get($object->value, 'Count')); + } else { + $this->pages[] = $object; + } + } + }; + + $catalog = $this->parser->getCatalog(); + $pages = PdfType::resolve(PdfDictionary::get($catalog, 'Pages'), $this->parser); + $count = PdfType::resolve(PdfDictionary::get($pages, 'Count'), $this->parser); + $kids = PdfType::resolve(PdfDictionary::get($pages, 'Kids'), $this->parser); + $readPages($kids, $count); + } +} diff --git a/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/PdfReaderException.php b/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/PdfReaderException.php new file mode 100644 index 0000000..c1159ba --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/PdfReader/PdfReaderException.php @@ -0,0 +1,35 @@ +cleanUp(); + } + + /** + * Get the next template id. + * + * @return int + */ + protected function getNextTemplateId() + { + return $this->templateId++; + } + + /** + * Draws an imported page onto the page or another template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size + * @see FpdiTrait::getTemplateSize() + */ + public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + return $this->useImportedPage($tpl, $x, $y, $width, $height, $adjustPageSize); + } + + /** + * Draws an imported page onto the page. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $pageId The page id + * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size. + * @see Fpdi::getTemplateSize() + */ + public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + $size = $this->fpdiUseImportedPage($pageId, $x, $y, $width, $height, $adjustPageSize); + if ($this->inxobj) { + $importedPage = $this->importedPages[$pageId]; + $this->xobjects[$this->xobjid]['importedPages'][$importedPage['id']] = $pageId; + } + + return $size; + } + + /** + * Get the size of an imported page. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getTemplateSize($tpl, $width = null, $height = null) + { + return $this->getImportedPageSize($tpl, $width, $height); + } + + /** + * @inheritdoc + */ + protected function _getxobjectdict() + { + $out = parent::_getxobjectdict(); + + foreach ($this->importedPages as $key => $pageData) { + $out .= '/' . $pageData['id'] . ' ' . $pageData['objectNumber'] . ' 0 R '; + } + + return $out; + } + + /** + * @inheritdoc + * @throws CrossReferenceException + * @throws PdfParserException + */ + protected function _putxobjects() + { + foreach ($this->importedPages as $key => $pageData) { + $this->currentObjectNumber = $this->_newobj(); + $this->importedPages[$key]['objectNumber'] = $this->currentObjectNumber; + $this->currentReaderId = $pageData['readerId']; + $this->writePdfType($pageData['stream']); + $this->_put('endobj'); + } + + foreach (\array_keys($this->readers) as $readerId) { + $parser = $this->getPdfReader($readerId)->getParser(); + $this->currentReaderId = $readerId; + + while (($objectNumber = \array_pop($this->objectsToCopy[$readerId])) !== null) { + try { + $object = $parser->getIndirectObject($objectNumber); + + } catch (CrossReferenceException $e) { + if ($e->getCode() === CrossReferenceException::OBJECT_NOT_FOUND) { + $object = PdfIndirectObject::create($objectNumber, 0, new PdfNull()); + } else { + throw $e; + } + } + + $this->writePdfType($object); + } + } + + // let's prepare resources for imported pages in templates + foreach ($this->xobjects as $xObjectId => $data) { + if (!isset($data['importedPages'])) { + continue; + } + + foreach ($data['importedPages'] as $id => $pageKey) { + $page = $this->importedPages[$pageKey]; + $this->xobjects[$xObjectId]['xobjects'][$id] = ['n' => $page['objectNumber']]; + } + } + + + parent::_putxobjects(); + $this->currentObjectNumber = null; + } + + /** + * Append content to the buffer of TCPDF. + * + * @param string $s + * @param bool $newLine + */ + protected function _put($s, $newLine = true) + { + if ($newLine) { + $this->setBuffer($s . "\n"); + } else { + $this->setBuffer($s); + } + } + + /** + * Begin a new object and return the object number. + * + * @param int|string $objid Object ID (leave empty to get a new ID). + * @return int object number + */ + protected function _newobj($objid = '') + { + $this->_out($this->_getobj($objid)); + return $this->n; + } + + /** + * Writes a PdfType object to the resulting buffer. + * + * @param PdfType $value + * @throws PdfTypeException + */ + protected function writePdfType(PdfType $value) + { + if (!$this->encrypted) { + $this->fpdiWritePdfType($value); + return; + } + + if ($value instanceof PdfString) { + $string = PdfString::unescape($value->value); + $string = $this->_encrypt_data($this->currentObjectNumber, $string); + $value->value = \TCPDF_STATIC::_escape($string); + + } elseif ($value instanceof PdfHexString) { + $filter = new AsciiHex(); + $string = $filter->decode($value->value); + $string = $this->_encrypt_data($this->currentObjectNumber, $string); + $value->value = $filter->encode($string, true); + + } elseif ($value instanceof PdfStream) { + $stream = $value->getStream(); + $stream = $this->_encrypt_data($this->currentObjectNumber, $stream); + $dictionary = $value->value; + $dictionary->value['Length'] = PdfNumeric::create(\strlen($stream)); + $value = PdfStream::create($dictionary, $stream); + + } elseif ($value instanceof PdfIndirectObject) { + /** + * @var $value PdfIndirectObject + */ + $this->currentObjectNumber = $this->objectMap[$this->currentReaderId][$value->objectNumber]; + } + + $this->fpdiWritePdfType($value); + } +} \ No newline at end of file diff --git a/lib/MPDF/vendor/setasign/fpdi/src/TcpdfFpdi.php b/lib/MPDF/vendor/setasign/fpdi/src/TcpdfFpdi.php new file mode 100644 index 0000000..6b617a1 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/TcpdfFpdi.php @@ -0,0 +1,23 @@ +_protectedPutimages(); + } + + /** + * Make the method public as in tFPDF. + */ + public function _putxobjectdict() + { + $this->_protectedPutxobjectdict(); + } + + /** + * Set the page format of the current page. + * + * @param array $size An array with two values defining the size. + * @param string $orientation "L" for landscape, "P" for portrait. + * @throws \BadMethodCallException + */ + public function setPageFormat($size, $orientation) + { + if ($this->currentTemplateId !== null) { + throw new \BadMethodCallException('The page format cannot be changed when writing to a template.'); + } + + if (!\in_array($orientation, ['P', 'L'], true)) { + throw new \InvalidArgumentException(\sprintf( + 'Invalid page orientation "%s"! Only "P" and "L" are allowed!', + $orientation + )); + } + + $size = $this->_getpagesize($size); + + if ($orientation != $this->CurOrientation + || $size[0] != $this->CurPageSize[0] + || $size[1] != $this->CurPageSize[1] + ) { + // New size or orientation + if ($orientation === 'P') { + $this->w = $size[0]; + $this->h = $size[1]; + } else { + $this->w = $size[1]; + $this->h = $size[0]; + } + $this->wPt = $this->w * $this->k; + $this->hPt = $this->h * $this->k; + $this->PageBreakTrigger = $this->h - $this->bMargin; + $this->CurOrientation = $orientation; + $this->CurPageSize = $size; + + $this->PageSizes[$this->page] = array($this->wPt, $this->hPt); + } + } + + /** + * @inheritdoc + */ + protected function _put($s, $newLine = true) + { + if ($newLine) { + $this->buffer .= $s . "\n"; + } else { + $this->buffer .= $s; + } + } +} \ No newline at end of file diff --git a/lib/MPDF/vendor/setasign/fpdi/src/Tfpdf/Fpdi.php b/lib/MPDF/vendor/setasign/fpdi/src/Tfpdf/Fpdi.php new file mode 100644 index 0000000..f22625f --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/Tfpdf/Fpdi.php @@ -0,0 +1,164 @@ +cleanUp(); + } + + /** + * Draws an imported page or a template onto the page or another template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array + * with the keys "x", "y", "width", "height", "adjustPageSize". + * @param float|int $y The ordinate of upper-left corner. + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @param bool $adjustPageSize + * @return array The size + * @see Fpdi::getTemplateSize() + */ + public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) + { + if (isset($this->importedPages[$tpl])) { + $size = $this->useImportedPage($tpl, $x, $y, $width, $height, $adjustPageSize); + if ($this->currentTemplateId !== null) { + $this->templates[$this->currentTemplateId]['resources']['templates']['importedPages'][$tpl] = $tpl; + } + return $size; + } + + return parent::useTemplate($tpl, $x, $y, $width, $height, $adjustPageSize); + } + + /** + * Get the size of an imported page or template. + * + * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the + * aspect ratio. + * + * @param mixed $tpl The template id + * @param float|int|null $width The width. + * @param float|int|null $height The height. + * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) + */ + public function getTemplateSize($tpl, $width = null, $height = null) + { + $size = parent::getTemplateSize($tpl, $width, $height); + if ($size === false) { + return $this->getImportedPageSize($tpl, $width, $height); + } + + return $size; + } + + /** + * @inheritdoc + * @throws CrossReferenceException + * @throws PdfParserException + */ + public function _putimages() + { + $this->currentReaderId = null; + parent::_putimages(); + + foreach ($this->importedPages as $key => $pageData) { + $this->_newobj(); + $this->importedPages[$key]['objectNumber'] = $this->n; + $this->currentReaderId = $pageData['readerId']; + $this->writePdfType($pageData['stream']); + $this->_put('endobj'); + } + + foreach (\array_keys($this->readers) as $readerId) { + $parser = $this->getPdfReader($readerId)->getParser(); + $this->currentReaderId = $readerId; + + while (($objectNumber = \array_pop($this->objectsToCopy[$readerId])) !== null) { + try { + $object = $parser->getIndirectObject($objectNumber); + + } catch (CrossReferenceException $e) { + if ($e->getCode() === CrossReferenceException::OBJECT_NOT_FOUND) { + $object = PdfIndirectObject::create($objectNumber, 0, new PdfNull()); + } else { + throw $e; + } + } + + $this->writePdfType($object); + } + } + + $this->currentReaderId = null; + } + + /** + * @inheritdoc + */ + public function _putxobjectdict() + { + foreach ($this->importedPages as $key => $pageData) { + $this->_put('/' . $pageData['id'] . ' ' . $pageData['objectNumber'] . ' 0 R'); + } + + parent::_putxobjectdict(); + } + + /** + * @inheritdoc + */ + public function _newobj($n = null) + { + // Begin a new object + if($n === null) + $n = ++$this->n; + $this->offsets[$n] = $this->_getoffset(); + $this->_put($n.' 0 obj'); + } + + /** + * @inheritdoc + */ + protected function _getoffset() + { + return strlen($this->buffer); + } +} \ No newline at end of file diff --git a/lib/MPDF/vendor/setasign/fpdi/src/autoload.php b/lib/MPDF/vendor/setasign/fpdi/src/autoload.php new file mode 100644 index 0000000..fd7d472 --- /dev/null +++ b/lib/MPDF/vendor/setasign/fpdi/src/autoload.php @@ -0,0 +1,20 @@ +