diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..42f75e1 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "smalot/pdfparser": "^2.12" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..262ff8e --- /dev/null +++ b/composer.lock @@ -0,0 +1,155 @@ +{ + "_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": "f7c071b38f69a0b82ff94c1527c44426", + "packages": [ + { + "name": "smalot/pdfparser", + "version": "v2.12.5", + "source": { + "type": "git", + "url": "https://github.com/smalot/pdfparser.git", + "reference": "2cfa0d92bd557875c9f52a75fde0e8392302a354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/smalot/pdfparser/zipball/2cfa0d92bd557875c9f52a75fde0e8392302a354", + "reference": "2cfa0d92bd557875c9f52a75fde0e8392302a354", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-zlib": "*", + "php": ">=7.1", + "symfony/polyfill-mbstring": "^1.18" + }, + "type": "library", + "autoload": { + "psr-0": { + "Smalot\\PdfParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Sebastien MALOT", + "email": "sebastien@malot.fr" + } + ], + "description": "Pdf parser library. Can read and extract information from pdf file.", + "homepage": "https://www.pdfparser.org", + "keywords": [ + "extract", + "parse", + "parser", + "pdf", + "text" + ], + "support": { + "issues": "https://github.com/smalot/pdfparser/issues", + "source": "https://github.com/smalot/pdfparser/tree/v2.12.5" + }, + "time": "2026-04-17T11:37:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/modules/food/includes/api/import-pdf.php b/modules/food/includes/api/import-pdf.php index 552387e..affc8b2 100644 --- a/modules/food/includes/api/import-pdf.php +++ b/modules/food/includes/api/import-pdf.php @@ -2,6 +2,7 @@ // modules/meals/includes/api/import-pdf.php require dirname(__DIR__, 4) . '/includes/auth.php'; require dirname(__DIR__, 4) . '/includes/db.php'; +require dirname(__DIR__, 4) . '/vendor/autoload.php'; require_login(); header('Content-Type: application/json; charset=utf-8'); @@ -18,55 +19,29 @@ if ($personId <= 0) { } // --------------------------------------------------------- -// PARSEUR PDF SPATIAL NATIF (100% PHP SANS LIBRAIRIE) +// PARSEUR PDF SPATIAL (via smalot/pdfparser pour un décodage +// fiable des polices Type0/CID + tables ToUnicode) // --------------------------------------------------------- class NativePdfParser { + // strtoupper() seul ne gère pas les accents (ex: "Déclinaison" -> "DCLINAISON" + // au lieu de "DECLINAISON"), ce qui empêchait ce label d'être reconnu. + private static function cleanLabel($text) { + $ascii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text); + return strtoupper(preg_replace('/[^A-Z]/i', '', $ascii ?: $text)); + } + public static function getMealData($filename) { - $content = file_get_contents($filename); $texts = []; - - // 1. Décompression et Tokenization Mathématique - if (preg_match_all('/stream(.*?)endstream/is', $content, $matches)) { - foreach ($matches[1] as $stream) { - $stream = ltrim($stream, "\r\n"); - $decoded = @gzuncompress($stream); - if (!$decoded) $decoded = $stream; - - // On éclate le flux par opérateurs PDF clés (Position et Texte) - $parts = preg_split('/(Tm|Td|Tj|TJ)/', $decoded, -1, PREG_SPLIT_DELIM_CAPTURE); - $x = 0; $y = 0; - - for ($i = 0; $i < count($parts); $i++) { - $token = $parts[$i]; - if ($token === 'Tm' || $token === 'Td') { - $prev = $parts[$i-1]; - preg_match_all('/[0-9.-]+/', $prev, $nums); - if (count($nums[0]) >= 2) { - $yRaw = array_pop($nums[0]); - $xRaw = array_pop($nums[0]); - if ($token === 'Tm') { - $x = (float)$xRaw; $y = (float)$yRaw; - } else { - $x += (float)$xRaw; $y += (float)$yRaw; - } - } - } elseif ($token === 'Tj') { - $prev = $parts[$i-1]; - if (preg_match('/\((.*?)\)$/', trim($prev), $m)) { - self::addText($texts, $m[1], $x, $y); - } - } elseif ($token === 'TJ') { - $prev = $parts[$i-1]; - if (preg_match('/\[(.*?)\]$/', trim($prev), $m)) { - preg_match_all('/\((.*?)\)/', $m[1], $tjs); - $tjX = $x; - foreach ($tjs[1] as $tj) { - self::addText($texts, $tj, $tjX, $y); - $tjX += 10; // Décalage estimé pour les mots suivants du tableau - } - } - } - } + + $parser = new \Smalot\PdfParser\Parser(); + $pdf = $parser->parseFile($filename); + + foreach ($pdf->getPages() as $page) { + foreach ($page->getDataTm() as $entry) { + [$tm, $rawText] = $entry; + $textVal = trim((string) $rawText); + if ($textVal === '') continue; + self::addText($texts, $textVal, (float) $tm[4], (float) $tm[5]); } } @@ -76,7 +51,7 @@ class NativePdfParser { $dayLabels = ['LUNDI', 'MARDI', 'MERCREDI', 'JEUDI', 'VENDREDI']; $colXs = []; foreach ($texts as $t) { - $clean = strtoupper(preg_replace('/[^A-Z]/', '', $t['text'])); + $clean = self::cleanLabel($t['text']); if (in_array($clean, $dayLabels)) { $colXs[$clean] = $t['x']; } @@ -103,7 +78,7 @@ class NativePdfParser { // 3. Définition des Tranches Horizontales (Y) via les Titres de Rangée (Zone Extrême Gauche) $rowHeaders = []; foreach ($texts as $t) { - $clean = strtoupper(preg_replace('/[^A-Z]/', '', $t['text'])); + $clean = self::cleanLabel($t['text']); if (in_array($clean, ['HORSDEUVRE', 'DECLINAISON', 'PLATCHAUD', 'GARNITURE', 'PRODUITLAITIER', 'DESSERT', 'LESGOUTERS'])) { // On s'assure de ne capter que la colonne tout à gauche if ($t['x'] < ($sortedXs[0] - 20)) { @@ -137,7 +112,7 @@ class NativePdfParser { // 4. Affectation Chirurgicale par Intersection Grille (X, Y) $days = [[], [], [], [], []]; foreach ($texts as $t) { - $cleanAlpha = strtoupper(preg_replace('/[^A-Z]/', '', $t['text'])); + $cleanAlpha = self::cleanLabel($t['text']); // Rejet des labels génériques et de la fameuse ligne "Déclinaison" if (in_array($cleanAlpha, ['DECLINAISON', 'MENUVEGETARIEN', 'PLATCHAUD', 'GARNITURE', 'PRODUITLAITIER', 'HORSDEUVRE', 'LESGOUTERS', 'DESSERT'])) continue; @@ -150,8 +125,11 @@ class NativePdfParser { if (strlen($textVal) < 3) continue; // Filtre Vertical : Est-on dans la tranche Plat Chaud ou Garniture ? (Tolérance +/- 5 unités) - $inPlat = ($t['y'] <= $yPlatTop + 5 && $t['y'] >= $yPlatBot + 5); - $inGar = ($t['y'] <= $yGarTop + 5 && $t['y'] >= $yGarBot + 5); + // Le texte d'une rangée déborde souvent au-dessus de son propre label (lignes + // empilées vers le haut), y compris pour le label de la rangée suivante qui + // sert de limite basse : on applique donc la même marge des deux côtés. + $inPlat = ($t['y'] <= $yPlatTop + 20 && $t['y'] >= $yPlatBot + 20); + $inGar = ($t['y'] <= $yGarTop + 20 && $t['y'] >= $yGarBot + 20); if ($inPlat || $inGar) { // Filtre Horizontal : Quel est le jour le plus proche ? @@ -206,21 +184,11 @@ class NativePdfParser { } private static function addText(&$texts, $rawText, $x, $y) { - // Décodage de l'octal \123 - $m = preg_replace_callback('/\\\\([0-7]{1,3})/', function($cb) { return chr(octdec($cb[1])); }, $rawText); - - // Détection de l'UTF-16BE (Le BOM \xFE\xFF indique que le texte est double encodé) - if (str_starts_with($m, "\xFE\xFF")) { - $m = mb_convert_encoding(substr($m, 2), 'UTF-8', 'UTF-16BE'); - } else { - // Fallback Latin-1 si pas de BOM - $m = mb_convert_encoding($m, 'UTF-8', 'ISO-8859-1'); - } - - // Suppression radicale des octets nuls responsables de l'espacement forcé (E s c a l o p e) - $m = str_replace("\0", "", $m); + // smalot/pdfparser décode déjà correctement les polices Type0/CID + ToUnicode : + // il ne reste plus qu'à nettoyer les caractères parasites résiduels. + $m = str_replace("\0", "", $rawText); $m = trim(preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\+&\'’\-]/u', '', $m)); - + if ($m !== '') { $texts[] = ['text' => $m, 'x' => $x, 'y' => $y]; } diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..55ef304 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,22 @@ + + * 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 https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + 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 list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $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 list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $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] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $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 list|string $paths The PSR-0 base directories + * + * @return void + */ + 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 list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + 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 + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * 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; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + 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; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..2052022 --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,396 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/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/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..ee3a2d6 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,10 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..27020d2 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($vendorDir . '/smalot/pdfparser/src'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..c07235c --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,10 @@ + array($vendorDir . '/symfony/polyfill-mbstring'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..27084d4 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInit3ffbe28fb745b145fa2fcd19500ebf5e::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..6b2c3fe --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,51 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Polyfill\\Mbstring\\' => 26, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + ); + + public static $prefixesPsr0 = array ( + 'S' => + array ( + 'Smalot\\PdfParser\\' => + array ( + 0 => __DIR__ . '/..' . '/smalot/pdfparser/src', + ), + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit3ffbe28fb745b145fa2fcd19500ebf5e::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit3ffbe28fb745b145fa2fcd19500ebf5e::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit3ffbe28fb745b145fa2fcd19500ebf5e::$prefixesPsr0; + $loader->classMap = ComposerStaticInit3ffbe28fb745b145fa2fcd19500ebf5e::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..f0ca1d8 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,148 @@ +{ + "packages": [ + { + "name": "smalot/pdfparser", + "version": "v2.12.5", + "version_normalized": "2.12.5.0", + "source": { + "type": "git", + "url": "https://github.com/smalot/pdfparser.git", + "reference": "2cfa0d92bd557875c9f52a75fde0e8392302a354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/smalot/pdfparser/zipball/2cfa0d92bd557875c9f52a75fde0e8392302a354", + "reference": "2cfa0d92bd557875c9f52a75fde0e8392302a354", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-zlib": "*", + "php": ">=7.1", + "symfony/polyfill-mbstring": "^1.18" + }, + "time": "2026-04-17T11:37:58+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Smalot\\PdfParser\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "Sebastien MALOT", + "email": "sebastien@malot.fr" + } + ], + "description": "Pdf parser library. Can read and extract information from pdf file.", + "homepage": "https://www.pdfparser.org", + "keywords": [ + "extract", + "parse", + "parser", + "pdf", + "text" + ], + "support": { + "issues": "https://github.com/smalot/pdfparser/issues", + "source": "https://github.com/smalot/pdfparser/tree/v2.12.5" + }, + "install-path": "../smalot/pdfparser" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "version_normalized": "1.38.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2026-05-27T06:59:30+00:00", + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..6d11596 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,41 @@ + array( + 'name' => '__root__', + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => '1.0.0+no-version-set', + 'version' => '1.0.0.0', + 'reference' => null, + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'smalot/pdfparser' => array( + 'pretty_version' => 'v2.12.5', + 'version' => '2.12.5.0', + 'reference' => '2cfa0d92bd557875c9f52a75fde0e8392302a354', + 'type' => 'library', + 'install_path' => __DIR__ . '/../smalot/pdfparser', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.38.2', + 'version' => '1.38.2.0', + 'reference' => 'd3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..6cd6b53 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,25 @@ += 70200)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + throw new \RuntimeException( + 'Composer detected issues in your platform: ' . implode(' ', $issues) + ); +} diff --git a/vendor/smalot/pdfparser/LICENSE.txt b/vendor/smalot/pdfparser/LICENSE.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/vendor/smalot/pdfparser/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/smalot/pdfparser/README.md b/vendor/smalot/pdfparser/README.md new file mode 100644 index 0000000..febcf30 --- /dev/null +++ b/vendor/smalot/pdfparser/README.md @@ -0,0 +1,59 @@ +# PDF parser + +[![Version](https://poser.pugx.org/smalot/pdfparser/v)](//packagist.org/packages/smalot/pdfparser) +![CI](https://github.com/smalot/pdfparser/workflows/CI/badge.svg) +![CS](https://github.com/smalot/pdfparser/workflows/CS/badge.svg) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/smalot/pdfparser/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/smalot/pdfparser/?branch=master) +[![Downloads](https://poser.pugx.org/smalot/pdfparser/downloads)](//packagist.org/packages/smalot/pdfparser) + +The `smalot/pdfparser` is a standalone PHP package that provides various tools to extract data from PDF files. + +This library is under **active maintenance**. +There is no active development by the author of this library (at the moment), but we welcome any pull request adding/extending functionality! +See [CONTRIBUTING.md](./CONTRIBUTING.md) for further information about how to contribute. + +## Features + +- Load/parse objects and headers +- Extract metadata (author, description, ...) +- Extract text from ordered pages +- Support of compressed PDFs +- Support of MAC OS Roman charset encoding +- Handling of hexa and octal encoding in text sections +- Create custom configurations (see [CustomConfig.md](/doc/CustomConfig.md)). + +Currently, secured documents and extracting form data are not supported. + +## License + +This library is under the [LGPLv3 license](https://github.com/smalot/pdfparser/blob/master/LICENSE.txt). + +## Install + +This library requires PHP 7.1+ since [v1](https://github.com/smalot/pdfparser/releases/tag/v1.0.0). +You can install it via [Composer](https://getcomposer.org/): + +```bash +composer require smalot/pdfparser +``` + +In case you can't use Composer, you can include `alt_autoload.php-dist`. It will include all required files automatically. + +## Quick example + +```php +parseFile('/path/to/document.pdf'); + +$text = $pdf->getText(); +echo $text; +``` + +Further usage information can be found [here](/doc/Usage.md). + +## Documentation + +Documentation can be found in the [doc](/doc) folder. diff --git a/vendor/smalot/pdfparser/composer.json b/vendor/smalot/pdfparser/composer.json new file mode 100644 index 0000000..e9f425f --- /dev/null +++ b/vendor/smalot/pdfparser/composer.json @@ -0,0 +1,37 @@ +{ + "name": "smalot/pdfparser", + "description": "Pdf parser library. Can read and extract information from pdf file.", + "keywords": ["PDF", "text", "parser", "parse", "extract"], + "type": "library", + "license": "LGPL-3.0", + "authors": [ + { + "name": "Sebastien MALOT", + "email": "sebastien@malot.fr" + } + ], + "support": { + "issues": "https://github.com/smalot/pdfparser/issues" + }, + "homepage": "https://www.pdfparser.org", + "require": { + "php": ">=7.1", + "symfony/polyfill-mbstring": "^1.18", + "ext-zlib": "*", + "ext-iconv": "*" + }, + "autoload": { + "psr-0": { + "Smalot\\PdfParser\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PerformanceTests\\": "tests/Performance/", + "PHPUnitTests\\": "tests/PHPUnit/" + } + }, + "config": { + "process-timeout": 1200 + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Config.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Config.php new file mode 100644 index 0000000..e44b164 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Config.php @@ -0,0 +1,175 @@ + + * + * @date 2020-11-22 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +/** + * This class contains configurations used in various classes. You can override them + * manually, in case default values aren't working. + * + * @see https://github.com/smalot/pdfparser/issues/305 + */ +class Config +{ + private $fontSpaceLimit = -50; + + /** + * @var string + */ + private $horizontalOffset = ' '; + + /** + * Represents: (NUL, HT, LF, FF, CR, SP) + * + * @var string + */ + private $pdfWhitespaces = "\0\t\n\f\r "; + + /** + * Represents: (NUL, HT, LF, FF, CR, SP) + * + * @var string + */ + private $pdfWhitespacesRegex = '[\0\t\n\f\r ]'; + + /** + * Whether to retain raw image data as content or discard it to save memory + * + * @var bool + */ + private $retainImageContent = true; + + /** + * Memory limit to use when de-compressing files, in bytes. + * + * @var int + */ + private $decodeMemoryLimit = 0; + + /** + * Whether to include font id and size in dataTm array + * + * @var bool + */ + private $dataTmFontInfoHasToBeIncluded = false; + + /** + * Whether to attempt to read PDFs even if they are marked as encrypted. + * + * @var bool + */ + private $ignoreEncryption = false; + + public function getFontSpaceLimit() + { + return $this->fontSpaceLimit; + } + + public function setFontSpaceLimit($value) + { + $this->fontSpaceLimit = $value; + } + + public function getHorizontalOffset(): string + { + return $this->horizontalOffset; + } + + public function setHorizontalOffset($value): void + { + $this->horizontalOffset = $value; + } + + public function getPdfWhitespaces(): string + { + return $this->pdfWhitespaces; + } + + public function setPdfWhitespaces(string $pdfWhitespaces): void + { + $this->pdfWhitespaces = $pdfWhitespaces; + } + + public function getPdfWhitespacesRegex(): string + { + return $this->pdfWhitespacesRegex; + } + + public function setPdfWhitespacesRegex(string $pdfWhitespacesRegex): void + { + $this->pdfWhitespacesRegex = $pdfWhitespacesRegex; + } + + public function getRetainImageContent(): bool + { + return $this->retainImageContent; + } + + public function setRetainImageContent(bool $retainImageContent): void + { + $this->retainImageContent = $retainImageContent; + } + + public function getDecodeMemoryLimit(): int + { + return $this->decodeMemoryLimit; + } + + public function setDecodeMemoryLimit(int $decodeMemoryLimit): void + { + $this->decodeMemoryLimit = $decodeMemoryLimit; + } + + public function getDataTmFontInfoHasToBeIncluded(): bool + { + return $this->dataTmFontInfoHasToBeIncluded; + } + + public function setDataTmFontInfoHasToBeIncluded(bool $dataTmFontInfoHasToBeIncluded): void + { + $this->dataTmFontInfoHasToBeIncluded = $dataTmFontInfoHasToBeIncluded; + } + + public function getIgnoreEncryption(): bool + { + return $this->ignoreEncryption; + } + + /** + * @deprecated this is a temporary workaround, don't rely on it + * @see https://github.com/smalot/pdfparser/pull/653 + */ + public function setIgnoreEncryption(bool $ignoreEncryption): void + { + $this->ignoreEncryption = $ignoreEncryption; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Document.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Document.php new file mode 100644 index 0000000..1fad8b1 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Document.php @@ -0,0 +1,470 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Encoding\PDFDocEncoding; +use Smalot\PdfParser\Exception\MissingCatalogException; + +/** + * Technical references : + * - http://www.mactech.com/articles/mactech/Vol.15/15.09/PDFIntro/index.html + * - http://framework.zend.com/issues/secure/attachment/12512/Pdf.php + * - http://www.php.net/manual/en/ref.pdf.php#74211 + * - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin1Encoding.pm + * - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin9Encoding.pm + * - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/StandardEncoding.pm + * - http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/WinAnsiEncoding.pm + * + * Class Document + */ +class Document +{ + /** + * @var PDFObject[] + */ + protected $objects = []; + + /** + * @var array + */ + protected $dictionary = []; + + /** + * @var Header + */ + protected $trailer; + + /** + * @var array + */ + protected $metadata = []; + + /** + * @var array + */ + protected $details; + + public function __construct() + { + $this->trailer = new Header([], $this); + } + + public function init() + { + $this->buildDictionary(); + + $this->buildDetails(); + + // Propagate init to objects. + foreach ($this->objects as $object) { + $object->getHeader()->init(); + $object->init(); + } + } + + /** + * Build dictionary based on type header field. + */ + protected function buildDictionary() + { + // Build dictionary. + $this->dictionary = []; + + foreach ($this->objects as $id => $object) { + // Cache objects by type and subtype + $type = $object->getHeader()->get('Type')->getContent(); + + if (null != $type) { + if (!isset($this->dictionary[$type])) { + $this->dictionary[$type] = [ + 'all' => [], + 'subtype' => [], + ]; + } + + $this->dictionary[$type]['all'][$id] = $object; + + $subtype = $object->getHeader()->get('Subtype')->getContent(); + if (null != $subtype) { + if (!isset($this->dictionary[$type]['subtype'][$subtype])) { + $this->dictionary[$type]['subtype'][$subtype] = []; + } + $this->dictionary[$type]['subtype'][$subtype][$id] = $object; + } + } + } + } + + /** + * Build details array. + */ + protected function buildDetails() + { + // Build details array. + $details = []; + + // Extract document info + if ($this->trailer->has('Info')) { + /** @var PDFObject $info */ + $info = $this->trailer->get('Info'); + // This could be an ElementMissing object, so we need to check for + // the getHeader method first. + if (null !== $info && method_exists($info, 'getHeader')) { + $details = $info->getHeader()->getDetails(); + } + } + + // Retrieve the page count + try { + $pages = $this->getPages(); + $details['Pages'] = \count($pages); + } catch (\Exception $e) { + $details['Pages'] = 0; + } + + // Decode and repair encoded document properties + foreach ($details as $key => $value) { + if (\is_string($value)) { + // If the string is already UTF-8 encoded, that means we only + // need to repair Adobe's ham-fisted insertion of line-feeds + // every ~127 characters, which doesn't seem to be multi-byte + // safe + if (mb_check_encoding($value, 'UTF-8')) { + // Remove literal backslash + line-feed "\\r" + $value = str_replace("\x5c\x0d", '', $value); + + // Remove backslash plus bytes written into high part of + // multibyte unicode character + while (preg_match("/\x5c\x5c\xe0([\xb4-\xb8])(.)/", $value, $match)) { + $diff = (\ord($match[1]) - 182) * 64; + $newbyte = PDFDocEncoding::convertPDFDoc2UTF8(\chr(\ord($match[2]) + $diff)); + $value = preg_replace("/\x5c\x5c\xe0".$match[1].$match[2].'/', $newbyte, $value); + } + + // Remove bytes written into low part of multibyte unicode + // character + while (preg_match("/(.)\x9c\xe0([\xb3-\xb7])/", $value, $match)) { + $diff = \ord($match[2]) - 181; + $newbyte = \chr(\ord($match[1]) + $diff); + $value = preg_replace('/'.$match[1]."\x9c\xe0".$match[2].'/', $newbyte, $value); + } + + // Remove this byte string that Adobe occasionally adds + // between two single byte characters in a unicode string + $value = str_replace("\xe5\xb0\x8d", '', $value); + + $details[$key] = $value; + } else { + // If the string is just PDFDocEncoding, remove any line-feeds + // and decode the whole thing. + $value = str_replace("\\\r", '', $value); + $details[$key] = PDFDocEncoding::convertPDFDoc2UTF8($value); + } + } + } + + $details = array_merge($details, $this->metadata); + + $this->details = $details; + } + + /** + * Extract XMP Metadata + */ + public function extractXMPMetadata(string $content): void + { + $xml = xml_parser_create(); + xml_parser_set_option($xml, \XML_OPTION_SKIP_WHITE, 1); + + if (1 === xml_parse_into_struct($xml, $content, $values, $index)) { + /* + * short overview about the following code parts: + * + * The output of xml_parse_into_struct is a single dimensional array (= $values), and the $stack is a last-on, + * first-off array of pointers to positions in $metadata, while iterating through it, that potentially turn the + * results into a more intuitive multi-dimensional array. When an "open" XML tag is encountered, + * we save the current $metadata context in the $stack, then create a child array of $metadata and + * make that the current $metadata context. When a "close" XML tag is encountered, the operations are + * reversed: the most recently added $metadata context from $stack (IOW, the parent of the current + * element) is set as the current $metadata context. + */ + $metadata = []; + $stack = []; + foreach ($values as $val) { + // Standardize to lowercase + $val['tag'] = strtolower($val['tag']); + + // Ignore structural x: and rdf: XML elements + if (0 === strpos($val['tag'], 'x:')) { + continue; + } elseif (0 === strpos($val['tag'], 'rdf:') && 'rdf:li' != $val['tag']) { + continue; + } + + switch ($val['type']) { + case 'open': + // Create an array of list items + if ('rdf:li' == $val['tag']) { + $metadata[] = []; + + // Move up one level in the stack + $stack[\count($stack)] = &$metadata; + $metadata = &$metadata[\count($metadata) - 1]; + } else { + // Else create an array of named values + $metadata[$val['tag']] = []; + + // Move up one level in the stack + $stack[\count($stack)] = &$metadata; + $metadata = &$metadata[$val['tag']]; + } + break; + + case 'complete': + if (isset($val['value'])) { + // Assign a value to this list item + if ('rdf:li' == $val['tag']) { + $metadata[] = $val['value']; + + // Else assign a value to this property + } else { + $metadata[$val['tag']] = $val['value']; + } + } + break; + + case 'close': + // If the value of this property is an array + if (\is_array($metadata)) { + // If the value is a single element array + // where the element is of type string, use + // the value of the first list item as the + // value for this property + if (1 == \count($metadata) && isset($metadata[0]) && \is_string($metadata[0])) { + $metadata = $metadata[0]; + } elseif (0 == \count($metadata)) { + // if the value is an empty array, set + // the value of this property to the empty + // string + $metadata = ''; + } + } + + // Move down one level in the stack + $metadata = &$stack[\count($stack) - 1]; + unset($stack[\count($stack) - 1]); + break; + } + } + + // Only use this metadata if it's referring to a PDF + if (!isset($metadata['dc:format']) || 'application/pdf' == $metadata['dc:format']) { + // According to the XMP specifications: 'Conflict resolution + // for separate packets that describe the same resource is + // beyond the scope of this document.' - Section 6.1 + // Source: https://www.adobe.com/devnet/xmp.html + // Source: https://github.com/adobe/XMP-Toolkit-SDK/blob/main/docs/XMPSpecificationPart1.pdf + // So if there are multiple XMP blocks, just merge the values + // of each found block over top of the existing values + $this->metadata = array_merge($this->metadata, $metadata); + } + } + + // TODO: remove this if-clause and its content when dropping PHP 7 support + if (version_compare(PHP_VERSION, '8.0.0', '<')) { + // ref: https://www.php.net/manual/en/function.xml-parser-free.php + xml_parser_free($xml); + + // to avoid memory leaks; documentation said: + // > it was necessary to also explicitly unset the reference to parser to avoid memory leaks + unset($xml); + } + } + + public function getDictionary(): array + { + return $this->dictionary; + } + + /** + * @param PDFObject[] $objects + */ + public function setObjects($objects = []) + { + $this->objects = (array) $objects; + + $this->init(); + } + + /** + * @return PDFObject[] + */ + public function getObjects() + { + return $this->objects; + } + + /** + * @return PDFObject|Font|Page|Element|null + */ + public function getObjectById(string $id) + { + if (isset($this->objects[$id])) { + return $this->objects[$id]; + } + + return null; + } + + public function hasObjectsByType(string $type, ?string $subtype = null): bool + { + return 0 < \count($this->getObjectsByType($type, $subtype)); + } + + public function getObjectsByType(string $type, ?string $subtype = null): array + { + if (!isset($this->dictionary[$type])) { + return []; + } + + if (null != $subtype) { + if (!isset($this->dictionary[$type]['subtype'][$subtype])) { + return []; + } + + return $this->dictionary[$type]['subtype'][$subtype]; + } + + return $this->dictionary[$type]['all']; + } + + /** + * @return Font[] + */ + public function getFonts() + { + return $this->getObjectsByType('Font'); + } + + public function getFirstFont(): ?Font + { + $fonts = $this->getFonts(); + if ([] === $fonts) { + return null; + } + + return reset($fonts); + } + + /** + * @return Page[] + * + * @throws MissingCatalogException + */ + public function getPages() + { + if ($this->hasObjectsByType('Catalog')) { + // Search for catalog to list pages. + $catalogues = $this->getObjectsByType('Catalog'); + $catalogue = reset($catalogues); + + /** @var Pages $object */ + $object = $catalogue->get('Pages'); + if (method_exists($object, 'getPages')) { + return $object->getPages(true); + } + } + + if ($this->hasObjectsByType('Pages')) { + // Search for pages to list kids. + $pages = []; + + /** @var Pages[] $objects */ + $objects = $this->getObjectsByType('Pages'); + foreach ($objects as $object) { + $pages = array_merge($pages, $object->getPages(true)); + } + + return $pages; + } + + if ($this->hasObjectsByType('Page')) { + // Search for 'page' (unordered pages). + $pages = $this->getObjectsByType('Page'); + + return array_values($pages); + } + + throw new MissingCatalogException('Missing catalog.'); + } + + public function getText(?int $pageLimit = null): string + { + $texts = []; + $pages = $this->getPages(); + + // Only use the first X number of pages if $pageLimit is set and numeric. + if (\is_int($pageLimit) && 0 < $pageLimit) { + $pages = \array_slice($pages, 0, $pageLimit); + } + + foreach ($pages as $index => $page) { + /** + * In some cases, the $page variable may be null. + */ + if (null === $page) { + continue; + } + if ($text = trim($page->getText())) { + $texts[] = $text; + } + } + + return implode("\n\n", $texts); + } + + public function getTrailer(): Header + { + return $this->trailer; + } + + public function setTrailer(Header $trailer) + { + $this->trailer = $trailer; + } + + public function getDetails(): array + { + return $this->details; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element.php new file mode 100644 index 0000000..8066030 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element.php @@ -0,0 +1,156 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Element\ElementArray; +use Smalot\PdfParser\Element\ElementBoolean; +use Smalot\PdfParser\Element\ElementDate; +use Smalot\PdfParser\Element\ElementHexa; +use Smalot\PdfParser\Element\ElementName; +use Smalot\PdfParser\Element\ElementNull; +use Smalot\PdfParser\Element\ElementNumeric; +use Smalot\PdfParser\Element\ElementString; +use Smalot\PdfParser\Element\ElementStruct; +use Smalot\PdfParser\Element\ElementXRef; + +/** + * Class Element + */ +class Element +{ + /** + * @var Document|null + */ + protected $document; + + protected $value; + + public function __construct($value, ?Document $document = null) + { + $this->value = $value; + $this->document = $document; + } + + public function init() + { + } + + public function equals($value): bool + { + return $value == $this->value; + } + + public function contains($value): bool + { + if (\is_array($this->value)) { + /** @var Element $val */ + foreach ($this->value as $val) { + if ($val->equals($value)) { + return true; + } + } + + return false; + } + + return $this->equals($value); + } + + public function getContent() + { + return $this->value; + } + + public function __toString(): string + { + return (string) $this->value; + } + + public static function parse(string $content, ?Document $document = null, int &$position = 0) + { + $args = \func_get_args(); + $only_values = isset($args[3]) ? $args[3] : false; + $content = trim($content); + $values = []; + + do { + $old_position = $position; + + if (!$only_values) { + if (!preg_match('/\G\s*(?P\/[A-Z#0-9\._]+)(?P.*)/si', $content, $match, 0, $position)) { + break; + } else { + $name = preg_replace_callback( + '/#([0-9a-f]{2})/i', + function ($m) { + return \chr(base_convert($m[1], 16, 10)); + }, + ltrim($match['name'], '/') + ); + $value = $match['value']; + $position = strpos($content, $value, $position + \strlen($match['name'])); + } + } else { + $name = \count($values); + $value = substr($content, $position); + } + + if ($element = ElementName::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementXRef::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementNumeric::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementStruct::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementBoolean::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementNull::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementDate::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementString::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementHexa::parse($value, $document, $position)) { + $values[$name] = $element; + } elseif ($element = ElementArray::parse($value, $document, $position)) { + $values[$name] = $element; + } else { + $position = $old_position; + break; + } + } while ($position < \strlen($content)); + + return $values; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementArray.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementArray.php new file mode 100644 index 0000000..b54bf84 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementArray.php @@ -0,0 +1,139 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; +use Smalot\PdfParser\Header; +use Smalot\PdfParser\PDFObject; + +/** + * Class ElementArray + */ +class ElementArray extends Element +{ + public function __construct($value, ?Document $document = null) + { + parent::__construct($value, $document); + } + + public function getContent() + { + foreach ($this->value as $name => $element) { + $this->resolveXRef($name); + } + + return parent::getContent(); + } + + public function getRawContent(): array + { + return $this->value; + } + + public function getDetails(bool $deep = true): array + { + $values = []; + $elements = $this->getContent(); + + foreach ($elements as $key => $element) { + if ($element instanceof Header && $deep) { + $values[$key] = $element->getDetails($deep); + } elseif ($element instanceof PDFObject && $deep) { + $values[$key] = $element->getDetails(false); + } elseif ($element instanceof self) { + if ($deep) { + $values[$key] = $element->getDetails(); + } + } elseif ($element instanceof Element && !($element instanceof self)) { + $values[$key] = $element->getContent(); + } + } + + return $values; + } + + public function __toString(): string + { + return implode(',', $this->value); + } + + /** + * @return Element|PDFObject + */ + protected function resolveXRef(string $name) + { + if (($obj = $this->value[$name]) instanceof ElementXRef) { + /** @var ElementXRef $obj */ + $obj = $this->document->getObjectById($obj->getId()); + $this->value[$name] = $obj; + } + + return $this->value[$name]; + } + + /** + * @todo: These methods return mixed and mismatched types throughout the hierarchy + * + * @return bool|ElementArray + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*\[(?P.*)/is', $content, $match)) { + preg_match_all('/(.*?)(\[|\])/s', trim($content), $matches); + + $level = 0; + $sub = ''; + foreach ($matches[0] as $part) { + $sub .= $part; + $level += (false !== strpos($part, '[') ? 1 : -1); + if ($level <= 0) { + break; + } + } + + // Removes 1 level [ and ]. + $sub = substr(trim($sub), 1, -1); + $sub_offset = 0; + $values = Element::parse($sub, $document, $sub_offset, true); + + $offset += strpos($content, '[') + 1; + // Find next ']' position + $offset += \strlen($sub) + 1; + + return new self($values, $document); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementBoolean.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementBoolean.php new file mode 100644 index 0000000..55fb463 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementBoolean.php @@ -0,0 +1,75 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; + +/** + * Class ElementBoolean + */ +class ElementBoolean extends Element +{ + /** + * @param string|bool $value + */ + public function __construct($value) + { + parent::__construct('true' == strtolower($value) || true === $value, null); + } + + public function __toString(): string + { + return $this->value ? 'true' : 'false'; + } + + public function equals($value): bool + { + return $this->getContent() === $value; + } + + /** + * @return bool|ElementBoolean + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*(?Ptrue|false)/is', $content, $match)) { + $value = $match['value']; + $offset += strpos($content, $value) + \strlen($value); + + return new self($value); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementDate.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementDate.php new file mode 100644 index 0000000..f1f2df6 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementDate.php @@ -0,0 +1,139 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHPi, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; + +/** + * Class ElementDate + */ +class ElementDate extends ElementString +{ + /** + * @var array + */ + protected static $formats = [ + 4 => 'Y', + 6 => 'Ym', + 8 => 'Ymd', + 10 => 'YmdH', + 12 => 'YmdHi', + 14 => 'YmdHis', + 15 => 'YmdHise', + 17 => 'YmdHisO', + 18 => 'YmdHisO', + 19 => 'YmdHisO', + ]; + + /** + * @var string + */ + protected $format = 'c'; + + /** + * @var \DateTime + */ + protected $value; + + public function __construct($value) + { + if (!($value instanceof \DateTime)) { + throw new \Exception('DateTime required.'); // FIXME: Sometimes strings are passed to this function + } + + parent::__construct($value); + } + + public function setFormat(string $format) + { + $this->format = $format; + } + + public function equals($value): bool + { + if ($value instanceof \DateTime) { + $timestamp = $value->getTimeStamp(); + } else { + $timestamp = strtotime($value); + } + + return $timestamp == $this->value->getTimeStamp(); + } + + public function __toString(): string + { + return (string) $this->value->format($this->format); + } + + /** + * @return bool|ElementDate + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*\(D\:(?P.*?)\)/s', $content, $match)) { + $name = $match['name']; + $name = str_replace("'", '', $name); + $date = false; + + // Smallest format : Y + // Full format : YmdHisP + if (preg_match('/^\d{4}(\d{2}(\d{2}(\d{2}(\d{2}(\d{2}(Z(\d{2,4})?|[\+-]?\d{2}(\d{2})?)?)?)?)?)?)?$/', $name)) { + if ($pos = strpos($name, 'Z')) { + $name = substr($name, 0, $pos + 1); + } elseif (18 == \strlen($name) && preg_match('/[^\+-]0000$/', $name)) { + $name = substr($name, 0, -4).'+0000'; + } + + $format = self::$formats[\strlen($name)]; + $date = \DateTime::createFromFormat($format, $name, new \DateTimeZone('UTC')); + } else { + // special cases + if (preg_match('/^\d{1,2}-\d{1,2}-\d{4},?\s+\d{2}:\d{2}:\d{2}[\+-]\d{4}$/', $name)) { + $name = str_replace(',', '', $name); + $format = 'n-j-Y H:i:sO'; + $date = \DateTime::createFromFormat($format, $name, new \DateTimeZone('UTC')); + } + } + + if (!$date) { + return false; + } + + $offset += strpos($content, '(D:') + \strlen($match['name']) + 4; // 1 for '(D:' and ')' + + return new self($date); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementHexa.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementHexa.php new file mode 100644 index 0000000..3fc3413 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementHexa.php @@ -0,0 +1,91 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; + +/** + * Class ElementHexa + */ +class ElementHexa extends ElementString +{ + /** + * @return bool|ElementHexa|ElementDate + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*\<(?P[A-F0-9]+)\>/is', $content, $match)) { + $name = $match['name']; + $offset += strpos($content, '<'.$name) + \strlen($name) + 2; // 1 for '>' + // repackage string as standard + $name = '('.self::decode($name).')'; + $element = ElementDate::parse($name, $document); + + if (!$element) { + $element = ElementString::parse($name, $document); + } + + return $element; + } + + return false; + } + + public static function decode(string $value): string + { + $text = ''; + + // Filter $value of non-hexadecimal characters + $value = (string) preg_replace('/[^0-9a-f]/i', '', $value); + + // Check for leading zeros (4-byte hexadecimal indicator), or + // the BE BOM + if ('00' === substr($value, 0, 2) || 'feff' === strtolower(substr($value, 0, 4))) { + $value = (string) preg_replace('/^feff/i', '', $value); + for ($i = 0, $length = \strlen($value); $i < $length; $i += 4) { + $hex = substr($value, $i, 4); + $text .= '&#'.str_pad(hexdec($hex), 4, '0', \STR_PAD_LEFT).';'; + } + } else { + // Otherwise decode this as 2-byte hexadecimal + for ($i = 0, $length = \strlen($value); $i < $length; $i += 2) { + $hex = substr($value, $i, 2); + $text .= \chr(hexdec($hex)); + } + } + + $text = html_entity_decode($text, \ENT_NOQUOTES, 'UTF-8'); + + return $text; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementMissing.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementMissing.php new file mode 100644 index 0000000..d2fc000 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementMissing.php @@ -0,0 +1,66 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Element; + +/** + * Class ElementMissing + */ +class ElementMissing extends Element +{ + public function __construct() + { + parent::__construct(null, null); + } + + public function equals($value): bool + { + return false; + } + + public function contains($value): bool + { + return false; + } + + public function getContent(): bool + { + return false; + } + + public function __toString(): string + { + return ''; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementName.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementName.php new file mode 100644 index 0000000..6e8d97a --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementName.php @@ -0,0 +1,69 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; +use Smalot\PdfParser\Font; + +/** + * Class ElementName + */ +class ElementName extends Element +{ + public function __construct(string $value) + { + parent::__construct($value, null); + } + + public function equals($value): bool + { + return $value == $this->value; + } + + /** + * @return bool|ElementName + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*\/([A-Z0-9\-\+,#\.]+)/is', $content, $match)) { + $name = $match[1]; + $offset += strpos($content, $name) + \strlen($name); + $name = Font::decodeEntities($name); + + return new self($name); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementNull.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementNull.php new file mode 100644 index 0000000..9af8843 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementNull.php @@ -0,0 +1,71 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; + +/** + * Class ElementNull + */ +class ElementNull extends Element +{ + public function __construct() + { + parent::__construct(null, null); + } + + public function __toString(): string + { + return 'null'; + } + + public function equals($value): bool + { + return $this->getContent() === $value; + } + + /** + * @return bool|ElementNull + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*(null)/s', $content, $match)) { + $offset += strpos($content, 'null') + \strlen('null'); + + return new self(); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementNumeric.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementNumeric.php new file mode 100644 index 0000000..5454acc --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementNumeric.php @@ -0,0 +1,62 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; + +/** + * Class ElementNumeric + */ +class ElementNumeric extends Element +{ + public function __construct(string $value) + { + parent::__construct((float) $value, null); + } + + /** + * @return bool|ElementNumeric + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*(?P\-?[0-9\.]+)/s', $content, $match)) { + $value = $match['value']; + $offset += strpos($content, $value) + \strlen($value); + + return new self($value); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementString.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementString.php new file mode 100644 index 0000000..011bcf4 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementString.php @@ -0,0 +1,93 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; +use Smalot\PdfParser\Font; + +/** + * Class ElementString + */ +class ElementString extends Element +{ + public function __construct($value) + { + parent::__construct($value, null); + } + + public function equals($value): bool + { + return $value == $this->value; + } + + /** + * @return bool|ElementString + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*\((?P.*)/s', $content, $match)) { + $name = $match['name']; + + // Find next ')' not escaped. + $cur_start_text = $start_search_end = 0; + while (false !== ($cur_start_pos = strpos($name, ')', $start_search_end))) { + $cur_extract = substr($name, $cur_start_text, $cur_start_pos - $cur_start_text); + preg_match('/(?P[\\\]*)$/s', $cur_extract, $match); + if (!(\strlen($match['escape']) % 2)) { + break; + } + $start_search_end = $cur_start_pos + 1; + } + + // Extract string. + $name = substr($name, 0, (int) $cur_start_pos); + $offset += strpos($content, '(') + $cur_start_pos + 2; // 2 for '(' and ')' + $name = str_replace( + ['\\\\', '\\ ', '\\/', '\(', '\)', '\n', '\r', '\t'], + ['\\', ' ', '/', '(', ')', "\n", "\r", "\t"], + $name + ); + + // Decode string. + $name = Font::decodeOctal($name); + $name = Font::decodeEntities($name); + $name = Font::decodeHexadecimal($name, false); + $name = Font::decodeUnicode($name); + + return new self($name); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementStruct.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementStruct.php new file mode 100644 index 0000000..c37b6da --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementStruct.php @@ -0,0 +1,75 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; +use Smalot\PdfParser\Header; + +/** + * Class ElementStruct + */ +class ElementStruct extends Element +{ + /** + * @return false|Header + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*<<(?P.*)/is', $content)) { + preg_match_all('/(.*?)(<<|>>)/s', trim($content), $matches); + + $level = 0; + $sub = ''; + foreach ($matches[0] as $part) { + $sub .= $part; + $level += (false !== strpos($part, '<<') ? 1 : -1); + if ($level <= 0) { + break; + } + } + + $offset += strpos($content, '<<') + \strlen(rtrim($sub)); + + // Removes '<<' and '>>'. + $sub = trim((string) preg_replace('/^\s*<<(.*)>>\s*$/s', '\\1', $sub)); + + $position = 0; + $elements = Element::parse($sub, $document, $position); + + return new Header($elements, $document); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementXRef.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementXRef.php new file mode 100644 index 0000000..ebba71a --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Element/ElementXRef.php @@ -0,0 +1,98 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Element; + +use Smalot\PdfParser\Document; +use Smalot\PdfParser\Element; + +/** + * Class ElementXRef + */ +class ElementXRef extends Element +{ + public function getId(): string + { + return $this->getContent(); + } + + public function getObject() + { + return $this->document->getObjectById($this->getId()); + } + + public function equals($value): bool + { + /** + * In case $value is a number and $this->value is a string like 5_0 + * + * Without this if-clause code like: + * + * $element = new ElementXRef('5_0'); + * $this->assertTrue($element->equals(5)); + * + * would fail (= 5_0 and 5 are not equal in PHP 8.0+). + */ + if ( + true === is_numeric($value) + && true === \is_string($this->getContent()) + && 1 === preg_match('/[0-9]+\_[0-9]+/', $this->getContent(), $matches) + ) { + return (float) $this->getContent() == $value; + } + + $id = ($value instanceof self) ? $value->getId() : $value; + + return $this->getId() == $id; + } + + public function __toString(): string + { + return '#Obj#'.$this->getId(); + } + + /** + * @return bool|ElementXRef + */ + public static function parse(string $content, ?Document $document = null, int &$offset = 0) + { + if (preg_match('/^\s*(?P[0-9]+\s+[0-9]+\s+R)/s', $content, $match)) { + $id = $match['id']; + $offset += strpos($content, $id) + \strlen($id); + $id = str_replace(' ', '_', rtrim($id, ' R')); + + return new self($id, $document); + } + + return false; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding.php new file mode 100644 index 0000000..511411b --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding.php @@ -0,0 +1,162 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Element\ElementNumeric; +use Smalot\PdfParser\Encoding\EncodingLocator; +use Smalot\PdfParser\Encoding\PostScriptGlyphs; +use Smalot\PdfParser\Exception\EncodingNotFoundException; + +/** + * Class Encoding + */ +class Encoding extends PDFObject +{ + /** + * @var array + */ + protected $encoding; + + /** + * @var array + */ + protected $differences; + + /** + * @var array + */ + protected $mapping; + + public function init() + { + $this->mapping = []; + $this->differences = []; + $this->encoding = []; + + if ($this->has('BaseEncoding')) { + $this->encoding = EncodingLocator::getEncoding($this->getEncodingClass())->getTranslations(); + + // Build table including differences. + $differences = $this->get('Differences')->getContent(); + $code = 0; + + if (!\is_array($differences)) { + return; + } + + foreach ($differences as $difference) { + /** @var ElementNumeric $difference */ + if ($difference instanceof ElementNumeric) { + $code = $difference->getContent(); + continue; + } + + // ElementName + $this->differences[$code] = $difference; + if (\is_object($difference)) { + $this->differences[$code] = $difference->getContent(); + } + + // For the next char. + ++$code; + } + + $this->mapping = $this->encoding; + foreach ($this->differences as $code => $difference) { + /* @var string $difference */ + $this->mapping[$code] = $difference; + } + } + } + + public function getDetails(bool $deep = true): array + { + $details = []; + + $details['BaseEncoding'] = ($this->has('BaseEncoding') ? (string) $this->get('BaseEncoding') : 'Ansi'); + $details['Differences'] = ($this->has('Differences') ? (string) $this->get('Differences') : ''); + + $details += parent::getDetails($deep); + + return $details; + } + + public function translateChar($dec): ?int + { + if (isset($this->mapping[$dec])) { + $dec = $this->mapping[$dec]; + } + + return PostScriptGlyphs::getCodePoint($dec); + } + + /** + * Returns encoding class name if available or empty string (only prior PHP 7.4). + * + * @throws \Exception On PHP 7.4+ an exception is thrown if encoding class doesn't exist. + */ + public function __toString(): string + { + try { + return $this->getEncodingClass(); + } catch (\Exception $e) { + // prior to PHP 7.4 toString has to return an empty string. + if (version_compare(\PHP_VERSION, '7.4.0', '<')) { + return ''; + } + throw $e; + } + } + + /** + * @throws EncodingNotFoundException + */ + protected function getEncodingClass(): string + { + // Load reference table charset. + $baseEncoding = preg_replace('/[^A-Z0-9]/is', '', $this->get('BaseEncoding')->getContent()); + + // Check for empty BaseEncoding field value + if (!\is_string($baseEncoding) || 0 == \strlen($baseEncoding)) { + $baseEncoding = 'StandardEncoding'; + } + + $className = '\\Smalot\\PdfParser\\Encoding\\'.$baseEncoding; + + if (!class_exists($className)) { + throw new EncodingNotFoundException('Missing encoding data for: "'.$baseEncoding.'".'); + } + + return $className; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/AbstractEncoding.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/AbstractEncoding.php new file mode 100644 index 0000000..aea9c02 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/AbstractEncoding.php @@ -0,0 +1,8 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin1Encoding.pm + +namespace Smalot\PdfParser\Encoding; + +/** + * Class ISOLatin1Encoding + */ +class ISOLatin1Encoding extends AbstractEncoding +{ + public function getTranslations(): array + { + $encoding = + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + 'space exclam quotedbl numbersign dollar percent ampersand quoteright '. + 'parenleft parenright asterisk plus comma minus period slash zero one '. + 'two three four five six seven eight nine colon semicolon less equal '. + 'greater question at A B C D E F G H I J K L M N O P Q R S T U V W X '. + 'Y Z bracketleft backslash bracketright asciicircum underscore '. + 'quoteleft a b c d e f g h i j k l m n o p q r s t u v w x y z '. + 'braceleft bar braceright asciitilde .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef dotlessi grave acute '. + 'circumflex tilde macron breve dotaccent dieresis .notdef ring '. + 'cedilla .notdef hungarumlaut ogonek caron space exclamdown cent '. + 'sterling currency yen brokenbar section dieresis copyright '. + 'ordfeminine guillemotleft logicalnot hyphen registered macron degree '. + 'plusminus twosuperior threesuperior acute mu paragraph '. + 'periodcentered cedilla onesuperior ordmasculine guillemotright '. + 'onequarter onehalf threequarters questiondown Agrave Aacute '. + 'Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute '. + 'Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde '. + 'Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave '. + 'Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute '. + 'acircumflex atilde adieresis aring ae ccedilla egrave eacute '. + 'ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde '. + 'ograve oacute ocircumflex otilde odieresis divide oslash ugrave '. + 'uacute ucircumflex udieresis yacute thorn ydieresis'; + + return explode(' ', $encoding); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/ISOLatin9Encoding.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/ISOLatin9Encoding.php new file mode 100644 index 0000000..616a0f5 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/ISOLatin9Encoding.php @@ -0,0 +1,76 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/ISOLatin9Encoding.pm + +namespace Smalot\PdfParser\Encoding; + +/** + * Class ISOLatin9Encoding + */ +class ISOLatin9Encoding extends AbstractEncoding +{ + public function getTranslations(): array + { + $encoding = + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + 'space exclam quotedbl numbersign dollar percent ampersand quoteright '. + 'parenleft parenright asterisk plus comma minus period slash zero one '. + 'two three four five six seven eight nine colon semicolon less equal '. + 'greater question at A B C D E F G H I J K L M N O P Q R S T U V W X '. + 'Y Z bracketleft backslash bracketright asciicircum underscore '. + 'quoteleft a b c d e f g h i j k l m n o p q r s t u v w x y z '. + 'braceleft bar braceright asciitilde .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef dotlessi grave acute '. + 'circumflex tilde macron breve dotaccent dieresis .notdef ring '. + 'cedilla .notdef hungarumlaut ogonek caron space exclamdown cent '. + 'sterling Euro yen Scaron section scaron copyright '. + 'ordfeminine guillemotleft logicalnot hyphen registered macron degree '. + 'plusminus twosuperior threesuperior Zcaron mu paragraph '. + 'periodcentered zcaron onesuperior ordmasculine guillemotright '. + 'OE oe Ydieresis questiondown Agrave Aacute '. + 'Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute '. + 'Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde '. + 'Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave '. + 'Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute '. + 'acircumflex atilde adieresis aring ae ccedilla egrave eacute '. + 'ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde '. + 'ograve oacute ocircumflex otilde odieresis divide oslash ugrave '. + 'uacute ucircumflex udieresis yacute thorn ydieresis'; + + return explode(' ', $encoding); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/MacRomanEncoding.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/MacRomanEncoding.php new file mode 100644 index 0000000..c47131c --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/MacRomanEncoding.php @@ -0,0 +1,80 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +// Source : http://www.opensource.apple.com/source/vim/vim-34/vim/runtime/print/mac-roman.ps + +namespace Smalot\PdfParser\Encoding; + +/** + * Class MacRomanEncoding + */ +class MacRomanEncoding extends AbstractEncoding +{ + public function getTranslations(): array + { + $encoding = + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + 'space exclam quotedbl numbersign dollar percent ampersand quotesingle '. + 'parenleft parenright asterisk plus comma minus period slash '. + 'zero one two three four five six seven '. + 'eight nine colon semicolon less equal greater question '. + 'at A B C D E F G '. + 'H I J K L M N O '. + 'P Q R S T U V W '. + 'X Y Z bracketleft backslash bracketright asciicircum underscore '. + 'grave a b c d e f g '. + 'h i j k l m n o '. + 'p q r s t u v w '. + 'x y z braceleft bar braceright asciitilde .notdef '. + 'Adieresis Aring Ccedilla Eacute Ntilde Odieresis Udieresis aacute '. + 'agrave acircumflex adieresis atilde aring ccedilla eacute egrave '. + 'ecircumflex edieresis iacute igrave icircumflex idieresis ntilde oacute '. + 'ograve ocircumflex odieresis otilde uacute ugrave ucircumflex udieresis '. + 'dagger degree cent sterling section bullet paragraph germandbls '. + 'registered copyright trademark acute dieresis notequal AE Oslash '. + 'infinity plusminus lessequal greaterequal yen mu partialdiff summation '. + 'Pi pi integral ordfeminine ordmasculine Omega ae oslash '. + 'questiondown exclamdown logicalnot radical florin approxequal delta guillemotleft '. + 'guillemotright ellipsis space Agrave Atilde Otilde OE oe '. + 'endash emdash quotedblleft quotedblright quoteleft quoteright divide lozenge '. + 'ydieresis Ydieresis fraction currency guilsinglleft guilsinglright fi fl '. + 'daggerdbl periodcentered quotesinglbase quotedblbase perthousand Acircumflex Ecircumflex Aacute '. + 'Edieresis Egrave Iacute Icircumflex Idieresis Igrave Oacute Ocircumflex '. + 'heart Ograve Uacute Ucircumflex Ugrave dotlessi circumflex tilde '. + 'macron breve dotaccent ring cedilla hungarumlaut ogonek caron'; + + return explode(' ', $encoding); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/PDFDocEncoding.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/PDFDocEncoding.php new file mode 100644 index 0000000..70bc48c --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/PDFDocEncoding.php @@ -0,0 +1,189 @@ + + * + * @date 2023-06-28 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +// Source : https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.2.pdf +// Source : https://ia801001.us.archive.org/1/items/pdf1.7/pdf_reference_1-7.pdf + +namespace Smalot\PdfParser\Encoding; + +/** + * Class PDFDocEncoding + */ +class PDFDocEncoding +{ + public static function getCodePage(): array + { + return [ + "\x18" => "\u{02d8}", // breve + "\x19" => "\u{02c7}", // caron + "\x1a" => "\u{02c6}", // circumflex + "\x1b" => "\u{02d9}", // dotaccent + "\x1c" => "\u{02dd}", // hungarumlaut + "\x1d" => "\u{02db}", // ogonek + "\x1e" => "\u{02de}", // ring + "\x1f" => "\u{02dc}", // tilde + "\x7f" => '', + "\x80" => "\u{2022}", // bullet + "\x81" => "\u{2020}", // dagger + "\x82" => "\u{2021}", // daggerdbl + "\x83" => "\u{2026}", // ellipsis + "\x84" => "\u{2014}", // emdash + "\x85" => "\u{2013}", // endash + "\x86" => "\u{0192}", // florin + "\x87" => "\u{2044}", // fraction + "\x88" => "\u{2039}", // guilsinglleft + "\x89" => "\u{203a}", // guilsinglright + "\x8a" => "\u{2212}", // minus + "\x8b" => "\u{2030}", // perthousand + "\x8c" => "\u{201e}", // quotedblbase + "\x8d" => "\u{201c}", // quotedblleft + "\x8e" => "\u{201d}", // quotedblright + "\x8f" => "\u{2018}", // quoteleft + "\x90" => "\u{2019}", // quoteright + "\x91" => "\u{201a}", // quotesinglbase + "\x92" => "\u{2122}", // trademark + "\x93" => "\u{fb01}", // fi + "\x94" => "\u{fb02}", // fl + "\x95" => "\u{0141}", // Lslash + "\x96" => "\u{0152}", // OE + "\x97" => "\u{0160}", // Scaron + "\x98" => "\u{0178}", // Ydieresis + "\x99" => "\u{017d}", // Zcaron + "\x9a" => "\u{0131}", // dotlessi + "\x9b" => "\u{0142}", // lslash + "\x9c" => "\u{0153}", // oe + "\x9d" => "\u{0161}", // scaron + "\x9e" => "\u{017e}", // zcaron + "\x9f" => '', + "\xa0" => "\u{20ac}", // Euro + "\xa1" => "\u{00a1}", // exclamdown + "\xa2" => "\u{00a2}", // cent + "\xa3" => "\u{00a3}", // sterling + "\xa4" => "\u{00a4}", // currency + "\xa5" => "\u{00a5}", // yen + "\xa6" => "\u{00a6}", // brokenbar + "\xa7" => "\u{00a7}", // section + "\xa8" => "\u{00a8}", // dieresis + "\xa9" => "\u{00a9}", // copyright + "\xaa" => "\u{00aa}", // ordfeminine + "\xab" => "\u{00ab}", // guillemotleft + "\xac" => "\u{00ac}", // logicalnot + "\xad" => '', + "\xae" => "\u{00ae}", // registered + "\xaf" => "\u{00af}", // macron + "\xb0" => "\u{00b0}", // degree + "\xb1" => "\u{00b1}", // plusminus + "\xb2" => "\u{00b2}", // twosuperior + "\xb3" => "\u{00b3}", // threesuperior + "\xb4" => "\u{00b4}", // acute + "\xb5" => "\u{00b5}", // mu + "\xb6" => "\u{00b6}", // paragraph + "\xb7" => "\u{00b7}", // periodcentered + "\xb8" => "\u{00b8}", // cedilla + "\xb9" => "\u{00b9}", // onesuperior + "\xba" => "\u{00ba}", // ordmasculine + "\xbb" => "\u{00bb}", // guillemotright + "\xbc" => "\u{00bc}", // onequarter + "\xbd" => "\u{00bd}", // onehalf + "\xbe" => "\u{00be}", // threequarters + "\xbf" => "\u{00bf}", // questiondown + "\xc0" => "\u{00c0}", // Agrave + "\xc1" => "\u{00c1}", // Aacute + "\xc2" => "\u{00c2}", // Acircumflex + "\xc3" => "\u{00c3}", // Atilde + "\xc4" => "\u{00c4}", // Adieresis + "\xc5" => "\u{00c5}", // Aring + "\xc6" => "\u{00c6}", // AE + "\xc7" => "\u{00c7}", // Ccedill + "\xc8" => "\u{00c8}", // Egrave + "\xc9" => "\u{00c9}", // Eacute + "\xca" => "\u{00ca}", // Ecircumflex + "\xcb" => "\u{00cb}", // Edieresis + "\xcc" => "\u{00cc}", // Igrave + "\xcd" => "\u{00cd}", // Iacute + "\xce" => "\u{00ce}", // Icircumflex + "\xcf" => "\u{00cf}", // Idieresis + "\xd0" => "\u{00d0}", // Eth + "\xd1" => "\u{00d1}", // Ntilde + "\xd2" => "\u{00d2}", // Ograve + "\xd3" => "\u{00d3}", // Oacute + "\xd4" => "\u{00d4}", // Ocircumflex + "\xd5" => "\u{00d5}", // Otilde + "\xd6" => "\u{00d6}", // Odieresis + "\xd7" => "\u{00d7}", // multiply + "\xd8" => "\u{00d8}", // Oslash + "\xd9" => "\u{00d9}", // Ugrave + "\xda" => "\u{00da}", // Uacute + "\xdb" => "\u{00db}", // Ucircumflex + "\xdc" => "\u{00dc}", // Udieresis + "\xdd" => "\u{00dd}", // Yacute + "\xde" => "\u{00de}", // Thorn + "\xdf" => "\u{00df}", // germandbls + "\xe0" => "\u{00e0}", // agrave + "\xe1" => "\u{00e1}", // aacute + "\xe2" => "\u{00e2}", // acircumflex + "\xe3" => "\u{00e3}", // atilde + "\xe4" => "\u{00e4}", // adieresis + "\xe5" => "\u{00e5}", // aring + "\xe6" => "\u{00e6}", // ae + "\xe7" => "\u{00e7}", // ccedilla + "\xe8" => "\u{00e8}", // egrave + "\xe9" => "\u{00e9}", // eacute + "\xea" => "\u{00ea}", // ecircumflex + "\xeb" => "\u{00eb}", // edieresis + "\xec" => "\u{00ec}", // igrave + "\xed" => "\u{00ed}", // iacute + "\xee" => "\u{00ee}", // icircumflex + "\xef" => "\u{00ef}", // idieresis + "\xf0" => "\u{00f0}", // eth + "\xf1" => "\u{00f1}", // ntilde + "\xf2" => "\u{00f2}", // ograve + "\xf3" => "\u{00f3}", // oacute + "\xf4" => "\u{00f4}", // ocircumflex + "\xf5" => "\u{00f5}", // otilde + "\xf6" => "\u{00f6}", // odieresis + "\xf7" => "\u{00f7}", // divide + "\xf8" => "\u{00f8}", // oslash + "\xf9" => "\u{00f9}", // ugrave + "\xfa" => "\u{00fa}", // uacute + "\xfb" => "\u{00fb}", // ucircumflex + "\xfc" => "\u{00fc}", // udieresis + "\xfd" => "\u{00fd}", // yacute + "\xfe" => "\u{00fe}", // thorn + "\xff" => "\u{00ff}", // ydieresis + ]; + } + + public static function convertPDFDoc2UTF8(string $content): string + { + return strtr($content, static::getCodePage()); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/PostScriptGlyphs.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/PostScriptGlyphs.php new file mode 100644 index 0000000..fbe1af4 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/PostScriptGlyphs.php @@ -0,0 +1,1099 @@ + + * + * @date 2019-09-17 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Encoding; + +/** + * Class PostScriptGlyphs + */ +class PostScriptGlyphs +{ + /** + * The mapping tables have been converted from https://github.com/OpenPrinting/cups-filters/blob/master/fontembed/aglfn13.c, + * part of the OpenPrinting/cups-filters package, which itself is licensed under the MIT license and lists this specific code part as: + * Copyright 2008,2012 Tobias Hoffmann under the Expat license (https://www.gnu.org/licenses/license-list.html#Expat) + */ + public static function getGlyphs(): array + { + return [ + 'space' => '0x00a0', + 'exclam' => '0x0021', + 'quotedbl' => '0x0022', + 'numbersign' => '0x0023', + 'dollar' => '0x0024', + 'percent' => '0x0025', + 'ampersand' => '0x0026', + 'quotesingle' => '0x0027', + 'parenleft' => '0x0028', + 'parenright' => '0x0029', + 'asterisk' => '0x002a', + 'plus' => '0x002b', + 'comma' => '0x002c', + 'hyphen' => '0x002d', + 'period' => '0x002e', + 'slash' => '0x002f', + 'zero' => '0x0030', + 'one' => '0x0031', + 'two' => '0x0032', + 'three' => '0x0033', + 'four' => '0x0034', + 'five' => '0x0035', + 'six' => '0x0036', + 'seven' => '0x0037', + 'eight' => '0x0038', + 'nine' => '0x0039', + 'colon' => '0x003a', + 'semicolon' => '0x003b', + 'less' => '0x003c', + 'equal' => '0x003d', + 'greater' => '0x003e', + 'question' => '0x003f', + 'at' => '0x0040', + 'A' => '0x0041', + 'B' => '0x0042', + 'C' => '0x0043', + 'D' => '0x0044', + 'E' => '0x0045', + 'F' => '0x0046', + 'G' => '0x0047', + 'H' => '0x0048', + 'I' => '0x0049', + 'J' => '0x004a', + 'K' => '0x004b', + 'L' => '0x004c', + 'M' => '0x004d', + 'N' => '0x004e', + 'O' => '0x004f', + 'P' => '0x0050', + 'Q' => '0x0051', + 'R' => '0x0052', + 'S' => '0x0053', + 'T' => '0x0054', + 'U' => '0x0055', + 'V' => '0x0056', + 'W' => '0x0057', + 'X' => '0x0058', + 'Y' => '0x0059', + 'Z' => '0x005a', + 'bracketleft' => '0x005b', + 'backslash' => '0x005c', + 'bracketright' => '0x005d', + 'asciicircum' => '0x005e', + 'underscore' => '0x005f', + 'grave' => '0x0060', + 'a' => '0x0061', + 'b' => '0x0062', + 'c' => '0x0063', + 'd' => '0x0064', + 'e' => '0x0065', + 'f' => '0x0066', + 'g' => '0x0067', + 'h' => '0x0068', + 'i' => '0x0069', + 'j' => '0x006a', + 'k' => '0x006b', + 'l' => '0x006c', + 'm' => '0x006d', + 'n' => '0x006e', + 'o' => '0x006f', + 'p' => '0x0070', + 'q' => '0x0071', + 'r' => '0x0072', + 's' => '0x0073', + 't' => '0x0074', + 'u' => '0x0075', + 'v' => '0x0076', + 'w' => '0x0077', + 'x' => '0x0078', + 'y' => '0x0079', + 'z' => '0x007a', + 'braceleft' => '0x007b', + 'bar' => '0x007c', + 'braceright' => '0x007d', + 'asciitilde' => '0x007e', + 'exclamdown' => '0x00a1', + 'cent' => '0x00a2', + 'sterling' => '0x00a3', + 'currency' => '0x00a4', + 'yen' => '0x00a5', + 'brokenbar' => '0x00a6', + 'section' => '0x00a7', + 'dieresis' => '0x00a8', + 'copyright' => '0x00a9', + 'ordfeminine' => '0x00aa', + 'guillemotleft' => '0x00ab', + 'logicalnot' => '0x00ac', + 'minus' => '0x2212', + 'registered' => '0x00ae', + 'macron' => '0x02c9', + 'degree' => '0x00b0', + 'plusminus' => '0x00b1', + 'twosuperior' => '0x00b2', + 'threesuperior' => '0x00b3', + 'acute' => '0x00b4', + 'mu' => '0x03bc', + 'paragraph' => '0x00b6', + 'periodcentered' => '0x2219', + 'cedilla' => '0x00b8', + 'onesuperior' => '0x00b9', + 'ordmasculine' => '0x00ba', + 'guillemotright' => '0x00bb', + 'onequarter' => '0x00bc', + 'onehalf' => '0x00bd', + 'threequarters' => '0x00be', + 'questiondown' => '0x00bf', + 'Agrave' => '0x00c0', + 'Aacute' => '0x00c1', + 'Acircumflex' => '0x00c2', + 'Atilde' => '0x00c3', + 'Adieresis' => '0x00c4', + 'Aring' => '0x00c5', + 'AE' => '0x00c6', + 'Ccedilla' => '0x00c7', + 'Egrave' => '0x00c8', + 'Eacute' => '0x00c9', + 'Ecircumflex' => '0x00ca', + 'Edieresis' => '0x00cb', + 'Igrave' => '0x00cc', + 'Iacute' => '0x00cd', + 'Icircumflex' => '0x00ce', + 'Idieresis' => '0x00cf', + 'Eth' => '0x00d0', + 'Ntilde' => '0x00d1', + 'Ograve' => '0x00d2', + 'Oacute' => '0x00d3', + 'Ocircumflex' => '0x00d4', + 'Otilde' => '0x00d5', + 'Odieresis' => '0x00d6', + 'multiply' => '0x00d7', + 'Oslash' => '0x00d8', + 'Ugrave' => '0x00d9', + 'Uacute' => '0x00da', + 'Ucircumflex' => '0x00db', + 'Udieresis' => '0x00dc', + 'Yacute' => '0x00dd', + 'Thorn' => '0x00de', + 'germandbls' => '0x00df', + 'agrave' => '0x00e0', + 'aacute' => '0x00e1', + 'acircumflex' => '0x00e2', + 'atilde' => '0x00e3', + 'adieresis' => '0x00e4', + 'aring' => '0x00e5', + 'ae' => '0x00e6', + 'ccedilla' => '0x00e7', + 'egrave' => '0x00e8', + 'eacute' => '0x00e9', + 'ecircumflex' => '0x00ea', + 'edieresis' => '0x00eb', + 'igrave' => '0x00ec', + 'iacute' => '0x00ed', + 'icircumflex' => '0x00ee', + 'idieresis' => '0x00ef', + 'eth' => '0x00f0', + 'ntilde' => '0x00f1', + 'ograve' => '0x00f2', + 'oacute' => '0x00f3', + 'ocircumflex' => '0x00f4', + 'otilde' => '0x00f5', + 'odieresis' => '0x00f6', + 'divide' => '0x00f7', + 'oslash' => '0x00f8', + 'ugrave' => '0x00f9', + 'uacute' => '0x00fa', + 'ucircumflex' => '0x00fb', + 'udieresis' => '0x00fc', + 'yacute' => '0x00fd', + 'thorn' => '0x00fe', + 'ydieresis' => '0x00ff', + 'Amacron' => '0x0100', + 'amacron' => '0x0101', + 'Abreve' => '0x0102', + 'abreve' => '0x0103', + 'Aogonek' => '0x0104', + 'aogonek' => '0x0105', + 'Cacute' => '0x0106', + 'cacute' => '0x0107', + 'Ccircumflex' => '0x0108', + 'ccircumflex' => '0x0109', + 'Cdotaccent' => '0x010a', + 'cdotaccent' => '0x010b', + 'Ccaron' => '0x010c', + 'ccaron' => '0x010d', + 'Dcaron' => '0x010e', + 'dcaron' => '0x010f', + 'Dcroat' => '0x0110', + 'dcroat' => '0x0111', + 'Emacron' => '0x0112', + 'emacron' => '0x0113', + 'Ebreve' => '0x0114', + 'ebreve' => '0x0115', + 'Edotaccent' => '0x0116', + 'edotaccent' => '0x0117', + 'Eogonek' => '0x0118', + 'eogonek' => '0x0119', + 'Ecaron' => '0x011a', + 'ecaron' => '0x011b', + 'Gcircumflex' => '0x011c', + 'gcircumflex' => '0x011d', + 'Gbreve' => '0x011e', + 'gbreve' => '0x011f', + 'Gdotaccent' => '0x0120', + 'gdotaccent' => '0x0121', + 'Gcommaaccent' => '0x0122', + 'gcommaaccent' => '0x0123', + 'Hcircumflex' => '0x0124', + 'hcircumflex' => '0x0125', + 'Hbar' => '0x0126', + 'hbar' => '0x0127', + 'Itilde' => '0x0128', + 'itilde' => '0x0129', + 'Imacron' => '0x012a', + 'imacron' => '0x012b', + 'Ibreve' => '0x012c', + 'ibreve' => '0x012d', + 'Iogonek' => '0x012e', + 'iogonek' => '0x012f', + 'Idotaccent' => '0x0130', + 'dotlessi' => '0x0131', + 'IJ' => '0x0132', + 'ij' => '0x0133', + 'Jcircumflex' => '0x0134', + 'jcircumflex' => '0x0135', + 'Kcommaaccent' => '0x0136', + 'kcommaaccent' => '0x0137', + 'kgreenlandic' => '0x0138', + 'Lacute' => '0x0139', + 'lacute' => '0x013a', + 'Lcommaaccent' => '0x013b', + 'lcommaaccent' => '0x013c', + 'Lcaron' => '0x013d', + 'lcaron' => '0x013e', + 'Ldot' => '0x013f', + 'ldot' => '0x0140', + 'Lslash' => '0x0141', + 'lslash' => '0x0142', + 'Nacute' => '0x0143', + 'nacute' => '0x0144', + 'Ncommaaccent' => '0x0145', + 'ncommaaccent' => '0x0146', + 'Ncaron' => '0x0147', + 'ncaron' => '0x0148', + 'napostrophe' => '0x0149', + 'Eng' => '0x014a', + 'eng' => '0x014b', + 'Omacron' => '0x014c', + 'omacron' => '0x014d', + 'Obreve' => '0x014e', + 'obreve' => '0x014f', + 'Ohungarumlaut' => '0x0150', + 'ohungarumlaut' => '0x0151', + 'OE' => '0x0152', + 'oe' => '0x0153', + 'Racute' => '0x0154', + 'racute' => '0x0155', + 'Rcommaaccent' => '0x0156', + 'rcommaaccent' => '0x0157', + 'Rcaron' => '0x0158', + 'rcaron' => '0x0159', + 'Sacute' => '0x015a', + 'sacute' => '0x015b', + 'Scircumflex' => '0x015c', + 'scircumflex' => '0x015d', + 'Scedilla' => '0xf6c1', + 'scedilla' => '0xf6c2', + 'Scaron' => '0x0160', + 'scaron' => '0x0161', + 'Tcommaaccent' => '0x021a', + 'tcommaaccent' => '0x021b', + 'Tcaron' => '0x0164', + 'tcaron' => '0x0165', + 'Tbar' => '0x0166', + 'tbar' => '0x0167', + 'Utilde' => '0x0168', + 'utilde' => '0x0169', + 'Umacron' => '0x016a', + 'umacron' => '0x016b', + 'Ubreve' => '0x016c', + 'ubreve' => '0x016d', + 'Uring' => '0x016e', + 'uring' => '0x016f', + 'Uhungarumlaut' => '0x0170', + 'uhungarumlaut' => '0x0171', + 'Uogonek' => '0x0172', + 'uogonek' => '0x0173', + 'Wcircumflex' => '0x0174', + 'wcircumflex' => '0x0175', + 'Ycircumflex' => '0x0176', + 'ycircumflex' => '0x0177', + 'Ydieresis' => '0x0178', + 'Zacute' => '0x0179', + 'zacute' => '0x017a', + 'Zdotaccent' => '0x017b', + 'zdotaccent' => '0x017c', + 'Zcaron' => '0x017d', + 'zcaron' => '0x017e', + 'longs' => '0x017f', + 'florin' => '0x0192', + 'Ohorn' => '0x01a0', + 'ohorn' => '0x01a1', + 'Uhorn' => '0x01af', + 'uhorn' => '0x01b0', + 'Gcaron' => '0x01e6', + 'gcaron' => '0x01e7', + 'Aringacute' => '0x01fa', + 'aringacute' => '0x01fb', + 'AEacute' => '0x01fc', + 'aeacute' => '0x01fd', + 'Oslashacute' => '0x01fe', + 'oslashacute' => '0x01ff', + 'Scommaaccent' => '0x0218', + 'scommaaccent' => '0x0219', + 'afii57929' => '0x02bc', + 'afii64937' => '0x02bd', + 'circumflex' => '0x02c6', + 'caron' => '0x02c7', + 'breve' => '0x02d8', + 'dotaccent' => '0x02d9', + 'ring' => '0x02da', + 'ogonek' => '0x02db', + 'tilde' => '0x02dc', + 'hungarumlaut' => '0x02dd', + 'gravecomb' => '0x0300', + 'acutecomb' => '0x0301', + 'tildecomb' => '0x0303', + 'hookabovecomb' => '0x0309', + 'dotbelowcomb' => '0x0323', + 'tonos' => '0x0384', + 'dieresistonos' => '0x0385', + 'Alphatonos' => '0x0386', + 'anoteleia' => '0x0387', + 'Epsilontonos' => '0x0388', + 'Etatonos' => '0x0389', + 'Iotatonos' => '0x038a', + 'Omicrontonos' => '0x038c', + 'Upsilontonos' => '0x038e', + 'Omegatonos' => '0x038f', + 'iotadieresistonos' => '0x0390', + 'Alpha' => '0x0391', + 'Beta' => '0x0392', + 'Gamma' => '0x0393', + 'Delta' => '0x2206', + 'Epsilon' => '0x0395', + 'Zeta' => '0x0396', + 'Eta' => '0x0397', + 'Theta' => '0x0398', + 'Iota' => '0x0399', + 'Kappa' => '0x039a', + 'Lambda' => '0x039b', + 'Mu' => '0x039c', + 'Nu' => '0x039d', + 'Xi' => '0x039e', + 'Omicron' => '0x039f', + 'Pi' => '0x03a0', + 'Rho' => '0x03a1', + 'Sigma' => '0x03a3', + 'Tau' => '0x03a4', + 'Upsilon' => '0x03a5', + 'Phi' => '0x03a6', + 'Chi' => '0x03a7', + 'Psi' => '0x03a8', + 'Omega' => '0x2126', + 'Iotadieresis' => '0x03aa', + 'Upsilondieresis' => '0x03ab', + 'alphatonos' => '0x03ac', + 'epsilontonos' => '0x03ad', + 'etatonos' => '0x03ae', + 'iotatonos' => '0x03af', + 'upsilondieresistonos' => '0x03b0', + 'alpha' => '0x03b1', + 'beta' => '0x03b2', + 'gamma' => '0x03b3', + 'delta' => '0x03b4', + 'epsilon' => '0x03b5', + 'zeta' => '0x03b6', + 'eta' => '0x03b7', + 'theta' => '0x03b8', + 'iota' => '0x03b9', + 'kappa' => '0x03ba', + 'lambda' => '0x03bb', + 'nu' => '0x03bd', + 'xi' => '0x03be', + 'omicron' => '0x03bf', + 'pi' => '0x03c0', + 'rho' => '0x03c1', + 'sigma1' => '0x03c2', + 'sigma' => '0x03c3', + 'tau' => '0x03c4', + 'upsilon' => '0x03c5', + 'phi' => '0x03c6', + 'chi' => '0x03c7', + 'psi' => '0x03c8', + 'omega' => '0x03c9', + 'iotadieresis' => '0x03ca', + 'upsilondieresis' => '0x03cb', + 'omicrontonos' => '0x03cc', + 'upsilontonos' => '0x03cd', + 'omegatonos' => '0x03ce', + 'theta1' => '0x03d1', + 'Upsilon1' => '0x03d2', + 'phi1' => '0x03d5', + 'omega1' => '0x03d6', + 'afii10023' => '0x0401', + 'afii10051' => '0x0402', + 'afii10052' => '0x0403', + 'afii10053' => '0x0404', + 'afii10054' => '0x0405', + 'afii10055' => '0x0406', + 'afii10056' => '0x0407', + 'afii10057' => '0x0408', + 'afii10058' => '0x0409', + 'afii10059' => '0x040a', + 'afii10060' => '0x040b', + 'afii10061' => '0x040c', + 'afii10062' => '0x040e', + 'afii10145' => '0x040f', + 'afii10017' => '0x0410', + 'afii10018' => '0x0411', + 'afii10019' => '0x0412', + 'afii10020' => '0x0413', + 'afii10021' => '0x0414', + 'afii10022' => '0x0415', + 'afii10024' => '0x0416', + 'afii10025' => '0x0417', + 'afii10026' => '0x0418', + 'afii10027' => '0x0419', + 'afii10028' => '0x041a', + 'afii10029' => '0x041b', + 'afii10030' => '0x041c', + 'afii10031' => '0x041d', + 'afii10032' => '0x041e', + 'afii10033' => '0x041f', + 'afii10034' => '0x0420', + 'afii10035' => '0x0421', + 'afii10036' => '0x0422', + 'afii10037' => '0x0423', + 'afii10038' => '0x0424', + 'afii10039' => '0x0425', + 'afii10040' => '0x0426', + 'afii10041' => '0x0427', + 'afii10042' => '0x0428', + 'afii10043' => '0x0429', + 'afii10044' => '0x042a', + 'afii10045' => '0x042b', + 'afii10046' => '0x042c', + 'afii10047' => '0x042d', + 'afii10048' => '0x042e', + 'afii10049' => '0x042f', + 'afii10065' => '0x0430', + 'afii10066' => '0x0431', + 'afii10067' => '0x0432', + 'afii10068' => '0x0433', + 'afii10069' => '0x0434', + 'afii10070' => '0x0435', + 'afii10072' => '0x0436', + 'afii10073' => '0x0437', + 'afii10074' => '0x0438', + 'afii10075' => '0x0439', + 'afii10076' => '0x043a', + 'afii10077' => '0x043b', + 'afii10078' => '0x043c', + 'afii10079' => '0x043d', + 'afii10080' => '0x043e', + 'afii10081' => '0x043f', + 'afii10082' => '0x0440', + 'afii10083' => '0x0441', + 'afii10084' => '0x0442', + 'afii10085' => '0x0443', + 'afii10086' => '0x0444', + 'afii10087' => '0x0445', + 'afii10088' => '0x0446', + 'afii10089' => '0x0447', + 'afii10090' => '0x0448', + 'afii10091' => '0x0449', + 'afii10092' => '0x044a', + 'afii10093' => '0x044b', + 'afii10094' => '0x044c', + 'afii10095' => '0x044d', + 'afii10096' => '0x044e', + 'afii10097' => '0x044f', + 'afii10071' => '0x0451', + 'afii10099' => '0x0452', + 'afii10100' => '0x0453', + 'afii10101' => '0x0454', + 'afii10102' => '0x0455', + 'afii10103' => '0x0456', + 'afii10104' => '0x0457', + 'afii10105' => '0x0458', + 'afii10106' => '0x0459', + 'afii10107' => '0x045a', + 'afii10108' => '0x045b', + 'afii10109' => '0x045c', + 'afii10110' => '0x045e', + 'afii10193' => '0x045f', + 'afii10146' => '0x0462', + 'afii10194' => '0x0463', + 'afii10147' => '0x0472', + 'afii10195' => '0x0473', + 'afii10148' => '0x0474', + 'afii10196' => '0x0475', + 'afii10050' => '0x0490', + 'afii10098' => '0x0491', + 'afii10846' => '0x04d9', + 'afii57799' => '0x05b0', + 'afii57801' => '0x05b1', + 'afii57800' => '0x05b2', + 'afii57802' => '0x05b3', + 'afii57793' => '0x05b4', + 'afii57794' => '0x05b5', + 'afii57795' => '0x05b6', + 'afii57798' => '0x05b7', + 'afii57797' => '0x05b8', + 'afii57806' => '0x05b9', + 'afii57796' => '0x05bb', + 'afii57807' => '0x05bc', + 'afii57839' => '0x05bd', + 'afii57645' => '0x05be', + 'afii57841' => '0x05bf', + 'afii57842' => '0x05c0', + 'afii57804' => '0x05c1', + 'afii57803' => '0x05c2', + 'afii57658' => '0x05c3', + 'afii57664' => '0x05d0', + 'afii57665' => '0x05d1', + 'afii57666' => '0x05d2', + 'afii57667' => '0x05d3', + 'afii57668' => '0x05d4', + 'afii57669' => '0x05d5', + 'afii57670' => '0x05d6', + 'afii57671' => '0x05d7', + 'afii57672' => '0x05d8', + 'afii57673' => '0x05d9', + 'afii57674' => '0x05da', + 'afii57675' => '0x05db', + 'afii57676' => '0x05dc', + 'afii57677' => '0x05dd', + 'afii57678' => '0x05de', + 'afii57679' => '0x05df', + 'afii57680' => '0x05e0', + 'afii57681' => '0x05e1', + 'afii57682' => '0x05e2', + 'afii57683' => '0x05e3', + 'afii57684' => '0x05e4', + 'afii57685' => '0x05e5', + 'afii57686' => '0x05e6', + 'afii57687' => '0x05e7', + 'afii57688' => '0x05e8', + 'afii57689' => '0x05e9', + 'afii57690' => '0x05ea', + 'afii57716' => '0x05f0', + 'afii57717' => '0x05f1', + 'afii57718' => '0x05f2', + 'afii57388' => '0x060c', + 'afii57403' => '0x061b', + 'afii57407' => '0x061f', + 'afii57409' => '0x0621', + 'afii57410' => '0x0622', + 'afii57411' => '0x0623', + 'afii57412' => '0x0624', + 'afii57413' => '0x0625', + 'afii57414' => '0x0626', + 'afii57415' => '0x0627', + 'afii57416' => '0x0628', + 'afii57417' => '0x0629', + 'afii57418' => '0x062a', + 'afii57419' => '0x062b', + 'afii57420' => '0x062c', + 'afii57421' => '0x062d', + 'afii57422' => '0x062e', + 'afii57423' => '0x062f', + 'afii57424' => '0x0630', + 'afii57425' => '0x0631', + 'afii57426' => '0x0632', + 'afii57427' => '0x0633', + 'afii57428' => '0x0634', + 'afii57429' => '0x0635', + 'afii57430' => '0x0636', + 'afii57431' => '0x0637', + 'afii57432' => '0x0638', + 'afii57433' => '0x0639', + 'afii57434' => '0x063a', + 'afii57440' => '0x0640', + 'afii57441' => '0x0641', + 'afii57442' => '0x0642', + 'afii57443' => '0x0643', + 'afii57444' => '0x0644', + 'afii57445' => '0x0645', + 'afii57446' => '0x0646', + 'afii57470' => '0x0647', + 'afii57448' => '0x0648', + 'afii57449' => '0x0649', + 'afii57450' => '0x064a', + 'afii57451' => '0x064b', + 'afii57452' => '0x064c', + 'afii57453' => '0x064d', + 'afii57454' => '0x064e', + 'afii57455' => '0x064f', + 'afii57456' => '0x0650', + 'afii57457' => '0x0651', + 'afii57458' => '0x0652', + 'afii57392' => '0x0660', + 'afii57393' => '0x0661', + 'afii57394' => '0x0662', + 'afii57395' => '0x0663', + 'afii57396' => '0x0664', + 'afii57397' => '0x0665', + 'afii57398' => '0x0666', + 'afii57399' => '0x0667', + 'afii57400' => '0x0668', + 'afii57401' => '0x0669', + 'afii57381' => '0x066a', + 'afii63167' => '0x066d', + 'afii57511' => '0x0679', + 'afii57506' => '0x067e', + 'afii57507' => '0x0686', + 'afii57512' => '0x0688', + 'afii57513' => '0x0691', + 'afii57508' => '0x0698', + 'afii57505' => '0x06a4', + 'afii57509' => '0x06af', + 'afii57514' => '0x06ba', + 'afii57519' => '0x06d2', + 'afii57534' => '0x06d5', + 'Wgrave' => '0x1e80', + 'wgrave' => '0x1e81', + 'Wacute' => '0x1e82', + 'wacute' => '0x1e83', + 'Wdieresis' => '0x1e84', + 'wdieresis' => '0x1e85', + 'Ygrave' => '0x1ef2', + 'ygrave' => '0x1ef3', + 'afii61664' => '0x200c', + 'afii301' => '0x200d', + 'afii299' => '0x200e', + 'afii300' => '0x200f', + 'figuredash' => '0x2012', + 'endash' => '0x2013', + 'emdash' => '0x2014', + 'afii00208' => '0x2015', + 'underscoredbl' => '0x2017', + 'quoteleft' => '0x2018', + 'quoteright' => '0x2019', + 'quotesinglbase' => '0x201a', + 'quotereversed' => '0x201b', + 'quotedblleft' => '0x201c', + 'quotedblright' => '0x201d', + 'quotedblbase' => '0x201e', + 'dagger' => '0x2020', + 'daggerdbl' => '0x2021', + 'bullet' => '0x2022', + 'onedotenleader' => '0x2024', + 'twodotenleader' => '0x2025', + 'ellipsis' => '0x2026', + 'afii61573' => '0x202c', + 'afii61574' => '0x202d', + 'afii61575' => '0x202e', + 'perthousand' => '0x2030', + 'minute' => '0x2032', + 'second' => '0x2033', + 'guilsinglleft' => '0x2039', + 'guilsinglright' => '0x203a', + 'exclamdbl' => '0x203c', + 'fraction' => '0x2215', + 'zerosuperior' => '0x2070', + 'foursuperior' => '0x2074', + 'fivesuperior' => '0x2075', + 'sixsuperior' => '0x2076', + 'sevensuperior' => '0x2077', + 'eightsuperior' => '0x2078', + 'ninesuperior' => '0x2079', + 'parenleftsuperior' => '0x207d', + 'parenrightsuperior' => '0x207e', + 'nsuperior' => '0x207f', + 'zeroinferior' => '0x2080', + 'oneinferior' => '0x2081', + 'twoinferior' => '0x2082', + 'threeinferior' => '0x2083', + 'fourinferior' => '0x2084', + 'fiveinferior' => '0x2085', + 'sixinferior' => '0x2086', + 'seveninferior' => '0x2087', + 'eightinferior' => '0x2088', + 'nineinferior' => '0x2089', + 'parenleftinferior' => '0x208d', + 'parenrightinferior' => '0x208e', + 'colonmonetary' => '0x20a1', + 'franc' => '0x20a3', + 'lira' => '0x20a4', + 'peseta' => '0x20a7', + 'afii57636' => '0x20aa', + 'dong' => '0x20ab', + 'Euro' => '0x20ac', + 'afii61248' => '0x2105', + 'Ifraktur' => '0x2111', + 'afii61289' => '0x2113', + 'afii61352' => '0x2116', + 'weierstrass' => '0x2118', + 'Rfraktur' => '0x211c', + 'prescription' => '0x211e', + 'trademark' => '0x2122', + 'estimated' => '0x212e', + 'aleph' => '0x2135', + 'onethird' => '0x2153', + 'twothirds' => '0x2154', + 'oneeighth' => '0x215b', + 'threeeighths' => '0x215c', + 'fiveeighths' => '0x215d', + 'seveneighths' => '0x215e', + 'arrowleft' => '0x2190', + 'arrowup' => '0x2191', + 'arrowright' => '0x2192', + 'arrowdown' => '0x2193', + 'arrowboth' => '0x2194', + 'arrowupdn' => '0x2195', + 'arrowupdnbse' => '0x21a8', + 'carriagereturn' => '0x21b5', + 'arrowdblleft' => '0x21d0', + 'arrowdblup' => '0x21d1', + 'arrowdblright' => '0x21d2', + 'arrowdbldown' => '0x21d3', + 'arrowdblboth' => '0x21d4', + 'universal' => '0x2200', + 'partialdiff' => '0x2202', + 'existential' => '0x2203', + 'emptyset' => '0x2205', + 'gradient' => '0x2207', + 'element' => '0x2208', + 'notelement' => '0x2209', + 'suchthat' => '0x220b', + 'product' => '0x220f', + 'summation' => '0x2211', + 'asteriskmath' => '0x2217', + 'radical' => '0x221a', + 'proportional' => '0x221d', + 'infinity' => '0x221e', + 'orthogonal' => '0x221f', + 'angle' => '0x2220', + 'logicaland' => '0x2227', + 'logicalor' => '0x2228', + 'intersection' => '0x2229', + 'union' => '0x222a', + 'integral' => '0x222b', + 'therefore' => '0x2234', + 'similar' => '0x223c', + 'congruent' => '0x2245', + 'approxequal' => '0x2248', + 'notequal' => '0x2260', + 'equivalence' => '0x2261', + 'lessequal' => '0x2264', + 'greaterequal' => '0x2265', + 'propersubset' => '0x2282', + 'propersuperset' => '0x2283', + 'notsubset' => '0x2284', + 'reflexsubset' => '0x2286', + 'reflexsuperset' => '0x2287', + 'circleplus' => '0x2295', + 'circlemultiply' => '0x2297', + 'perpendicular' => '0x22a5', + 'dotmath' => '0x22c5', + 'house' => '0x2302', + 'revlogicalnot' => '0x2310', + 'integraltp' => '0x2320', + 'integralbt' => '0x2321', + 'angleleft' => '0x2329', + 'angleright' => '0x232a', + 'SF100000' => '0x2500', + 'SF110000' => '0x2502', + 'SF010000' => '0x250c', + 'SF030000' => '0x2510', + 'SF020000' => '0x2514', + 'SF040000' => '0x2518', + 'SF080000' => '0x251c', + 'SF090000' => '0x2524', + 'SF060000' => '0x252c', + 'SF070000' => '0x2534', + 'SF050000' => '0x253c', + 'SF430000' => '0x2550', + 'SF240000' => '0x2551', + 'SF510000' => '0x2552', + 'SF520000' => '0x2553', + 'SF390000' => '0x2554', + 'SF220000' => '0x2555', + 'SF210000' => '0x2556', + 'SF250000' => '0x2557', + 'SF500000' => '0x2558', + 'SF490000' => '0x2559', + 'SF380000' => '0x255a', + 'SF280000' => '0x255b', + 'SF270000' => '0x255c', + 'SF260000' => '0x255d', + 'SF360000' => '0x255e', + 'SF370000' => '0x255f', + 'SF420000' => '0x2560', + 'SF190000' => '0x2561', + 'SF200000' => '0x2562', + 'SF230000' => '0x2563', + 'SF470000' => '0x2564', + 'SF480000' => '0x2565', + 'SF410000' => '0x2566', + 'SF450000' => '0x2567', + 'SF460000' => '0x2568', + 'SF400000' => '0x2569', + 'SF540000' => '0x256a', + 'SF530000' => '0x256b', + 'SF440000' => '0x256c', + 'upblock' => '0x2580', + 'dnblock' => '0x2584', + 'block' => '0x2588', + 'lfblock' => '0x258c', + 'rtblock' => '0x2590', + 'ltshade' => '0x2591', + 'shade' => '0x2592', + 'dkshade' => '0x2593', + 'filledbox' => '0x25a0', + 'H22073' => '0x25a1', + 'H18543' => '0x25aa', + 'H18551' => '0x25ab', + 'filledrect' => '0x25ac', + 'triagup' => '0x25b2', + 'triagrt' => '0x25ba', + 'triagdn' => '0x25bc', + 'triaglf' => '0x25c4', + 'lozenge' => '0x25ca', + 'circle' => '0x25cb', + 'H18533' => '0x25cf', + 'invbullet' => '0x25d8', + 'invcircle' => '0x25d9', + 'openbullet' => '0x25e6', + 'smileface' => '0x263a', + 'invsmileface' => '0x263b', + 'sun' => '0x263c', + 'female' => '0x2640', + 'male' => '0x2642', + 'spade' => '0x2660', + 'club' => '0x2663', + 'heart' => '0x2665', + 'diamond' => '0x2666', + 'musicalnote' => '0x266a', + 'musicalnotedbl' => '0x266b', + 'dotlessj' => '0xf6be', + 'LL' => '0xf6bf', + 'll' => '0xf6c0', + 'commaaccent' => '0xf6c3', + 'afii10063' => '0xf6c4', + 'afii10064' => '0xf6c5', + 'afii10192' => '0xf6c6', + 'afii10831' => '0xf6c7', + 'afii10832' => '0xf6c8', + 'Acute' => '0xf6c9', + 'Caron' => '0xf6ca', + 'Dieresis' => '0xf6cb', + 'DieresisAcute' => '0xf6cc', + 'DieresisGrave' => '0xf6cd', + 'Grave' => '0xf6ce', + 'Hungarumlaut' => '0xf6cf', + 'Macron' => '0xf6d0', + 'cyrBreve' => '0xf6d1', + 'cyrFlex' => '0xf6d2', + 'dblGrave' => '0xf6d3', + 'cyrbreve' => '0xf6d4', + 'cyrflex' => '0xf6d5', + 'dblgrave' => '0xf6d6', + 'dieresisacute' => '0xf6d7', + 'dieresisgrave' => '0xf6d8', + 'copyrightserif' => '0xf6d9', + 'registerserif' => '0xf6da', + 'trademarkserif' => '0xf6db', + 'onefitted' => '0xf6dc', + 'rupiah' => '0xf6dd', + 'threequartersemdash' => '0xf6de', + 'centinferior' => '0xf6df', + 'centsuperior' => '0xf6e0', + 'commainferior' => '0xf6e1', + 'commasuperior' => '0xf6e2', + 'dollarinferior' => '0xf6e3', + 'dollarsuperior' => '0xf6e4', + 'hypheninferior' => '0xf6e5', + 'hyphensuperior' => '0xf6e6', + 'periodinferior' => '0xf6e7', + 'periodsuperior' => '0xf6e8', + 'asuperior' => '0xf6e9', + 'bsuperior' => '0xf6ea', + 'dsuperior' => '0xf6eb', + 'esuperior' => '0xf6ec', + 'isuperior' => '0xf6ed', + 'lsuperior' => '0xf6ee', + 'msuperior' => '0xf6ef', + 'osuperior' => '0xf6f0', + 'rsuperior' => '0xf6f1', + 'ssuperior' => '0xf6f2', + 'tsuperior' => '0xf6f3', + 'Brevesmall' => '0xf6f4', + 'Caronsmall' => '0xf6f5', + 'Circumflexsmall' => '0xf6f6', + 'Dotaccentsmall' => '0xf6f7', + 'Hungarumlautsmall' => '0xf6f8', + 'Lslashsmall' => '0xf6f9', + 'OEsmall' => '0xf6fa', + 'Ogoneksmall' => '0xf6fb', + 'Ringsmall' => '0xf6fc', + 'Scaronsmall' => '0xf6fd', + 'Tildesmall' => '0xf6fe', + 'Zcaronsmall' => '0xf6ff', + 'exclamsmall' => '0xf721', + 'dollaroldstyle' => '0xf724', + 'ampersandsmall' => '0xf726', + 'zerooldstyle' => '0xf730', + 'oneoldstyle' => '0xf731', + 'twooldstyle' => '0xf732', + 'threeoldstyle' => '0xf733', + 'fouroldstyle' => '0xf734', + 'fiveoldstyle' => '0xf735', + 'sixoldstyle' => '0xf736', + 'sevenoldstyle' => '0xf737', + 'eightoldstyle' => '0xf738', + 'nineoldstyle' => '0xf739', + 'questionsmall' => '0xf73f', + 'Gravesmall' => '0xf760', + 'Asmall' => '0xf761', + 'Bsmall' => '0xf762', + 'Csmall' => '0xf763', + 'Dsmall' => '0xf764', + 'Esmall' => '0xf765', + 'Fsmall' => '0xf766', + 'Gsmall' => '0xf767', + 'Hsmall' => '0xf768', + 'Ismall' => '0xf769', + 'Jsmall' => '0xf76a', + 'Ksmall' => '0xf76b', + 'Lsmall' => '0xf76c', + 'Msmall' => '0xf76d', + 'Nsmall' => '0xf76e', + 'Osmall' => '0xf76f', + 'Psmall' => '0xf770', + 'Qsmall' => '0xf771', + 'Rsmall' => '0xf772', + 'Ssmall' => '0xf773', + 'Tsmall' => '0xf774', + 'Usmall' => '0xf775', + 'Vsmall' => '0xf776', + 'Wsmall' => '0xf777', + 'Xsmall' => '0xf778', + 'Ysmall' => '0xf779', + 'Zsmall' => '0xf77a', + 'exclamdownsmall' => '0xf7a1', + 'centoldstyle' => '0xf7a2', + 'Dieresissmall' => '0xf7a8', + 'Macronsmall' => '0xf7af', + 'Acutesmall' => '0xf7b4', + 'Cedillasmall' => '0xf7b8', + 'questiondownsmall' => '0xf7bf', + 'Agravesmall' => '0xf7e0', + 'Aacutesmall' => '0xf7e1', + 'Acircumflexsmall' => '0xf7e2', + 'Atildesmall' => '0xf7e3', + 'Adieresissmall' => '0xf7e4', + 'Aringsmall' => '0xf7e5', + 'AEsmall' => '0xf7e6', + 'Ccedillasmall' => '0xf7e7', + 'Egravesmall' => '0xf7e8', + 'Eacutesmall' => '0xf7e9', + 'Ecircumflexsmall' => '0xf7ea', + 'Edieresissmall' => '0xf7eb', + 'Igravesmall' => '0xf7ec', + 'Iacutesmall' => '0xf7ed', + 'Icircumflexsmall' => '0xf7ee', + 'Idieresissmall' => '0xf7ef', + 'Ethsmall' => '0xf7f0', + 'Ntildesmall' => '0xf7f1', + 'Ogravesmall' => '0xf7f2', + 'Oacutesmall' => '0xf7f3', + 'Ocircumflexsmall' => '0xf7f4', + 'Otildesmall' => '0xf7f5', + 'Odieresissmall' => '0xf7f6', + 'Oslashsmall' => '0xf7f8', + 'Ugravesmall' => '0xf7f9', + 'Uacutesmall' => '0xf7fa', + 'Ucircumflexsmall' => '0xf7fb', + 'Udieresissmall' => '0xf7fc', + 'Yacutesmall' => '0xf7fd', + 'Thornsmall' => '0xf7fe', + 'Ydieresissmall' => '0xf7ff', + 'radicalex' => '0xf8e5', + 'arrowvertex' => '0xf8e6', + 'arrowhorizex' => '0xf8e7', + 'registersans' => '0xf8e8', + 'copyrightsans' => '0xf8e9', + 'trademarksans' => '0xf8ea', + 'parenlefttp' => '0xf8eb', + 'parenleftex' => '0xf8ec', + 'parenleftbt' => '0xf8ed', + 'bracketlefttp' => '0xf8ee', + 'bracketleftex' => '0xf8ef', + 'bracketleftbt' => '0xf8f0', + 'bracelefttp' => '0xf8f1', + 'braceleftmid' => '0xf8f2', + 'braceleftbt' => '0xf8f3', + 'braceex' => '0xf8f4', + 'integralex' => '0xf8f5', + 'parenrighttp' => '0xf8f6', + 'parenrightex' => '0xf8f7', + 'parenrightbt' => '0xf8f8', + 'bracketrighttp' => '0xf8f9', + 'bracketrightex' => '0xf8fa', + 'bracketrightbt' => '0xf8fb', + 'bracerighttp' => '0xf8fc', + 'bracerightmid' => '0xf8fd', + 'bracerightbt' => '0xf8fe', + 'ff' => '0xfb00', + 'fi' => '0xfb01', + 'fl' => '0xfb02', + 'ffi' => '0xfb03', + 'ffl' => '0xfb04', + 'afii57705' => '0xfb1f', + 'afii57694' => '0xfb2a', + 'afii57695' => '0xfb2b', + 'afii57723' => '0xfb35', + 'afii57700' => '0xfb4b', + ]; + } + + public static function getCodePoint($glyph): ?int + { + $glyphsMap = static::getGlyphs(); + + if (isset($glyphsMap[$glyph])) { + return hexdec($glyphsMap[$glyph]); + } + + return null; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/StandardEncoding.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/StandardEncoding.php new file mode 100644 index 0000000..01d0a1c --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/StandardEncoding.php @@ -0,0 +1,76 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/StandardEncoding.pm + +namespace Smalot\PdfParser\Encoding; + +/** + * Class StandardEncoding + */ +class StandardEncoding extends AbstractEncoding +{ + public function getTranslations(): array + { + $encoding = + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + 'space exclam quotedbl numbersign dollar percent ampersand quoteright '. + 'parenleft parenright asterisk plus comma hyphen period slash zero '. + 'one two three four five six seven eight nine colon semicolon less '. + 'equal greater question at A B C D E F G H I J K L M N O P Q R S T U '. + 'V W X Y Z bracketleft backslash bracketright asciicircum underscore '. + 'quoteleft a b c d e f g h i j k l m n o p q r s t u v w x y z '. + 'braceleft bar braceright asciitilde .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef exclamdown cent '. + 'sterling fraction yen florin section currency quotesingle '. + 'quotedblleft guillemotleft guilsinglleft guilsinglright fi fl '. + '.notdef endash dagger daggerdbl periodcentered .notdef paragraph '. + 'bullet quotesinglbase quotedblbase quotedblright guillemotright '. + 'ellipsis perthousand .notdef questiondown .notdef grave acute '. + 'circumflex tilde macron breve dotaccent dieresis .notdef ring '. + 'cedilla .notdef hungarumlaut ogonek caron emdash .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef AE .notdef '. + 'ordfeminine .notdef .notdef .notdef .notdef Lslash Oslash OE '. + 'ordmasculine .notdef .notdef .notdef .notdef .notdef ae .notdef '. + '.notdef .notdef dotlessi .notdef .notdef lslash oslash oe germandbls '. + '.notdef .notdef .notdef .notdef'; + + return explode(' ', $encoding); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/WinAnsiEncoding.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/WinAnsiEncoding.php new file mode 100644 index 0000000..1938f55 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Encoding/WinAnsiEncoding.php @@ -0,0 +1,76 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +// Source : http://cpansearch.perl.org/src/JV/PostScript-Font-1.10.02/lib/PostScript/WinANSIEncoding.pm + +namespace Smalot\PdfParser\Encoding; + +/** + * Class WinAnsiEncoding + */ +class WinAnsiEncoding extends AbstractEncoding +{ + public function getTranslations(): array + { + $encoding = + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + '.notdef .notdef .notdef .notdef .notdef .notdef .notdef .notdef '. + 'space exclam quotedbl numbersign dollar percent ampersand quotesingle '. + 'parenleft parenright asterisk plus comma hyphen period slash zero one '. + 'two three four five six seven eight nine colon semicolon less equal '. + 'greater question at A B C D E F G H I J K L M N O P Q R S T U V W X '. + 'Y Z bracketleft backslash bracketright asciicircum underscore '. + 'grave a b c d e f g h i j k l m n o p q r s t u v w x y z '. + 'braceleft bar braceright asciitilde bullet Euro bullet quotesinglbase '. + 'florin quotedblbase ellipsis dagger daggerdbl circumflex perthousand '. + 'Scaron guilsinglleft OE bullet Zcaron bullet bullet quoteleft quoteright '. + 'quotedblleft quotedblright bullet endash emdash tilde trademark scaron '. + 'guilsinglright oe bullet zcaron Ydieresis space exclamdown cent '. + 'sterling currency yen brokenbar section dieresis copyright '. + 'ordfeminine guillemotleft logicalnot hyphen registered macron degree '. + 'plusminus twosuperior threesuperior acute mu paragraph '. + 'periodcentered cedilla onesuperior ordmasculine guillemotright '. + 'onequarter onehalf threequarters questiondown Agrave Aacute '. + 'Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute '. + 'Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis Eth Ntilde '. + 'Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave '. + 'Uacute Ucircumflex Udieresis Yacute Thorn germandbls agrave aacute '. + 'acircumflex atilde adieresis aring ae ccedilla egrave eacute '. + 'ecircumflex edieresis igrave iacute icircumflex idieresis eth ntilde '. + 'ograve oacute ocircumflex otilde odieresis divide oslash ugrave '. + 'uacute ucircumflex udieresis yacute thorn ydieresis'; + + return explode(' ', $encoding); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Exception/EmptyPdfException.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Exception/EmptyPdfException.php new file mode 100644 index 0000000..9eda9ce --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Exception/EmptyPdfException.php @@ -0,0 +1,12 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Encoding\WinAnsiEncoding; +use Smalot\PdfParser\Exception\EncodingNotFoundException; + +/** + * Class Font + */ +class Font extends PDFObject +{ + public const MISSING = '?'; + + /** + * @var array + */ + protected $table; + + /** + * @var array + */ + protected $tableSizes; + + /** + * Caches results from uchr. + * + * @var array + */ + private static $uchrCache = []; + + /** + * In some PDF-files encoding could be referenced by object id but object itself does not contain + * `/Type /Encoding` in its dictionary. These objects wouldn't be initialized as Encoding in + * \Smalot\PdfParser\PDFObject::factory() during file parsing (they would be just PDFObject). + * + * Therefore, we create an instance of Encoding from them during decoding and cache this value in this property. + * + * @var Encoding + * + * @see https://github.com/smalot/pdfparser/pull/500 + */ + private $initializedEncodingByPdfObject; + + public function init() + { + // Load translate table. + $this->loadTranslateTable(); + } + + public function getName(): string + { + return $this->has('BaseFont') ? (string) $this->get('BaseFont') : '[Unknown]'; + } + + public function getType(): string + { + return (string) $this->header->get('Subtype'); + } + + public function getDetails(bool $deep = true): array + { + $details = []; + + $details['Name'] = $this->getName(); + $details['Type'] = $this->getType(); + $details['Encoding'] = ($this->has('Encoding') ? (string) $this->get('Encoding') : 'Ansi'); + + $details += parent::getDetails($deep); + + return $details; + } + + /** + * @return string|bool + */ + public function translateChar(string $char, bool $use_default = true) + { + $dec = hexdec(bin2hex($char)); + + if (\array_key_exists($dec, $this->table)) { + return $this->table[$dec]; + } + + // fallback for decoding single-byte ANSI characters that are not in the lookup table + $fallbackDecoded = $char; + if ( + \strlen($char) < 2 + && $this->has('Encoding') + && $this->get('Encoding') instanceof Encoding + ) { + try { + if (WinAnsiEncoding::class === $this->get('Encoding')->__toString()) { + $fallbackDecoded = self::uchr($dec); + } + } catch (EncodingNotFoundException $e) { + // Encoding->getEncodingClass() throws EncodingNotFoundException when BaseEncoding doesn't exists + // See table 5.11 on PDF 1.5 specs for more info + } + } + + return $use_default ? self::MISSING : $fallbackDecoded; + } + + /** + * Convert unicode character code to "utf-8" encoded string. + * + * @param int|float $code Unicode character code. Will be casted to int internally! + */ + public static function uchr($code): string + { + // note: + // $code was typed as int before, but changed in https://github.com/smalot/pdfparser/pull/623 + // because in some cases uchr was called with a float instead of an integer. + $code = (int) $code; + + if (!isset(self::$uchrCache[$code])) { + // html_entity_decode() will not work with UTF-16 or UTF-32 char entities, + // therefore, we use mb_convert_encoding() instead + self::$uchrCache[$code] = mb_convert_encoding("&#{$code};", 'UTF-8', 'HTML-ENTITIES'); + } + + return self::$uchrCache[$code]; + } + + /** + * Init internal chars translation table by ToUnicode CMap. + */ + public function loadTranslateTable(): array + { + if (null !== $this->table) { + return $this->table; + } + + $this->table = []; + $this->tableSizes = [ + 'from' => 1, + 'to' => 1, + ]; + + if ($this->has('ToUnicode')) { + $content = $this->get('ToUnicode')->getContent(); + $matches = []; + + // Support for multiple spacerange sections + if (preg_match_all('/begincodespacerange(?P.*?)endcodespacerange/s', $content, $matches)) { + foreach ($matches['sections'] as $section) { + $regexp = '/<(?P[0-9A-F]+)> *<(?P[0-9A-F]+)>[ \r\n]+/is'; + + preg_match_all($regexp, $section, $matches); + + $this->tableSizes = [ + 'from' => max(1, \strlen(current($matches['from'])) / 2), + 'to' => max(1, \strlen(current($matches['to'])) / 2), + ]; + + break; + } + } + + // Support for multiple bfchar sections + if (preg_match_all('/beginbfchar(?P.*?)endbfchar/s', $content, $matches)) { + foreach ($matches['sections'] as $section) { + $regexp = '/<(?P[0-9A-F]+)> *<(?P[0-9A-F]+)>[ \r\n]+/is'; + + preg_match_all($regexp, $section, $matches); + + $this->tableSizes['from'] = max(1, \strlen(current($matches['from'])) / 2); + + foreach ($matches['from'] as $key => $from) { + $parts = preg_split( + '/([0-9A-F]{4})/i', + $matches['to'][$key], + 0, + \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE + ); + $text = ''; + foreach ($parts as $part) { + $text .= self::uchr(hexdec($part)); + } + $this->table[hexdec($from)] = $text; + } + } + } + + // Support for multiple bfrange sections + if (preg_match_all('/beginbfrange(?P.*?)endbfrange/s', $content, $matches)) { + foreach ($matches['sections'] as $section) { + /** + * Regexp to capture , , and either or [...] items. + * - (?P...) Source range's start + * - (?P...) Source range's end + * - (?P...) Destination range's offset or each char code + * Some PDF file has 2-byte Unicode values on new lines > added \r\n + */ + $regexp = '/<(?P[0-9A-F]+)> *<(?P[0-9A-F]+)> *(?P<[0-9A-F]+>|\[[\r\n<>0-9A-F ]+\])[ \r\n]+/is'; + + preg_match_all($regexp, $section, $matches); + + foreach ($matches['from'] as $key => $from) { + $char_from = hexdec($from); + $char_to = hexdec($matches['to'][$key]); + $dest = $matches['dest'][$key]; + + if (1 === preg_match('/^<(?P[0-9A-F]+)>$/i', $dest, $offset_matches)) { + // Support for : + $offset = hexdec($offset_matches['offset']); + + for ($char = $char_from; $char <= $char_to; ++$char) { + $this->table[$char] = self::uchr($char - $char_from + $offset); + } + } else { + // Support for : [ ... ] + $strings = []; + $matched = preg_match_all('/<(?P[0-9A-F]+)> */is', $dest, $strings); + if (false === $matched || 0 === $matched) { + continue; + } + + foreach ($strings['string'] as $position => $string) { + $parts = preg_split( + '/([0-9A-F]{4})/i', + $string, + 0, + \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE + ); + if (false === $parts) { + continue; + } + $text = ''; + foreach ($parts as $part) { + $text .= self::uchr(hexdec($part)); + } + $this->table[$char_from + $position] = $text; + } + } + } + } + } + } + + return $this->table; + } + + /** + * Set custom char translation table where: + * - key - integer character code; + * - value - "utf-8" encoded value; + * + * @return void + */ + public function setTable(array $table) + { + $this->table = $table; + } + + /** + * Calculate text width with data from header 'Widths'. If width of character is not found then character is added to missing array. + */ + public function calculateTextWidth(string $text, ?array &$missing = null): ?float + { + $index_map = array_flip($this->table); + $details = $this->getDetails(); + + // Usually, Widths key is set in $details array, but if it isn't use an empty array instead. + $widths = $details['Widths'] ?? []; + + /* + * Widths array is zero indexed but table is not. We must map them based on FirstChar and LastChar + * + * Note: Without the change you would see warnings in PHP 8.4 because the values of FirstChar or LastChar + * can be null sometimes. + */ + $width_map = array_flip(range((int) $details['FirstChar'], (int) $details['LastChar'])); + + $width = null; + $missing = []; + $textLength = mb_strlen($text); + for ($i = 0; $i < $textLength; ++$i) { + $char = mb_substr($text, $i, 1); + if ( + !\array_key_exists($char, $index_map) + || !\array_key_exists($index_map[$char], $width_map) + || !\array_key_exists($width_map[$index_map[$char]], $widths) + ) { + $missing[] = $char; + continue; + } + $width_index = $width_map[$index_map[$char]]; + $width += $widths[$width_index]; + } + + return $width; + } + + /** + * Decode hexadecimal encoded string. If $add_braces is true result value would be wrapped by parentheses. + */ + public static function decodeHexadecimal(string $hexa, bool $add_braces = false): string + { + // Special shortcut for XML content. + if (false !== stripos($hexa, ')/si', $hexa, -1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE); + + foreach ($parts as $part) { + if (preg_match('/^<[a-f0-9\s]+>$/si', $part)) { + // strip whitespace + $part = preg_replace("/\s/", '', $part); + $part = trim($part, '<>'); + if ($add_braces) { + $text .= '('; + } + + $part = pack('H*', $part); + $text .= ($add_braces ? preg_replace('/\\\/s', '\\\\\\', $part) : $part); + + if ($add_braces) { + $text .= ')'; + } + } else { + $text .= $part; + } + } + + return $text; + } + + /** + * Decode string with octal-decoded chunks. + */ + public static function decodeOctal(string $text): string + { + // Replace all double backslashes \\ with a special string + $text = strtr($text, ['\\\\' => '[**pdfparserdblslsh**]']); + + // Now we can replace all octal codes without worrying about + // escaped backslashes + $text = preg_replace_callback('/\\\\([0-7]{1,3})/', function ($m) { + return \chr(octdec($m[1])); + }, $text); + + // Unescape any parentheses + $text = str_replace(['\\(', '\\)'], ['(', ')'], $text); + + // Replace instances of the special string with a single backslash + return str_replace('[**pdfparserdblslsh**]', '\\', $text); + } + + /** + * Decode string with html entity encoded chars. + */ + public static function decodeEntities(string $text): string + { + return preg_replace_callback('/#([0-9a-f]{2})/i', function ($m) { + return \chr(hexdec($m[1])); + }, $text); + } + + /** + * Check if given string is Unicode text (by BOM); + * If true - decode to "utf-8" encoded string. + * Otherwise - return text as is. + * + * @todo Rename in next major release to make the name correspond to reality (for ex. decodeIfUnicode()) + */ + public static function decodeUnicode(string $text): string + { + if ("\xFE\xFF" === substr($text, 0, 2)) { + // Strip U+FEFF byte order marker. + $decode = substr($text, 2); + $text = ''; + $length = \strlen($decode); + + for ($i = 0; $i < $length; $i += 2) { + $text .= self::uchr(hexdec(bin2hex(substr($decode, $i, 2)))); + } + } + + return $text; + } + + /** + * @todo Deprecated, use $this->config->getFontSpaceLimit() instead. + */ + protected function getFontSpaceLimit(): int + { + return $this->config->getFontSpaceLimit(); + } + + /** + * Decode text by commands array. + */ + public function decodeText(array $commands, float $fontFactor = 4): string + { + $word_position = 0; + $words = []; + $font_space = $this->getFontSpaceLimit() * abs($fontFactor) / 4; + + foreach ($commands as $command) { + switch ($command[PDFObject::TYPE]) { + case 'n': + $offset = (float) trim($command[PDFObject::COMMAND]); + if ($offset - (float) $font_space < 0) { + $word_position = \count($words); + } + continue 2; + case '<': + // Decode hexadecimal. + $text = self::decodeHexadecimal('<'.$command[PDFObject::COMMAND].'>'); + break; + + default: + // Decode octal (if necessary). + $text = self::decodeOctal($command[PDFObject::COMMAND]); + } + + // replace escaped chars + $text = str_replace( + ['\\\\', '\(', '\)', '\n', '\r', '\t', '\f', '\ ', '\b'], + [\chr(92), \chr(40), \chr(41), \chr(10), \chr(13), \chr(9), \chr(12), \chr(32), \chr(8)], + $text + ); + + // add content to result string + if (isset($words[$word_position])) { + $words[$word_position] .= $text; + } else { + $words[$word_position] = $text; + } + } + + foreach ($words as &$word) { + $word = $this->decodeContent($word); + $word = str_replace("\t", ' ', $word); + } + + // Remove internal "words" that are just spaces, but leave them + // if they are at either end of the array of words. This fixes, + // for example, lines that are justified to fill + // a whole row. + for ($x = \count($words) - 2; $x >= 1; --$x) { + if ('' === trim($words[$x], ' ')) { + unset($words[$x]); + } + } + $words = array_values($words); + + // Cut down on the number of unnecessary internal spaces by + // imploding the string on the null byte, and checking if the + // text includes extra spaces on either side. If so, merge + // where appropriate. + $words = implode("\x00\x00", $words); + $words = str_replace( + [" \x00\x00 ", "\x00\x00 ", " \x00\x00", "\x00\x00"], + [' ', ' ', ' ', ' '], + $words + ); + + return $words; + } + + /** + * Decode given $text to "utf-8" encoded string. + * + * @param bool $unicode This parameter is deprecated and might be removed in a future release + */ + public function decodeContent(string $text, ?bool &$unicode = null): string + { + // If this string begins with a UTF-16BE BOM, then decode it + // directly as Unicode + if ("\xFE\xFF" === substr($text, 0, 2)) { + return $this->decodeUnicode($text); + } + + if ($this->has('ToUnicode')) { + return $this->decodeContentByToUnicodeCMapOrDescendantFonts($text); + } + + if ($this->has('Encoding')) { + $result = $this->decodeContentByEncoding($text); + + if (null !== $result) { + return $result; + } + } + + return $this->decodeContentByAutodetectIfNecessary($text); + } + + /** + * First try to decode $text by ToUnicode CMap. + * If char translation not found in ToUnicode CMap tries: + * - If DescendantFonts exists tries to decode char by one of that fonts. + * - If have no success to decode by DescendantFonts interpret $text as a string with "Windows-1252" encoding. + * - If DescendantFonts does not exist just return "?" as decoded char. + * + * @todo Seems this is invalid algorithm that do not follow pdf-format specification. Must be rewritten. + */ + private function decodeContentByToUnicodeCMapOrDescendantFonts(string $text): string + { + $bytes = $this->tableSizes['from']; + + if ($bytes) { + $result = ''; + $length = \strlen($text); + + for ($i = 0; $i < $length; $i += $bytes) { + $char = substr($text, $i, $bytes); + + if (false !== ($decoded = $this->translateChar($char, false))) { + $char = $decoded; + } elseif ($this->has('DescendantFonts')) { + if ($this->get('DescendantFonts') instanceof PDFObject) { + $fonts = $this->get('DescendantFonts')->getHeader()->getElements(); + } else { + $fonts = $this->get('DescendantFonts')->getContent(); + } + $decoded = false; + + foreach ($fonts as $font) { + if ($font instanceof self) { + if (false !== ($decoded = $font->translateChar($char, false))) { + $decoded = mb_convert_encoding($decoded, 'UTF-8', 'Windows-1252'); + break; + } + } + } + + if (false !== $decoded) { + $char = $decoded; + } else { + $char = mb_convert_encoding($char, 'UTF-8', 'Windows-1252'); + } + } else { + $char = self::MISSING; + } + + $result .= $char; + } + + $text = $result; + } + + return $text; + } + + /** + * Decode content by any type of Encoding (dictionary's item) instance. + */ + private function decodeContentByEncoding(string $text): ?string + { + $encoding = $this->get('Encoding'); + + // When Encoding referenced by object id (/Encoding 520 0 R) but object itself does not contain `/Type /Encoding` in it's dictionary. + if ($encoding instanceof PDFObject) { + $encoding = $this->getInitializedEncodingByPdfObject($encoding); + } + + // When Encoding referenced by object id (/Encoding 520 0 R) but object itself contains `/Type /Encoding` in it's dictionary. + if ($encoding instanceof Encoding) { + return $this->decodeContentByEncodingEncoding($text, $encoding); + } + + // When Encoding is just string (/Encoding /WinAnsiEncoding) + if ($encoding instanceof Element) { // todo: ElementString class must by used? + return $this->decodeContentByEncodingElement($text, $encoding); + } + + // don't double-encode strings already in UTF-8 + if (!mb_check_encoding($text, 'UTF-8')) { + return mb_convert_encoding($text, 'UTF-8', 'Windows-1252'); + } + + return $text; + } + + /** + * Returns already created or create a new one if not created before Encoding instance by PDFObject instance. + */ + private function getInitializedEncodingByPdfObject(PDFObject $PDFObject): Encoding + { + if (!$this->initializedEncodingByPdfObject) { + $this->initializedEncodingByPdfObject = $this->createInitializedEncodingByPdfObject($PDFObject); + } + + return $this->initializedEncodingByPdfObject; + } + + /** + * Decode content when $encoding (given by $this->get('Encoding')) is instance of Encoding. + */ + private function decodeContentByEncodingEncoding(string $text, Encoding $encoding): string + { + $result = ''; + $length = \strlen($text); + + for ($i = 0; $i < $length; ++$i) { + $dec_av = hexdec(bin2hex($text[$i])); + $dec_ap = $encoding->translateChar($dec_av); + $result .= self::uchr($dec_ap ?? $dec_av); + } + + return $result; + } + + /** + * Decode content when $encoding (given by $this->get('Encoding')) is instance of Element. + */ + private function decodeContentByEncodingElement(string $text, Element $encoding): ?string + { + $pdfEncodingName = $encoding->getContent(); + + // mb_convert_encoding does not support MacRoman/macintosh, + // so we use iconv() here + $iconvEncodingName = $this->getIconvEncodingNameOrNullByPdfEncodingName($pdfEncodingName); + + return $iconvEncodingName ? iconv($iconvEncodingName, 'UTF-8//TRANSLIT//IGNORE', $text) : null; + } + + /** + * Convert PDF encoding name to iconv-known encoding name. + */ + private function getIconvEncodingNameOrNullByPdfEncodingName(string $pdfEncodingName): ?string + { + $pdfToIconvEncodingNameMap = [ + 'StandardEncoding' => 'ISO-8859-1', + 'MacRomanEncoding' => 'MACINTOSH', + 'WinAnsiEncoding' => 'CP1252', + ]; + + return \array_key_exists($pdfEncodingName, $pdfToIconvEncodingNameMap) + ? $pdfToIconvEncodingNameMap[$pdfEncodingName] + : null; + } + + /** + * If string seems like "utf-8" encoded string do nothing and just return given string as is. + * Otherwise, interpret string as "Window-1252" encoded string. + * + * @return string|false + */ + private function decodeContentByAutodetectIfNecessary(string $text) + { + if (mb_check_encoding($text, 'UTF-8')) { + return $text; + } + + return mb_convert_encoding($text, 'UTF-8', 'Windows-1252'); + // todo: Why exactly `Windows-1252` used? + } + + /** + * Create Encoding instance by PDFObject instance and init it. + */ + private function createInitializedEncodingByPdfObject(PDFObject $PDFObject): Encoding + { + $encoding = $this->createEncodingByPdfObject($PDFObject); + $encoding->init(); + + return $encoding; + } + + /** + * Create Encoding instance by PDFObject instance (without init). + */ + private function createEncodingByPdfObject(PDFObject $PDFObject): Encoding + { + $document = $PDFObject->getDocument(); + $header = $PDFObject->getHeader(); + $content = $PDFObject->getContent(); + $config = $PDFObject->getConfig(); + + return new Encoding($document, $header, $content, $config); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontCIDFontType0.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontCIDFontType0.php new file mode 100644 index 0000000..310c44c --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontCIDFontType0.php @@ -0,0 +1,42 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Font; + +use Smalot\PdfParser\Font; + +/** + * Class FontCIDFontType0 + */ +class FontCIDFontType0 extends Font +{ +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontCIDFontType2.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontCIDFontType2.php new file mode 100644 index 0000000..077d6e7 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontCIDFontType2.php @@ -0,0 +1,42 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Font; + +use Smalot\PdfParser\Font; + +/** + * Class FontCIDFontType2 + */ +class FontCIDFontType2 extends Font +{ +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontTrueType.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontTrueType.php new file mode 100644 index 0000000..8a55c00 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontTrueType.php @@ -0,0 +1,42 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Font; + +use Smalot\PdfParser\Font; + +/** + * Class FontTrueType + */ +class FontTrueType extends Font +{ +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType0.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType0.php new file mode 100644 index 0000000..4e5cc6d --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType0.php @@ -0,0 +1,42 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Font; + +use Smalot\PdfParser\Font; + +/** + * Class FontType0 + */ +class FontType0 extends Font +{ +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType1.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType1.php new file mode 100644 index 0000000..ee93e69 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType1.php @@ -0,0 +1,42 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Font; + +use Smalot\PdfParser\Font; + +/** + * Class FontType1 + */ +class FontType1 extends Font +{ +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType3.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType3.php new file mode 100644 index 0000000..08f8da0 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Font/FontType3.php @@ -0,0 +1,42 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\Font; + +use Smalot\PdfParser\Font; + +/** + * Class FontType3 + */ +class FontType3 extends Font +{ +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Header.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Header.php new file mode 100644 index 0000000..b58773a --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Header.php @@ -0,0 +1,194 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Element\ElementArray; +use Smalot\PdfParser\Element\ElementMissing; +use Smalot\PdfParser\Element\ElementStruct; +use Smalot\PdfParser\Element\ElementXRef; + +/** + * Class Header + */ +class Header +{ + /** + * @var Document|null + */ + protected $document; + + /** + * @var Element[] + */ + protected $elements; + + /** + * @param Element[] $elements list of elements + * @param Document $document document + */ + public function __construct(array $elements = [], ?Document $document = null) + { + $this->elements = $elements; + $this->document = $document; + } + + public function init() + { + foreach ($this->elements as $element) { + if ($element instanceof Element) { + $element->init(); + } + } + } + + /** + * Returns all elements. + */ + public function getElements() + { + foreach ($this->elements as $name => $element) { + $this->resolveXRef($name); + } + + return $this->elements; + } + + /** + * Used only for debug. + */ + public function getElementTypes(): array + { + $types = []; + + foreach ($this->elements as $key => $element) { + $types[$key] = \get_class($element); + } + + return $types; + } + + public function getDetails(bool $deep = true): array + { + $values = []; + $elements = $this->getElements(); + + foreach ($elements as $key => $element) { + if ($element instanceof self && $deep) { + $values[$key] = $element->getDetails($deep); + } elseif ($element instanceof PDFObject && $deep) { + $values[$key] = $element->getDetails(false); + } elseif ($element instanceof ElementArray) { + if ($deep) { + $values[$key] = $element->getDetails(); + } + } elseif ($element instanceof Element) { + $values[$key] = (string) $element; + } + } + + return $values; + } + + /** + * Indicate if an element name is available in header. + * + * @param string $name the name of the element + */ + public function has(string $name): bool + { + return \array_key_exists($name, $this->elements); + } + + /** + * @return Element|PDFObject + */ + public function get(string $name) + { + if (\array_key_exists($name, $this->elements) && $element = $this->resolveXRef($name)) { + return $element; + } + + return new ElementMissing(); + } + + /** + * Resolve XRef to object. + * + * @return Element|PDFObject + * + * @throws \Exception + */ + protected function resolveXRef(string $name) + { + if (($obj = $this->elements[$name]) instanceof ElementXRef && null !== $this->document) { + /** @var ElementXRef $obj */ + $object = $this->document->getObjectById($obj->getId()); + + if (null === $object) { + return new ElementMissing(); + } + + // Update elements list for future calls. + $this->elements[$name] = $object; + } + + return $this->elements[$name]; + } + + /** + * @param string $content The content to parse + * @param Document $document The document + * @param int $position The new position of the cursor after parsing + */ + public static function parse(string $content, Document $document, int &$position = 0): self + { + /* @var Header $header */ + if ('<<' == substr(trim($content), 0, 2)) { + $header = ElementStruct::parse($content, $document, $position); + } else { + $elements = ElementArray::parse($content, $document, $position); + $header = new self([], $document); + + if ($elements) { + $header = new self($elements->getRawContent(), null); + } + } + + if ($header) { + return $header; + } + + // Build an empty header. + return new self([], $document); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/PDFObject.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/PDFObject.php new file mode 100644 index 0000000..378ae15 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/PDFObject.php @@ -0,0 +1,1215 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Exception\InvalidDictionaryObjectException; +use Smalot\PdfParser\XObject\Form; +use Smalot\PdfParser\XObject\Image; + +/** + * Class PDFObject + */ +class PDFObject +{ + public const TYPE = 't'; + + public const OPERATOR = 'o'; + + public const COMMAND = 'c'; + + /** + * The recursion stack. + * + * @var array + */ + public static $recursionStack = []; + + /** + * @var Document|null + */ + protected $document; + + /** + * @var Header + */ + protected $header; + + /** + * @var string + */ + protected $content; + + /** + * @var Config|null + */ + protected $config; + + /** + * @var bool + */ + protected $addPositionWhitespace = false; + + public function __construct( + Document $document, + ?Header $header = null, + ?string $content = null, + ?Config $config = null + ) { + $this->document = $document; + $this->header = $header ?? new Header(); + $this->content = $content; + $this->config = $config; + } + + public function init() + { + } + + public function getDocument(): Document + { + return $this->document; + } + + public function getHeader(): ?Header + { + return $this->header; + } + + public function getConfig(): ?Config + { + return $this->config; + } + + /** + * @return Element|PDFObject|Header + */ + public function get(string $name) + { + return $this->header->get($name); + } + + public function has(string $name): bool + { + return $this->header->has($name); + } + + public function getDetails(bool $deep = true): array + { + return $this->header->getDetails($deep); + } + + public function getContent(): ?string + { + return $this->content; + } + + /** + * Creates a duplicate of the document stream with + * strings and other items replaced by $char. Formerly + * getSectionsText() used this output to more easily gather offset + * values to extract text from the *actual* document stream. + * + * @deprecated function is no longer used and will be removed in a future release + * + * @internal + */ + public function cleanContent(string $content, string $char = 'X') + { + $char = $char[0]; + $content = str_replace(['\\\\', '\\)', '\\('], $char.$char, $content); + + // Remove image bloc with binary content + preg_match_all('/\s(BI\s.*?(\sID\s).*?(\sEI))\s/s', $content, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $part) { + $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0])); + } + + // Clean content in square brackets [.....] + preg_match_all('/\[((\(.*?\)|[0-9\.\-\s]*)*)\]/s', $content, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[1] as $part) { + $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0])); + } + + // Clean content in round brackets (.....) + preg_match_all('/\((.*?)\)/s', $content, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[1] as $part) { + $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0])); + } + + // Clean structure + if ($parts = preg_split('/(<|>)/s', $content, -1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE)) { + $content = ''; + $level = 0; + foreach ($parts as $part) { + if ('<' == $part) { + ++$level; + } + + $content .= (0 == $level ? $part : str_repeat($char, \strlen($part))); + + if ('>' == $part) { + --$level; + } + } + } + + // Clean BDC and EMC markup + preg_match_all( + '/(\/[A-Za-z0-9\_]*\s*'.preg_quote($char).'*BDC)/s', + $content, + $matches, + \PREG_OFFSET_CAPTURE + ); + foreach ($matches[1] as $part) { + $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0])); + } + + preg_match_all('/\s(EMC)\s/s', $content, $matches, \PREG_OFFSET_CAPTURE); + foreach ($matches[1] as $part) { + $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0])); + } + + return $content; + } + + /** + * Takes a string of PDF document stream text and formats + * it into a multi-line string with one PDF command on each line, + * separated by \r\n. If the given string is null, or binary data + * is detected instead of a document stream then return an empty + * string. + */ + private function formatContent(?string $content): string + { + if (null === $content) { + return ''; + } + + // Outside of (String) and inline image content in PDF document + // streams, all text should conform to UTF-8. Test for binary + // content by deleting everything after the first open- + // parenthesis ( which indicates the beginning of a string, or + // the first ID command which indicates the beginning of binary + // inline image content. Then test what remains for valid + // UTF-8. If it's not UTF-8, return an empty string as this + // $content is most likely binary. Unfortunately, using + // mb_check_encoding(..., 'UTF-8') is not strict enough, so the + // following regexp, adapted from the W3, is used. See: + // https://www.w3.org/International/questions/qa-forms-utf-8.en + // We use preg_replace() instead of preg_match() to avoid "JIT + // stack limit exhausted" errors on larger files. + $utf8Filter = preg_replace('/( + [\x09\x0A\x0D\x20-\x7E] | # ASCII + [\xC2-\xDF][\x80-\xBF] | # non-overlong 2-byte + \xE0[\xA0-\xBF][\x80-\xBF] | # excluding overlongs + [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} | # straight 3-byte + \xED[\x80-\x9F][\x80-\xBF] | # excluding surrogates + \xF0[\x90-\xBF][\x80-\xBF]{2} | # planes 1-3 + [\xF1-\xF3][\x80-\xBF]{3} | # planes 4-15 + \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )/xs', '', preg_replace('/(\(|ID\s).*$/s', '', $content)); + + if ('' !== $utf8Filter) { + return ''; + } + + // Find all inline image content and replace them so they aren't + // affected by the next steps + $pdfInlineImages = []; + $offsetBI = 0; + while (preg_match('/\sBI\s(\/.+?)\sID\s(.+?)\sEI(?=\s|$)/s', $content, $text, \PREG_OFFSET_CAPTURE, $offsetBI)) { + // Attempt to detemine if this instance of the 'BI' command + // actually occured within a (string) using the following + // steps: + + // Step 1: Remove any escaped slashes and parentheses from + // the alleged image characteristics data + $para = str_replace(['\\\\', '\\(', '\\)'], '', $text[1][0]); + + // Step 2: Remove all correctly ordered and balanced + // parentheses from (strings) + do { + $paraTest = $para; + $para = preg_replace('/\(([^()]*)\)/', '$1', $paraTest); + } while ($para != $paraTest); + + $paraOpen = strpos($para, '('); + $paraClose = strpos($para, ')'); + + // Check: If the remaining text contains a close parenthesis + // ')' AND it occurs before any open parenthesis, then we + // are almost certain to be inside a (string) + if (0 < $paraClose && (false === $paraOpen || $paraClose < $paraOpen)) { + // Bump the search offset forward and match again + $offsetBI = (int) $text[1][1]; + continue; + } + + // Step 3: Double check that this is actually inline image + // data by parsing the alleged image characteristics as a + // dictionary + $dict = $this->parseDictionary('<<'.$text[1][0].'>>'); + + // Check if an image Width and Height are set in the dict + if ((isset($dict['W']) || isset($dict['Width'])) + && (isset($dict['H']) || isset($dict['Height']))) { + $id = uniqid('IMAGE_', true); + $pdfInlineImages[$id] = [ + preg_replace(['/\r\n/', '/\r/', '/\n/'], ' ', $text[1][0]), + preg_replace(['/\r\n/', '/\r/', '/\n/'], '', $text[2][0]), + ]; + $content = preg_replace( + '/'.preg_quote($text[0][0], '/').'/', + '^^^'.$id.'^^^', + $content, + 1 + ); + } else { + // If there was no valid dictionary, or a height and width + // weren't specified, then we don't know what this is, so + // just leave it alone; bump the search offset forward and + // match again + $offsetBI = (int) $text[1][1]; + } + } + + // Find all strings () and replace them so they aren't affected + // by the next steps + $pdfstrings = []; + $attempt = '('; + while (preg_match('/'.preg_quote($attempt, '/').'.*?\)/s', $content, $text)) { + // Remove all escaped slashes and parentheses from the target text + $para = str_replace(['\\\\', '\\(', '\\)'], '', $text[0]); + + // PDF strings can contain unescaped parentheses as long as + // they're balanced, so check for balanced parentheses + $left = preg_match_all('/\(/', $para); + $right = preg_match_all('/\)/', $para); + + if (')' == $para[-1] && $left == $right) { + // Replace the string with a unique placeholder + $id = uniqid('STRING_', true); + $pdfstrings[$id] = $text[0]; + $content = preg_replace( + '/'.preg_quote($text[0], '/').'/', + '@@@'.$id.'@@@', + $content, + 1 + ); + + // Reset to search for the next string + $attempt = '('; + } else { + // We had unbalanced parentheses, so use the current + // match as a base to find a longer string + $attempt = $text[0]; + } + } + + // Remove all carriage returns and line-feeds from the document stream + $content = str_replace(["\r", "\n"], ' ', trim($content)); + + // Find all dictionary << >> commands and replace them so they + // aren't affected by the next steps + $dictstore = []; + while (preg_match('/(<<.*?>> *)(BDC|BMC|DP|MP)/s', $content, $dicttext)) { + $dictid = uniqid('DICT_', true); + $dictstore[$dictid] = $dicttext[1]; + $content = preg_replace( + '/'.preg_quote($dicttext[0], '/').'/', + ' ###'.$dictid.'###'.$dicttext[2], + $content, + 1 + ); + } + + // Normalize white-space in the document stream + $content = preg_replace('/\s{2,}/', ' ', $content); + + // Find all valid PDF operators and add \r\n after each; this + // ensures there is just one command on every line + // Source: https://ia801001.us.archive.org/1/items/pdf1.7/pdf_reference_1-7.pdf - Appendix A + // Source: https://archive.org/download/pdf320002008/PDF32000_2008.pdf - Annex A + // Note: PDF Reference 1.7 lists 'I' and 'rI' as valid commands, while + // PDF 32000:2008 lists them as 'i' and 'ri' respectively. Both versions + // appear here in the list for completeness. + $operators = [ + 'b*', 'b', 'BDC', 'BMC', 'B*', 'BI', 'BT', 'BX', 'B', 'cm', 'cs', 'c', 'CS', + 'd0', 'd1', 'd', 'Do', 'DP', 'EMC', 'EI', 'ET', 'EX', 'f*', 'f', 'F', 'gs', + 'g', 'G', 'h', 'i', 'ID', 'I', 'j', 'J', 'k', 'K', 'l', 'm', 'MP', 'M', 'n', + 'q', 'Q', 're', 'rg', 'ri', 'rI', 'RG', 'scn', 'sc', 'sh', 's', 'SCN', 'SC', + 'S', 'T*', 'Tc', 'Td', 'TD', 'Tf', 'TJ', 'Tj', 'TL', 'Tm', 'Tr', 'Ts', 'Tw', + 'Tz', 'v', 'w', 'W*', 'W', 'y', '\'', '"', + ]; + foreach ($operators as $operator) { + $content = preg_replace( + '/(?> commands + $dictstore = array_reverse($dictstore, true); + foreach ($dictstore as $id => $dict) { + $content = str_replace('###'.$id.'###', $dict, $content); + } + + // Restore the original string content + $pdfstrings = array_reverse($pdfstrings, true); + foreach ($pdfstrings as $id => $text) { + // Strings may contain escaped newlines, or literal newlines + // and we should clean these up before replacing the string + // back into the content stream; this ensures no strings are + // split between two lines (every command must be on one line) + $text = str_replace( + ["\\\r\n", "\\\r", "\\\n", "\r", "\n"], + ['', '', '', '\r', '\n'], + $text + ); + + $content = str_replace('@@@'.$id.'@@@', $text, $content); + } + + // Restore the original content of any inline images + $pdfInlineImages = array_reverse($pdfInlineImages, true); + foreach ($pdfInlineImages as $id => $image) { + $content = str_replace( + '^^^'.$id.'^^^', + "\r\nBI\r\n".$image[0]." ID\r\n".$image[1]." EI\r\n", + $content + ); + } + + $content = trim(preg_replace(['/(\r\n){2,}/', '/\r\n +/'], "\r\n", $content)); + + return $content; + } + + /** + * getSectionsText() now takes an entire, unformatted + * document stream as a string, cleans it, then filters out + * commands that aren't needed for text positioning/extraction. It + * returns an array of unprocessed PDF commands, one command per + * element. + * + * @internal + */ + public function getSectionsText(?string $content): array + { + $sections = []; + + // A cleaned stream has one command on every line, so split the + // cleaned stream content on \r\n into an array + $textCleaned = preg_split( + '/(\r\n|\n|\r)/', + $this->formatContent($content), + -1, + \PREG_SPLIT_NO_EMPTY + ); + + $inTextBlock = false; + foreach ($textCleaned as $line) { + $line = trim($line); + + // Skip empty lines + if ('' === $line) { + continue; + } + + // If a 'BT' is encountered, set the $inTextBlock flag + if (preg_match('/BT$/', $line)) { + $inTextBlock = true; + $sections[] = $line; + + // If an 'ET' is encountered, unset the $inTextBlock flag + } elseif ('ET' == $line) { + $inTextBlock = false; + $sections[] = $line; + } elseif ($inTextBlock) { + // If we are inside a BT ... ET text block, save all lines + $sections[] = trim($line); + } else { + // Otherwise, if we are outside of a text block, only + // save specific, necessary lines. Care should be taken + // to ensure a command being checked for *only* matches + // that command. For instance, a simple search for 'c' + // may also match the 'sc' command. See the command + // list in the formatContent() method above. + // Add more commands to save here as you find them in + // weird PDFs! + if ('q' == $line[-1] || 'Q' == $line[-1]) { + // Save and restore graphics state commands + $sections[] = $line; + } elseif (preg_match('/(?getFonts(); + } + + $firstFont = $this->document->getFirstFont(); + if (null !== $firstFont) { + $fonts[] = $firstFont; + } + + if (\count($fonts) > 0) { + return reset($fonts); + } + + return new Font($this->document, null, null, $this->config); + } + + /** + * Decode a '[]TJ' command and attempt to use alternate + * fonts if the current font results in output that contains + * Unicode control characters. + * + * @internal + * + * @param array> $command + */ + private function getTJUsingFontFallback(Font $font, array $command, ?Page $page = null, float $fontFactor = 4): string + { + $orig_text = $font->decodeText($command, $fontFactor); + $text = $orig_text; + + // If we make this a Config option, we can add a check if it's + // enabled here. + if (null !== $page) { + $font_ids = array_keys($page->getFonts()); + + // If the decoded text contains UTF-8 control characters + // then the font page being used is probably the wrong one. + // Loop through the rest of the fonts to see if we can get + // a good decode. Allow x09 to x0d which are whitespace. + while (preg_match('/[\x00-\x08\x0e-\x1f\x7f]/u', $text) || false !== strpos(bin2hex($text), '00')) { + // If we're out of font IDs, then give up and use the + // original string + if (0 == \count($font_ids)) { + return $orig_text; + } + + // Try the next font ID + $font = $page->getFont(array_shift($font_ids)); + $text = $font->decodeText($command, $fontFactor); + } + } + + return $text; + } + + /** + * Expects a string that is a full PDF dictionary object, + * including the outer enclosing << >> angle brackets + * + * @internal + * + * @throws InvalidDictionaryObjectException + */ + public function parseDictionary(string $dictionary): array + { + // Normalize whitespace + $dictionary = preg_replace(['/\r/', '/\n/', '/\s{2,}/'], ' ', trim($dictionary)); + + if ('<<' != substr($dictionary, 0, 2)) { + throw new InvalidDictionaryObjectException('Not a valid dictionary object.'); + } + + $parsed = []; + $stack = []; + $currentName = ''; + $arrayTypeNumeric = false; + + // Remove outer layer of dictionary, and split on tokens + $split = preg_split( + '/(<<|>>|\[|\]|\/[^\s\/\[\]\(\)<>]*)/', + trim(preg_replace('/^<<|>>$/', '', $dictionary)), + -1, + \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE + ); + + foreach ($split as $token) { + $token = trim($token); + switch ($token) { + case '': + break; + + // Open numeric array + case '[': + $parsed[$currentName] = []; + $arrayTypeNumeric = true; + + // Move up one level in the stack + $stack[\count($stack)] = &$parsed; + $parsed = &$parsed[$currentName]; + $currentName = ''; + break; + + // Open hashed array + case '<<': + $parsed[$currentName] = []; + $arrayTypeNumeric = false; + + // Move up one level in the stack + $stack[\count($stack)] = &$parsed; + $parsed = &$parsed[$currentName]; + $currentName = ''; + break; + + // Close numeric array + case ']': + // Revert string type arrays back to a single element + if (\is_array($parsed) && 1 == \count($parsed) + && isset($parsed[0]) && \is_string($parsed[0]) + && '' !== $parsed[0] && '/' != $parsed[0][0]) { + $parsed = '['.$parsed[0].']'; + } + // Close hashed array + // no break + case '>>': + $arrayTypeNumeric = false; + + // Move down one level in the stack + $parsed = &$stack[\count($stack) - 1]; + unset($stack[\count($stack) - 1]); + break; + + default: + // If value begins with a slash, then this is a name + // Add it to the appropriate array + if ('/' == substr($token, 0, 1)) { + $currentName = substr($token, 1); + if (true == $arrayTypeNumeric) { + $parsed[] = $currentName; + $currentName = ''; + } + } elseif ('' != $currentName) { + if (false == $arrayTypeNumeric) { + $parsed[$currentName] = $token; + } + $currentName = ''; + } elseif ('' == $currentName) { + $parsed[] = $token; + } + } + } + + return $parsed; + } + + /** + * Returns the text content of a PDF as a string. Attempts to add + * whitespace for spacing and line-breaks where appropriate. + * + * getText() leverages getTextArray() to get the content + * of the document, setting the addPositionWhitespace flag to true + * so whitespace is inserted in a logical way for reading by + * humans. + */ + public function getText(?Page $page = null): string + { + $this->addPositionWhitespace = true; + $result = $this->getTextArray($page); + $this->addPositionWhitespace = false; + + return implode('', $result).' '; + } + + /** + * Returns the text content of a PDF as an array of strings. No + * extra whitespace is inserted besides what is actually encoded in + * the PDF text. + * + * @throws \Exception + */ + public function getTextArray(?Page $page = null): array + { + $result = []; + $text = []; + + $marked_stack = []; + $last_written_position = false; + + $sections = $this->getSectionsText($this->content); + $current_font = $this->getDefaultFont($page); + $current_font_size = 1; + $current_text_leading = 0; + + $current_position = ['x' => false, 'y' => false]; + $current_position_tm = [ + 'a' => 1, 'b' => 0, 'c' => 0, + 'i' => 0, 'j' => 1, 'k' => 0, + 'x' => 0, 'y' => 0, 'z' => 1, + ]; + $current_position_td = ['x' => 0, 'y' => 0]; + $current_position_cm = [ + 'a' => 1, 'b' => 0, 'c' => 0, + 'i' => 0, 'j' => 1, 'k' => 0, + 'x' => 0, 'y' => 0, 'z' => 1, + ]; + + $clipped_font = []; + $clipped_position_cm = []; + + self::$recursionStack[] = $this->getUniqueId(); + + foreach ($sections as $section) { + $commands = $this->getCommandsText($section); + foreach ($commands as $command) { + switch ($command[self::OPERATOR]) { + // Begin text object + case 'BT': + // Reset text positioning matrices + $current_position_tm = [ + 'a' => 1, 'b' => 0, 'c' => 0, + 'i' => 0, 'j' => 1, 'k' => 0, + 'x' => 0, 'y' => 0, 'z' => 1, + ]; + $current_position_td = ['x' => 0, 'y' => 0]; + $current_text_leading = 0; + break; + + // Begin marked content sequence with property list + case 'BDC': + if (preg_match('/(<<.*>>)$/', $command[self::COMMAND], $match)) { + $dict = $this->parseDictionary($match[1]); + + // Check for ActualText block + if (isset($dict['ActualText']) && \is_string($dict['ActualText']) && '' !== $dict['ActualText']) { + if ('[' == $dict['ActualText'][0]) { + // Simulate a 'TJ' command on the stack + $marked_stack[] = [ + 'ActualText' => $this->getCommandsText($dict['ActualText'].'TJ')[0], + ]; + } elseif ('<' == $dict['ActualText'][0] || '(' == $dict['ActualText'][0]) { + // Simulate a 'Tj' command on the stack + $marked_stack[] = [ + 'ActualText' => $this->getCommandsText($dict['ActualText'].'Tj')[0], + ]; + } + } + } + break; + + // Begin marked content sequence + case 'BMC': + if ('ReversedChars' == $command[self::COMMAND]) { + // Upon encountering a ReversedChars command, + // add the characters we've built up so far to + // the result array + $result = array_merge($result, $text); + + // Start a fresh $text array that will contain + // reversed characters + $text = []; + + // Add the reversed text flag to the stack + $marked_stack[] = ['ReversedChars' => true]; + } + break; + + // set graphics position matrix + case 'cm': + $args = preg_split('/\s+/s', $command[self::COMMAND]); + $current_position_cm = [ + 'a' => (float) $args[0], 'b' => (float) $args[1], 'c' => 0, + 'i' => (float) $args[2], 'j' => (float) $args[3], 'k' => 0, + 'x' => (float) $args[4], 'y' => (float) $args[5], 'z' => 1, + ]; + break; + + case 'Do': + if (is_null($page)) { + break; + } + + $args = preg_split('/\s/s', $command[self::COMMAND]); + $id = trim(array_pop($args), '/ '); + $xobject = $page->getXObject($id); + + // Check we got a PDFObject back. + if (!$xobject instanceof self) { + break; + } + + // If the PDFObject is an Image, do nothing as images + // aren't text. + if ($xobject instanceof Image) { + break; + } + + // Check this is not a circular reference. + if (\in_array($xobject->getUniqueId(), self::$recursionStack, true)) { + break; + } + + $objectText = $xobject->getText($page); + + // If the PDFObject is a Form and doesn't have any text, + // skip it. + if ($xobject instanceof Form && $objectText === ' ') { + break; + } + + $text[] = $objectText; + break; + + // Marked content point with (DP) & without (MP) property list + case 'DP': + case 'MP': + break; + + // End text object + case 'ET': + break; + + // Store current selected font and graphics matrix + case 'q': + $clipped_font[] = [$current_font, $current_font_size]; + $clipped_position_cm[] = $current_position_cm; + break; + + // Restore previous selected font and graphics matrix + case 'Q': + list($current_font, $current_font_size) = array_pop($clipped_font); + $current_position_cm = array_pop($clipped_position_cm); + break; + + // End marked content sequence + case 'EMC': + $data = false; + if (\count($marked_stack)) { + $marked = array_pop($marked_stack); + $action = key($marked); + $data = $marked[$action]; + + switch ($action) { + // If we are in ReversedChars mode... + case 'ReversedChars': + // Reverse the characters we've built up so far + foreach ($text as $key => $t) { + $text[$key] = implode('', array_reverse( + mb_str_split($t, 1, mb_internal_encoding()) + )); + } + + // Add these characters to the result array + $result = array_merge($result, $text); + + // Start a fresh $text array that will contain + // non-reversed characters + $text = []; + break; + + case 'ActualText': + // Use the content of the ActualText as a command + $command = $data; + break; + } + } + + // If this EMC command has been transformed into a 'Tj' + // or 'TJ' command because of being ActualText, then bypass + // the break to proceed to the writing section below. + if ('Tj' != $command[self::OPERATOR] && 'TJ' != $command[self::OPERATOR]) { + break; + } + + // no break + case "'": + case '"': + if ("'" == $command[self::OPERATOR] || '"' == $command[self::OPERATOR]) { + // Move to next line and write text + $current_position['x'] = 0; + $current_position_td['x'] = 0; + $current_position_td['y'] += $current_text_leading; + } + // no break + case 'Tj': + $command[self::COMMAND] = [$command]; + // no break + case 'TJ': + // Check the marked content stack for flags + $actual_text = false; + $reverse_text = false; + foreach ($marked_stack as $marked) { + if (isset($marked['ActualText'])) { + $actual_text = true; + } + if (isset($marked['ReversedChars'])) { + $reverse_text = true; + } + } + + // Account for text position ONLY just before we write text + if (false === $actual_text && \is_array($last_written_position)) { + // If $last_written_position is an array, that + // means we have stored text position coordinates + // for placing an ActualText + $currentX = $last_written_position[0]; + $currentY = $last_written_position[1]; + $last_written_position = false; + } else { + $currentX = $current_position_cm['x'] + $current_position_tm['x'] + $current_position_td['x']; + $currentY = $current_position_cm['y'] + $current_position_tm['y'] + $current_position_td['y']; + } + $whiteSpace = ''; + + $factorX = -$current_font_size * $current_position_tm['a'] - $current_font_size * $current_position_tm['i']; + $factorY = $current_font_size * $current_position_tm['b'] + $current_font_size * $current_position_tm['j']; + + if (true === $this->addPositionWhitespace && false !== $current_position['x']) { + $curY = $currentY - $current_position['y']; + if (abs($curY) >= abs($factorY) / 4) { + $whiteSpace = "\n"; + } else { + if (true === $reverse_text) { + $curX = $current_position['x'] - $currentX; + } else { + $curX = $currentX - $current_position['x']; + } + + // In abs($factorX * 7) below, the 7 is chosen arbitrarily + // as the number of apparent "spaces" in a document we + // would need before considering them a "tab". In the + // future, we might offer this value to users as a config + // option. + if ($curX >= abs($factorX * 7)) { + $whiteSpace = "\t"; + } elseif ($curX >= abs($factorX * 2)) { + $whiteSpace = ' '; + } + } + } + + $newtext = $this->getTJUsingFontFallback( + $current_font, + $command[self::COMMAND], + $page, + $factorX + ); + + // If there is no ActualText pending then write + if (false === $actual_text) { + $newtext = str_replace(["\r", "\n"], '', $newtext); + if (false !== $reverse_text) { + // If we are in ReversedChars mode, add the whitespace last + $text[] = preg_replace('/ $/', ' ', $newtext.$whiteSpace); + } else { + // Otherwise add the whitespace first + if (' ' === $whiteSpace && isset($text[\count($text) - 1])) { + $text[\count($text) - 1] = preg_replace('/ $/', '', $text[\count($text) - 1]); + } + $text[] = preg_replace('/^[ \t]{2}/', ' ', $whiteSpace.$newtext); + } + + // Record the position of this inserted text for comparison + // with the next text block. + // Provide a 'fudge' factor guess on how wide this text block + // is based on the number of characters. This helps limit the + // number of tabs inserted, but isn't perfect. + $factor = $factorX / 2; + $current_position = [ + 'x' => $currentX - mb_strlen($newtext) * $factor, + 'y' => $currentY, + ]; + } elseif (false === $last_written_position) { + // If there is an ActualText in the pipeline + // store the position this undisplayed text + // *would* have been written to, so the + // ActualText is displayed in the right spot + $last_written_position = [$currentX, $currentY]; + $current_position['x'] = $currentX; + } + break; + + // move to start of next line + case 'T*': + $current_position['x'] = 0; + $current_position_td['x'] = 0; + $current_position_td['y'] += $current_text_leading; + break; + + // set character spacing + case 'Tc': + break; + + // move text current point and set leading + case 'Td': + case 'TD': + // move text current point + $args = preg_split('/\s+/s', $command[self::COMMAND]); + $y = (float) array_pop($args); + $x = (float) array_pop($args); + + if ('TD' == $command[self::OPERATOR]) { + $current_text_leading = -$y * $current_position_tm['b'] - $y * $current_position_tm['j']; + } + + $current_position_td = [ + 'x' => $current_position_td['x'] + $x * $current_position_tm['a'] + $x * $current_position_tm['i'], + 'y' => $current_position_td['y'] + $y * $current_position_tm['b'] + $y * $current_position_tm['j'], + ]; + break; + + case 'Tf': + $args = preg_split('/\s/s', $command[self::COMMAND]); + $size = (float) array_pop($args); + $id = trim(array_pop($args), '/'); + if (null !== $page) { + $new_font = $page->getFont($id); + // If an invalid font ID is given, do not update the font. + // This should theoretically never happen, as the PDF spec states for the Tf operator: + // "The specified font value shall match a resource name in the Font entry of the default resource dictionary" + // (https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf, page 435) + // But we want to make sure that malformed PDFs do not simply crash. + if (null !== $new_font) { + $current_font = $new_font; + $current_font_size = $size; + } + } + break; + + // set leading + case 'TL': + $y = (float) $command[self::COMMAND]; + $current_text_leading = -$y * $current_position_tm['b'] + -$y * $current_position_tm['j']; + break; + + // set text position matrix + case 'Tm': + $args = preg_split('/\s+/s', $command[self::COMMAND]); + $current_position_tm = [ + 'a' => (float) $args[0], 'b' => (float) $args[1], 'c' => 0, + 'i' => (float) $args[2], 'j' => (float) $args[3], 'k' => 0, + 'x' => (float) $args[4], 'y' => (float) $args[5], 'z' => 1, + ]; + break; + + // set text rendering mode + case 'Ts': + break; + + // set super/subscripting text rise + case 'Ts': + break; + + // set word spacing + case 'Tw': + break; + + // set horizontal scaling + case 'Tz': + break; + + default: + } + } + } + + $result = array_merge($result, $text); + + return $result; + } + + /** + * getCommandsText() expects the content of $text_part to be an + * already formatted, single-line command from a document stream. + * The companion function getSectionsText() returns a document + * stream as an array of single commands for just this purpose. + * Because of this, the argument $offset is no longer used, and + * may be removed in a future PdfParser release. + * + * A better name for this function would be getCommandText() + * since it now always works on just one command. + */ + public function getCommandsText(string $text_part, int &$offset = 0): array + { + $commands = $matches = []; + + preg_match('/^(([\/\[\(<])?.*)(? '(', + self::OPERATOR => 'TJ', + self::COMMAND => $tjmatch[1], + ]; + if (isset($tjmatch[2]) && trim($tjmatch[2])) { + $subcommand[] = [ + self::TYPE => 'n', + self::OPERATOR => '', + self::COMMAND => $tjmatch[2], + ]; + } + $command = substr($command, \strlen($tjmatch[0])); + } + + // Search for hexadecimal <> format + if (preg_match('/^ *<([0-9a-f\s]*)> *(-?[\d.]+)?/i', $command, $tjmatch)) { + $tjmatch[1] = preg_replace('/\s/', '', $tjmatch[1]); + $subcommand[] = [ + self::TYPE => '<', + self::OPERATOR => 'TJ', + self::COMMAND => $tjmatch[1], + ]; + if (isset($tjmatch[2]) && trim($tjmatch[2])) { + $subcommand[] = [ + self::TYPE => 'n', + self::OPERATOR => '', + self::COMMAND => $tjmatch[2], + ]; + } + $command = substr($command, \strlen($tjmatch[0])); + } + } while ($command != $oldCommand); + + $command = $subcommand; + } elseif ('Tj' == $operator || "'" == $operator || '"' == $operator) { + // Depending on the string type, trim the data of the + // appropriate delimiters + if ('(' == $type) { + // Don't use trim() here since a () string may end with + // a balanced or escaped right parentheses, and trim() + // will delete both. Both strings below are valid: + // eg. (String()) + // eg. (String\)) + $command = preg_replace('/^\(|\)$/', '', $command); + } elseif ('<' == $type) { + $command = trim($command, '<>'); + } + } elseif ('/' == $type) { + $command = substr($command, 1); + } + + $commands[] = [ + self::TYPE => $type, + self::OPERATOR => $operator, + self::COMMAND => $command, + ]; + + return $commands; + } + + public static function factory( + Document $document, + Header $header, + ?string $content, + ?Config $config = null + ): self { + switch ($header->get('Type')->getContent()) { + case 'XObject': + switch ($header->get('Subtype')->getContent()) { + case 'Image': + return new Image($document, $header, $config->getRetainImageContent() ? $content : null, $config); + + case 'Form': + return new Form($document, $header, $content, $config); + } + + return new self($document, $header, $content, $config); + + case 'Pages': + return new Pages($document, $header, $content, $config); + + case 'Page': + return new Page($document, $header, $content, $config); + + case 'Encoding': + return new Encoding($document, $header, $content, $config); + + case 'Font': + $subtype = $header->get('Subtype')->getContent(); + $classname = '\Smalot\PdfParser\Font\Font'.$subtype; + + if (class_exists($classname)) { + return new $classname($document, $header, $content, $config); + } + + return new Font($document, $header, $content, $config); + + default: + return new self($document, $header, $content, $config); + } + } + + /** + * Returns unique id identifying the object. + */ + protected function getUniqueId(): string + { + return spl_object_hash($this); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Page.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Page.php new file mode 100644 index 0000000..1bd29e1 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Page.php @@ -0,0 +1,1014 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Element\ElementArray; +use Smalot\PdfParser\Element\ElementMissing; +use Smalot\PdfParser\Element\ElementNull; +use Smalot\PdfParser\Element\ElementXRef; + +class Page extends PDFObject +{ + /** + * @var Font[] + */ + protected $fonts; + + /** + * @var PDFObject[] + */ + protected $xobjects; + + /** + * @var array + */ + protected $dataTm; + + /** + * @param array<\Smalot\PdfParser\Font> $fonts + * + * @internal + */ + public function setFonts($fonts) + { + if (empty($this->fonts)) { + $this->fonts = $fonts; + } + } + + /** + * @return Font[] + */ + public function getFonts() + { + if (null !== $this->fonts) { + return $this->fonts; + } + + $resources = $this->get('Resources'); + + if (method_exists($resources, 'has') && $resources->has('Font')) { + if ($resources->get('Font') instanceof ElementMissing) { + return []; + } + + if ($resources->get('Font') instanceof Header) { + $fonts = $resources->get('Font')->getElements(); + } else { + $fonts = $resources->get('Font')->getHeader()->getElements(); + } + + $table = []; + + foreach ($fonts as $id => $font) { + if ($font instanceof Font) { + $table[$id] = $font; + + // Store too on cleaned id value (only numeric) + $id = preg_replace('/[^0-9\.\-_]/', '', $id); + if ('' != $id) { + $table[$id] = $font; + } + } + } + + return $this->fonts = $table; + } + + return []; + } + + public function getFont(string $id): ?Font + { + $fonts = $this->getFonts(); + + if (isset($fonts[$id])) { + return $fonts[$id]; + } + + // According to the PDF specs (https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf, page 238) + // "The font resource name presented to the Tf operator is arbitrary, as are the names for all kinds of resources" + // Instead, we search for the unfiltered name first and then do this cleaning as a fallback, so all tests still pass. + + if (isset($fonts[$id])) { + return $fonts[$id]; + } else { + $id = preg_replace('/[^0-9\.\-_]/', '', $id); + if (isset($fonts[$id])) { + return $fonts[$id]; + } + } + + return null; + } + + /** + * Support for XObject + * + * @return PDFObject[] + */ + public function getXObjects() + { + if (null !== $this->xobjects) { + return $this->xobjects; + } + + $resources = $this->get('Resources'); + + if (method_exists($resources, 'has') && $resources->has('XObject')) { + if ($resources->get('XObject') instanceof Header) { + $xobjects = $resources->get('XObject')->getElements(); + } else { + $xobjects = $resources->get('XObject')->getHeader()->getElements(); + } + + $table = []; + + foreach ($xobjects as $id => $xobject) { + $table[$id] = $xobject; + + // Store too on cleaned id value (only numeric) + $id = preg_replace('/[^0-9\.\-_]/', '', $id); + if ('' != $id) { + $table[$id] = $xobject; + } + } + + return $this->xobjects = $table; + } + + return []; + } + + public function getXObject(string $id): ?PDFObject + { + $xobjects = $this->getXObjects(); + + if (isset($xobjects[$id])) { + return $xobjects[$id]; + } + + return null; + /*$id = preg_replace('/[^0-9\.\-_]/', '', $id); + + if (isset($xobjects[$id])) { + return $xobjects[$id]; + } else { + return null; + }*/ + } + + public function getText(?self $page = null): string + { + if ($contents = $this->get('Contents')) { + if ($contents instanceof ElementMissing) { + return ''; + } elseif ($contents instanceof ElementNull) { + return ''; + } elseif ($contents instanceof PDFObject) { + $elements = $contents->getHeader()->getElements(); + + if (is_numeric(key($elements))) { + $new_content = ''; + + foreach ($elements as $element) { + if ($element instanceof ElementXRef) { + $new_content .= $element->getObject()->getContent(); + } else { + $new_content .= $element->getContent(); + } + } + + $header = new Header([], $this->document); + $contents = new PDFObject($this->document, $header, $new_content, $this->config); + } + } elseif ($contents instanceof ElementArray) { + // Create a virtual global content. + $new_content = ''; + + foreach ($contents->getContent() as $content) { + $new_content .= $content->getContent()."\n"; + } + + $header = new Header([], $this->document); + $contents = new PDFObject($this->document, $header, $new_content, $this->config); + } + + /* + * Elements referencing each other on the same page can cause endless loops during text parsing. + * To combat this we keep a recursionStack containing already parsed elements on the page. + * The stack is only emptied here after getting text from a page. + */ + $contentsText = $contents->getText($this); + PDFObject::$recursionStack = []; + + return $contentsText; + } + + return ''; + } + + /** + * Return true if the current page is a (setasign\Fpdi\Fpdi) FPDI/FPDF document + * + * The metadata 'Producer' should have the value of "FPDF" . FPDF_VERSION if the + * pdf file was generated by FPDF/Fpfi. + * + * @return bool true is the current page is a FPDI/FPDF document + */ + public function isFpdf(): bool + { + if (\array_key_exists('Producer', $this->document->getDetails()) + && \is_string($this->document->getDetails()['Producer']) + && 0 === strncmp($this->document->getDetails()['Producer'], 'FPDF', 4)) { + return true; + } + + return false; + } + + /** + * Return the page number of the PDF document of the page object + * + * @return int the page number + */ + public function getPageNumber(): int + { + $pages = $this->document->getPages(); + $numOfPages = \count($pages); + for ($pageNum = 0; $pageNum < $numOfPages; ++$pageNum) { + if ($pages[$pageNum] === $this) { + break; + } + } + + return $pageNum; + } + + /** + * Return the Object of the page if the document is a FPDF/FPDI document + * + * If the document was generated by FPDF/FPDI it returns the + * PDFObject of the given page + * + * @return PDFObject The PDFObject for the page + */ + public function getPDFObjectForFpdf(): PDFObject + { + $pageNum = $this->getPageNumber(); + $xObjects = $this->getXObjects(); + + return $xObjects[$pageNum]; + } + + /** + * Return a new PDFObject of the document created with FPDF/FPDI + * + * For a document generated by FPDF/FPDI, it generates a + * new PDFObject for that document + * + * @return PDFObject The PDFObject + */ + public function createPDFObjectForFpdf(): PDFObject + { + $pdfObject = $this->getPDFObjectForFpdf(); + $new_content = $pdfObject->getContent(); + $header = $pdfObject->getHeader(); + $config = $pdfObject->config; + + return new PDFObject($pdfObject->document, $header, $new_content, $config); + } + + /** + * Return page if document is a FPDF/FPDI document + * + * @return Page The page + */ + public function createPageForFpdf(): self + { + $pdfObject = $this->getPDFObjectForFpdf(); + $new_content = $pdfObject->getContent(); + $header = $pdfObject->getHeader(); + $config = $pdfObject->config; + + return new self($pdfObject->document, $header, $new_content, $config); + } + + public function getTextArray(?self $page = null): array + { + if ($this->isFpdf()) { + $pdfObject = $this->getPDFObjectForFpdf(); + $newPdfObject = $this->createPDFObjectForFpdf(); + + return $newPdfObject->getTextArray($pdfObject); + } else { + if ($contents = $this->get('Contents')) { + if ($contents instanceof ElementMissing) { + return []; + } elseif ($contents instanceof ElementNull) { + return []; + } elseif ($contents instanceof PDFObject) { + $elements = $contents->getHeader()->getElements(); + + if (is_numeric(key($elements))) { + $new_content = ''; + + /** @var PDFObject $element */ + foreach ($elements as $element) { + if ($element instanceof ElementXRef) { + $new_content .= $element->getObject()->getContent(); + } else { + $new_content .= $element->getContent(); + } + } + + $header = new Header([], $this->document); + $contents = new PDFObject($this->document, $header, $new_content, $this->config); + } else { + try { + $contents->getTextArray($this); + } catch (\Throwable $e) { + return $contents->getTextArray(); + } + } + } elseif ($contents instanceof ElementArray) { + // Create a virtual global content. + $new_content = ''; + + /** @var PDFObject $content */ + foreach ($contents->getContent() as $content) { + $new_content .= $content->getContent()."\n"; + } + + $header = new Header([], $this->document); + $contents = new PDFObject($this->document, $header, $new_content, $this->config); + } + + return $contents->getTextArray($this); + } + + return []; + } + } + + /** + * Gets all the text data with its internal representation of the page. + * + * Returns an array with the data and the internal representation + */ + public function extractRawData(): array + { + /* + * Now you can get the complete content of the object with the text on it + */ + $extractedData = []; + $content = $this->get('Contents'); + $values = $content->getContent(); + if (isset($values) && \is_array($values)) { + $text = ''; + foreach ($values as $section) { + $text .= $section->getContent(); + } + $sectionsText = $this->getSectionsText($text); + foreach ($sectionsText as $sectionText) { + $commandsText = $this->getCommandsText($sectionText); + foreach ($commandsText as $command) { + $extractedData[] = $command; + } + } + } else { + if ($this->isFpdf()) { + $content = $this->getPDFObjectForFpdf(); + } + $sectionsText = $content->getSectionsText($content->getContent()); + foreach ($sectionsText as $sectionText) { + $commandsText = $content->getCommandsText($sectionText); + foreach ($commandsText as $command) { + $extractedData[] = $command; + } + } + } + + return $extractedData; + } + + /** + * Gets all the decoded text data with it internal representation from a page. + * + * @param array $extractedRawData the extracted data return by extractRawData or + * null if extractRawData should be called + * + * @return array An array with the data and the internal representation + */ + public function extractDecodedRawData(?array $extractedRawData = null): array + { + if (!isset($extractedRawData) || !$extractedRawData) { + $extractedRawData = $this->extractRawData(); + } + $currentFont = null; /** @var Font $currentFont */ + $clippedFont = null; + $fpdfPage = null; + if ($this->isFpdf()) { + $fpdfPage = $this->createPageForFpdf(); + } + foreach ($extractedRawData as &$command) { + if ('Tj' == $command['o'] || 'TJ' == $command['o']) { + $data = $command['c']; + if (!\is_array($data)) { + $tmpText = ''; + if (isset($currentFont)) { + $tmpText = $currentFont->decodeOctal($data); + // $tmpText = $currentFont->decodeHexadecimal($tmpText, false); + } + $tmpText = str_replace( + ['\\\\', '\(', '\)', '\n', '\r', '\t', '\ '], + ['\\', '(', ')', "\n", "\r", "\t", ' '], + $tmpText + ); + $tmpText = mb_convert_encoding($tmpText, 'UTF-8', 'ISO-8859-1'); + if (isset($currentFont)) { + $tmpText = $currentFont->decodeContent($tmpText); + } + $command['c'] = $tmpText; + continue; + } + $numText = \count($data); + for ($i = 0; $i < $numText; ++$i) { + if (0 != ($i % 2)) { + continue; + } + $tmpText = $data[$i]['c']; + $decodedText = isset($currentFont) ? $currentFont->decodeOctal($tmpText) : $tmpText; + $decodedText = str_replace( + ['\\\\', '\(', '\)', '\n', '\r', '\t', '\ '], + ['\\', '(', ')', "\n", "\r", "\t", ' '], + $decodedText + ); + + $decodedText = mb_convert_encoding($decodedText, 'UTF-8', 'ISO-8859-1'); + + if (isset($currentFont)) { + $decodedText = $currentFont->decodeContent($decodedText); + } + $command['c'][$i]['c'] = $decodedText; + continue; + } + } elseif ('Tf' == $command['o'] || 'TF' == $command['o']) { + $fontId = explode(' ', $command['c'])[0]; + // If document is a FPDI/FPDF the $page has the correct font + $currentFont = isset($fpdfPage) ? $fpdfPage->getFont($fontId) : $this->getFont($fontId); + continue; + } elseif ('Q' == $command['o']) { + $currentFont = $clippedFont; + } elseif ('q' == $command['o']) { + $clippedFont = $currentFont; + } + } + + return $extractedRawData; + } + + /** + * Gets just the Text commands that are involved in text positions and + * Text Matrix (Tm) + * + * It extract just the PDF commands that are involved with text positions, and + * the Text Matrix (Tm). These are: BT, ET, TL, Td, TD, Tm, T*, Tj, ', ", and TJ + * + * @param array $extractedDecodedRawData The data extracted by extractDecodeRawData. + * If it is null, the method extractDecodeRawData is called. + * + * @return array An array with the text command of the page + */ + public function getDataCommands(?array $extractedDecodedRawData = null): array + { + if (!isset($extractedDecodedRawData) || !$extractedDecodedRawData) { + $extractedDecodedRawData = $this->extractDecodedRawData(); + } + $extractedData = []; + foreach ($extractedDecodedRawData as $command) { + switch ($command['o']) { + /* + * BT + * Begin a text object, inicializind the Tm and Tlm to identity matrix + */ + case 'BT': + $extractedData[] = $command; + break; + /* + * cm + * Concatenation Matrix that will transform all following Tm + */ + case 'cm': + $extractedData[] = $command; + break; + /* + * ET + * End a text object, discarding the text matrix + */ + case 'ET': + $extractedData[] = $command; + break; + + /* + * leading TL + * Set the text leading, Tl, to leading. Tl is used by the T*, ' and " operators. + * Initial value: 0 + */ + case 'TL': + $extractedData[] = $command; + break; + + /* + * tx ty Td + * Move to the start of the next line, offset form the start of the + * current line by tx, ty. + */ + case 'Td': + $extractedData[] = $command; + break; + + /* + * tx ty TD + * Move to the start of the next line, offset form the start of the + * current line by tx, ty. As a side effect, this operator set the leading + * parameter in the text state. This operator has the same effect as the + * code: + * -ty TL + * tx ty Td + */ + case 'TD': + $extractedData[] = $command; + break; + + /* + * a b c d e f Tm + * Set the text matrix, Tm, and the text line matrix, Tlm. The operands are + * all numbers, and the initial value for Tm and Tlm is the identity matrix + * [1 0 0 1 0 0] + */ + case 'Tm': + $extractedData[] = $command; + break; + + /* + * T* + * Move to the start of the next line. This operator has the same effect + * as the code: + * 0 Tl Td + * Where Tl is the current leading parameter in the text state. + */ + case 'T*': + $extractedData[] = $command; + break; + + /* + * string Tj + * Show a Text String + */ + case 'Tj': + $extractedData[] = $command; + break; + + /* + * string ' + * Move to the next line and show a text string. This operator has the + * same effect as the code: + * T* + * string Tj + */ + case "'": + $extractedData[] = $command; + break; + + /* + * aw ac string " + * Move to the next lkine and show a text string, using aw as the word + * spacing and ac as the character spacing. This operator has the same + * effect as the code: + * aw Tw + * ac Tc + * string ' + * Tw set the word spacing, Tw, to wordSpace. + * Tc Set the character spacing, Tc, to charsSpace. + */ + case '"': + $extractedData[] = $command; + break; + + case 'Tf': + case 'TF': + $extractedData[] = $command; + break; + + /* + * array TJ + * Show one or more text strings allow individual glyph positioning. + * Each lement of array con be a string or a number. If the element is + * a string, this operator shows the string. If it is a number, the + * operator adjust the text position by that amount; that is, it translates + * the text matrix, Tm. This amount is substracted form the current + * horizontal or vertical coordinate, depending on the writing mode. + * in the default coordinate system, a positive adjustment has the effect + * of moving the next glyph painted either to the left or down by the given + * amount. + */ + case 'TJ': + $extractedData[] = $command; + break; + /* + * q + * Save current graphics state to stack + */ + case 'q': + /* + * Q + * Load last saved graphics state from stack + */ + case 'Q': + $extractedData[] = $command; + break; + default: + } + } + + return $extractedData; + } + + /** + * Gets the Text Matrix of the text in the page + * + * Return an array where every item is an array where the first item is the + * Text Matrix (Tm) and the second is a string with the text data. The Text matrix + * is an array of 6 numbers. The last 2 numbers are the coordinates X and Y of the + * text. The first 4 numbers has to be with Scalation, Rotation and Skew of the text. + * + * @param array $dataCommands the data extracted by getDataCommands + * if null getDataCommands is called + * + * @return array an array with the data of the page including the Tm information + * of any text in the page + */ + public function getDataTm(?array $dataCommands = null): array + { + if (!isset($dataCommands) || !$dataCommands) { + $dataCommands = $this->getDataCommands(); + } + + /* + * At the beginning of a text object Tm is the identity matrix + */ + $defaultTm = ['1', '0', '0', '1', '0', '0']; + $concatTm = ['1', '0', '0', '1', '0', '0']; + $graphicsStatesStack = []; + /* + * Set the text leading used by T*, ' and " operators + */ + $defaultTl = 0; + + /* + * Set default values for font data + */ + $defaultFontId = -1; + $defaultFontSize = 1; + + /* + * Indexes of horizontal/vertical scaling and X,Y-coordinates in the matrix (Tm) + */ + $hSc = 0; // horizontal scaling + /** + * index of vertical scaling in the array that encodes the text matrix. + * for more information: https://github.com/smalot/pdfparser/pull/559#discussion_r1053415500 + */ + $vSc = 3; + $x = 4; + $y = 5; + + /* + * x,y-coordinates of text space origin in user units + * + * These will be assigned the value of the currently printed string + */ + $Tx = 0; + $Ty = 0; + + $Tm = $defaultTm; + $Tl = $defaultTl; + $fontId = $defaultFontId; + $fontSize = $defaultFontSize; // reflects fontSize set by Tf or Tfs + + $extractedTexts = $this->getTextArray(); + $extractedData = []; + foreach ($dataCommands as $command) { + // If we've used up all the texts from getTextArray(), exit + // so we aren't accessing non-existent array indices + // Fixes 'undefined array key' errors in Issues #575, #576 + if (\count($extractedTexts) <= \count($extractedData)) { + break; + } + $currentText = $extractedTexts[\count($extractedData)]; + switch ($command['o']) { + /* + * BT + * Begin a text object, initializing the Tm and Tlm to identity matrix + */ + case 'BT': + $Tm = $defaultTm; + $Tl = $defaultTl; + $Tx = 0; + $Ty = 0; + break; + + case 'cm': + $newConcatTm = (array) explode(' ', $command['c']); + $TempMatrix = []; + // Multiply with previous concatTm + $TempMatrix[0] = (float) $concatTm[0] * (float) $newConcatTm[0] + (float) $concatTm[1] * (float) $newConcatTm[2]; + $TempMatrix[1] = (float) $concatTm[0] * (float) $newConcatTm[1] + (float) $concatTm[1] * (float) $newConcatTm[3]; + $TempMatrix[2] = (float) $concatTm[2] * (float) $newConcatTm[0] + (float) $concatTm[3] * (float) $newConcatTm[2]; + $TempMatrix[3] = (float) $concatTm[2] * (float) $newConcatTm[1] + (float) $concatTm[3] * (float) $newConcatTm[3]; + $TempMatrix[4] = (float) $concatTm[4] * (float) $newConcatTm[0] + (float) $concatTm[5] * (float) $newConcatTm[2] + (float) $newConcatTm[4]; + $TempMatrix[5] = (float) $concatTm[4] * (float) $newConcatTm[1] + (float) $concatTm[5] * (float) $newConcatTm[3] + (float) $newConcatTm[5]; + $concatTm = $TempMatrix; + break; + /* + * ET + * End a text object + */ + case 'ET': + break; + + /* + * text leading TL + * Set the text leading, Tl, to leading. Tl is used by the T*, ' and " operators. + * Initial value: 0 + */ + case 'TL': + // scaled text leading + $Tl = (float) $command['c'] * (float) $Tm[$vSc]; + break; + + /* + * tx ty Td + * Move to the start of the next line, offset from the start of the + * current line by tx, ty. + */ + case 'Td': + $coord = explode(' ', $command['c']); + $Tx += (float) $coord[0] * (float) $Tm[$hSc]; + $Ty += (float) $coord[1] * (float) $Tm[$vSc]; + $Tm[$x] = (string) $Tx; + $Tm[$y] = (string) $Ty; + break; + + /* + * tx ty TD + * Move to the start of the next line, offset form the start of the + * current line by tx, ty. As a side effect, this operator set the leading + * parameter in the text state. This operator has the same effect as the + * code: + * -ty TL + * tx ty Td + */ + case 'TD': + $coord = explode(' ', $command['c']); + $Tl = -((float) $coord[1] * (float) $Tm[$vSc]); + $Tx += (float) $coord[0] * (float) $Tm[$hSc]; + $Ty += (float) $coord[1] * (float) $Tm[$vSc]; + $Tm[$x] = (string) $Tx; + $Tm[$y] = (string) $Ty; + break; + + /* + * a b c d e f Tm + * Set the text matrix, Tm, and the text line matrix, Tlm. The operands are + * all numbers, and the initial value for Tm and Tlm is the identity matrix + * [1 0 0 1 0 0] + */ + case 'Tm': + $Tm = explode(' ', $command['c']); + $TempMatrix = []; + $TempMatrix[0] = (float) $Tm[0] * (float) $concatTm[0] + (float) $Tm[1] * (float) $concatTm[2]; + $TempMatrix[1] = (float) $Tm[0] * (float) $concatTm[1] + (float) $Tm[1] * (float) $concatTm[3]; + $TempMatrix[2] = (float) $Tm[2] * (float) $concatTm[0] + (float) $Tm[3] * (float) $concatTm[2]; + $TempMatrix[3] = (float) $Tm[2] * (float) $concatTm[1] + (float) $Tm[3] * (float) $concatTm[3]; + $TempMatrix[4] = (float) $Tm[4] * (float) $concatTm[0] + (float) $Tm[5] * (float) $concatTm[2] + (float) $concatTm[4]; + $TempMatrix[5] = (float) $Tm[4] * (float) $concatTm[1] + (float) $Tm[5] * (float) $concatTm[3] + (float) $concatTm[5]; + $Tm = $TempMatrix; + $Tx = (float) $Tm[$x]; + $Ty = (float) $Tm[$y]; + break; + + /* + * T* + * Move to the start of the next line. This operator has the same effect + * as the code: + * 0 Tl Td + * Where Tl is the current leading parameter in the text state. + */ + case 'T*': + $Ty -= $Tl; + $Tm[$y] = (string) $Ty; + break; + + /* + * string Tj + * Show a Text String + */ + case 'Tj': + $data = [$Tm, $currentText]; + if ($this->config->getDataTmFontInfoHasToBeIncluded()) { + $data[] = $fontId; + $data[] = $fontSize; + } + $extractedData[] = $data; + break; + + /* + * string ' + * Move to the next line and show a text string. This operator has the + * same effect as the code: + * T* + * string Tj + */ + case "'": + $Ty -= $Tl; + $Tm[$y] = (string) $Ty; + $extractedData[] = [$Tm, $currentText]; + break; + + /* + * aw ac string " + * Move to the next line and show a text string, using aw as the word + * spacing and ac as the character spacing. This operator has the same + * effect as the code: + * aw Tw + * ac Tc + * string ' + * Tw set the word spacing, Tw, to wordSpace. + * Tc Set the character spacing, Tc, to charsSpace. + */ + case '"': + $data = explode(' ', $currentText); + $Ty -= $Tl; + $Tm[$y] = (string) $Ty; + $extractedData[] = [$Tm, $data[2]]; // Verify + break; + + case 'Tf': + /* + * From PDF 1.0 specification, page 106: + * fontname size Tf Set font and size + * Sets the text font and text size in the graphics state. There is no default value for + * either fontname or size; they must be selected using Tf before drawing any text. + * fontname is a resource name. size is a number expressed in text space units. + * + * Source: https://ia902503.us.archive.org/10/items/pdfy-0vt8s-egqFwDl7L2/PDF%20Reference%201.0.pdf + * Introduced with https://github.com/smalot/pdfparser/pull/516 + */ + list($fontId, $fontSize) = explode(' ', $command['c'], 2); + break; + + /* + * array TJ + * Show one or more text strings allow individual glyph positioning. + * Each lement of array con be a string or a number. If the element is + * a string, this operator shows the string. If it is a number, the + * operator adjust the text position by that amount; that is, it translates + * the text matrix, Tm. This amount is substracted form the current + * horizontal or vertical coordinate, depending on the writing mode. + * in the default coordinate system, a positive adjustment has the effect + * of moving the next glyph painted either to the left or down by the given + * amount. + */ + case 'TJ': + $data = [$Tm, $currentText]; + if ($this->config->getDataTmFontInfoHasToBeIncluded()) { + $data[] = $fontId; + $data[] = $fontSize; + } + $extractedData[] = $data; + break; + /* + * q + * Save current graphics state to stack + */ + case 'q': + $graphicsStatesStack[] = $concatTm; + break; + /* + * Q + * Load last saved graphics state from stack + */ + case 'Q': + $concatTm = array_pop($graphicsStatesStack); + break; + default: + } + } + $this->dataTm = $extractedData; + + return $extractedData; + } + + /** + * Gets text data that are around the given coordinates (X,Y) + * + * If the text is in near the given coordinates (X,Y) (or the TM info), + * the text is returned. The extractedData return by getDataTm, could be use to see + * where is the coordinates of a given text, using the TM info for it. + * + * @param float $x The X value of the coordinate to search for. if null + * just the Y value is considered (same Row) + * @param float $y The Y value of the coordinate to search for + * just the X value is considered (same column) + * @param float $xError The value less or more to consider an X to be "near" + * @param float $yError The value less or more to consider an Y to be "near" + * + * @return array An array of text that are near the given coordinates. If no text + * "near" the x,y coordinate, an empty array is returned. If Both, x + * and y coordinates are null, null is returned. + */ + public function getTextXY(?float $x = null, ?float $y = null, float $xError = 0, float $yError = 0): array + { + if (!isset($this->dataTm) || !$this->dataTm) { + $this->getDataTm(); + } + + if (null !== $x) { + $x = (float) $x; + } + + if (null !== $y) { + $y = (float) $y; + } + + if (null === $x && null === $y) { + return []; + } + + $xError = (float) $xError; + $yError = (float) $yError; + + $extractedData = []; + foreach ($this->dataTm as $item) { + $tm = $item[0]; + $xTm = (float) $tm[4]; + $yTm = (float) $tm[5]; + $text = $item[1]; + if (null === $y) { + if (($xTm >= ($x - $xError)) + && ($xTm <= ($x + $xError))) { + $extractedData[] = [$tm, $text]; + continue; + } + } + if (null === $x) { + if (($yTm >= ($y - $yError)) + && ($yTm <= ($y + $yError))) { + $extractedData[] = [$tm, $text]; + continue; + } + } + if (($xTm >= ($x - $xError)) + && ($xTm <= ($x + $xError)) + && ($yTm >= ($y - $yError)) + && ($yTm <= ($y + $yError))) { + $extractedData[] = [$tm, $text]; + continue; + } + } + + return $extractedData; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Pages.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Pages.php new file mode 100644 index 0000000..f95134b --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Pages.php @@ -0,0 +1,131 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Element\ElementArray; + +/** + * Class Pages + */ +class Pages extends PDFObject +{ + /** + * @var array<\Smalot\PdfParser\Font>|null + */ + protected $fonts; + + /** + * @todo Objects other than Pages or Page might need to be treated specifically + * in order to get Page objects out of them. + * + * @see https://github.com/smalot/pdfparser/issues/331 + */ + public function getPages(bool $deep = false): array + { + if (!$this->has('Kids')) { + return []; + } + + /** @var ElementArray $kidsElement */ + $kidsElement = $this->get('Kids'); + + if (!$deep) { + return $kidsElement->getContent(); + } + + // Prepare to apply the Pages' object's fonts to each page + if (false === \is_array($this->fonts)) { + $this->setupFonts(); + } + $fontsAvailable = 0 < \count($this->fonts); + + $kids = $kidsElement->getContent(); + $pages = []; + + foreach ($kids as $kid) { + if ($kid instanceof self) { + $pages = array_merge($pages, $kid->getPages(true)); + } elseif ($kid instanceof Page) { + if ($fontsAvailable) { + $kid->setFonts($this->fonts); + } + $pages[] = $kid; + } + } + + return $pages; + } + + /** + * Gathers information about fonts and collects them in a list. + * + * @return void + * + * @internal + */ + protected function setupFonts() + { + $resources = $this->get('Resources'); + + if (method_exists($resources, 'has') && $resources->has('Font')) { + // no fonts available, therefore stop here + if ($resources->get('Font') instanceof Element\ElementMissing) { + return; + } + + if ($resources->get('Font') instanceof Header) { + $fonts = $resources->get('Font')->getElements(); + } else { + $fonts = $resources->get('Font')->getHeader()->getElements(); + } + + $table = []; + + foreach ($fonts as $id => $font) { + if ($font instanceof Font) { + $table[$id] = $font; + + // Store too on cleaned id value (only numeric) + $id = preg_replace('/[^0-9\.\-_]/', '', $id); + if ('' != $id) { + $table[$id] = $font; + } + } + } + + $this->fonts = $table; + } else { + $this->fonts = []; + } + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/Parser.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Parser.php new file mode 100644 index 0000000..b051f11 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/Parser.php @@ -0,0 +1,331 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser; + +use Smalot\PdfParser\Element\ElementArray; +use Smalot\PdfParser\Element\ElementBoolean; +use Smalot\PdfParser\Element\ElementDate; +use Smalot\PdfParser\Element\ElementHexa; +use Smalot\PdfParser\Element\ElementName; +use Smalot\PdfParser\Element\ElementNull; +use Smalot\PdfParser\Element\ElementNumeric; +use Smalot\PdfParser\Element\ElementString; +use Smalot\PdfParser\Element\ElementXRef; +use Smalot\PdfParser\RawData\RawDataParser; + +/** + * Class Parser + */ +class Parser +{ + /** + * @var Config + */ + private $config; + + /** + * @var PDFObject[] + */ + protected $objects = []; + + protected $rawDataParser; + + public function __construct($cfg = [], ?Config $config = null) + { + $this->config = $config ?: new Config(); + $this->rawDataParser = new RawDataParser($cfg, $this->config); + } + + public function getConfig(): Config + { + return $this->config; + } + + /** + * @throws \Exception + */ + public function parseFile(string $filename): Document + { + $content = file_get_contents($filename); + + /* + * 2018/06/20 @doganoo as multiple times a + * users have complained that the parseFile() + * method dies silently, it is an better option + * to remove the error control operator (@) and + * let the users know that the method throws an exception + * by adding @throws tag to PHPDoc. + * + * See here for an example: https://github.com/smalot/pdfparser/issues/204 + */ + return $this->parseContent($content); + } + + /** + * @param string $content PDF content to parse + * + * @throws \Exception if secured PDF file was detected + * @throws \Exception if no object list was found + */ + public function parseContent(string $content): Document + { + // Create structure from raw data. + list($xref, $data) = $this->rawDataParser->parseData($content); + + if (isset($xref['trailer']['encrypt']) && false === $this->config->getIgnoreEncryption()) { + throw new \Exception('Secured pdf file are currently not supported.'); + } + + if (empty($data)) { + throw new \Exception('Object list not found. Possible secured file.'); + } + + // Create destination object. + $document = new Document(); + $this->objects = []; + + foreach ($data as $id => $structure) { + $this->parseObject($id, $structure, $document); + unset($data[$id]); + } + + $document->setTrailer($this->parseTrailer($xref['trailer'], $document)); + $document->setObjects($this->objects); + + return $document; + } + + protected function parseTrailer(array $structure, ?Document $document) + { + $trailer = []; + + foreach ($structure as $name => $values) { + $name = ucfirst($name); + + if (is_numeric($values)) { + $trailer[$name] = new ElementNumeric($values); + } elseif (\is_array($values)) { + $value = $this->parseTrailer($values, null); + $trailer[$name] = new ElementArray($value, null); + } elseif (false !== strpos($values, '_')) { + $trailer[$name] = new ElementXRef($values, $document); + } else { + $trailer[$name] = $this->parseHeaderElement('(', $values, $document); + } + } + + return new Header($trailer, $document); + } + + protected function parseObject(string $id, array $structure, ?Document $document) + { + $header = new Header([], $document); + $content = ''; + + foreach ($structure as $position => $part) { + if (\is_int($part)) { + $part = [null, null]; + } + switch ($part[0]) { + case '[': + $elements = []; + + foreach ($part[1] as $sub_element) { + $sub_type = $sub_element[0]; + $sub_value = $sub_element[1]; + $elements[] = $this->parseHeaderElement($sub_type, $sub_value, $document); + } + + $header = new Header($elements, $document); + break; + + case '<<': + $header = $this->parseHeader($part[1], $document); + break; + + case 'stream': + $content = isset($part[3][0]) ? $part[3][0] : $part[1]; + + if ($header->get('Type')->equals('ObjStm')) { + $match = []; + + // Split xrefs and contents. + preg_match('/^((\d+\s+\d+\s*)*)(.*)$/s', $content, $match); + $content = $match[3]; + + // Extract xrefs. + $xrefs = preg_split( + '/(\d+\s+\d+\s*)/s', + $match[1], + -1, + \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE + ); + $table = []; + + foreach ($xrefs as $xref) { + list($id, $position) = preg_split("/\s+/", trim($xref)); + $table[$position] = $id; + } + + ksort($table); + + $ids = array_values($table); + $positions = array_keys($table); + + foreach ($positions as $index => $position) { + $id = $ids[$index].'_0'; + $next_position = isset($positions[$index + 1]) ? $positions[$index + 1] : \strlen($content); + $sub_content = substr($content, $position, (int) $next_position - (int) $position); + + $sub_header = Header::parse($sub_content, $document); + $object = PDFObject::factory($document, $sub_header, '', $this->config); + $this->objects[$id] = $object; + } + + // It is not necessary to store this content. + + return; + } elseif ($header->get('Type')->equals('Metadata')) { + // Attempt to parse XMP XML Metadata + $document->extractXMPMetadata($content); + } + break; + + default: + if ('null' != $part) { + $element = $this->parseHeaderElement($part[0], $part[1], $document); + + if ($element) { + $header = new Header([$element], $document); + } + } + break; + } + } + + if (!isset($this->objects[$id])) { + $this->objects[$id] = PDFObject::factory($document, $header, $content, $this->config); + } + } + + /** + * @throws \Exception + */ + protected function parseHeader(array $structure, ?Document $document): Header + { + $elements = []; + $count = \count($structure); + + for ($position = 0; $position < $count; $position += 2) { + $name = $structure[$position][1]; + $type = $structure[$position + 1][0]; + $value = $structure[$position + 1][1]; + + $elements[$name] = $this->parseHeaderElement($type, $value, $document); + } + + return new Header($elements, $document); + } + + /** + * @param string|array $value + * + * @return Element|Header|null + * + * @throws \Exception + */ + protected function parseHeaderElement(?string $type, $value, ?Document $document) + { + $valueIsEmpty = null == $value || '' == $value || false == $value; + if (('<<' === $type || '>>' === $type) && $valueIsEmpty) { + $value = []; + } + + switch ($type) { + case '<<': + case '>>': + $header = $this->parseHeader($value, $document); + PDFObject::factory($document, $header, null, $this->config); + + return $header; + + case 'numeric': + return new ElementNumeric($value); + + case 'boolean': + return new ElementBoolean($value); + + case 'null': + return new ElementNull(); + + case '(': + if ($date = ElementDate::parse('('.$value.')', $document)) { + return $date; + } + + return ElementString::parse('('.$value.')', $document); + + case '<': + return $this->parseHeaderElement('(', ElementHexa::decode($value), $document); + + case '/': + return ElementName::parse('/'.$value, $document); + + case 'ojbref': // old mistake in tcpdf parser + case 'objref': + return new ElementXRef($value, $document); + + case '[': + $values = []; + + if (\is_array($value)) { + foreach ($value as $sub_element) { + $sub_type = $sub_element[0]; + $sub_value = $sub_element[1]; + $values[] = $this->parseHeaderElement($sub_type, $sub_value, $document); + } + } + + return new ElementArray($values, $document); + + case 'endstream': + case 'obj': // I don't know what it means but got my project fixed. + case '': + // Nothing to do with. + return null; + + default: + throw new \Exception('Invalid type: "'.$type.'".'); + } + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/RawData/FilterHelper.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/RawData/FilterHelper.php new file mode 100644 index 0000000..87f5524 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/RawData/FilterHelper.php @@ -0,0 +1,427 @@ + + * + * @date 2020-01-06 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\RawData; + +use Smalot\PdfParser\Exception\NotImplementedException; + +class FilterHelper +{ + protected $availableFilters = ['ASCIIHexDecode', 'ASCII85Decode', 'LZWDecode', 'FlateDecode', 'RunLengthDecode']; + + /** + * Decode data using the specified filter type. + * + * @param string $filter Filter name + * @param string $data Data to decode + * + * @return string Decoded data string + * + * @throws \Exception + * @throws \Smalot\PdfParser\Exception\NotImplementedException if a certain decode function is not implemented yet + */ + public function decodeFilter(string $filter, string $data, int $decodeMemoryLimit = 0): string + { + switch ($filter) { + case 'ASCIIHexDecode': + return $this->decodeFilterASCIIHexDecode($data); + + case 'ASCII85Decode': + return $this->decodeFilterASCII85Decode($data); + + case 'LZWDecode': + return $this->decodeFilterLZWDecode($data); + + case 'FlateDecode': + return $this->decodeFilterFlateDecode($data, $decodeMemoryLimit); + + case 'RunLengthDecode': + return $this->decodeFilterRunLengthDecode($data); + + case 'CCITTFaxDecode': + throw new NotImplementedException('Decode CCITTFaxDecode not implemented yet.'); + case 'JBIG2Decode': + throw new NotImplementedException('Decode JBIG2Decode not implemented yet.'); + case 'DCTDecode': + throw new NotImplementedException('Decode DCTDecode not implemented yet.'); + case 'JPXDecode': + throw new NotImplementedException('Decode JPXDecode not implemented yet.'); + case 'Crypt': + throw new NotImplementedException('Decode Crypt not implemented yet.'); + default: + return $data; + } + } + + /** + * ASCIIHexDecode + * + * Decodes data encoded in an ASCII hexadecimal representation, reproducing the original binary data. + * + * @param string $data Data to decode + * + * @return string data string + * + * @throws \Exception + */ + protected function decodeFilterASCIIHexDecode(string $data): string + { + // all white-space characters shall be ignored + $data = preg_replace('/[\s]/', '', $data); + // check for EOD character: GREATER-THAN SIGN (3Eh) + $eod = strpos($data, '>'); + if (false !== $eod) { + // remove EOD and extra data (if any) + $data = substr($data, 0, $eod); + $eod = true; + } + // get data length + $data_length = \strlen($data); + if (0 != ($data_length % 2)) { + // odd number of hexadecimal digits + if ($eod) { + // EOD shall behave as if a 0 (zero) followed the last digit + $data = substr($data, 0, -1).'0'.substr($data, -1); + } else { + throw new \Exception('decodeFilterASCIIHexDecode: invalid code'); + } + } + // check for invalid characters + if (preg_match('/[^a-fA-F\d]/', $data) > 0) { + throw new \Exception('decodeFilterASCIIHexDecode: invalid code'); + } + // get one byte of binary data for each pair of ASCII hexadecimal digits + $decoded = pack('H*', $data); + + return $decoded; + } + + /** + * ASCII85Decode + * + * Decodes data encoded in an ASCII base-85 representation, reproducing the original binary data. + * + * @param string $data Data to decode + * + * @return string data string + * + * @throws \Exception + */ + protected function decodeFilterASCII85Decode(string $data): string + { + // initialize string to return + $decoded = ''; + // all white-space characters shall be ignored + $data = preg_replace('/[\s]/', '', $data); + // remove start sequence 2-character sequence <~ (3Ch)(7Eh) + if (0 === strpos($data, '<~')) { + // remove EOD and extra data (if any) + $data = substr($data, 2); + } + // check for EOD: 2-character sequence ~> (7Eh)(3Eh) + $eod = strpos($data, '~>'); + if (\strlen($data) - 2 === $eod) { + // remove EOD and extra data (if any) + $data = substr($data, 0, $eod); + } + // data length + $data_length = \strlen($data); + // check for invalid characters + if (preg_match('/[^\x21-\x75,\x74]/', $data) > 0) { + throw new \Exception('decodeFilterASCII85Decode: invalid code'); + } + // z sequence + $zseq = \chr(0).\chr(0).\chr(0).\chr(0); + // position inside a group of 4 bytes (0-3) + $group_pos = 0; + $tuple = 0; + $pow85 = [85 * 85 * 85 * 85, 85 * 85 * 85, 85 * 85, 85, 1]; + + // for each byte + for ($i = 0; $i < $data_length; ++$i) { + // get char value + $char = \ord($data[$i]); + if (122 == $char) { // 'z' + if (0 == $group_pos) { + $decoded .= $zseq; + } else { + throw new \Exception('decodeFilterASCII85Decode: invalid code'); + } + } else { + // the value represented by a group of 5 characters should never be greater than 2^32 - 1 + $tuple += (($char - 33) * $pow85[$group_pos]); + if (4 == $group_pos) { + // The following if-clauses are an attempt to fix/suppress the following deprecation warning: + // chr(): Providing a value not in-between 0 and 255 is deprecated, this is because a byte value + // must be in the [0, 255] interval. The value used will be constrained using % 256 + // I know this is ugly and there might be more fancier ways. If you know one, feel free to provide a pull request. + if (255 < $tuple >> 8) { + $chr8Part = \chr(($tuple >> 8) % 256); + } else { + $chr8Part = \chr($tuple >> 8); + } + + if (255 < $tuple >> 16) { + $chr16Part = \chr(($tuple >> 16) % 256); + } else { + $chr16Part = \chr($tuple >> 16); + } + + if (255 < $tuple >> 24) { + $chr24Part = \chr(($tuple >> 24) % 256); + } else { + $chr24Part = \chr(($tuple >> 24) & 0xFF); + } + + if (255 < $tuple) { + $chrTuple = \chr($tuple % 256); + } else { + $chrTuple = \chr($tuple); + } + + $decoded .= $chr24Part . $chr16Part . $chr8Part . $chrTuple; + $tuple = 0; + $group_pos = 0; + } else { + ++$group_pos; + } + } + } + if ($group_pos > 1) { + $tuple += $pow85[$group_pos - 1]; + } + // last tuple (if any) + switch ($group_pos) { + case 4: + $decoded .= \chr(($tuple >> 24) & 0xFF).\chr(($tuple >> 16) & 0xFF).\chr(($tuple >> 8) & 0xFF); + break; + + case 3: + $decoded .= \chr(($tuple >> 24) & 0xFF).\chr(($tuple >> 16) & 0xFF); + break; + + case 2: + $decoded .= \chr(($tuple >> 24) & 0xFF); + break; + + case 1: + throw new \Exception('decodeFilterASCII85Decode: invalid code'); + } + + return $decoded; + } + + /** + * FlateDecode + * + * Decompresses data encoded using the zlib/deflate compression method, reproducing the original text or binary data. + * + * @param string $data Data to decode + * @param int $decodeMemoryLimit Memory limit on deflation + * + * @return string data string + * + * @throws \Exception + */ + protected function decodeFilterFlateDecode(string $data, int $decodeMemoryLimit): ?string + { + // Uncatchable E_WARNING for "data error" is @ suppressed + // so execution may proceed with an alternate decompression + // method. + $decoded = @gzuncompress($data, $decodeMemoryLimit); + + if (false === $decoded) { + // If gzuncompress() failed, try again using the compress.zlib:// + // wrapper to decode it in a file-based context. + // See: https://www.php.net/manual/en/function.gzuncompress.php#79042 + // Issue: https://github.com/smalot/pdfparser/issues/592 + $ztmp = tmpfile(); + if (false != $ztmp) { + fwrite($ztmp, "\x1f\x8b\x08\x00\x00\x00\x00\x00".$data); + $file = stream_get_meta_data($ztmp)['uri']; + if (0 === $decodeMemoryLimit) { + $decoded = file_get_contents('compress.zlib://'.$file); + } else { + $decoded = file_get_contents('compress.zlib://'.$file, false, null, 0, $decodeMemoryLimit); + } + fclose($ztmp); + } + } + + if (false === \is_string($decoded) || '' === $decoded) { + // If the decoded string is empty, that means decoding failed. + throw new \Exception('decodeFilterFlateDecode: invalid data'); + } + + return $decoded; + } + + /** + * LZWDecode + * + * Decompresses data encoded using the LZW (Lempel-Ziv-Welch) adaptive compression method, reproducing the original text or binary data. + * + * @param string $data Data to decode + * + * @return string Data string + */ + protected function decodeFilterLZWDecode(string $data): string + { + // initialize string to return + $decoded = ''; + // data length + $data_length = \strlen($data); + // convert string to binary string + $bitstring = ''; + for ($i = 0; $i < $data_length; ++$i) { + $bitstring .= \sprintf('%08b', \ord($data[$i])); + } + // get the number of bits + $data_length = \strlen($bitstring); + // initialize code length in bits + $bitlen = 9; + // initialize dictionary index + $dix = 258; + // initialize the dictionary (with the first 256 entries). + $dictionary = []; + for ($i = 0; $i < 256; ++$i) { + $dictionary[$i] = \chr($i); + } + // previous val + $prev_index = 0; + // while we encounter EOD marker (257), read code_length bits + while (($data_length > 0) && (257 != ($index = bindec(substr($bitstring, 0, $bitlen))))) { + // remove read bits from string + $bitstring = substr($bitstring, $bitlen); + // update number of bits + $data_length -= $bitlen; + if (256 == $index) { // clear-table marker + // reset code length in bits + $bitlen = 9; + // reset dictionary index + $dix = 258; + $prev_index = 256; + // reset the dictionary (with the first 256 entries). + $dictionary = []; + for ($i = 0; $i < 256; ++$i) { + $dictionary[$i] = \chr($i); + } + } elseif (256 == $prev_index) { + // first entry + $decoded .= $dictionary[$index]; + $prev_index = $index; + } else { + // check if index exist in the dictionary + if ($index < $dix) { + // index exist on dictionary + $decoded .= $dictionary[$index]; + $dic_val = $dictionary[$prev_index].$dictionary[$index][0]; + // store current index + $prev_index = $index; + } else { + // index do not exist on dictionary + $dic_val = $dictionary[$prev_index].$dictionary[$prev_index][0]; + $decoded .= $dic_val; + } + // update dictionary + $dictionary[$dix] = $dic_val; + ++$dix; + // change bit length by case + if (2047 == $dix) { + $bitlen = 12; + } elseif (1023 == $dix) { + $bitlen = 11; + } elseif (511 == $dix) { + $bitlen = 10; + } + } + } + + return $decoded; + } + + /** + * RunLengthDecode + * + * Decompresses data encoded using a byte-oriented run-length encoding algorithm. + * + * @param string $data Data to decode + */ + protected function decodeFilterRunLengthDecode(string $data): string + { + // initialize string to return + $decoded = ''; + // data length + $data_length = \strlen($data); + $i = 0; + while ($i < $data_length) { + // get current byte value + $byte = \ord($data[$i]); + if (128 == $byte) { + // a length value of 128 denote EOD + break; + } elseif ($byte < 128) { + // if the length byte is in the range 0 to 127 + // the following length + 1 (1 to 128) bytes shall be copied literally during decompression + $decoded .= substr($data, $i + 1, $byte + 1); + // move to next block + $i += ($byte + 2); + } else { + // if length is in the range 129 to 255, + // the following single byte shall be copied 257 - length (2 to 128) times during decompression + $decoded .= str_repeat($data[$i + 1], 257 - $byte); + // move to next block + $i += 2; + } + } + + return $decoded; + } + + /** + * @return array list of available filters + */ + public function getAvailableFilters(): array + { + return $this->availableFilters; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/RawData/RawDataParser.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/RawData/RawDataParser.php new file mode 100644 index 0000000..ec8d01e --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/RawData/RawDataParser.php @@ -0,0 +1,990 @@ + + * + * @date 2020-01-06 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\RawData; + +use Smalot\PdfParser\Config; +use Smalot\PdfParser\Exception\EmptyPdfException; +use Smalot\PdfParser\Exception\MissingPdfHeaderException; + +class RawDataParser +{ + /** + * @var Config + */ + private $config; + + /** + * Configuration array. + * + * @var array + */ + protected $cfg = [ + // if `true` ignore filter decoding errors + 'ignore_filter_decoding_errors' => true, + // if `true` ignore missing filter decoding errors + 'ignore_missing_filter_decoders' => true, + ]; + + protected $filterHelper; + protected $objects; + + /** + * @param array $cfg Configuration array, default is [] + */ + public function __construct($cfg = [], ?Config $config = null) + { + // merge given array with default values + $this->cfg = array_merge($this->cfg, $cfg); + + $this->filterHelper = new FilterHelper(); + $this->config = $config ?: new Config(); + } + + /** + * Decode the specified stream. + * + * @param string $pdfData PDF data + * @param array $sdic Stream's dictionary array + * @param string $stream Stream to decode + * + * @return array containing decoded stream data and remaining filters + * + * @throws \Exception + */ + protected function decodeStream(string $pdfData, array $xref, array $sdic, string $stream): array + { + // get stream length and filters + $slength = \strlen($stream); + if ($slength <= 0) { + return ['', []]; + } + $filters = []; + foreach ($sdic as $k => $v) { + if ('/' == $v[0]) { + if (('Length' == $v[1]) && (isset($sdic[$k + 1])) && ('numeric' == $sdic[$k + 1][0])) { + // get declared stream length + $declength = (int) $sdic[$k + 1][1]; + if ($declength < $slength) { + $stream = substr($stream, 0, $declength); + $slength = $declength; + } + } elseif (('Filter' == $v[1]) && (isset($sdic[$k + 1]))) { + // resolve indirect object + $objval = $this->getObjectVal($pdfData, $xref, $sdic[$k + 1]); + if ('/' == $objval[0]) { + // single filter + $filters[] = $objval[1]; + } elseif ('[' == $objval[0]) { + // array of filters + foreach ($objval[1] as $flt) { + if ('/' == $flt[0]) { + $filters[] = $flt[1]; + } + } + } + } + } + } + + // decode the stream + $remaining_filters = []; + foreach ($filters as $filter) { + if (\in_array($filter, $this->filterHelper->getAvailableFilters(), true)) { + try { + $stream = $this->filterHelper->decodeFilter($filter, $stream, $this->config->getDecodeMemoryLimit()); + } catch (\Exception $e) { + $emsg = $e->getMessage(); + if ((('~' == $emsg[0]) && !$this->cfg['ignore_missing_filter_decoders']) + || (('~' != $emsg[0]) && !$this->cfg['ignore_filter_decoding_errors']) + ) { + throw new \Exception($e->getMessage()); + } + } + } else { + // add missing filter to array + $remaining_filters[] = $filter; + } + } + + return [$stream, $remaining_filters]; + } + + /** + * Decode the Cross-Reference section + * + * @param string $pdfData PDF data + * @param int $startxref Offset at which the xref section starts (position of the 'xref' keyword) + * @param array $xref Previous xref array (if any) + * @param array $visitedOffsets Array of visited offsets to prevent infinite loops + * + * @return array containing xref and trailer data + * + * @throws \Exception + */ + protected function decodeXref(string $pdfData, int $startxref, array $xref = [], array $visitedOffsets = []): array + { + $startxref += 4; // 4 is the length of the word 'xref' + // skip initial white space chars + $offset = $startxref + strspn($pdfData, $this->config->getPdfWhitespaces(), $startxref); + // initialize object number + $obj_num = 0; + // search for cross-reference entries or subsection + while (preg_match('/([0-9]+)[\x20]([0-9]+)[\x20]?([nf]?)(\r\n|[\x20]?[\r\n])/', $pdfData, $matches, \PREG_OFFSET_CAPTURE, $offset) > 0) { + if ($matches[0][1] != $offset) { + // we are on another section + break; + } + $offset += \strlen($matches[0][0]); + if ('n' == $matches[3][0]) { + // create unique object index: [object number]_[generation number] + $index = $obj_num.'_'.(int) $matches[2][0]; + // check if object already exist + if (!isset($xref['xref'][$index])) { + // store object offset position + $xref['xref'][$index] = (int) $matches[1][0]; + } + ++$obj_num; + } elseif ('f' == $matches[3][0]) { + ++$obj_num; + } else { + // object number (index) + $obj_num = (int) $matches[1][0]; + } + } + // get trailer data + if (preg_match('/trailer[\s]*<<(.*)>>/isU', $pdfData, $matches, \PREG_OFFSET_CAPTURE, $offset) > 0) { + $trailer_data = $matches[1][0]; + if (!isset($xref['trailer']) || empty($xref['trailer'])) { + // get only the last updated version + $xref['trailer'] = []; + // parse trailer_data + if (preg_match('/Size[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { + $xref['trailer']['size'] = (int) $matches[1]; + } + if (preg_match('/Root[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + $xref['trailer']['root'] = (int) $matches[1].'_'.(int) $matches[2]; + } + if (preg_match('/Encrypt[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + $xref['trailer']['encrypt'] = (int) $matches[1].'_'.(int) $matches[2]; + } + if (preg_match('/Info[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + $xref['trailer']['info'] = (int) $matches[1].'_'.(int) $matches[2]; + } + if (preg_match('/ID[\s]*[\[][\s]*[<]([^>]*)[>][\s]*[<]([^>]*)[>]/i', $trailer_data, $matches) > 0) { + $xref['trailer']['id'] = []; + $xref['trailer']['id'][0] = $matches[1]; + $xref['trailer']['id'][1] = $matches[2]; + } + } + if (preg_match('/Prev[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { + $offset = (int) $matches[1]; + if (0 != $offset) { + // get previous xref + $xref = $this->getXrefData($pdfData, $offset, $xref, $visitedOffsets); + } + } + } else { + throw new \Exception('Unable to find trailer'); + } + + return $xref; + } + + /** + * Decode the Cross-Reference Stream section + * + * @param string $pdfData PDF data + * @param int $startxref Offset at which the xref section starts + * @param array $xref Previous xref array (if any) + * @param array $visitedOffsets Array of visited offsets to prevent infinite loops + * + * @return array containing xref and trailer data + * + * @throws \Exception if unknown PNG predictor detected + */ + protected function decodeXrefStream(string $pdfData, int $startxref, array $xref = [], array $visitedOffsets = []): array + { + // try to read Cross-Reference Stream + $xrefobj = $this->getRawObject($pdfData, $startxref); + $xrefcrs = $this->getIndirectObject($pdfData, $xref, $xrefobj[1], $startxref, true); + if (!isset($xref['trailer']) || empty($xref['trailer'])) { + // get only the last updated version + $xref['trailer'] = []; + $filltrailer = true; + } else { + $filltrailer = false; + } + if (!isset($xref['xref'])) { + $xref['xref'] = []; + } + $valid_crs = false; + $columns = 0; + $predictor = null; + $sarr = $xrefcrs[0][1]; + if (!\is_array($sarr)) { + $sarr = []; + } + + $wb = []; + + foreach ($sarr as $k => $v) { + if ( + ('/' == $v[0]) + && ('Type' == $v[1]) + && ( + isset($sarr[$k + 1]) + && '/' == $sarr[$k + 1][0] + && 'XRef' == $sarr[$k + 1][1] + ) + ) { + $valid_crs = true; + } elseif (('/' == $v[0]) && ('Index' == $v[1]) && (isset($sarr[$k + 1]))) { + // initialize list for: first object number in the subsection / number of objects + $index_blocks = []; + for ($m = 0; $m < \count($sarr[$k + 1][1]); $m += 2) { + $index_blocks[] = [$sarr[$k + 1][1][$m][1], $sarr[$k + 1][1][$m + 1][1]]; + } + } elseif (('/' == $v[0]) && ('Prev' == $v[1]) && (isset($sarr[$k + 1]) && ('numeric' == $sarr[$k + 1][0]))) { + // get previous xref offset + $prevxref = (int) $sarr[$k + 1][1]; + } elseif (('/' == $v[0]) && ('W' == $v[1]) && (isset($sarr[$k + 1]))) { + // number of bytes (in the decoded stream) of the corresponding field + $wb[0] = (int) $sarr[$k + 1][1][0][1]; + $wb[1] = (int) $sarr[$k + 1][1][1][1]; + $wb[2] = (int) $sarr[$k + 1][1][2][1]; + } elseif (('/' == $v[0]) && ('DecodeParms' == $v[1]) && (isset($sarr[$k + 1][1]))) { + $decpar = $sarr[$k + 1][1]; + foreach ($decpar as $kdc => $vdc) { + if ( + '/' == $vdc[0] + && 'Columns' == $vdc[1] + && ( + isset($decpar[$kdc + 1]) + && 'numeric' == $decpar[$kdc + 1][0] + ) + ) { + $columns = (int) $decpar[$kdc + 1][1]; + } elseif ( + '/' == $vdc[0] + && 'Predictor' == $vdc[1] + && ( + isset($decpar[$kdc + 1]) + && 'numeric' == $decpar[$kdc + 1][0] + ) + ) { + $predictor = (int) $decpar[$kdc + 1][1]; + } + } + } elseif ($filltrailer) { + if (('/' == $v[0]) && ('Size' == $v[1]) && (isset($sarr[$k + 1]) && ('numeric' == $sarr[$k + 1][0]))) { + $xref['trailer']['size'] = $sarr[$k + 1][1]; + } elseif (('/' == $v[0]) && ('Root' == $v[1]) && (isset($sarr[$k + 1]) && ('objref' == $sarr[$k + 1][0]))) { + $xref['trailer']['root'] = $sarr[$k + 1][1]; + } elseif (('/' == $v[0]) && ('Info' == $v[1]) && (isset($sarr[$k + 1]) && ('objref' == $sarr[$k + 1][0]))) { + $xref['trailer']['info'] = $sarr[$k + 1][1]; + } elseif (('/' == $v[0]) && ('Encrypt' == $v[1]) && (isset($sarr[$k + 1]) && ('objref' == $sarr[$k + 1][0]))) { + $xref['trailer']['encrypt'] = $sarr[$k + 1][1]; + } elseif (('/' == $v[0]) && ('ID' == $v[1]) && (isset($sarr[$k + 1]))) { + $xref['trailer']['id'] = []; + $xref['trailer']['id'][0] = $sarr[$k + 1][1][0][1]; + $xref['trailer']['id'][1] = $sarr[$k + 1][1][1][1]; + } + } + } + + // decode data + if ($valid_crs && isset($xrefcrs[1][3][0])) { + if (null !== $predictor) { + // number of bytes in a row + $rowlen = ($columns + 1); + // convert the stream into an array of integers + /** @var array */ + $sdata = unpack('C*', $xrefcrs[1][3][0]); + // TODO: Handle the case when unpack returns false + + // split the rows + $sdata = array_chunk($sdata, $rowlen); + + // initialize decoded array + $ddata = []; + // initialize first row with zeros + $prev_row = array_fill(0, $rowlen, 0); + // for each row apply PNG unpredictor + foreach ($sdata as $k => $row) { + // initialize new row + $ddata[$k] = []; + // get PNG predictor value + $predictor = (10 + $row[0]); + // for each byte on the row + for ($i = 1; $i <= $columns; ++$i) { + // new index + $j = ($i - 1); + $row_up = $prev_row[$j]; + if (1 == $i) { + $row_left = 0; + $row_upleft = 0; + } else { + $row_left = $row[$i - 1]; + $row_upleft = $prev_row[$j - 1]; + } + switch ($predictor) { + case 10: // PNG prediction (on encoding, PNG None on all rows) + $ddata[$k][$j] = $row[$i]; + break; + + case 11: // PNG prediction (on encoding, PNG Sub on all rows) + $ddata[$k][$j] = (($row[$i] + $row_left) & 0xFF); + break; + + case 12: // PNG prediction (on encoding, PNG Up on all rows) + $ddata[$k][$j] = (($row[$i] + $row_up) & 0xFF); + break; + + case 13: // PNG prediction (on encoding, PNG Average on all rows) + $ddata[$k][$j] = (($row[$i] + (($row_left + $row_up) / 2)) & 0xFF); + break; + + case 14: // PNG prediction (on encoding, PNG Paeth on all rows) + // initial estimate + $p = ($row_left + $row_up - $row_upleft); + // distances + $pa = abs($p - $row_left); + $pb = abs($p - $row_up); + $pc = abs($p - $row_upleft); + $pmin = min($pa, $pb, $pc); + // return minimum distance + switch ($pmin) { + case $pa: + $ddata[$k][$j] = (($row[$i] + $row_left) & 0xFF); + break; + + case $pb: + $ddata[$k][$j] = (($row[$i] + $row_up) & 0xFF); + break; + + case $pc: + $ddata[$k][$j] = (($row[$i] + $row_upleft) & 0xFF); + break; + } + break; + + default: // PNG prediction (on encoding, PNG optimum) + throw new \Exception('Unknown PNG predictor: '.$predictor); + } + } + $prev_row = $ddata[$k]; + } // end for each row + // complete decoding + } else { + // number of bytes in a row + $rowlen = array_sum($wb); + if (0 < $rowlen) { + // convert the stream into an array of integers + $sdata = unpack('C*', $xrefcrs[1][3][0]); + // split the rows + $ddata = array_chunk($sdata, $rowlen); + } else { + // if the row length is zero, $ddata should be an empty array as well + $ddata = []; + } + } + + $sdata = []; + + // for every row + foreach ($ddata as $k => $row) { + // initialize new row + $sdata[$k] = [0, 0, 0]; + if (0 == $wb[0]) { + // default type field + $sdata[$k][0] = 1; + } + $i = 0; // count bytes in the row + // for every column + for ($c = 0; $c < 3; ++$c) { + // for every byte on the column + for ($b = 0; $b < $wb[$c]; ++$b) { + if (isset($row[$i])) { + $sdata[$k][$c] += ($row[$i] << (($wb[$c] - 1 - $b) * 8)); + } + ++$i; + } + } + } + + // fill xref + if (isset($index_blocks)) { + // load the first object number of the first /Index entry + $obj_num = $index_blocks[0][0]; + } else { + $obj_num = 0; + } + foreach ($sdata as $k => $row) { + switch ($row[0]) { + case 0: // (f) linked list of free objects + break; + + case 1: // (n) objects that are in use but are not compressed + // create unique object index: [object number]_[generation number] + $index = $obj_num.'_'.$row[2]; + // check if object already exist + if (!isset($xref['xref'][$index])) { + // store object offset position + $xref['xref'][$index] = $row[1]; + } + break; + + case 2: // compressed objects + // $row[1] = object number of the object stream in which this object is stored + // $row[2] = index of this object within the object stream + $index = $row[1].'_0_'.$row[2]; + $xref['xref'][$index] = -1; + break; + + default: // null objects + break; + } + ++$obj_num; + if (isset($index_blocks)) { + // reduce the number of remaining objects + --$index_blocks[0][1]; + if (0 == $index_blocks[0][1]) { + // remove the actual used /Index entry + array_shift($index_blocks); + if (0 < \count($index_blocks)) { + // load the first object number of the following /Index entry + $obj_num = $index_blocks[0][0]; + } else { + // if there are no more entries, remove $index_blocks to avoid actions on an empty array + unset($index_blocks); + } + } + } + } + } // end decoding data + if (isset($prevxref)) { + // get previous xref + $xref = $this->getXrefData($pdfData, $prevxref, $xref, $visitedOffsets); + } + + return $xref; + } + + protected function getObjectHeaderPattern(array $objRefs): string + { + // consider all whitespace character (PDF specifications) + return '/'.$objRefs[0].$this->config->getPdfWhitespacesRegex().$objRefs[1].$this->config->getPdfWhitespacesRegex().'obj/'; + } + + protected function getObjectHeaderLen(array $objRefs): int + { + // "4 0 obj" + // 2 whitespaces + strlen("obj") = 5 + return 5 + \strlen($objRefs[0]) + \strlen($objRefs[1]); + } + + /** + * Get content of indirect object. + * + * @param string $pdfData PDF data + * @param string $objRef Object number and generation number separated by underscore character + * @param int $offset Object offset + * @param bool $decoding If true decode streams + * + * @return array containing object data + * + * @throws \Exception if invalid object reference found + */ + protected function getIndirectObject(string $pdfData, array $xref, string $objRef, int $offset = 0, bool $decoding = true): array + { + /* + * build indirect object header + */ + // $objHeader = "[object number] [generation number] obj" + $objRefArr = explode('_', $objRef); + if (2 !== \count($objRefArr)) { + throw new \Exception('Invalid object reference for $obj.'); + } + + $objHeaderLen = $this->getObjectHeaderLen($objRefArr); + + /* + * check if we are in position + */ + // ignore whitespace characters at offset + $offset += strspn($pdfData, $this->config->getPdfWhitespaces(), $offset); + // ignore leading zeros for object number + $offset += strspn($pdfData, '0', $offset); + if (0 == preg_match($this->getObjectHeaderPattern($objRefArr), substr($pdfData, $offset, $objHeaderLen))) { + // an indirect reference to an undefined object shall be considered a reference to the null object + return ['null', 'null', $offset]; + } + + /* + * get content + */ + // starting position of object content + $offset += $objHeaderLen; + $objContentArr = []; + $i = 0; // object main index + $header = null; + do { + $oldOffset = $offset; + // get element + $element = $this->getRawObject($pdfData, $offset, null != $header ? $header[1] : null); + $offset = $element[2]; + // decode stream using stream's dictionary information + if ($decoding && ('stream' === $element[0]) && null != $header) { + $element[3] = $this->decodeStream($pdfData, $xref, $header[1], $element[1]); + } + $objContentArr[$i] = $element; + $header = isset($element[0]) && '<<' === $element[0] ? $element : null; + ++$i; + } while (('endobj' !== $element[0]) && ($offset !== $oldOffset)); + // remove closing delimiter + array_pop($objContentArr); + + /* + * return raw object content + */ + return $objContentArr; + } + + /** + * Get the content of object, resolving indirect object reference if necessary. + * + * @param string $pdfData PDF data + * @param array $obj Object value + * + * @return array containing object data + * + * @throws \Exception + */ + protected function getObjectVal(string $pdfData, $xref, array $obj): array + { + if ('objref' == $obj[0]) { + // reference to indirect object + if (isset($this->objects[$obj[1]])) { + // this object has been already parsed + return $this->objects[$obj[1]]; + } elseif (isset($xref[$obj[1]])) { + // parse new object + $this->objects[$obj[1]] = $this->getIndirectObject($pdfData, $xref, $obj[1], $xref[$obj[1]], false); + + return $this->objects[$obj[1]]; + } + } + + return $obj; + } + + /** + * Get object type, raw value and offset to next object + * + * @param int $offset Object offset + * @param array|null $headerDic obj header's dictionary, parsed by getRawObject. Used for stream parsing optimization + * + * @return array containing object type, raw value and offset to next object + */ + protected function getRawObject(string $pdfData, int $offset = 0, ?array $headerDic = null): array + { + $objtype = ''; // object type to be returned + $objval = ''; // object value to be returned + + // skip initial white space chars + $offset += strspn($pdfData, $this->config->getPdfWhitespaces(), $offset); + + // get first char + $char = $pdfData[$offset]; + // get object type + switch ($char) { + case '%': // \x25 PERCENT SIGN + // skip comment and search for next token + $next = strcspn($pdfData, "\r\n", $offset); + if ($next > 0) { + $offset += $next; + + return $this->getRawObject($pdfData, $offset); + } + break; + + case '/': // \x2F SOLIDUS + // name object + $objtype = $char; + ++$offset; + $span = strcspn($pdfData, "\x00\x09\x0a\x0c\x0d\x20\n\t\r\v\f\x28\x29\x3c\x3e\x5b\x5d\x7b\x7d\x2f\x25", $offset, 256); + if ($span > 0) { + $objval = substr($pdfData, $offset, $span); // unescaped value + $offset += $span; + } + break; + + case '(': // \x28 LEFT PARENTHESIS + case ')': // \x29 RIGHT PARENTHESIS + // literal string object + $objtype = $char; + ++$offset; + $strpos = $offset; + if ('(' == $char) { + $open_bracket = 1; + while ($open_bracket > 0) { + if (!isset($pdfData[$strpos])) { + break; + } + $ch = $pdfData[$strpos]; + switch ($ch) { + case '\\': // REVERSE SOLIDUS (5Ch) (Backslash) + // skip next character + ++$strpos; + break; + + case '(': // LEFT PARENHESIS (28h) + ++$open_bracket; + break; + + case ')': // RIGHT PARENTHESIS (29h) + --$open_bracket; + break; + } + ++$strpos; + } + $objval = substr($pdfData, $offset, $strpos - $offset - 1); + $offset = $strpos; + } + break; + + case '[': // \x5B LEFT SQUARE BRACKET + case ']': // \x5D RIGHT SQUARE BRACKET + // array object + $objtype = $char; + ++$offset; + if ('[' == $char) { + // get array content + $objval = []; + do { + $oldOffset = $offset; + // get element + $element = $this->getRawObject($pdfData, $offset); + $offset = $element[2]; + $objval[] = $element; + } while ((']' != $element[0]) && ($offset != $oldOffset)); + // remove closing delimiter + array_pop($objval); + } + break; + + case '<': // \x3C LESS-THAN SIGN + case '>': // \x3E GREATER-THAN SIGN + if (isset($pdfData[$offset + 1]) && ($pdfData[$offset + 1] == $char)) { + // dictionary object + $objtype = $char.$char; + $offset += 2; + if ('<' == $char) { + // get array content + $objval = []; + do { + $oldOffset = $offset; + // get element + $element = $this->getRawObject($pdfData, $offset); + $offset = $element[2]; + $objval[] = $element; + } while (('>>' != $element[0]) && ($offset != $oldOffset)); + // remove closing delimiter + array_pop($objval); + } + } else { + // hexadecimal string object + $objtype = $char; + ++$offset; + + $span = strspn($pdfData, "0123456789abcdefABCDEF\x09\x0a\x0c\x0d\x20", $offset); + $dataToCheck = $pdfData[$offset + $span] ?? null; + if ('<' == $char && $span > 0 && '>' == $dataToCheck) { + // remove white space characters + $objval = strtr(substr($pdfData, $offset, $span), $this->config->getPdfWhitespaces(), ''); + $offset += $span + 1; + } elseif (false !== ($endpos = strpos($pdfData, '>', $offset))) { + $offset = $endpos + 1; + } + } + break; + + default: + if ('endobj' == substr($pdfData, $offset, 6)) { + // indirect object + $objtype = 'endobj'; + $offset += 6; + } elseif ('null' == substr($pdfData, $offset, 4)) { + // null object + $objtype = 'null'; + $offset += 4; + $objval = 'null'; + } elseif ('true' == substr($pdfData, $offset, 4)) { + // boolean true object + $objtype = 'boolean'; + $offset += 4; + $objval = 'true'; + } elseif ('false' == substr($pdfData, $offset, 5)) { + // boolean false object + $objtype = 'boolean'; + $offset += 5; + $objval = 'false'; + } elseif ('stream' == substr($pdfData, $offset, 6)) { + // start stream object + $objtype = 'stream'; + $offset += 6; + if (1 == preg_match('/^( *[\r]?[\n])/isU', substr($pdfData, $offset, 4), $matches)) { + $offset += \strlen($matches[0]); + + // we get stream length here to later help preg_match test less data + $streamLen = (int) $this->getHeaderValue($headerDic, 'Length', 'numeric', 0); + $skip = false === $this->config->getRetainImageContent() && 'XObject' == $this->getHeaderValue($headerDic, 'Type', '/') && 'Image' == $this->getHeaderValue($headerDic, 'Subtype', '/'); + + $pregResult = preg_match( + '/(endstream)[\x09\x0a\x0c\x0d\x20]/isU', + $pdfData, + $matches, + \PREG_OFFSET_CAPTURE, + $offset + $streamLen + ); + + if (1 == $pregResult) { + $objval = $skip ? '' : substr($pdfData, $offset, $matches[0][1] - $offset); + $offset = $matches[1][1]; + } + } + } elseif ('endstream' == substr($pdfData, $offset, 9)) { + // end stream object + $objtype = 'endstream'; + $offset += 9; + } elseif (1 == preg_match('/^([0-9]+)[\s]+([0-9]+)[\s]+R/iU', substr($pdfData, $offset, 33), $matches)) { + // indirect object reference + $objtype = 'objref'; + $offset += \strlen($matches[0]); + $objval = (int) $matches[1].'_'.(int) $matches[2]; + } elseif (1 == preg_match('/^([0-9]+)[\s]+([0-9]+)[\s]+obj/iU', substr($pdfData, $offset, 33), $matches)) { + // object start + $objtype = 'obj'; + $objval = (int) $matches[1].'_'.(int) $matches[2]; + $offset += \strlen($matches[0]); + } elseif (($numlen = strspn($pdfData, '+-.0123456789', $offset)) > 0) { + // numeric object + $objtype = 'numeric'; + $objval = substr($pdfData, $offset, $numlen); + $offset += $numlen; + } + break; + } + + return [$objtype, $objval, $offset]; + } + + /** + * Get value of an object header's section (obj << YYY >> part ). + * + * It is similar to Header::get('...')->getContent(), the only difference is it can be used during the parsing process, + * when no Smalot\PdfParser\Header objects are created yet. + * + * @param string $key header's section name + * @param string $type type of the section (i.e. 'numeric', '/', '<<', etc.) + * @param string|array|null $default default value for header's section + * + * @return string|array|null value of obj header's section, or default value if none found, or its type doesn't match $type param + */ + private function getHeaderValue(?array $headerDic, string $key, string $type, $default = '') + { + if (false === \is_array($headerDic)) { + return $default; + } + + /* + * It recieves dictionary of header fields, as it is returned by RawDataParser::getRawObject, + * iterates over it, searching for section of type '/' whith requested key. + * If such a section is found, it tries to receive it's value (next object in dictionary), + * returning it, if it matches requested type, or default value otherwise. + */ + foreach ($headerDic as $i => $val) { + $isSectionName = \is_array($val) && 3 == \count($val) && '/' == $val[0]; + if ( + $isSectionName + && $val[1] == $key + && isset($headerDic[$i + 1]) + ) { + $isSectionValue = \is_array($headerDic[$i + 1]) && 1 < \count($headerDic[$i + 1]); + + return $isSectionValue && $type == $headerDic[$i + 1][0] + ? $headerDic[$i + 1][1] + : $default; + } + } + + return $default; + } + + /** + * Get Cross-Reference (xref) table and trailer data from PDF document data. + * + * @param int $offset xref offset (if known) + * @param array $xref previous xref array (if any) + * @param array $visitedOffsets array of visited offsets to prevent infinite loops + * + * @return array containing xref and trailer data + * + * @throws \Exception if it was unable to find startxref + * @throws \Exception if it was unable to find xref + */ + protected function getXrefData(string $pdfData, int $offset = 0, array $xref = [], array $visitedOffsets = []): array + { + // Check for circular references to prevent infinite loops + if (\in_array($offset, $visitedOffsets, true)) { + // We've already processed this offset, skip to avoid infinite loop + return $xref; + } + + // Track this offset as visited + $visitedOffsets[] = $offset; + // If the $offset is currently pointed at whitespace, bump it + // forward until it isn't; affects loosely targetted offsets + // for the 'xref' keyword + // See: https://github.com/smalot/pdfparser/issues/673 + $bumpOffset = $offset; + while (preg_match('/\s/', substr($pdfData, $bumpOffset, 1))) { + ++$bumpOffset; + } + + // Find all startxref tables from this $offset forward + $startxrefPreg = preg_match_all( + '/(?<=[\r\n])startxref[\s]*[\r\n]+([0-9]+)[\s]*[\r\n]+%%EOF/i', + $pdfData, + $startxrefMatches, + \PREG_SET_ORDER, + $offset + ); + + if (0 == $startxrefPreg) { + // No startxref tables were found + throw new \Exception('Unable to find startxref'); + } elseif (0 == $offset) { + // Use the last startxref in the document + $startxref = (int) $startxrefMatches[\count($startxrefMatches) - 1][1]; + } elseif (strpos($pdfData, 'xref', $bumpOffset) == $bumpOffset) { + // Already pointing at the xref table + $startxref = $bumpOffset; + } elseif (preg_match('/([0-9]+[\s][0-9]+[\s]obj)/i', $pdfData, $matches, 0, $bumpOffset)) { + // Cross-Reference Stream object + $startxref = $bumpOffset; + } else { + // Use the next startxref from this $offset + $startxref = (int) $startxrefMatches[0][1]; + } + + if ($startxref > \strlen($pdfData)) { + throw new \Exception('Unable to find xref (PDF corrupted?)'); + } + + // check xref position + if (strpos($pdfData, 'xref', $startxref) == $startxref) { + // Cross-Reference + $xref = $this->decodeXref($pdfData, $startxref, $xref, $visitedOffsets); + } else { + // Check if the $pdfData might have the wrong line-endings + $pdfDataUnix = str_replace("\r\n", "\n", $pdfData); + if ($startxref < \strlen($pdfDataUnix) && strpos($pdfDataUnix, 'xref', $startxref) == $startxref) { + // Return Unix-line-ending flag + $xref = ['Unix' => true]; + } else { + // Cross-Reference Stream + $xref = $this->decodeXrefStream($pdfData, $startxref, $xref, $visitedOffsets); + } + } + if (empty($xref)) { + throw new \Exception('Unable to find xref'); + } + + return $xref; + } + + /** + * Parses PDF data and returns extracted data as array. + * + * @param string $data PDF data to parse + * + * @return array array of parsed PDF document objects + * + * @throws EmptyPdfException if empty PDF data given + * @throws MissingPdfHeaderException if PDF data missing `%PDF-` header + */ + public function parseData(string $data): array + { + if (empty($data)) { + throw new EmptyPdfException('Empty PDF data given.'); + } + // find the pdf header starting position + if (false === ($trimpos = strpos($data, '%PDF-'))) { + throw new MissingPdfHeaderException('Invalid PDF data: Missing `%PDF-` header.'); + } + + // get PDF content string + $pdfData = $trimpos > 0 ? substr($data, $trimpos) : $data; + + // get xref and trailer data + $xref = $this->getXrefData($pdfData); + + // If we found Unix line-endings + if (isset($xref['Unix'])) { + $pdfData = str_replace("\r\n", "\n", $pdfData); + $xref = $this->getXrefData($pdfData); + } + + // parse all document objects + $objects = []; + foreach ($xref['xref'] as $obj => $offset) { + if (!isset($objects[$obj]) && ($offset > 0)) { + // decode objects with positive offset + $objects[$obj] = $this->getIndirectObject($pdfData, $xref, $obj, $offset, true); + } + } + + return [$xref, $objects]; + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/XObject/Form.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/XObject/Form.php new file mode 100644 index 0000000..8e60647 --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/XObject/Form.php @@ -0,0 +1,51 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\XObject; + +use Smalot\PdfParser\Header; +use Smalot\PdfParser\Page; +use Smalot\PdfParser\PDFObject; + +/** + * Class Form + */ +class Form extends Page +{ + public function getText(?Page $page = null): string + { + $header = new Header([], $this->document); + $contents = new PDFObject($this->document, $header, $this->content, $this->config); + + return $contents->getText($this); + } +} diff --git a/vendor/smalot/pdfparser/src/Smalot/PdfParser/XObject/Image.php b/vendor/smalot/pdfparser/src/Smalot/PdfParser/XObject/Image.php new file mode 100644 index 0000000..6dc6b0a --- /dev/null +++ b/vendor/smalot/pdfparser/src/Smalot/PdfParser/XObject/Image.php @@ -0,0 +1,47 @@ + + * + * @date 2017-01-03 + * + * @license LGPLv3 + * + * @url + * + * PdfParser is a pdf library written in PHP, extraction oriented. + * Copyright (C) 2017 - Sébastien MALOT + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. + * If not, see . + */ + +namespace Smalot\PdfParser\XObject; + +use Smalot\PdfParser\Page; +use Smalot\PdfParser\PDFObject; + +/** + * Class Image + */ +class Image extends PDFObject +{ + public function getText(?Page $page = null): string + { + return ''; + } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..6e3afce --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +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/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..e3d1f96 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,1138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * - mb_ucfirst - Make a string's first character uppercase + * - mb_lcfirst - Make a string's first character lowercase + * - mb_trim - Strip whitespace (or other characters) from the beginning and end of a string + * - mb_ltrim - Strip whitespace (or other characters) from the beginning of a string + * - mb_rtrim - Strip whitespace (or other characters) from the end of a string + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const SIMPLE_CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $iconvSupportsIgnore; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($s)) { + $r = []; + foreach ($s as $str) { + $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding); + } + + return $r; + } + + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = self::iconv($fromEncoding, 'UTF-8', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $decodeControlChars = static function ($m) { + $code = '' !== ($m[2] ?? '') ? hexdec($m[2]) : (int) $m[1]; + + if ($code < 32 || 127 === $code) { + return \chr($code); + } + if (128 <= $code && $code <= 159) { + return "\xC2".\chr(0x80 | ($code & 0x3F)); + } + + return $m[0]; + }; + + if (\PHP_VERSION_ID >= 70400) { + $s = html_entity_decode($s, \ENT_QUOTES, 'UTF-8'); + // html_entity_decode() leaves numeric entities for C0/C1 control + // characters as-is (HTML spec), but mb_convert_encoding() decodes + // them. Catch what html_entity_decode() missed. + if (false !== strpos($s, '&#')) { + $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); + } + } else { + // PHP < 7.4: html_entity_decode() truncates strings at NUL bytes, + // so decode the control character entities first then call + // html_entity_decode() on each NUL-delimited chunk independently. + $s = preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); + $s = implode("\0", array_map(static function ($chunk) { + return html_entity_decode($chunk, \ENT_QUOTES, 'UTF-8'); + }, explode("\0", $s))); + } + $fromEncoding = 'UTF-8'; + } + + return self::iconv($fromEncoding, $toEncoding, $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, static function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @self::iconv('UTF-8', 'UTF-8', $s); + } + } else { + $s = self::iconv($encoding, 'UTF-8', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return self::iconv('UTF-8', $encoding, $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @self::iconv('UTF-8', 'UTF-8', $s); + } + } else { + $s = self::iconv($encoding, 'UTF-8', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return self::iconv('UTF-8', $encoding, $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @self::iconv('UTF-8', 'UTF-8', $s); + } + } else { + $s = self::iconv($encoding, 'UTF-8', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = strtr($s, $caseFolding); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return self::iconv('UTF-8', $encoding, $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return false; + } + } + + return true; + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + if (false !== $len = @iconv_strlen($s, $encoding)) { + return $len; + } + + if ('UTF-8' !== $encoding) { + return $len; + } + + return preg_match_all('/[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]?|[\xE0-\xEF][\x80-\xBF]{0,2}|[\xF0-\xF7][\x80-\xBF]{0,3}|[\xF8-\xFB][\x80-\xBF]{0,4}|[\xFC-\xFD][\x80-\xBF]{0,5}|[\x80-\xBF\xFE\xFF]/s', $s); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [ + self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), + self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding), + ]); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + + $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = self::iconv($encoding, 'UTF-8', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + /** @return string|false */ + public static function mb_scrub(?string $string, ?string $encoding = null): string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_scrub(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return false; + } + + return self::mb_convert_encoding((string) $string, $encoding, $encoding); + } + + /** @return string|false */ + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) { + return false; + } + + if (self::mb_strlen($pad_string, $encoding) <= 0) { + if (\PHP_VERSION_ID < 80000) { + trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) { + if (\PHP_VERSION_ID < 80000) { + trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + + $paddingRequired = $length - self::mb_strlen($string, $encoding); + + if ($paddingRequired < 1) { + return $string; + } + + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string; + case \STR_PAD_RIGHT: + return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + + return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + + /** @return string|false */ + public static function mb_ucfirst(string $string, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return false; + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + /** @return string|false */ + public static function mb_lcfirst(string $string, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return false; + } + + $firstChar = mb_substr($string, 0, 1, $encoding); + $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); + + return $firstChar.mb_substr($string, 1, null, $encoding); + } + + /** @return string|false */ + public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** @return string|false */ + public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); + } + + /** @return string|false */ + public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + if ('UTF-32' === $encoding) { + return 'UTF-32BE'; + } + + if ('UTF-16' === $encoding) { + return 'UTF-16BE'; + } + + return $encoding; + } + + private static function iconv($fromEncoding, $toEncoding, $s) + { + if (null === self::$iconvSupportsIgnore) { + self::$iconvSupportsIgnore = false !== @iconv('UTF-8', 'UTF-8//IGNORE', ''); + } + + return self::$iconvSupportsIgnore + ? iconv($fromEncoding, $toEncoding.'//IGNORE', $s) + : iconv($fromEncoding, $toEncoding, $s); + } + + /** @return string|false */ + private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) { + return false; + } + + if ('' === $characters) { + return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding); + } + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $string)) { + $string = @self::iconv('UTF-8', 'UTF-8', $string); + } + if (null !== $characters && !preg_match('//u', $characters)) { + $characters = @self::iconv('UTF-8', 'UTF-8', $characters); + } + } else { + $string = self::iconv($encoding, 'UTF-8', $string); + + if (null !== $characters) { + $characters = self::iconv($encoding, 'UTF-8', $characters); + } + } + + if (null === $characters) { + $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"; + } else { + $characters = preg_quote($characters); + } + + $string = preg_replace(\sprintf($regex, $characters), '', $string); + + if (null === $encoding) { + return $string; + } + + return self::iconv('UTF-8', $encoding, $string); + } + + private static function assertEncoding(string $encoding, string $errorFormat): bool + { + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + + if (!$validEncoding) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING); + } else { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + } + + return $validEncoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..478b40d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 0000000..512bba0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,119 @@ + 'i̇', + 'µ' => 'μ', + 'ſ' => 's', + 'ͅ' => 'ι', + 'ς' => 'σ', + 'ϐ' => 'β', + 'ϑ' => 'θ', + 'ϕ' => 'φ', + 'ϖ' => 'π', + 'ϰ' => 'κ', + 'ϱ' => 'ρ', + 'ϵ' => 'ε', + 'ẛ' => 'ṡ', + 'ι' => 'ι', + 'ß' => 'ss', + 'ʼn' => 'ʼn', + 'ǰ' => 'ǰ', + 'ΐ' => 'ΐ', + 'ΰ' => 'ΰ', + 'և' => 'եւ', + 'ẖ' => 'ẖ', + 'ẗ' => 'ẗ', + 'ẘ' => 'ẘ', + 'ẙ' => 'ẙ', + 'ẚ' => 'aʾ', + 'ẞ' => 'ss', + 'ὐ' => 'ὐ', + 'ὒ' => 'ὒ', + 'ὔ' => 'ὔ', + 'ὖ' => 'ὖ', + 'ᾀ' => 'ἀι', + 'ᾁ' => 'ἁι', + 'ᾂ' => 'ἂι', + 'ᾃ' => 'ἃι', + 'ᾄ' => 'ἄι', + 'ᾅ' => 'ἅι', + 'ᾆ' => 'ἆι', + 'ᾇ' => 'ἇι', + 'ᾈ' => 'ἀι', + 'ᾉ' => 'ἁι', + 'ᾊ' => 'ἂι', + 'ᾋ' => 'ἃι', + 'ᾌ' => 'ἄι', + 'ᾍ' => 'ἅι', + 'ᾎ' => 'ἆι', + 'ᾏ' => 'ἇι', + 'ᾐ' => 'ἠι', + 'ᾑ' => 'ἡι', + 'ᾒ' => 'ἢι', + 'ᾓ' => 'ἣι', + 'ᾔ' => 'ἤι', + 'ᾕ' => 'ἥι', + 'ᾖ' => 'ἦι', + 'ᾗ' => 'ἧι', + 'ᾘ' => 'ἠι', + 'ᾙ' => 'ἡι', + 'ᾚ' => 'ἢι', + 'ᾛ' => 'ἣι', + 'ᾜ' => 'ἤι', + 'ᾝ' => 'ἥι', + 'ᾞ' => 'ἦι', + 'ᾟ' => 'ἧι', + 'ᾠ' => 'ὠι', + 'ᾡ' => 'ὡι', + 'ᾢ' => 'ὢι', + 'ᾣ' => 'ὣι', + 'ᾤ' => 'ὤι', + 'ᾥ' => 'ὥι', + 'ᾦ' => 'ὦι', + 'ᾧ' => 'ὧι', + 'ᾨ' => 'ὠι', + 'ᾩ' => 'ὡι', + 'ᾪ' => 'ὢι', + 'ᾫ' => 'ὣι', + 'ᾬ' => 'ὤι', + 'ᾭ' => 'ὥι', + 'ᾮ' => 'ὦι', + 'ᾯ' => 'ὧι', + 'ᾲ' => 'ὰι', + 'ᾳ' => 'αι', + 'ᾴ' => 'άι', + 'ᾶ' => 'ᾶ', + 'ᾷ' => 'ᾶι', + 'ᾼ' => 'αι', + 'ῂ' => 'ὴι', + 'ῃ' => 'ηι', + 'ῄ' => 'ήι', + 'ῆ' => 'ῆ', + 'ῇ' => 'ῆι', + 'ῌ' => 'ηι', + 'ῒ' => 'ῒ', + 'ῖ' => 'ῖ', + 'ῗ' => 'ῗ', + 'ῢ' => 'ῢ', + 'ῤ' => 'ῤ', + 'ῦ' => 'ῦ', + 'ῧ' => 'ῧ', + 'ῲ' => 'ὼι', + 'ῳ' => 'ωι', + 'ῴ' => 'ώι', + 'ῶ' => 'ῶ', + 'ῷ' => 'ῶι', + 'ῼ' => 'ωι', + 'ff' => 'ff', + 'fi' => 'fi', + 'fl' => 'fl', + 'ffi' => 'ffi', + 'ffl' => 'ffl', + 'ſt' => 'st', + 'st' => 'st', + 'ﬓ' => 'մն', + 'ﬔ' => 'մե', + 'ﬕ' => 'մի', + 'ﬖ' => 'վն', + 'ﬗ' => 'մխ', +]; diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..fac60b0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..0e934d7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +return require __DIR__.'/bootstrap72.php'; diff --git a/vendor/symfony/polyfill-mbstring/bootstrap72.php b/vendor/symfony/polyfill-mbstring/bootstrap72.php new file mode 100644 index 0000000..2e3a06d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap72.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { return p\Mbstring::mb_scrub($string, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + /** @return string|false */ + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + /** @return string|false */ + function mb_ucfirst(?string $string, ?string $encoding = null) { return p\Mbstring::mb_ucfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + /** @return string|false */ + function mb_lcfirst(?string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_trim')) { + /** @return string|false */ + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + /** @return string|false */ + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + /** @return string|false */ + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..5fde10e --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false|null { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_scrub($string, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_trim')) { + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..daa07f8 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2", + "ext-iconv": "*" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +}