File manager - Edit - /home/autoph/public_html/projects/Rating-AutoHub/public/css/dompdf.tar
Back
dompdf/LICENSE.LGPL 0000644 00000057440 15024772104 0007573 0 ustar 00 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. 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 not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the 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 specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. dompdf/composer.json 0000644 00000002435 15024772104 0010545 0 ustar 00 { "name": "dompdf/dompdf", "type": "library", "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", "homepage": "https://github.com/dompdf/dompdf", "license": "LGPL-2.1", "authors": [ { "name": "The Dompdf Community", "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" } ], "autoload": { "psr-4": { "Dompdf\\": "src/" }, "classmap": [ "lib/" ] }, "autoload-dev": { "psr-4": { "Dompdf\\Tests\\": "tests/" } }, "require": { "php": "^7.1 || ^8.0", "ext-dom": "*", "ext-mbstring": "*", "masterminds/html5": "^2.0", "phenx/php-font-lib": ">=0.5.4 <1.0.0", "phenx/php-svg-lib": ">=0.3.3 <1.0.0" }, "require-dev": { "ext-json": "*", "ext-zip": "*", "phpunit/phpunit": "^7.5 || ^8 || ^9", "squizlabs/php_codesniffer": "^3.5", "mockery/mockery": "^1.3" }, "suggest": { "ext-gd": "Needed to process images", "ext-imagick": "Improves image processing performance", "ext-gmagick": "Improves image processing performance", "ext-zlib": "Needed for pdf stream compression" } } dompdf/src/Options.php 0000644 00000076311 15024772104 0010762 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; class Options { /** * The root of your DOMPDF installation * * @var string */ private $rootDir; /** * The location of a temporary directory. * * The directory specified must be writable by the executing process. * The temporary directory is required to download remote images and when * using the PFDLib back end. * * @var string */ private $tempDir; /** * The location of the DOMPDF font directory * * The location of the directory where DOMPDF will store fonts and font metrics * Note: This directory must exist and be writable by the executing process. * * @var string */ private $fontDir; /** * The location of the DOMPDF font cache directory * * This directory contains the cached font metrics for the fonts used by DOMPDF. * This directory can be the same as $fontDir * * Note: This directory must exist and be writable by the executing process. * * @var string */ private $fontCache; /** * dompdf's "chroot" * * Utilized by Dompdf's default file:// protocol URI validation rule. * All local files opened by dompdf must be in a subdirectory of the directory * or directories specified by this option. * DO NOT set this value to '/' since this could allow an attacker to use dompdf to * read any files on the server. This should be an absolute path. * * ==== IMPORTANT ==== * This setting may increase the risk of system exploit. Do not change * this settings without understanding the consequences. Additional * documentation is available on the dompdf wiki at: * https://github.com/dompdf/dompdf/wiki * * @var array */ private $chroot; /** * Protocol whitelist * * Protocols and PHP wrappers allowed in URIs, and the validation rules * that determine if a resouce may be loaded. Full support is not guaranteed * for the protocols/wrappers specified * by this array. * * @var array */ private $allowedProtocols = [ "file://" => ["rules" => []], "http://" => ["rules" => []], "https://" => ["rules" => []] ]; /** * @var string */ private $logOutputFile; /** * Styles targeted to this media type are applied to the document. * This is on top of the media types that are always applied: * all, static, visual, bitmap, paged, dompdf * * @var string */ private $defaultMediaType = "screen"; /** * The default paper size. * * North America standard is "letter"; other countries generally "a4" * @see \Dompdf\Adapter\CPDF::PAPER_SIZES for valid sizes * * @var string|float[] */ private $defaultPaperSize = "letter"; /** * The default paper orientation. * * The orientation of the page (portrait or landscape). * * @var string */ private $defaultPaperOrientation = "portrait"; /** * The default font family * * Used if no suitable fonts can be found. This must exist in the font folder. * * @var string */ private $defaultFont = "serif"; /** * Image DPI setting * * This setting determines the default DPI setting for images and fonts. The * DPI may be overridden for inline images by explicitly setting the * image's width & height style attributes (i.e. if the image's native * width is 600 pixels and you specify the image's width as 72 points, * the image will have a DPI of 600 in the rendered PDF. The DPI of * background images can not be overridden and is controlled entirely * via this parameter. * * For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI). * If a size in html is given as px (or without unit as image size), * this tells the corresponding size in pt at 72 DPI. * This adjusts the relative sizes to be similar to the rendering of the * html page in a reference browser. * * In pdf, always 1 pt = 1/72 inch * * @var int */ private $dpi = 96; /** * A ratio applied to the fonts height to be more like browsers' line height * * @var float */ private $fontHeightRatio = 1.1; /** * Enable embedded PHP * * If this setting is set to true then DOMPDF will automatically evaluate * embedded PHP contained within <script type="text/php"> ... </script> tags. * * ==== IMPORTANT ==== * Enabling this for documents you do not trust (e.g. arbitrary remote html * pages) is a security risk. Embedded scripts are run with the same level of * system access available to dompdf. Set this option to false (recommended) * if you wish to process untrusted documents. * * This setting may increase the risk of system exploit. Do not change * this settings without understanding the consequences. Additional * documentation is available on the dompdf wiki at: * https://github.com/dompdf/dompdf/wiki * * @var bool */ private $isPhpEnabled = false; /** * Enable remote file access * * If this setting is set to true, DOMPDF will access remote sites for * images and CSS files as required. * * ==== IMPORTANT ==== * This can be a security risk, in particular in combination with isPhpEnabled and * allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...); * This allows anonymous users to download legally doubtful internet content which on * tracing back appears to being downloaded by your server, or allows malicious php code * in remote html pages to be executed by your server with your account privileges. * * This setting may increase the risk of system exploit. Do not change * this settings without understanding the consequences. Additional * documentation is available on the dompdf wiki at: * https://github.com/dompdf/dompdf/wiki * * @var bool */ private $isRemoteEnabled = false; /** * Enable inline JavaScript * * If this setting is set to true then DOMPDF will automatically insert * JavaScript code contained within <script type="text/javascript"> ... </script> * tags as written into the PDF. * * NOTE: This is PDF-based JavaScript to be executed by the PDF viewer, * not browser-based JavaScript executed by Dompdf. * * @var bool */ private $isJavascriptEnabled = true; /** * Use the HTML5 Lib parser * * @deprecated * @var bool */ private $isHtml5ParserEnabled = true; /** * Whether to enable font subsetting or not. * * @var bool */ private $isFontSubsettingEnabled = true; /** * @var bool */ private $debugPng = false; /** * @var bool */ private $debugKeepTemp = false; /** * @var bool */ private $debugCss = false; /** * @var bool */ private $debugLayout = false; /** * @var bool */ private $debugLayoutLines = true; /** * @var bool */ private $debugLayoutBlocks = true; /** * @var bool */ private $debugLayoutInline = true; /** * @var bool */ private $debugLayoutPaddingBox = true; /** * The PDF rendering backend to use * * Valid settings are 'PDFLib', 'CPDF', 'GD', and 'auto'. 'auto' will * look for PDFLib and use it if found, or if not it will fall back on * CPDF. 'GD' renders PDFs to graphic files. {@link Dompdf\CanvasFactory} * ultimately determines which rendering class to instantiate * based on this setting. * * @var string */ private $pdfBackend = "CPDF"; /** * PDFlib license key * * If you are using a licensed, commercial version of PDFlib, specify * your license key here. If you are using PDFlib-Lite or are evaluating * the commercial version of PDFlib, comment out this setting. * * @link http://www.pdflib.com * * If pdflib present in web server and auto or selected explicitly above, * a real license code must exist! * * @var string */ private $pdflibLicense = ""; /** * HTTP context created with stream_context_create() * Will be used for file_get_contents * * @link https://www.php.net/manual/context.php * * @var resource */ private $httpContext; /** * @param array $attributes */ public function __construct(array $attributes = null) { $rootDir = realpath(__DIR__ . "/../"); $this->setChroot(array($rootDir)); $this->setRootDir($rootDir); $this->setTempDir(sys_get_temp_dir()); $this->setFontDir($rootDir . "/lib/fonts"); $this->setFontCache($this->getFontDir()); $ver = ""; $versionFile = realpath(__DIR__ . '/../VERSION'); if (($version = file_get_contents($versionFile)) !== false) { $version = trim($version); if ($version !== '$Format:<%h>$') { $ver = "/$version"; } } $this->setHttpContext([ "http" => [ "follow_location" => false, "user_agent" => "Dompdf$ver https://github.com/dompdf/dompdf" ] ]); $this->setAllowedProtocols(["file://", "http://", "https://"]); if (null !== $attributes) { $this->set($attributes); } } /** * @param array|string $attributes * @param null|mixed $value * @return $this */ public function set($attributes, $value = null) { if (!is_array($attributes)) { $attributes = [$attributes => $value]; } foreach ($attributes as $key => $value) { if ($key === 'tempDir' || $key === 'temp_dir') { $this->setTempDir($value); } elseif ($key === 'fontDir' || $key === 'font_dir') { $this->setFontDir($value); } elseif ($key === 'fontCache' || $key === 'font_cache') { $this->setFontCache($value); } elseif ($key === 'chroot') { $this->setChroot($value); } elseif ($key === 'allowedProtocols') { $this->setAllowedProtocols($value); } elseif ($key === 'logOutputFile' || $key === 'log_output_file') { $this->setLogOutputFile($value); } elseif ($key === 'defaultMediaType' || $key === 'default_media_type') { $this->setDefaultMediaType($value); } elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') { $this->setDefaultPaperSize($value); } elseif ($key === 'defaultPaperOrientation' || $key === 'default_paper_orientation') { $this->setDefaultPaperOrientation($value); } elseif ($key === 'defaultFont' || $key === 'default_font') { $this->setDefaultFont($value); } elseif ($key === 'dpi') { $this->setDpi($value); } elseif ($key === 'fontHeightRatio' || $key === 'font_height_ratio') { $this->setFontHeightRatio($value); } elseif ($key === 'isPhpEnabled' || $key === 'is_php_enabled' || $key === 'enable_php') { $this->setIsPhpEnabled($value); } elseif ($key === 'isRemoteEnabled' || $key === 'is_remote_enabled' || $key === 'enable_remote') { $this->setIsRemoteEnabled($value); } elseif ($key === 'isJavascriptEnabled' || $key === 'is_javascript_enabled' || $key === 'enable_javascript') { $this->setIsJavascriptEnabled($value); } elseif ($key === 'isHtml5ParserEnabled' || $key === 'is_html5_parser_enabled' || $key === 'enable_html5_parser') { $this->setIsHtml5ParserEnabled($value); } elseif ($key === 'isFontSubsettingEnabled' || $key === 'is_font_subsetting_enabled' || $key === 'enable_font_subsetting') { $this->setIsFontSubsettingEnabled($value); } elseif ($key === 'debugPng' || $key === 'debug_png') { $this->setDebugPng($value); } elseif ($key === 'debugKeepTemp' || $key === 'debug_keep_temp') { $this->setDebugKeepTemp($value); } elseif ($key === 'debugCss' || $key === 'debug_css') { $this->setDebugCss($value); } elseif ($key === 'debugLayout' || $key === 'debug_layout') { $this->setDebugLayout($value); } elseif ($key === 'debugLayoutLines' || $key === 'debug_layout_lines') { $this->setDebugLayoutLines($value); } elseif ($key === 'debugLayoutBlocks' || $key === 'debug_layout_blocks') { $this->setDebugLayoutBlocks($value); } elseif ($key === 'debugLayoutInline' || $key === 'debug_layout_inline') { $this->setDebugLayoutInline($value); } elseif ($key === 'debugLayoutPaddingBox' || $key === 'debug_layout_padding_box') { $this->setDebugLayoutPaddingBox($value); } elseif ($key === 'pdfBackend' || $key === 'pdf_backend') { $this->setPdfBackend($value); } elseif ($key === 'pdflibLicense' || $key === 'pdflib_license') { $this->setPdflibLicense($value); } elseif ($key === 'httpContext' || $key === 'http_context') { $this->setHttpContext($value); } } return $this; } /** * @param string $key * @return mixed */ public function get($key) { if ($key === 'tempDir' || $key === 'temp_dir') { return $this->getTempDir(); } elseif ($key === 'fontDir' || $key === 'font_dir') { return $this->getFontDir(); } elseif ($key === 'fontCache' || $key === 'font_cache') { return $this->getFontCache(); } elseif ($key === 'chroot') { return $this->getChroot(); } elseif ($key === 'allowedProtocols') { return $this->getAllowedProtocols(); } elseif ($key === 'logOutputFile' || $key === 'log_output_file') { return $this->getLogOutputFile(); } elseif ($key === 'defaultMediaType' || $key === 'default_media_type') { return $this->getDefaultMediaType(); } elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') { return $this->getDefaultPaperSize(); } elseif ($key === 'defaultPaperOrientation' || $key === 'default_paper_orientation') { return $this->getDefaultPaperOrientation(); } elseif ($key === 'defaultFont' || $key === 'default_font') { return $this->getDefaultFont(); } elseif ($key === 'dpi') { return $this->getDpi(); } elseif ($key === 'fontHeightRatio' || $key === 'font_height_ratio') { return $this->getFontHeightRatio(); } elseif ($key === 'isPhpEnabled' || $key === 'is_php_enabled' || $key === 'enable_php') { return $this->getIsPhpEnabled(); } elseif ($key === 'isRemoteEnabled' || $key === 'is_remote_enabled' || $key === 'enable_remote') { return $this->getIsRemoteEnabled(); } elseif ($key === 'isJavascriptEnabled' || $key === 'is_javascript_enabled' || $key === 'enable_javascript') { return $this->getIsJavascriptEnabled(); } elseif ($key === 'isHtml5ParserEnabled' || $key === 'is_html5_parser_enabled' || $key === 'enable_html5_parser') { return $this->getIsHtml5ParserEnabled(); } elseif ($key === 'isFontSubsettingEnabled' || $key === 'is_font_subsetting_enabled' || $key === 'enable_font_subsetting') { return $this->getIsFontSubsettingEnabled(); } elseif ($key === 'debugPng' || $key === 'debug_png') { return $this->getDebugPng(); } elseif ($key === 'debugKeepTemp' || $key === 'debug_keep_temp') { return $this->getDebugKeepTemp(); } elseif ($key === 'debugCss' || $key === 'debug_css') { return $this->getDebugCss(); } elseif ($key === 'debugLayout' || $key === 'debug_layout') { return $this->getDebugLayout(); } elseif ($key === 'debugLayoutLines' || $key === 'debug_layout_lines') { return $this->getDebugLayoutLines(); } elseif ($key === 'debugLayoutBlocks' || $key === 'debug_layout_blocks') { return $this->getDebugLayoutBlocks(); } elseif ($key === 'debugLayoutInline' || $key === 'debug_layout_inline') { return $this->getDebugLayoutInline(); } elseif ($key === 'debugLayoutPaddingBox' || $key === 'debug_layout_padding_box') { return $this->getDebugLayoutPaddingBox(); } elseif ($key === 'pdfBackend' || $key === 'pdf_backend') { return $this->getPdfBackend(); } elseif ($key === 'pdflibLicense' || $key === 'pdflib_license') { return $this->getPdflibLicense(); } elseif ($key === 'httpContext' || $key === 'http_context') { return $this->getHttpContext(); } return null; } /** * @param string $pdfBackend * @return $this */ public function setPdfBackend($pdfBackend) { $this->pdfBackend = $pdfBackend; return $this; } /** * @return string */ public function getPdfBackend() { return $this->pdfBackend; } /** * @param string $pdflibLicense * @return $this */ public function setPdflibLicense($pdflibLicense) { $this->pdflibLicense = $pdflibLicense; return $this; } /** * @return string */ public function getPdflibLicense() { return $this->pdflibLicense; } /** * @param array|string $chroot * @return $this */ public function setChroot($chroot, $delimiter = ',') { if (is_string($chroot)) { $this->chroot = explode($delimiter, $chroot); } elseif (is_array($chroot)) { $this->chroot = $chroot; } return $this; } /** * @return array */ public function getAllowedProtocols() { return $this->allowedProtocols; } /** * @param array $allowedProtocols The protocols to allow, as an array * formatted as ["protocol://" => ["rules" => [callable]], ...] * or ["protocol://", ...] * * @return $this */ public function setAllowedProtocols(array $allowedProtocols) { $protocols = []; foreach ($allowedProtocols as $protocol => $config) { if (is_string($protocol)) { $protocols[$protocol] = []; if (is_array($config)) { $protocols[$protocol] = $config; } } elseif (is_string($config)) { $protocols[$config] = []; } } $this->allowedProtocols = []; foreach ($protocols as $protocol => $config) { $this->addAllowedProtocol($protocol, ...($config["rules"] ?? [])); } return $this; } /** * Adds a new protocol to the allowed protocols collection * * @param string $protocol The scheme to add (e.g. "http://") * @param callable $rule A callable that validates the protocol * @return $this */ public function addAllowedProtocol(string $protocol, callable ...$rules) { $protocol = strtolower($protocol); if (empty($rules)) { $rules = []; switch ($protocol) { case "file://": $rules[] = [$this, "validateLocalUri"]; break; case "http://": case "https://": $rules[] = [$this, "validateRemoteUri"]; break; case "phar://": $rules[] = [$this, "validatePharUri"]; break; } } $this->allowedProtocols[$protocol] = ["rules" => $rules]; return $this; } /** * @return array */ public function getChroot() { $chroot = []; if (is_array($this->chroot)) { $chroot = $this->chroot; } return $chroot; } /** * @param boolean $debugCss * @return $this */ public function setDebugCss($debugCss) { $this->debugCss = $debugCss; return $this; } /** * @return boolean */ public function getDebugCss() { return $this->debugCss; } /** * @param boolean $debugKeepTemp * @return $this */ public function setDebugKeepTemp($debugKeepTemp) { $this->debugKeepTemp = $debugKeepTemp; return $this; } /** * @return boolean */ public function getDebugKeepTemp() { return $this->debugKeepTemp; } /** * @param boolean $debugLayout * @return $this */ public function setDebugLayout($debugLayout) { $this->debugLayout = $debugLayout; return $this; } /** * @return boolean */ public function getDebugLayout() { return $this->debugLayout; } /** * @param boolean $debugLayoutBlocks * @return $this */ public function setDebugLayoutBlocks($debugLayoutBlocks) { $this->debugLayoutBlocks = $debugLayoutBlocks; return $this; } /** * @return boolean */ public function getDebugLayoutBlocks() { return $this->debugLayoutBlocks; } /** * @param boolean $debugLayoutInline * @return $this */ public function setDebugLayoutInline($debugLayoutInline) { $this->debugLayoutInline = $debugLayoutInline; return $this; } /** * @return boolean */ public function getDebugLayoutInline() { return $this->debugLayoutInline; } /** * @param boolean $debugLayoutLines * @return $this */ public function setDebugLayoutLines($debugLayoutLines) { $this->debugLayoutLines = $debugLayoutLines; return $this; } /** * @return boolean */ public function getDebugLayoutLines() { return $this->debugLayoutLines; } /** * @param boolean $debugLayoutPaddingBox * @return $this */ public function setDebugLayoutPaddingBox($debugLayoutPaddingBox) { $this->debugLayoutPaddingBox = $debugLayoutPaddingBox; return $this; } /** * @return boolean */ public function getDebugLayoutPaddingBox() { return $this->debugLayoutPaddingBox; } /** * @param boolean $debugPng * @return $this */ public function setDebugPng($debugPng) { $this->debugPng = $debugPng; return $this; } /** * @return boolean */ public function getDebugPng() { return $this->debugPng; } /** * @param string $defaultFont * @return $this */ public function setDefaultFont($defaultFont) { if (!($defaultFont === null || trim($defaultFont) === "")) { $this->defaultFont = $defaultFont; } else { $this->defaultFont = "serif"; } return $this; } /** * @return string */ public function getDefaultFont() { return $this->defaultFont; } /** * @param string $defaultMediaType * @return $this */ public function setDefaultMediaType($defaultMediaType) { $this->defaultMediaType = $defaultMediaType; return $this; } /** * @return string */ public function getDefaultMediaType() { return $this->defaultMediaType; } /** * @param string|float[] $defaultPaperSize * @return $this */ public function setDefaultPaperSize($defaultPaperSize): self { $this->defaultPaperSize = $defaultPaperSize; return $this; } /** * @param string $defaultPaperOrientation * @return $this */ public function setDefaultPaperOrientation(string $defaultPaperOrientation): self { $this->defaultPaperOrientation = $defaultPaperOrientation; return $this; } /** * @return string|float[] */ public function getDefaultPaperSize() { return $this->defaultPaperSize; } /** * @return string */ public function getDefaultPaperOrientation(): string { return $this->defaultPaperOrientation; } /** * @param int $dpi * @return $this */ public function setDpi($dpi) { $this->dpi = $dpi; return $this; } /** * @return int */ public function getDpi() { return $this->dpi; } /** * @param string $fontCache * @return $this */ public function setFontCache($fontCache) { $this->fontCache = $fontCache; return $this; } /** * @return string */ public function getFontCache() { return $this->fontCache; } /** * @param string $fontDir * @return $this */ public function setFontDir($fontDir) { $this->fontDir = $fontDir; return $this; } /** * @return string */ public function getFontDir() { return $this->fontDir; } /** * @param float $fontHeightRatio * @return $this */ public function setFontHeightRatio($fontHeightRatio) { $this->fontHeightRatio = $fontHeightRatio; return $this; } /** * @return float */ public function getFontHeightRatio() { return $this->fontHeightRatio; } /** * @param boolean $isFontSubsettingEnabled * @return $this */ public function setIsFontSubsettingEnabled($isFontSubsettingEnabled) { $this->isFontSubsettingEnabled = $isFontSubsettingEnabled; return $this; } /** * @return boolean */ public function getIsFontSubsettingEnabled() { return $this->isFontSubsettingEnabled; } /** * @return boolean */ public function isFontSubsettingEnabled() { return $this->getIsFontSubsettingEnabled(); } /** * @deprecated * @param boolean $isHtml5ParserEnabled * @return $this */ public function setIsHtml5ParserEnabled($isHtml5ParserEnabled) { $this->isHtml5ParserEnabled = $isHtml5ParserEnabled; return $this; } /** * @deprecated * @return boolean */ public function getIsHtml5ParserEnabled() { return $this->isHtml5ParserEnabled; } /** * @deprecated * @return boolean */ public function isHtml5ParserEnabled() { return $this->getIsHtml5ParserEnabled(); } /** * @param boolean $isJavascriptEnabled * @return $this */ public function setIsJavascriptEnabled($isJavascriptEnabled) { $this->isJavascriptEnabled = $isJavascriptEnabled; return $this; } /** * @return boolean */ public function getIsJavascriptEnabled() { return $this->isJavascriptEnabled; } /** * @return boolean */ public function isJavascriptEnabled() { return $this->getIsJavascriptEnabled(); } /** * @param boolean $isPhpEnabled * @return $this */ public function setIsPhpEnabled($isPhpEnabled) { $this->isPhpEnabled = $isPhpEnabled; return $this; } /** * @return boolean */ public function getIsPhpEnabled() { return $this->isPhpEnabled; } /** * @return boolean */ public function isPhpEnabled() { return $this->getIsPhpEnabled(); } /** * @param boolean $isRemoteEnabled * @return $this */ public function setIsRemoteEnabled($isRemoteEnabled) { $this->isRemoteEnabled = $isRemoteEnabled; return $this; } /** * @return boolean */ public function getIsRemoteEnabled() { return $this->isRemoteEnabled; } /** * @return boolean */ public function isRemoteEnabled() { return $this->getIsRemoteEnabled(); } /** * @param string $logOutputFile * @return $this */ public function setLogOutputFile($logOutputFile) { $this->logOutputFile = $logOutputFile; return $this; } /** * @return string */ public function getLogOutputFile() { return $this->logOutputFile; } /** * @param string $tempDir * @return $this */ public function setTempDir($tempDir) { $this->tempDir = $tempDir; return $this; } /** * @return string */ public function getTempDir() { return $this->tempDir; } /** * @param string $rootDir * @return $this */ public function setRootDir($rootDir) { $this->rootDir = $rootDir; return $this; } /** * @return string */ public function getRootDir() { return $this->rootDir; } /** * Sets the HTTP context * * @param resource|array $httpContext * @return $this */ public function setHttpContext($httpContext) { $this->httpContext = is_array($httpContext) ? stream_context_create($httpContext) : $httpContext; return $this; } /** * Returns the HTTP context * * @return resource */ public function getHttpContext() { return $this->httpContext; } public function validateLocalUri(string $uri) { if ($uri === null || strlen($uri) === 0) { return [false, "The URI must not be empty."]; } $realfile = realpath(str_replace("file://", "", $uri)); $dirs = $this->chroot; $dirs[] = $this->rootDir; $chrootValid = false; foreach ($dirs as $chrootPath) { $chrootPath = realpath($chrootPath); if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) { $chrootValid = true; break; } } if ($chrootValid !== true) { return [false, "Permission denied. The file could not be found under the paths specified by Options::chroot."]; } if (!$realfile) { return [false, "File not found."]; } return [true, null]; } public function validatePharUri(string $uri) { if ($uri === null || strlen($uri) === 0) { return [false, "The URI must not be empty."]; } $file = substr(substr($uri, 0, strpos($uri, ".phar") + 5), 7); return $this->validateLocalUri($file); } public function validateRemoteUri(string $uri) { if ($uri === null || strlen($uri) === 0) { return [false, "The URI must not be empty."]; } if (!$this->isRemoteEnabled) { return [false, "Remote file requested, but remote file download is disabled."]; } return [true, null]; } } dompdf/src/Positioner/Fixed.php 0000644 00000007054 15024772104 0012517 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameReflower\Block; /** * Positions fixely positioned frames */ class Fixed extends Absolute { /** * @param AbstractFrameDecorator $frame */ function position(AbstractFrameDecorator $frame): void { if ($frame->get_reflower() instanceof Block) { parent::position($frame); } else { // Legacy positioning logic for image and table frames // TODO: Resolve dimensions, margins, and offsets similar to the // block case in the reflowers and use the simplified logic above $style = $frame->get_style(); $root = $frame->get_root(); $initialcb = $root->get_containing_block(); $initialcb_style = $root->get_style(); $p = $frame->find_block_parent(); if ($p) { $p->add_line(); } // Compute the margins of the @page style $margin_top = (float)$initialcb_style->length_in_pt($initialcb_style->margin_top, $initialcb["h"]); $margin_right = (float)$initialcb_style->length_in_pt($initialcb_style->margin_right, $initialcb["w"]); $margin_bottom = (float)$initialcb_style->length_in_pt($initialcb_style->margin_bottom, $initialcb["h"]); $margin_left = (float)$initialcb_style->length_in_pt($initialcb_style->margin_left, $initialcb["w"]); // The needed computed style of the element $height = (float)$style->length_in_pt($style->get_specified("height"), $initialcb["h"]); $width = (float)$style->length_in_pt($style->get_specified("width"), $initialcb["w"]); $top = $style->length_in_pt($style->get_specified("top"), $initialcb["h"]); $right = $style->length_in_pt($style->get_specified("right"), $initialcb["w"]); $bottom = $style->length_in_pt($style->get_specified("bottom"), $initialcb["h"]); $left = $style->length_in_pt($style->get_specified("left"), $initialcb["w"]); $y = $margin_top; if (isset($top)) { $y = (float)$top + $margin_top; if ($top === "auto") { $y = $margin_top; if (isset($bottom) && $bottom !== "auto") { $y = $initialcb["h"] - $bottom - $margin_bottom; if ($frame->is_auto_height()) { $y -= $height; } else { $y -= $frame->get_margin_height(); } } } } $x = $margin_left; if (isset($left)) { $x = (float)$left + $margin_left; if ($left === "auto") { $x = $margin_left; if (isset($right) && $right !== "auto") { $x = $initialcb["w"] - $right - $margin_right; if ($frame->is_auto_width()) { $x -= $width; } else { $x -= $frame->get_margin_width(); } } } } $frame->set_position($x, $y); foreach ($frame->get_children() as $child) { $child->set_position($x, $y); } } } } dompdf/src/Positioner/AbstractPositioner.php 0000644 00000002163 15024772104 0015273 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; /** * Base AbstractPositioner class * * Defines positioner interface * * @package dompdf */ abstract class AbstractPositioner { /** * @param AbstractFrameDecorator $frame */ abstract function position(AbstractFrameDecorator $frame): void; /** * @param AbstractFrameDecorator $frame * @param float $offset_x * @param float $offset_y * @param bool $ignore_self */ function move( AbstractFrameDecorator $frame, float $offset_x, float $offset_y, bool $ignore_self = false ): void { [$x, $y] = $frame->get_position(); if (!$ignore_self) { $frame->set_position($x + $offset_x, $y + $offset_y); } foreach ($frame->get_children() as $child) { $child->move($offset_x, $offset_y); } } } dompdf/src/Positioner/ListBullet.php 0000644 00000002464 15024772104 0013543 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\ListBullet as ListBulletFrameDecorator; /** * Positions list bullets * * @package dompdf */ class ListBullet extends AbstractPositioner { /** * @param ListBulletFrameDecorator $frame */ function position(AbstractFrameDecorator $frame): void { // List markers are positioned to the left of the border edge of their // parent element (FIXME: right for RTL) $parent = $frame->get_parent(); $style = $parent->get_style(); $cbw = $parent->get_containing_block("w"); $margin_left = (float) $style->length_in_pt($style->margin_left, $cbw); $border_edge = $parent->get_position("x") + $margin_left; // This includes the marker indentation $x = $border_edge - $frame->get_margin_width(); // The marker is later vertically aligned with the corresponding line // box and its vertical position is fine-tuned in the renderer $p = $frame->find_block_parent(); $y = $p->get_current_line_box()->y; $frame->set_position($x, $y); } } dompdf/src/Positioner/TableCell.php 0000644 00000001241 15024772104 0013277 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\Table; /** * Positions table cells * * @package dompdf */ class TableCell extends AbstractPositioner { /** * @param AbstractFrameDecorator $frame */ function position(AbstractFrameDecorator $frame): void { $table = Table::find_parent_table($frame); $cellmap = $table->get_cellmap(); $frame->set_position($cellmap->get_frame_position($frame)); } } dompdf/src/Positioner/Absolute.php 0000644 00000011074 15024772104 0013233 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameReflower\Block; /** * Positions absolutely positioned frames */ class Absolute extends AbstractPositioner { /** * @param AbstractFrameDecorator $frame */ function position(AbstractFrameDecorator $frame): void { if ($frame->get_reflower() instanceof Block) { $style = $frame->get_style(); [$cbx, $cby, $cbw, $cbh] = $frame->get_containing_block(); // If the `top` value is `auto`, the frame will be repositioned // after its height has been resolved $left = (float) $style->length_in_pt($style->left, $cbw); $top = (float) $style->length_in_pt($style->top, $cbh); $frame->set_position($cbx + $left, $cby + $top); } else { // Legacy positioning logic for image and table frames // TODO: Resolve dimensions, margins, and offsets similar to the // block case in the reflowers and use the simplified logic above $style = $frame->get_style(); $block_parent = $frame->find_block_parent(); $current_line = $block_parent->get_current_line_box(); list($x, $y, $w, $h) = $frame->get_containing_block(); $inflow_x = $block_parent->get_content_box()["x"] + $current_line->left + $current_line->w; $inflow_y = $current_line->y; $top = $style->length_in_pt($style->top, $h); $right = $style->length_in_pt($style->right, $w); $bottom = $style->length_in_pt($style->bottom, $h); $left = $style->length_in_pt($style->left, $w); list($width, $height) = [$frame->get_margin_width(), $frame->get_margin_height()]; $orig_width = $style->get_specified("width"); $orig_height = $style->get_specified("height"); /**************************** * * Width auto: * ____________| left=auto | left=fixed | * right=auto | A | B | * right=fixed | C | D | * * Width fixed: * ____________| left=auto | left=fixed | * right=auto | E | F | * right=fixed | G | H | *****************************/ if ($left === "auto") { if ($right === "auto") { // A or E - Keep the frame at the same position $x = $inflow_x; } else { if ($orig_width === "auto") { // C $x += $w - $width - $right; } else { // G $x += $w - $width - $right; } } } else { if ($right === "auto") { // B or F $x += (float)$left; } else { if ($orig_width === "auto") { // D - TODO change width $x += (float)$left; } else { // H - Everything is fixed: left + width win $x += (float)$left; } } } // The same vertically if ($top === "auto") { if ($bottom === "auto") { // A or E - Keep the frame at the same position $y = $inflow_y; } else { if ($orig_height === "auto") { // C $y += (float)$h - $height - (float)$bottom; } else { // G $y += (float)$h - $height - (float)$bottom; } } } else { if ($bottom === "auto") { // B or F $y += (float)$top; } else { if ($orig_height === "auto") { // D - TODO change height $y += (float)$top; } else { // H - Everything is fixed: top + height win $y += (float)$top; } } } $frame->set_position($x, $y); } } } dompdf/src/Positioner/TableRow.php 0000644 00000001353 15024772104 0013173 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; /** * Positions table rows * * @package dompdf */ class TableRow extends AbstractPositioner { /** * @param AbstractFrameDecorator $frame */ function position(AbstractFrameDecorator $frame): void { $cb = $frame->get_containing_block(); $p = $frame->get_prev_sibling(); if ($p) { $y = $p->get_position("y") + $p->get_margin_height(); } else { $y = $cb["y"]; } $frame->set_position($cb["x"], $y); } } dompdf/src/Positioner/NullPositioner.php 0000644 00000000757 15024772104 0014451 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; /** * Dummy positioner * * @package dompdf */ class NullPositioner extends AbstractPositioner { /** * @param AbstractFrameDecorator $frame */ function position(AbstractFrameDecorator $frame): void { return; } } dompdf/src/Positioner/Inline.php 0000644 00000002747 15024772104 0012702 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\Inline as InlineFrameDecorator; use Dompdf\Exception; use Dompdf\Helpers; /** * Positions inline frames * * @package dompdf */ class Inline extends AbstractPositioner { /** * @param AbstractFrameDecorator $frame * @throws Exception */ function position(AbstractFrameDecorator $frame): void { // Find our nearest block level parent and access its lines property $block = $frame->find_block_parent(); if (!$block) { throw new Exception("No block-level parent found. Not good."); } $cb = $frame->get_containing_block(); $line = $block->get_current_line_box(); if (!$frame->is_text_node() && !($frame instanceof InlineFrameDecorator)) { // Atomic inline boxes and replaced inline elements // (inline-block, inline-table, img etc.) $width = $frame->get_margin_width(); $available_width = $cb["w"] - $line->left - $line->w - $line->right; if (Helpers::lengthGreater($width, $available_width)) { $block->add_line(); $line = $block->get_current_line_box(); } } $frame->set_position($cb["x"] + $line->w, $line->y); } } dompdf/src/Positioner/Block.php 0000644 00000001535 15024772104 0012510 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Positioner; use Dompdf\FrameDecorator\AbstractFrameDecorator; /** * Positions block frames * * @package dompdf */ class Block extends AbstractPositioner { function position(AbstractFrameDecorator $frame): void { $style = $frame->get_style(); $cb = $frame->get_containing_block(); $p = $frame->find_block_parent(); if ($p) { $float = $style->float; if (!$float || $float === "none") { $p->add_line(true); } $y = $p->get_current_line_box()->y; } else { $y = $cb["y"]; } $x = $cb["x"]; $frame->set_position($x, $y); } } dompdf/src/Renderer/Image.php 0000644 00000005104 15024772104 0012107 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Frame; use Dompdf\FrameDecorator\Image as ImageFrameDecorator; use Dompdf\Image\Cache; /** * Image renderer * * @package dompdf */ class Image extends Block { /** * @param ImageFrameDecorator $frame */ function render(Frame $frame) { $style = $frame->get_style(); $border_box = $frame->get_border_box(); $this->_set_opacity($frame->get_opacity($style->opacity)); // Render background & borders $this->_render_background($frame, $border_box); $this->_render_border($frame, $border_box); $this->_render_outline($frame, $border_box); $content_box = $frame->get_content_box(); [$x, $y, $w, $h] = $content_box; $src = $frame->get_image_url(); $alt = null; if (Cache::is_broken($src) && $alt = $frame->get_node()->getAttribute("alt") ) { $font = $style->font_family; $size = $style->font_size; $word_spacing = $style->word_spacing; $letter_spacing = $style->letter_spacing; $this->_canvas->text( $x, $y, $alt, $font, $size, $style->color, $word_spacing, $letter_spacing ); } elseif ($w > 0 && $h > 0) { if ($style->has_border_radius()) { [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $content_box); $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl); } $this->_canvas->image($src, $x, $y, $w, $h, $style->image_resolution); if ($style->has_border_radius()) { $this->_canvas->clipping_end(); } } if ($msg = $frame->get_image_msg()) { $parts = preg_split("/\s*\n\s*/", $msg); $font = $style->font_family; $height = 10; $_y = $alt ? $y + $h - count($parts) * $height : $y; foreach ($parts as $i => $_part) { $this->_canvas->text($x, $_y + $i * $height, $_part, $font, $height * 0.8, [0.5, 0.5, 0.5]); } } $id = $frame->get_node()->getAttribute("id"); if (strlen($id) > 0) { $this->_canvas->add_named_dest($id); } $this->debugBlockLayout($frame, "blue"); } } dompdf/src/Renderer/ListBullet.php 0000644 00000015674 15024772104 0013165 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Helpers; use Dompdf\Frame; use Dompdf\FrameDecorator\ListBullet as ListBulletFrameDecorator; use Dompdf\FrameDecorator\ListBulletImage; use Dompdf\Image\Cache; /** * Renders list bullets * * @package dompdf */ class ListBullet extends AbstractRenderer { /** * @param $type * @return mixed|string */ static function get_counter_chars($type) { static $cache = []; if (isset($cache[$type])) { return $cache[$type]; } $uppercase = false; $text = ""; switch ($type) { case "decimal-leading-zero": case "decimal": case "1": return "0123456789"; case "upper-alpha": case "upper-latin": case "A": $uppercase = true; case "lower-alpha": case "lower-latin": case "a": $text = "abcdefghijklmnopqrstuvwxyz"; break; case "upper-roman": case "I": $uppercase = true; case "lower-roman": case "i": $text = "ivxlcdm"; break; case "lower-greek": for ($i = 0; $i < 24; $i++) { $text .= Helpers::unichr($i + 944); } break; } if ($uppercase) { $text = strtoupper($text); } return $cache[$type] = "$text."; } /** * @param int $n * @param string $type * @param int|null $pad * * @return string */ private function make_counter($n, $type, $pad = null) { $n = intval($n); $text = ""; $uppercase = false; switch ($type) { case "decimal-leading-zero": case "decimal": case "1": if ($pad) { $text = str_pad($n, $pad, "0", STR_PAD_LEFT); } else { $text = $n; } break; case "upper-alpha": case "upper-latin": case "A": $uppercase = true; case "lower-alpha": case "lower-latin": case "a": $text = chr((($n - 1) % 26) + ord('a')); break; case "upper-roman": case "I": $uppercase = true; case "lower-roman": case "i": $text = Helpers::dec2roman($n); break; case "lower-greek": $text = Helpers::unichr($n + 944); break; } if ($uppercase) { $text = strtoupper($text); } return "$text."; } /** * @param ListBulletFrameDecorator $frame */ function render(Frame $frame) { $li = $frame->get_parent(); $style = $frame->get_style(); $this->_set_opacity($frame->get_opacity($style->opacity)); // Don't render bullets twice if the list item was split if ($li->is_split_off) { return; } $font_family = $style->font_family; $font_size = $style->font_size; $baseline = $this->_canvas->get_font_baseline($font_family, $font_size); // Handle list-style-image // If list style image is requested but missing, fall back to predefined types if ($frame instanceof ListBulletImage && !Cache::is_broken($img = $frame->get_image_url())) { [$x, $y] = $frame->get_position(); $w = $frame->get_width(); $h = $frame->get_height(); $y += $baseline - $h; $this->_canvas->image($img, $x, $y, $w, $h); } else { $bullet_style = $style->list_style_type; switch ($bullet_style) { default: case "disc": case "circle": [$x, $y] = $frame->get_position(); $offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET; $r = ($font_size * ListBulletFrameDecorator::BULLET_SIZE) / 2; $x += $r; $y += $baseline - $r - $offset; $o = $font_size * ListBulletFrameDecorator::BULLET_THICKNESS; $this->_canvas->circle($x, $y, $r, $style->color, $o, null, $bullet_style !== "circle"); break; case "square": [$x, $y] = $frame->get_position(); $offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET; $w = $font_size * ListBulletFrameDecorator::BULLET_SIZE; $y += $baseline - $w - $offset; $this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color); break; case "decimal-leading-zero": case "decimal": case "lower-alpha": case "lower-latin": case "lower-roman": case "lower-greek": case "upper-alpha": case "upper-latin": case "upper-roman": case "1": // HTML 4.0 compatibility case "a": case "i": case "A": case "I": $pad = null; if ($bullet_style === "decimal-leading-zero") { $pad = strlen($li->get_parent()->get_node()->getAttribute("dompdf-children-count")); } $node = $frame->get_node(); if (!$node->hasAttribute("dompdf-counter")) { return; } $index = $node->getAttribute("dompdf-counter"); $text = $this->make_counter($index, $bullet_style, $pad); if (trim($text) === "") { return; } $word_spacing = $style->word_spacing; $letter_spacing = $style->letter_spacing; $text_width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font_family, $font_size, $word_spacing, $letter_spacing); [$x, $y] = $frame->get_position(); // Correct for static frame width applied by positioner $x += $frame->get_width() - $text_width; $this->_canvas->text($x, $y, $text, $font_family, $font_size, $style->color, $word_spacing, $letter_spacing); case "none": break; } } $id = $frame->get_node()->getAttribute("id"); if (strlen($id) > 0) { $this->_canvas->add_named_dest($id); } } } dompdf/src/Renderer/TableCell.php 0000644 00000014373 15024772104 0012724 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Frame; use Dompdf\FrameDecorator\Table; /** * Renders table cells * * @package dompdf */ class TableCell extends Block { /** * @param Frame $frame */ function render(Frame $frame) { $style = $frame->get_style(); if (trim($frame->get_node()->nodeValue) === "" && $style->empty_cells === "hide") { return; } $this->_set_opacity($frame->get_opacity($style->opacity)); $border_box = $frame->get_border_box(); $table = Table::find_parent_table($frame); if ($table->get_style()->border_collapse !== "collapse") { $this->_render_background($frame, $border_box); $this->_render_border($frame, $border_box); $this->_render_outline($frame, $border_box); } else { // The collapsed case is slightly complicated... $cells = $table->get_cellmap()->get_spanned_cells($frame); if (is_null($cells)) { return; } // Render the background to the padding box, as the cells are // rendered individually one after another, and we don't want the // background to overlap an adjacent border $padding_box = $frame->get_padding_box(); $this->_render_background($frame, $padding_box); $this->_render_collapsed_border($frame, $table); // FIXME: Outline should be drawn over other cells $this->_render_outline($frame, $border_box); } $id = $frame->get_node()->getAttribute("id"); if (strlen($id) > 0) { $this->_canvas->add_named_dest($id); } // $this->debugBlockLayout($frame, "red", false); } /** * @param Frame $frame * @param Table $table */ protected function _render_collapsed_border(Frame $frame, Table $table): void { $cellmap = $table->get_cellmap(); $cells = $cellmap->get_spanned_cells($frame); $num_rows = $cellmap->get_num_rows(); $num_cols = $cellmap->get_num_cols(); [$table_x, $table_y] = $table->get_position(); // Determine the top row spanned by this cell $i = $cells["rows"][0]; $top_row = $cellmap->get_row($i); // Determine if this cell borders on the bottom of the table. If so, // then we draw its bottom border. Otherwise the next row down will // draw its top border instead. if (in_array($num_rows - 1, $cells["rows"])) { $draw_bottom = true; $bottom_row = $cellmap->get_row($num_rows - 1); } else { $draw_bottom = false; } // Draw the horizontal borders foreach ($cells["columns"] as $j) { $bp = $cellmap->get_border_properties($i, $j); $col = $cellmap->get_column($j); $x = $table_x + $col["x"] - $bp["left"]["width"] / 2; $y = $table_y + $top_row["y"] - $bp["top"]["width"] / 2; $w = $col["used-width"] + ($bp["left"]["width"] + $bp["right"]["width"]) / 2; if ($bp["top"]["width"] > 0) { $widths = [ (float)$bp["top"]["width"], (float)$bp["right"]["width"], (float)$bp["bottom"]["width"], (float)$bp["left"]["width"] ]; $method = "_border_" . $bp["top"]["style"]; $this->$method($x, $y, $w, $bp["top"]["color"], $widths, "top", "square"); } if ($draw_bottom) { $bp = $cellmap->get_border_properties($num_rows - 1, $j); if ($bp["bottom"]["width"] <= 0) { continue; } $widths = [ (float)$bp["top"]["width"], (float)$bp["right"]["width"], (float)$bp["bottom"]["width"], (float)$bp["left"]["width"] ]; $y = $table_y + $bottom_row["y"] + $bottom_row["height"] + $bp["bottom"]["width"] / 2; $method = "_border_" . $bp["bottom"]["style"]; $this->$method($x, $y, $w, $bp["bottom"]["color"], $widths, "bottom", "square"); } } $j = $cells["columns"][0]; $left_col = $cellmap->get_column($j); if (in_array($num_cols - 1, $cells["columns"])) { $draw_right = true; $right_col = $cellmap->get_column($num_cols - 1); } else { $draw_right = false; } // Draw the vertical borders foreach ($cells["rows"] as $i) { $bp = $cellmap->get_border_properties($i, $j); $row = $cellmap->get_row($i); $x = $table_x + $left_col["x"] - $bp["left"]["width"] / 2; $y = $table_y + $row["y"] - $bp["top"]["width"] / 2; $h = $row["height"] + ($bp["top"]["width"] + $bp["bottom"]["width"]) / 2; if ($bp["left"]["width"] > 0) { $widths = [ (float)$bp["top"]["width"], (float)$bp["right"]["width"], (float)$bp["bottom"]["width"], (float)$bp["left"]["width"] ]; $method = "_border_" . $bp["left"]["style"]; $this->$method($x, $y, $h, $bp["left"]["color"], $widths, "left", "square"); } if ($draw_right) { $bp = $cellmap->get_border_properties($i, $num_cols - 1); if ($bp["right"]["width"] <= 0) { continue; } $widths = [ (float)$bp["top"]["width"], (float)$bp["right"]["width"], (float)$bp["bottom"]["width"], (float)$bp["left"]["width"] ]; $x = $table_x + $right_col["x"] + $right_col["used-width"] + $bp["right"]["width"] / 2; $method = "_border_" . $bp["right"]["style"]; $this->$method($x, $y, $h, $bp["right"]["color"], $widths, "right", "square"); } } } } dompdf/src/Renderer/AbstractRenderer.php 0000644 00000121240 15024772104 0014317 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Adapter\CPDF; use Dompdf\Css\Color; use Dompdf\Css\Style; use Dompdf\Dompdf; use Dompdf\Helpers; use Dompdf\Frame; use Dompdf\Image\Cache; /** * Base renderer class * * @package dompdf */ abstract class AbstractRenderer { /** * Rendering backend * * @var \Dompdf\Canvas */ protected $_canvas; /** * Current dompdf instance * * @var Dompdf */ protected $_dompdf; /** * Class constructor * * @param Dompdf $dompdf The current dompdf instance */ function __construct(Dompdf $dompdf) { $this->_dompdf = $dompdf; $this->_canvas = $dompdf->getCanvas(); } /** * Render a frame. * * Specialized in child classes * * @param Frame $frame The frame to render */ abstract function render(Frame $frame); /** * @param Frame $frame * @param float[] $border_box */ protected function _render_background(Frame $frame, array $border_box): void { $style = $frame->get_style(); $color = $style->background_color; $image = $style->background_image; [$x, $y, $w, $h] = $border_box; if ($color === "transparent" && $image === "none") { return; } if ($style->has_border_radius()) { [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box); $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl); } if ($color !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $color); } if ($image !== "none") { $this->_background_image($image, $x, $y, $w, $h, $style); } if ($style->has_border_radius()) { $this->_canvas->clipping_end(); } } /** * @param Frame $frame * @param float[] $border_box * @param string $corner_style */ protected function _render_border(Frame $frame, array $border_box, string $corner_style = "bevel"): void { $style = $frame->get_style(); $bp = $style->get_border_properties(); [$x, $y, $w, $h] = $border_box; [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box); // Short-cut: If all the borders are "solid" with the same color and // style, and no radius, we'd better draw a rectangle if ($bp["top"]["style"] === "solid" && $bp["top"] === $bp["right"] && $bp["right"] === $bp["bottom"] && $bp["bottom"] === $bp["left"] && !$style->has_border_radius() ) { $props = $bp["top"]; if ($props["color"] === "transparent" || $props["width"] <= 0) { return; } $width = (float)$style->length_in_pt($props["width"]); $this->_canvas->rectangle($x + $width / 2, $y + $width / 2, $w - $width, $h - $width, $props["color"], $width); return; } // Do it the long way $widths = [ (float)$style->length_in_pt($bp["top"]["width"]), (float)$style->length_in_pt($bp["right"]["width"]), (float)$style->length_in_pt($bp["bottom"]["width"]), (float)$style->length_in_pt($bp["left"]["width"]) ]; foreach ($bp as $side => $props) { if ($props["style"] === "none" || $props["style"] === "hidden" || $props["color"] === "transparent" || $props["width"] <= 0 ) { continue; } [$x, $y, $w, $h] = $border_box; $method = "_border_" . $props["style"]; switch ($side) { case "top": $length = $w; $r1 = $tl; $r2 = $tr; break; case "bottom": $length = $w; $y += $h; $r1 = $bl; $r2 = $br; break; case "left": $length = $h; $r1 = $tl; $r2 = $bl; break; case "right": $length = $h; $x += $w; $r1 = $tr; $r2 = $br; break; default: break; } // draw rounded corners $this->$method($x, $y, $length, $props["color"], $widths, $side, $corner_style, $r1, $r2); } } /** * @param Frame $frame * @param float[] $border_box * @param string $corner_style */ protected function _render_outline(Frame $frame, array $border_box, string $corner_style = "bevel"): void { $style = $frame->get_style(); $width = $style->outline_width; $outline_style = $style->outline_style; $color = $style->outline_color; if ($outline_style === "none" || $color === "transparent" || $width <= 0) { return; } $offset = $style->outline_offset; [$x, $y, $w, $h] = $border_box; $d = $width + $offset; $outline_box = [$x - $d, $y - $d, $w + $d * 2, $h + $d * 2]; [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $outline_box); $x -= $offset; $y -= $offset; $w += $offset * 2; $h += $offset * 2; // For a simple outline, we can draw a rectangle if ($outline_style === "solid" && !$style->has_border_radius()) { $x -= $width / 2; $y -= $width / 2; $w += $width; $h += $width; $this->_canvas->rectangle($x, $y, $w, $h, $color, $width); return; } $x -= $width; $y -= $width; $w += $width * 2; $h += $width * 2; $method = "_border_" . $outline_style; $widths = array_fill(0, 4, $width); $sides = ["top", "right", "left", "bottom"]; foreach ($sides as $side) { switch ($side) { case "top": $length = $w; $side_x = $x; $side_y = $y; $r1 = $tl; $r2 = $tr; break; case "bottom": $length = $w; $side_x = $x; $side_y = $y + $h; $r1 = $bl; $r2 = $br; break; case "left": $length = $h; $side_x = $x; $side_y = $y; $r1 = $tl; $r2 = $bl; break; case "right": $length = $h; $side_x = $x + $w; $side_y = $y; $r1 = $tr; $r2 = $br; break; default: break; } $this->$method($side_x, $side_y, $length, $color, $widths, $side, $corner_style, $r1, $r2); } } /** * Render a background image over a rectangular area * * @param string $url The background image to load * @param float $x The left edge of the rectangular area * @param float $y The top edge of the rectangular area * @param float $width The width of the rectangular area * @param float $height The height of the rectangular area * @param Style $style The associated Style object * * @throws \Exception */ protected function _background_image($url, $x, $y, $width, $height, $style) { if (!function_exists("imagecreatetruecolor")) { throw new \Exception("The PHP GD extension is required, but is not installed."); } $sheet = $style->get_stylesheet(); // Skip degenerate cases if ($width == 0 || $height == 0) { return; } $box_width = $width; $box_height = $height; //debugpng if ($this->_dompdf->getOptions()->getDebugPng()) { print '[_background_image ' . $url . ']'; } list($img, $type, /*$msg*/) = Cache::resolve_url( $url, $sheet->get_protocol(), $sheet->get_host(), $sheet->get_base_path(), $this->_dompdf->getOptions() ); // Bail if the image is no good if (Cache::is_broken($img)) { return; } //Try to optimize away reading and composing of same background multiple times //Postponing read with imagecreatefrom ...() //final composition parameters and name not known yet //Therefore read dimension directly from file, instead of creating gd object first. //$img_w = imagesx($src); $img_h = imagesy($src); list($img_w, $img_h) = Helpers::dompdf_getimagesize($img, $this->_dompdf->getHttpContext()); if ($img_w == 0 || $img_h == 0) { return; } // save for later check if file needs to be resized. $org_img_w = $img_w; $org_img_h = $img_h; $repeat = $style->background_repeat; $dpi = $this->_dompdf->getOptions()->getDpi(); //Increase background resolution and dependent box size according to image resolution to be placed in //Then image can be copied in without resize $bg_width = round((float)($width * $dpi) / 72); $bg_height = round((float)($height * $dpi) / 72); list($img_w, $img_h) = $this->_resize_background_image( $img_w, $img_h, $bg_width, $bg_height, $style->background_size, $dpi ); //Need %bg_x, $bg_y as background pos, where img starts, converted to pixel list($bg_x, $bg_y) = $style->background_position; if (Helpers::is_percent($bg_x)) { // The point $bg_x % from the left edge of the image is placed // $bg_x % from the left edge of the background rectangle $p = ((float)$bg_x) / 100.0; $x1 = $p * $img_w; $x2 = $p * $bg_width; $bg_x = $x2 - $x1; } else { $bg_x = (float)($style->length_in_pt($bg_x) * $dpi) / 72; } $bg_x = round($bg_x + (float)$style->length_in_pt($style->border_left_width) * $dpi / 72); if (Helpers::is_percent($bg_y)) { // The point $bg_y % from the left edge of the image is placed // $bg_y % from the left edge of the background rectangle $p = ((float)$bg_y) / 100.0; $y1 = $p * $img_h; $y2 = $p * $bg_height; $bg_y = $y2 - $y1; } else { $bg_y = (float)($style->length_in_pt($bg_y) * $dpi) / 72; } $bg_y = round($bg_y + (float)$style->length_in_pt($style->border_top_width) * $dpi / 72); //clip background to the image area on partial repeat. Nothing to do if img off area //On repeat, normalize start position to the tile at immediate left/top or 0/0 of area //On no repeat with positive offset: move size/start to have offset==0 //Handle x/y Dimensions separately if ($repeat !== "repeat" && $repeat !== "repeat-x") { //No repeat x if ($bg_x < 0) { $bg_width = $img_w + $bg_x; } else { $x += ($bg_x * 72) / $dpi; $bg_width = $bg_width - $bg_x; if ($bg_width > $img_w) { $bg_width = $img_w; } $bg_x = 0; } if ($bg_width <= 0) { return; } $width = (float)($bg_width * 72) / $dpi; } else { //repeat x if ($bg_x < 0) { $bg_x = -((-$bg_x) % $img_w); } else { $bg_x = $bg_x % $img_w; if ($bg_x > 0) { $bg_x -= $img_w; } } } if ($repeat !== "repeat" && $repeat !== "repeat-y") { //no repeat y if ($bg_y < 0) { $bg_height = $img_h + $bg_y; } else { $y += ($bg_y * 72) / $dpi; $bg_height = $bg_height - $bg_y; if ($bg_height > $img_h) { $bg_height = $img_h; } $bg_y = 0; } if ($bg_height <= 0) { return; } $height = (float)($bg_height * 72) / $dpi; } else { //repeat y if ($bg_y < 0) { $bg_y = -((-$bg_y) % $img_h); } else { $bg_y = $bg_y % $img_h; if ($bg_y > 0) { $bg_y -= $img_h; } } } //Optimization, if repeat has no effect if ($repeat === "repeat" && $bg_y <= 0 && $img_h + $bg_y >= $bg_height) { $repeat = "repeat-x"; } if ($repeat === "repeat" && $bg_x <= 0 && $img_w + $bg_x >= $bg_width) { $repeat = "repeat-y"; } if (($repeat === "repeat-x" && $bg_x <= 0 && $img_w + $bg_x >= $bg_width) || ($repeat === "repeat-y" && $bg_y <= 0 && $img_h + $bg_y >= $bg_height) ) { $repeat = "no-repeat"; } // Avoid rendering identical background-image variants multiple times // This is not dependent of background color of box! .'_'.(is_array($bg_color) ? $bg_color["hex"] : $bg_color) // Note: Here, bg_* are the start values, not end values after going through the tile loops! $key = implode("_", [$bg_width, $bg_height, $img_w, $img_h, $bg_x, $bg_y, $repeat]); // FIXME: This will fail when a file with that exact name exists in the // same directory, included in the document as regular image $cpdfKey = $img . "_" . $key; $tmpFile = Cache::getTempImage($img, $key); $cached = ($this->_canvas instanceof CPDF && $this->_canvas->get_cpdf()->image_iscached($cpdfKey)) || ($tmpFile !== null && file_exists($tmpFile)); if (!$cached) { // img: image url string // img_w, img_h: original image size in px // width, height: box size in pt // bg_width, bg_height: box size in px // x, y: left/top edge of box on page in pt // start_x, start_y: placement of image relative to pattern // $repeat: repeat mode // $bg: GD object of result image // $src: GD object of original image // Create a new image to fit over the background rectangle $bg = imagecreatetruecolor($bg_width, $bg_height); $cpdfFromGd = true; switch (strtolower($type)) { case "png": $cpdfFromGd = false; imagesavealpha($bg, true); imagealphablending($bg, false); $src = @imagecreatefrompng($img); break; case "jpeg": $src = @imagecreatefromjpeg($img); break; case "webp": $src = @imagecreatefromwebp($img); break; case "gif": $src = @imagecreatefromgif($img); break; case "bmp": $src = @Helpers::imagecreatefrombmp($img); break; default: return; // Unsupported image type } if ($src == null) { return; } if ($img_w != $org_img_w || $img_h != $org_img_h) { $newSrc = imagescale($src, $img_w, $img_h); imagedestroy($src); $src = $newSrc; } if ($src == null) { return; } //Background color if box is not relevant here //Non transparent image: box clipped to real size. Background non relevant. //Transparent image: The image controls the transparency and lets shine through whatever background. //However on transparent image preset the composed image with the transparency color, //to keep the transparency when copying over the non transparent parts of the tiles. $ti = imagecolortransparent($src); $palletsize = imagecolorstotal($src); if ($ti >= 0 && $ti < $palletsize) { $tc = imagecolorsforindex($src, $ti); $ti = imagecolorallocate($bg, $tc['red'], $tc['green'], $tc['blue']); imagefill($bg, 0, 0, $ti); imagecolortransparent($bg, $ti); } //This has only an effect for the non repeatable dimension. //compute start of src and dest coordinates of the single copy if ($bg_x < 0) { $dst_x = 0; $src_x = -$bg_x; } else { $src_x = 0; $dst_x = $bg_x; } if ($bg_y < 0) { $dst_y = 0; $src_y = -$bg_y; } else { $src_y = 0; $dst_y = $bg_y; } //For historical reasons exchange meanings of variables: //start_* will be the start values, while bg_* will be the temporary start values in the loops $start_x = $bg_x; $start_y = $bg_y; // Copy regions from the source image to the background if ($repeat === "no-repeat") { // Simply place the image on the background imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $img_h); } elseif ($repeat === "repeat-x") { for ($bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w) { if ($bg_x < 0) { $dst_x = 0; $src_x = -$bg_x; $w = $img_w + $bg_x; } else { $dst_x = $bg_x; $src_x = 0; $w = $img_w; } imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $img_h); } } elseif ($repeat === "repeat-y") { for ($bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h) { if ($bg_y < 0) { $dst_y = 0; $src_y = -$bg_y; $h = $img_h + $bg_y; } else { $dst_y = $bg_y; $src_y = 0; $h = $img_h; } imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $h); } } elseif ($repeat === "repeat") { for ($bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h) { for ($bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w) { if ($bg_x < 0) { $dst_x = 0; $src_x = -$bg_x; $w = $img_w + $bg_x; } else { $dst_x = $bg_x; $src_x = 0; $w = $img_w; } if ($bg_y < 0) { $dst_y = 0; $src_y = -$bg_y; $h = $img_h + $bg_y; } else { $dst_y = $bg_y; $src_y = 0; $h = $img_h; } imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $h); } } } else { print 'Unknown repeat!'; } imagedestroy($src); if ($cpdfFromGd && $this->_canvas instanceof CPDF) { // Skip writing temp file as the GD object is added directly } else { $tmpDir = $this->_dompdf->getOptions()->getTempDir(); $tmpName = @tempnam($tmpDir, "bg_dompdf_img_"); @unlink($tmpName); $tmpFile = "$tmpName.png"; imagepng($bg, $tmpFile); imagedestroy($bg); Cache::addTempImage($img, $tmpFile, $key); } } else { $bg = null; $cpdfFromGd = $tmpFile === null; } if ($this->_dompdf->getOptions()->getDebugPng()) { print '[_background_image ' . $tmpFile . ']'; } $this->_canvas->clipping_rectangle($x, $y, $box_width, $box_height); // When using cpdf and optimization to direct png creation from gd object is available, // don't create temp file, but place gd object directly into the pdf if ($cpdfFromGd && $this->_canvas instanceof CPDF) { // Note: CPDF_Adapter image converts y position $this->_canvas->get_cpdf()->addImagePng($bg, $cpdfKey, $x, $this->_canvas->get_height() - $y - $height, $width, $height); if (isset($bg)) { imagedestroy($bg); } } else { $this->_canvas->image($tmpFile, $x, $y, $width, $height); } $this->_canvas->clipping_end(); } // Border rendering functions /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_dotted($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dotted", $r1, $r2); } /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_dashed($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dashed", $r1, $r2); } /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_solid($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "solid", $r1, $r2); } /** * @param string $side * @param float $ratio * @param float $top * @param float $right * @param float $bottom * @param float $left * @param float $x * @param float $y * @param float $length * @param float $r1 * @param float $r2 */ protected function _apply_ratio($side, $ratio, $top, $right, $bottom, $left, &$x, &$y, &$length, &$r1, &$r2) { switch ($side) { case "top": $r1 -= $left * $ratio; $r2 -= $right * $ratio; $x += $left * $ratio; $y += $top * $ratio; $length -= $left * $ratio + $right * $ratio; break; case "bottom": $r1 -= $right * $ratio; $r2 -= $left * $ratio; $x += $left * $ratio; $y -= $bottom * $ratio; $length -= $left * $ratio + $right * $ratio; break; case "left": $r1 -= $top * $ratio; $r2 -= $bottom * $ratio; $x += $left * $ratio; $y += $top * $ratio; $length -= $top * $ratio + $bottom * $ratio; break; case "right": $r1 -= $bottom * $ratio; $r2 -= $top * $ratio; $x -= $right * $ratio; $y += $top * $ratio; $length -= $top * $ratio + $bottom * $ratio; break; default: return; } } /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_double($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { list($top, $right, $bottom, $left) = $widths; $third_widths = [$top / 3, $right / 3, $bottom / 3, $left / 3]; // draw the outer border $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2); $this->_apply_ratio($side, 2 / 3, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2); $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2); } /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_groove($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { list($top, $right, $bottom, $left) = $widths; $half_widths = [$top / 2, $right / 2, $bottom / 2, $left / 2]; $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2); $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2); $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2); } /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_ridge($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { list($top, $right, $bottom, $left) = $widths; $half_widths = [$top / 2, $right / 2, $bottom / 2, $left / 2]; $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2); $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2); $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2); } /** * @param $c * @return mixed */ protected function _tint($c) { if (!is_numeric($c)) { return $c; } return min(1, $c + 0.16); } /** * @param $c * @return mixed */ protected function _shade($c) { if (!is_numeric($c)) { return $c; } return max(0, $c - 0.33); } /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_inset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { switch ($side) { case "top": case "left": $shade = array_map([$this, "_shade"], $color); $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2); break; case "bottom": case "right": $tint = array_map([$this, "_tint"], $color); $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2); break; default: return; } } /** * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param float $r1 * @param float $r2 */ protected function _border_outset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) { switch ($side) { case "top": case "left": $tint = array_map([$this, "_tint"], $color); $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2); break; case "bottom": case "right": $shade = array_map([$this, "_shade"], $color); $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2); break; default: return; } } /** * Get the dash pattern and cap style for the given border style, width, and * line length. * * The base pattern is adjusted so that it fits the given line length * symmetrically. * * @param string $style * @param float $width * @param float $length * * @return array */ protected function dashPattern(string $style, float $width, float $length): array { if ($style === "dashed") { $w = 3 * $width; if ($length < $w) { $s = $w; } else { // Scale dashes and gaps $r = round($length / $w); $r = $r % 2 === 0 ? $r + 1 : $r; $s = $length / $r; } return [[$s], "butt"]; } if ($style === "dotted") { // Draw circles along the line // Round caps extend outwards by half line width, so a zero dash // width results in a circle $gap = $width <= 1 ? 2 : 1; $w = ($gap + 1) * $width; if ($length < $w) { $s = $w; } else { // Only scale gaps $l = $length - $width; $r = max(round($l / $w), 1); $s = $l / $r; } return [[0, $s], "round"]; } return [[], "butt"]; } /** * Draws a solid, dotted, or dashed line, observing the border radius * * @param float $x * @param float $y * @param float $length * @param array $color * @param float[] $widths * @param string $side * @param string $corner_style * @param string $pattern_name * @param float $r1 * @param float $r2 */ protected function _border_line($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $pattern_name = "none", $r1 = 0, $r2 = 0) { /** used by $$side */ [$top, $right, $bottom, $left] = $widths; $width = $$side; // No need to clip corners if border radius is large enough $cornerClip = $corner_style === "bevel" && ($r1 < $width || $r2 < $width); $lineLength = $length - $r1 - $r2; [$pattern, $cap] = $this->dashPattern($pattern_name, $width, $lineLength); // Determine arc border radius for corner arcs $halfWidth = $width / 2; $ar1 = max($r1 - $halfWidth, 0); $ar2 = max($r2 - $halfWidth, 0); // Small angle adjustments to prevent the background from shining through $adj1 = $ar1 / 80; $adj2 = $ar2 / 80; // Adjust line width and corner angles to account for the fact that // round caps extend outwards. The line is actually only shifted below, // not shortened, as otherwise the end dash (circle) will vanish // occasionally $dl = $cap === "round" ? $halfWidth : 0; if ($cap === "round" && $ar1 > 0) { $adj1 -= rad2deg(asin($halfWidth / $ar1)); } if ($cap === "round" && $ar2 > 0) { $adj2 -= rad2deg(asin($halfWidth / $ar2)); } switch ($side) { case "top": if ($cornerClip) { $points = [ $x, $y, $x, $y - 1, // Extend outwards to avoid gaps $x + $length, $y - 1, // Extend outwards to avoid gaps $x + $length, $y, $x + $length - max($right, $r2), $y + max($width, $r2), $x + max($left, $r1), $y + max($width, $r1) ]; $this->_canvas->clipping_polygon($points); } $y += $halfWidth; if ($ar1 > 0 && $adj1 > -22.5) { $this->_canvas->arc($x + $r1, $y + $ar1, $ar1, $ar1, 90 - $adj1, 135 + $adj1, $color, $width, $pattern, $cap); } if ($lineLength > 0) { $this->_canvas->line($x + $dl + $r1, $y, $x + $dl + $length - $r2, $y, $color, $width, $pattern, $cap); } if ($ar2 > 0 && $adj2 > -22.5) { $this->_canvas->arc($x + $length - $r2, $y + $ar2, $ar2, $ar2, 45 - $adj2, 90 + $adj2, $color, $width, $pattern, $cap); } break; case "bottom": if ($cornerClip) { $points = [ $x, $y, $x, $y + 1, // Extend outwards to avoid gaps $x + $length, $y + 1, // Extend outwards to avoid gaps $x + $length, $y, $x + $length - max($right, $r2), $y - max($width, $r2), $x + max($left, $r1), $y - max($width, $r1) ]; $this->_canvas->clipping_polygon($points); } $y -= $halfWidth; if ($ar1 > 0 && $adj1 > -22.5) { $this->_canvas->arc($x + $r1, $y - $ar1, $ar1, $ar1, 225 - $adj1, 270 + $adj1, $color, $width, $pattern, $cap); } if ($lineLength > 0) { $this->_canvas->line($x + $dl + $r1, $y, $x + $dl + $length - $r2, $y, $color, $width, $pattern, $cap); } if ($ar2 > 0 && $adj2 > -22.5) { $this->_canvas->arc($x + $length - $r2, $y - $ar2, $ar2, $ar2, 270 - $adj2, 315 + $adj2, $color, $width, $pattern, $cap); } break; case "left": if ($cornerClip) { $points = [ $x, $y, $x - 1, $y, // Extend outwards to avoid gaps $x - 1, $y + $length, // Extend outwards to avoid gaps $x, $y + $length, $x + max($width, $r2), $y + $length - max($bottom, $r2), $x + max($width, $r1), $y + max($top, $r1) ]; $this->_canvas->clipping_polygon($points); } $x += $halfWidth; if ($ar1 > 0 && $adj1 > -22.5) { $this->_canvas->arc($x + $ar1, $y + $r1, $ar1, $ar1, 135 - $adj1, 180 + $adj1, $color, $width, $pattern, $cap); } if ($lineLength > 0) { $this->_canvas->line($x, $y + $dl + $r1, $x, $y + $dl + $length - $r2, $color, $width, $pattern, $cap); } if ($ar2 > 0 && $adj2 > -22.5) { $this->_canvas->arc($x + $ar2, $y + $length - $r2, $ar2, $ar2, 180 - $adj2, 225 + $adj2, $color, $width, $pattern, $cap); } break; case "right": if ($cornerClip) { $points = [ $x, $y, $x + 1, $y, // Extend outwards to avoid gaps $x + 1, $y + $length, // Extend outwards to avoid gaps $x, $y + $length, $x - max($width, $r2), $y + $length - max($bottom, $r2), $x - max($width, $r1), $y + max($top, $r1) ]; $this->_canvas->clipping_polygon($points); } $x -= $halfWidth; if ($ar1 > 0 && $adj1 > -22.5) { $this->_canvas->arc($x - $ar1, $y + $r1, $ar1, $ar1, 0 - $adj1, 45 + $adj1, $color, $width, $pattern, $cap); } if ($lineLength > 0) { $this->_canvas->line($x, $y + $dl + $r1, $x, $y + $dl + $length - $r2, $color, $width, $pattern, $cap); } if ($ar2 > 0 && $adj2 > -22.5) { $this->_canvas->arc($x - $ar2, $y + $length - $r2, $ar2, $ar2, 315 - $adj2, 360 + $adj2, $color, $width, $pattern, $cap); } break; } if ($cornerClip) { $this->_canvas->clipping_end(); } } /** * @param float $opacity */ protected function _set_opacity(float $opacity): void { if ($opacity >= 0.0 && $opacity <= 1.0) { $this->_canvas->set_opacity($opacity); } } /** * @param float[] $box * @param string $color * @param array $style */ protected function _debug_layout($box, $color = "red", $style = []) { $this->_canvas->rectangle($box[0], $box[1], $box[2], $box[3], Color::parse($color), 0.1, $style); } /** * @param float $img_width * @param float $img_height * @param float $container_width * @param float $container_height * @param array|string $bg_resize * @param int $dpi * * @return array */ protected function _resize_background_image( $img_width, $img_height, $container_width, $container_height, $bg_resize, $dpi ) { // We got two some specific numbers and/or auto definitions if (is_array($bg_resize)) { $is_auto_width = $bg_resize[0] === 'auto'; if ($is_auto_width) { $new_img_width = $img_width; } else { $new_img_width = $bg_resize[0]; if (Helpers::is_percent($new_img_width)) { $new_img_width = round(($container_width / 100) * (float)$new_img_width); } else { $new_img_width = round($new_img_width * $dpi / 72); } } $is_auto_height = $bg_resize[1] === 'auto'; if ($is_auto_height) { $new_img_height = $img_height; } else { $new_img_height = $bg_resize[1]; if (Helpers::is_percent($new_img_height)) { $new_img_height = round(($container_height / 100) * (float)$new_img_height); } else { $new_img_height = round($new_img_height * $dpi / 72); } } // if one of both was set to auto the other one needs to scale proportionally if ($is_auto_width !== $is_auto_height) { if ($is_auto_height) { $new_img_height = round($new_img_width * ($img_height / $img_width)); } else { $new_img_width = round($new_img_height * ($img_width / $img_height)); } } } else { $container_ratio = $container_height / $container_width; if ($bg_resize === 'cover' || $bg_resize === 'contain') { $img_ratio = $img_height / $img_width; if ( ($bg_resize === 'cover' && $container_ratio > $img_ratio) || ($bg_resize === 'contain' && $container_ratio < $img_ratio) ) { $new_img_height = $container_height; $new_img_width = round($container_height / $img_ratio); } else { $new_img_width = $container_width; $new_img_height = round($container_width * $img_ratio); } } else { $new_img_width = $img_width; $new_img_height = $img_height; } } return [$new_img_width, $new_img_height]; } } dompdf/src/Renderer/TableRowGroup.php 0000644 00000001520 15024772104 0013617 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Frame; /** * Renders block frames * * @package dompdf */ class TableRowGroup extends Block { /** * @param Frame $frame */ function render(Frame $frame) { $style = $frame->get_style(); $this->_set_opacity($frame->get_opacity($style->opacity)); $border_box = $frame->get_border_box(); $this->_render_border($frame, $border_box); $this->_render_outline($frame, $border_box); $id = $frame->get_node()->getAttribute("id"); if (strlen($id) > 0) { $this->_canvas->add_named_dest($id); } $this->debugBlockLayout($frame, "red"); } } dompdf/src/Renderer/Inline.php 0000644 00000010357 15024772104 0012311 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Frame; use Dompdf\Helpers; /** * Renders inline frames * * @package dompdf */ class Inline extends AbstractRenderer { function render(Frame $frame) { if (!$frame->get_first_child()) { return; // No children, no service } $style = $frame->get_style(); $dompdf = $this->_dompdf; $this->_set_opacity($frame->get_opacity($style->opacity)); $do_debug_layout_line = $dompdf->getOptions()->getDebugLayout() && $dompdf->getOptions()->getDebugLayoutInline(); // Draw the background & border behind each child. To do this we need // to figure out just how much space each child takes: [$x, $y] = $frame->get_first_child()->get_position(); [$w, $h] = $this->get_child_size($frame, $do_debug_layout_line); [, , $cbw] = $frame->get_containing_block(); $margin_left = $style->length_in_pt($style->margin_left, $cbw); $pt = $style->length_in_pt($style->padding_top, $cbw); $pb = $style->length_in_pt($style->padding_bottom, $cbw); // Make sure that border and background start inside the left margin // Extend the drawn box by border and padding in vertical direction, as // these do not affect layout // FIXME: Using a small vertical offset of a fraction of the height here // to work around the vertical position being slightly off in general $x += $margin_left; $y -= $style->border_top_width + $pt - ($h * 0.1); $w += $style->border_left_width + $style->border_right_width; $h += $style->border_top_width + $pt + $style->border_bottom_width + $pb; $border_box = [$x, $y, $w, $h]; $this->_render_background($frame, $border_box); $this->_render_border($frame, $border_box); $this->_render_outline($frame, $border_box); $node = $frame->get_node(); $id = $node->getAttribute("id"); if (strlen($id) > 0) { $this->_canvas->add_named_dest($id); } // Only two levels of links frames $is_link_node = $node->nodeName === "a"; if ($is_link_node) { if (($name = $node->getAttribute("name"))) { $this->_canvas->add_named_dest($name); } } if ($frame->get_parent() && $frame->get_parent()->get_node()->nodeName === "a") { $link_node = $frame->get_parent()->get_node(); } // Handle anchors & links if ($is_link_node) { if ($href = $node->getAttribute("href")) { $href = Helpers::build_url($dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $href) ?? $href; $this->_canvas->add_link($href, $x, $y, $w, $h); } } } protected function get_child_size(Frame $frame, bool $do_debug_layout_line): array { $w = 0.0; $h = 0.0; foreach ($frame->get_children() as $child) { if ($child->get_node()->nodeValue === " " && $child->get_prev_sibling() && !$child->get_next_sibling()) { break; } $style = $child->get_style(); $auto_width = $style->width === "auto"; $auto_height = $style->height === "auto"; [, , $child_w, $child_h] = $child->get_padding_box(); if ($auto_width || $auto_height) { [$child_w2, $child_h2] = $this->get_child_size($child, $do_debug_layout_line); if ($auto_width) { $child_w = $child_w2; } if ($auto_height) { $child_h = $child_h2; } } $w += $child_w; $h = max($h, $child_h); if ($do_debug_layout_line) { $this->_debug_layout($child->get_border_box(), "blue"); if ($this->_dompdf->getOptions()->getDebugLayoutPaddingBox()) { $this->_debug_layout($child->get_padding_box(), "blue", [0.5, 0.5]); } } } return [$w, $h]; } } dompdf/src/Renderer/Block.php 0000644 00000005164 15024772104 0012125 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Frame; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\Helpers; /** * Renders block frames * * @package dompdf */ class Block extends AbstractRenderer { /** * @param Frame $frame */ function render(Frame $frame) { $style = $frame->get_style(); $node = $frame->get_node(); $dompdf = $this->_dompdf; $this->_set_opacity($frame->get_opacity($style->opacity)); [$x, $y, $w, $h] = $frame->get_border_box(); if ($node->nodeName === "body") { // Margins should be fully resolved at this point $mt = $style->margin_top; $mb = $style->margin_bottom; $h = $frame->get_containing_block("h") - $mt - $mb; } $border_box = [$x, $y, $w, $h]; // Draw our background, border and content $this->_render_background($frame, $border_box); $this->_render_border($frame, $border_box); $this->_render_outline($frame, $border_box); // Handle anchors & links if ($node->nodeName === "a" && $href = $node->getAttribute("href")) { $href = Helpers::build_url($dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $href) ?? $href; $this->_canvas->add_link($href, $x, $y, $w, $h); } $id = $frame->get_node()->getAttribute("id"); if (strlen($id) > 0) { $this->_canvas->add_named_dest($id); } $this->debugBlockLayout($frame, "red", false); } protected function debugBlockLayout(Frame $frame, ?string $color, bool $lines = false): void { $options = $this->_dompdf->getOptions(); $debugLayout = $options->getDebugLayout(); if (!$debugLayout) { return; } if ($color && $options->getDebugLayoutBlocks()) { $this->_debug_layout($frame->get_border_box(), $color); if ($options->getDebugLayoutPaddingBox()) { $this->_debug_layout($frame->get_padding_box(), $color, [0.5, 0.5]); } } if ($lines && $options->getDebugLayoutLines() && $frame instanceof BlockFrameDecorator) { [$cx, , $cw] = $frame->get_content_box(); foreach ($frame->get_line_boxes() as $line) { $lw = $cw - $line->left - $line->right; $this->_debug_layout([$cx + $line->left, $line->y, $lw, $line->h], "orange"); } } } } dompdf/src/Renderer/Text.php 0000644 00000012100 15024772104 0012003 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Renderer; use Dompdf\Adapter\CPDF; use Dompdf\Frame; /** * Renders text frames * * @package dompdf */ class Text extends AbstractRenderer { /** Thickness of underline. Screen: 0.08, print: better less, e.g. 0.04 */ const DECO_THICKNESS = 0.02; //Tweaking if $base and $descent are not accurate. //Check method_exists( $this->_canvas, "get_cpdf" ) //- For cpdf these can and must stay 0, because font metrics are used directly. //- For other renderers, if different values are wanted, separate the parameter sets. // But $size and $size-$height seem to be accurate enough /** Relative to bottom of text, as fraction of height */ const UNDERLINE_OFFSET = 0.0; /** Relative to top of text */ const OVERLINE_OFFSET = 0.0; /** Relative to centre of text. */ const LINETHROUGH_OFFSET = 0.0; /** How far to extend lines past either end, in pt */ const DECO_EXTENSION = 0.0; /** * @param \Dompdf\FrameDecorator\Text $frame */ function render(Frame $frame) { $style = $frame->get_style(); $text = $frame->get_text(); if ($text === "") { return; } $this->_set_opacity($frame->get_opacity($style->opacity)); list($x, $y) = $frame->get_position(); $cb = $frame->get_containing_block(); $ml = $style->margin_left; $pl = $style->padding_left; $bl = $style->border_left_width; $x += (float) $style->length_in_pt([$ml, $pl, $bl], $cb["w"]); $font = $style->font_family; $size = $style->font_size; $frame_font_size = $frame->get_dompdf()->getFontMetrics()->getFontHeight($font, $size); $word_spacing = $frame->get_text_spacing() + $style->word_spacing; $letter_spacing = $style->letter_spacing; $width = (float) $style->width; /*$text = str_replace( array("{PAGE_NUM}"), array($this->_canvas->get_page_number()), $text );*/ $this->_canvas->text($x, $y, $text, $font, $size, $style->color, $word_spacing, $letter_spacing); $line = $frame->get_containing_line(); // FIXME Instead of using the tallest frame to position, // the decoration, the text should be well placed if (false && $line->tallest_frame) { $base_frame = $line->tallest_frame; $style = $base_frame->get_style(); $size = $style->font_size; } $line_thickness = $size * self::DECO_THICKNESS; $underline_offset = $size * self::UNDERLINE_OFFSET; $overline_offset = $size * self::OVERLINE_OFFSET; $linethrough_offset = $size * self::LINETHROUGH_OFFSET; $underline_position = -0.08; if ($this->_canvas instanceof CPDF) { $cpdf_font = $this->_canvas->get_cpdf()->fonts[$style->font_family]; if (isset($cpdf_font["UnderlinePosition"])) { $underline_position = $cpdf_font["UnderlinePosition"] / 1000; } if (isset($cpdf_font["UnderlineThickness"])) { $line_thickness = $size * ($cpdf_font["UnderlineThickness"] / 1000); } } $descent = $size * $underline_position; $base = $frame_font_size; // Handle text decoration: // http://www.w3.org/TR/CSS21/text.html#propdef-text-decoration // Draw all applicable text-decorations. Start with the root and work our way down. $p = $frame; $stack = []; while ($p = $p->get_parent()) { $stack[] = $p; } while (isset($stack[0])) { $f = array_pop($stack); if (($text_deco = $f->get_style()->text_decoration) === "none") { continue; } $deco_y = $y; //$line->y; $color = $f->get_style()->color; switch ($text_deco) { default: continue 2; case "underline": $deco_y += $base - $descent + $underline_offset + $line_thickness / 2; break; case "overline": $deco_y += $overline_offset + $line_thickness / 2; break; case "line-through": $deco_y += $base * 0.7 + $linethrough_offset; break; } $dx = 0; $x1 = $x - self::DECO_EXTENSION; $x2 = $x + $width + $dx + self::DECO_EXTENSION; $this->_canvas->line($x1, $deco_y, $x2, $deco_y, $color, $line_thickness); } if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutLines()) { $text_width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $letter_spacing); $this->_debug_layout([$x, $y, $text_width, $frame_font_size], "orange", [0.5, 0.5]); } } } dompdf/src/FrameReflower/AbstractFrameReflower.php 0000644 00000054032 15024772104 0016307 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\Dompdf; use Dompdf\Helpers; use Dompdf\Frame; use Dompdf\Frame\Factory; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\Block; /** * Base reflower class * * Reflower objects are responsible for determining the width and height of * individual frames. They also create line and page breaks as necessary. * * @package dompdf */ abstract class AbstractFrameReflower { /** * Frame for this reflower * * @var AbstractFrameDecorator */ protected $_frame; /** * Cached min/max child size * * @var array */ protected $_min_max_child_cache; /** * Cached min/max size * * @var array */ protected $_min_max_cache; /** * AbstractFrameReflower constructor. * @param AbstractFrameDecorator $frame */ function __construct(AbstractFrameDecorator $frame) { $this->_frame = $frame; $this->_min_max_child_cache = null; $this->_min_max_cache = null; } /** * @return Dompdf */ function get_dompdf() { return $this->_frame->get_dompdf(); } public function reset(): void { $this->_min_max_child_cache = null; $this->_min_max_cache = null; } /** * Determine the actual containing block for absolute and fixed position. * * https://www.w3.org/TR/CSS21/visudet.html#containing-block-details */ protected function determine_absolute_containing_block(): void { $frame = $this->_frame; $style = $frame->get_style(); switch ($style->position) { case "absolute": $parent = $frame->find_positioned_parent(); if ($parent !== $frame->get_root()) { $parent_style = $parent->get_style(); $parent_padding_box = $parent->get_padding_box(); //FIXME: an accurate measure of the positioned parent height // is not possible until reflow has completed; // we'll fall back to the parent's containing block, // which is wrong for auto-height parents if ($parent_style->height === "auto") { $parent_containing_block = $parent->get_containing_block(); $containing_block_height = $parent_containing_block["h"] - (float)$parent_style->length_in_pt([ $parent_style->margin_top, $parent_style->margin_bottom, $parent_style->border_top_width, $parent_style->border_bottom_width ], $parent_containing_block["w"]); } else { $containing_block_height = $parent_padding_box["h"]; } $frame->set_containing_block($parent_padding_box["x"], $parent_padding_box["y"], $parent_padding_box["w"], $containing_block_height); break; } case "fixed": $initial_cb = $frame->get_root()->get_first_child()->get_containing_block(); $frame->set_containing_block($initial_cb["x"], $initial_cb["y"], $initial_cb["w"], $initial_cb["h"]); break; default: // Nothing to do, containing block already set via parent break; } } /** * Collapse frames margins * http://www.w3.org/TR/CSS21/box.html#collapsing-margins */ protected function _collapse_margins(): void { $frame = $this->_frame; // Margins of float/absolutely positioned/inline-level elements do not collapse if (!$frame->is_in_flow() || $frame->is_inline_level() || $frame->get_root() === $frame || $frame->get_parent() === $frame->get_root() ) { return; } $cb = $frame->get_containing_block(); $style = $frame->get_style(); $t = $style->length_in_pt($style->margin_top, $cb["w"]); $b = $style->length_in_pt($style->margin_bottom, $cb["w"]); // Handle 'auto' values if ($t === "auto") { $style->set_used("margin_top", 0.0); $t = 0.0; } if ($b === "auto") { $style->set_used("margin_bottom", 0.0); $b = 0.0; } // Collapse vertical margins: $n = $frame->get_next_sibling(); if ( $n && !($n->is_block_level() && $n->is_in_flow()) ) { while ($n = $n->get_next_sibling()) { if ($n->is_block_level() && $n->is_in_flow()) { break; } if (!$n->get_first_child()) { $n = null; break; } } } if ($n) { $n_style = $n->get_style(); $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["w"]); $b = $this->get_collapsed_margin_length($b, $n_t); $style->set_used("margin_bottom", $b); $n_style->set_used("margin_top", 0.0); } // Collapse our first child's margin, if there is no border or padding if ($style->border_top_width == 0 && $style->length_in_pt($style->padding_top) == 0) { $f = $this->_frame->get_first_child(); if ( $f && !($f->is_block_level() && $f->is_in_flow()) ) { while ($f = $f->get_next_sibling()) { if ($f->is_block_level() && $f->is_in_flow()) { break; } if (!$f->get_first_child()) { $f = null; break; } } } // Margins are collapsed only between block-level boxes if ($f) { $f_style = $f->get_style(); $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["w"]); $t = $this->get_collapsed_margin_length($t, $f_t); $style->set_used("margin_top", $t); $f_style->set_used("margin_top", 0.0); } } // Collapse our last child's margin, if there is no border or padding if ($style->border_bottom_width == 0 && $style->length_in_pt($style->padding_bottom) == 0) { $l = $this->_frame->get_last_child(); if ( $l && !($l->is_block_level() && $l->is_in_flow()) ) { while ($l = $l->get_prev_sibling()) { if ($l->is_block_level() && $l->is_in_flow()) { break; } if (!$l->get_last_child()) { $l = null; break; } } } // Margins are collapsed only between block-level boxes if ($l) { $l_style = $l->get_style(); $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["w"]); $b = $this->get_collapsed_margin_length($b, $l_b); $style->set_used("margin_bottom", $b); $l_style->set_used("margin_bottom", 0.0); } } } /** * Get the combined (collapsed) length of two adjoining margins. * * See http://www.w3.org/TR/CSS21/box.html#collapsing-margins. * * @param float $l1 * @param float $l2 * * @return float */ private function get_collapsed_margin_length(float $l1, float $l2): float { if ($l1 < 0 && $l2 < 0) { return min($l1, $l2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0 } if ($l1 < 0 || $l2 < 0) { return $l1 + $l2; // x + y = x - abs(y), if y < 0 } return max($l1, $l2); } /** * Handle relative positioning according to * https://www.w3.org/TR/CSS21/visuren.html#relative-positioning. * * @param AbstractFrameDecorator $frame The frame to handle. */ protected function position_relative(AbstractFrameDecorator $frame): void { $style = $frame->get_style(); if ($style->position === "relative") { $cb = $frame->get_containing_block(); $top = $style->length_in_pt($style->top, $cb["h"]); $right = $style->length_in_pt($style->right, $cb["w"]); $bottom = $style->length_in_pt($style->bottom, $cb["h"]); $left = $style->length_in_pt($style->left, $cb["w"]); // FIXME RTL case: // if ($left !== "auto" && $right !== "auto") $left = -$right; if ($left === "auto" && $right === "auto") { $left = 0; } elseif ($left === "auto") { $left = -$right; } if ($top === "auto" && $bottom === "auto") { $top = 0; } elseif ($top === "auto") { $top = -$bottom; } $frame->move($left, $top); } } /** * @param Block|null $block */ abstract function reflow(Block $block = null); /** * Resolve the `min-width` property. * * Resolves to 0 if not set or if a percentage and the containing-block * width is not defined. * * @param float|null $cbw Width of the containing block. * * @return float */ protected function resolve_min_width(?float $cbw): float { $style = $this->_frame->get_style(); $min_width = $style->min_width; return $min_width !== "auto" ? $style->length_in_pt($min_width, $cbw ?? 0) : 0.0; } /** * Resolve the `max-width` property. * * Resolves to `INF` if not set or if a percentage and the containing-block * width is not defined. * * @param float|null $cbw Width of the containing block. * * @return float */ protected function resolve_max_width(?float $cbw): float { $style = $this->_frame->get_style(); $max_width = $style->max_width; return $max_width !== "none" ? $style->length_in_pt($max_width, $cbw ?? INF) : INF; } /** * Resolve the `min-height` property. * * Resolves to 0 if not set or if a percentage and the containing-block * height is not defined. * * @param float|null $cbh Height of the containing block. * * @return float */ protected function resolve_min_height(?float $cbh): float { $style = $this->_frame->get_style(); $min_height = $style->min_height; return $min_height !== "auto" ? $style->length_in_pt($min_height, $cbh ?? 0) : 0.0; } /** * Resolve the `max-height` property. * * Resolves to `INF` if not set or if a percentage and the containing-block * height is not defined. * * @param float|null $cbh Height of the containing block. * * @return float */ protected function resolve_max_height(?float $cbh): float { $style = $this->_frame->get_style(); $max_height = $style->max_height; return $max_height !== "none" ? $style->length_in_pt($style->max_height, $cbh ?? INF) : INF; } /** * Get the minimum and maximum preferred width of the contents of the frame, * as requested by its children. * * @return array A two-element array of min and max width. */ public function get_min_max_child_width(): array { if (!is_null($this->_min_max_child_cache)) { return $this->_min_max_child_cache; } $low = []; $high = []; for ($iter = $this->_frame->get_children(); $iter->valid(); $iter->next()) { $inline_min = 0; $inline_max = 0; // Add all adjacent inline widths together to calculate max width while ($iter->valid() && ($iter->current()->is_inline_level() || $iter->current()->get_style()->display === "-dompdf-image")) { /** @var AbstractFrameDecorator */ $child = $iter->current(); $child->get_reflower()->_set_content(); $minmax = $child->get_min_max_width(); if (in_array($child->get_style()->white_space, ["pre", "nowrap"], true)) { $inline_min += $minmax["min"]; } else { $low[] = $minmax["min"]; } $inline_max += $minmax["max"]; $iter->next(); } if ($inline_min > 0) { $low[] = $inline_min; } if ($inline_max > 0) { $high[] = $inline_max; } // Skip children with absolute position if ($iter->valid() && !$iter->current()->is_absolute()) { /** @var AbstractFrameDecorator */ $child = $iter->current(); $child->get_reflower()->_set_content(); list($low[], $high[]) = $child->get_min_max_width(); } } $min = count($low) ? max($low) : 0; $max = count($high) ? max($high) : 0; return $this->_min_max_child_cache = [$min, $max]; } /** * Get the minimum and maximum preferred content-box width of the frame. * * @return array A two-element array of min and max width. */ public function get_min_max_content_width(): array { return $this->get_min_max_child_width(); } /** * Get the minimum and maximum preferred border-box width of the frame. * * Required for shrink-to-fit width calculation, as used in automatic table * layout, absolute positioning, float and inline-block. This provides a * basic implementation. Child classes should override this or * `get_min_max_content_width` as necessary. * * @return array An array `[0 => min, 1 => max, "min" => min, "max" => max]` * of min and max width. */ public function get_min_max_width(): array { if (!is_null($this->_min_max_cache)) { return $this->_min_max_cache; } $style = $this->_frame->get_style(); [$min, $max] = $this->get_min_max_content_width(); // Account for margins, borders, and padding $dims = [ $style->padding_left, $style->padding_right, $style->border_left_width, $style->border_right_width, $style->margin_left, $style->margin_right ]; // The containing block is not defined yet, treat percentages as 0 $delta = (float) $style->length_in_pt($dims, 0); $min += $delta; $max += $delta; return $this->_min_max_cache = [$min, $max, "min" => $min, "max" => $max]; } /** * Parses a CSS string containing quotes and escaped hex characters * * @param $string string The CSS string to parse * @param $single_trim * @return string */ protected function _parse_string($string, $single_trim = false) { if ($single_trim) { $string = preg_replace('/^[\"\']/', "", $string); $string = preg_replace('/[\"\']$/', "", $string); } else { $string = trim($string, "'\""); } $string = str_replace(["\\\n", '\\"', "\\'"], ["", '"', "'"], $string); // Convert escaped hex characters into ascii characters (e.g. \A => newline) $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/", function ($matches) { return \Dompdf\Helpers::unichr(hexdec($matches[1])); }, $string); return $string; } /** * Parses a CSS "quotes" property * * https://www.w3.org/TR/css-content-3/#quotes * * @return array An array of pairs of quotes */ protected function _parse_quotes(): array { $quotes = $this->_frame->get_style()->quotes; if ($quotes === "none") { return []; } if ($quotes === "auto") { // TODO: Use typographically appropriate quotes for the current // language here return [['"', '"'], ["'", "'"]]; } // Matches quote types $re = '/(\'[^\']*\')|(\"[^\"]*\")/'; // Split on spaces, except within quotes if (!preg_match_all($re, $quotes, $matches, PREG_SET_ORDER)) { return []; } $quotes_array = []; foreach ($matches as $_quote) { $quotes_array[] = $this->_parse_string($_quote[0], true); } return array_chunk($quotes_array, 2); } /** * Parses the CSS "content" property * * https://www.w3.org/TR/CSS21/generate.html#content * * @return string The resulting string */ protected function _parse_content(): string { $style = $this->_frame->get_style(); $content = $style->content; if ($content === "normal" || $content === "none") { return ""; } $quotes = $this->_parse_quotes(); $text = ""; foreach ($content as $val) { // String if (in_array(mb_substr($val, 0, 1), ['"', "'"], true)) { $text .= $this->_parse_string($val); continue; } $val = mb_strtolower($val); // Keywords if ($val === "open-quote") { // FIXME: Take quotation depth into account if (isset($quotes[0][0])) { $text .= $quotes[0][0]; } continue; } elseif ($val === "close-quote") { // FIXME: Take quotation depth into account if (isset($quotes[0][1])) { $text .= $quotes[0][1]; } continue; } elseif ($val === "no-open-quote") { // FIXME: Increment quotation depth continue; } elseif ($val === "no-close-quote") { // FIXME: Decrement quotation depth continue; } // attr() if (mb_substr($val, 0, 5) === "attr(") { $i = mb_strpos($val, ")"); if ($i === false) { continue; } $attr = trim(mb_substr($val, 5, $i - 5)); if ($attr === "") { continue; } $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr); continue; } // counter()/counters() if (mb_substr($val, 0, 7) === "counter") { // Handle counter() references: // http://www.w3.org/TR/CSS21/generate.html#content $i = mb_strpos($val, ")"); if ($i === false) { continue; } preg_match('/(counters?)(^\()*?\(\s*([^\s,]+)\s*(,\s*["\']?([^"\'\)]*)["\']?\s*(,\s*([^\s)]+)\s*)?)?\)/i', $val, $args); $counter_id = $args[3]; if (strtolower($args[1]) === "counter") { // counter(name [,style]) if (isset($args[5])) { $type = trim($args[5]); } else { $type = "decimal"; } $p = $this->_frame->lookup_counter_frame($counter_id); $text .= $p->counter_value($counter_id, $type); } elseif (strtolower($args[1]) === "counters") { // counters(name, string [,style]) if (isset($args[5])) { $string = $this->_parse_string($args[5]); } else { $string = ""; } if (isset($args[7])) { $type = trim($args[7]); } else { $type = "decimal"; } $p = $this->_frame->lookup_counter_frame($counter_id); $tmp = []; while ($p) { // We only want to use the counter values when they actually increment the counter if (array_key_exists($counter_id, $p->_counters)) { array_unshift($tmp, $p->counter_value($counter_id, $type)); } $p = $p->lookup_counter_frame($counter_id); } $text .= implode($string, $tmp); } else { // countertops? } continue; } } return $text; } /** * Handle counters and set generated content if the frame is a * generated-content frame. */ protected function _set_content(): void { $frame = $this->_frame; if ($frame->content_set) { return; } $style = $frame->get_style(); if (($reset = $style->counter_reset) !== "none") { $frame->reset_counters($reset); } if (($increment = $style->counter_increment) !== "none") { $frame->increment_counters($increment); } if ($frame->get_node()->nodeName === "dompdf_generated") { $content = $this->_parse_content(); if ($content !== "") { $node = $frame->get_node()->ownerDocument->createTextNode($content); $new_style = $style->get_stylesheet()->create_style(); $new_style->inherit($style); $new_frame = new Frame($node); $new_frame->set_style($new_style); Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root()); $frame->append_child($new_frame); } } $frame->content_set = true; } } dompdf/src/FrameReflower/Image.php 0000644 00000017444 15024772104 0013113 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\Helpers; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Image as ImageFrameDecorator; /** * Image reflower class * * @package dompdf */ class Image extends AbstractFrameReflower { /** * Image constructor. * @param ImageFrameDecorator $frame */ function __construct(ImageFrameDecorator $frame) { parent::__construct($frame); } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { $this->determine_absolute_containing_block(); // Counters and generated content $this->_set_content(); //FLOAT //$frame = $this->_frame; //$page = $frame->get_root(); //if ($frame->get_style()->float !== "none" ) { // $page->add_floating_frame($this); //} $this->resolve_dimensions(); $this->resolve_margins(); $frame = $this->_frame; $frame->position(); if ($block && $frame->is_in_flow()) { $block->add_frame_to_line($frame); } } public function get_min_max_content_width(): array { // TODO: While the containing block is not set yet on the frame, it can // already be determined in some cases due to fixed dimensions on the // ancestor forming the containing block. In such cases, percentage // values could be resolved here $style = $this->_frame->get_style(); [$width] = $this->calculate_size(null, null); $min_width = $this->resolve_min_width(null); $percent_width = Helpers::is_percent($style->width) || Helpers::is_percent($style->max_width) || ($style->width === "auto" && (Helpers::is_percent($style->height) || Helpers::is_percent($style->max_height))); // Use the specified min width as minimum when width or max width depend // on the containing block and cannot be resolved yet. This mimics // browser behavior $min = $percent_width ? $min_width : $width; $max = $width; return [$min, $max]; } /** * Calculate width and height, accounting for min/max constraints. * * * https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width * * https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height * * https://www.w3.org/TR/CSS21/visudet.html#min-max-widths * * https://www.w3.org/TR/CSS21/visudet.html#min-max-heights * * @param float|null $cbw Width of the containing block. * @param float|null $cbh Height of the containing block. * * @return float[] */ protected function calculate_size(?float $cbw, ?float $cbh): array { /** @var ImageFrameDecorator */ $frame = $this->_frame; $style = $frame->get_style(); $computed_width = $style->width; $computed_height = $style->height; $width = $cbw === null && Helpers::is_percent($computed_width) ? "auto" : $style->length_in_pt($computed_width, $cbw ?? 0); $height = $cbh === null && Helpers::is_percent($computed_height) ? "auto" : $style->length_in_pt($computed_height, $cbh ?? 0); $min_width = $this->resolve_min_width($cbw); $max_width = $this->resolve_max_width($cbw); $min_height = $this->resolve_min_height($cbh); $max_height = $this->resolve_max_height($cbh); if ($width === "auto" && $height === "auto") { // Use intrinsic dimensions, resampled to pt [$img_width, $img_height] = $frame->get_intrinsic_dimensions(); $w = $frame->resample($img_width); $h = $frame->resample($img_height); // Resolve min/max constraints according to the constraint-violation // table in https://www.w3.org/TR/CSS21/visudet.html#min-max-widths $max_width = max($min_width, $max_width); $max_height = max($min_height, $max_height); if (($w > $max_width && $h <= $max_height) || ($w > $max_width && $h > $max_height && $max_width / $w <= $max_height / $h) || ($w < $min_width && $h > $min_height) || ($w < $min_width && $h < $min_height && $min_width / $w > $min_height / $h) ) { $width = Helpers::clamp($w, $min_width, $max_width); $height = $width * ($img_height / $img_width); $height = Helpers::clamp($height, $min_height, $max_height); } else { $height = Helpers::clamp($h, $min_height, $max_height); $width = $height * ($img_width / $img_height); $width = Helpers::clamp($width, $min_width, $max_width); } } elseif ($height === "auto") { // Width is fixed, scale height according to aspect ratio [$img_width, $img_height] = $frame->get_intrinsic_dimensions(); $width = Helpers::clamp((float) $width, $min_width, $max_width); $height = $width * ($img_height / $img_width); $height = Helpers::clamp($height, $min_height, $max_height); } elseif ($width === "auto") { // Height is fixed, scale width according to aspect ratio [$img_width, $img_height] = $frame->get_intrinsic_dimensions(); $height = Helpers::clamp((float) $height, $min_height, $max_height); $width = $height * ($img_width / $img_height); $width = Helpers::clamp($width, $min_width, $max_width); } else { // Width and height are fixed $width = Helpers::clamp((float) $width, $min_width, $max_width); $height = Helpers::clamp((float) $height, $min_height, $max_height); } return [$width, $height]; } protected function resolve_dimensions(): void { /** @var ImageFrameDecorator */ $frame = $this->_frame; $style = $frame->get_style(); $debug_png = $this->get_dompdf()->getOptions()->getDebugPng(); if ($debug_png) { [$img_width, $img_height] = $frame->get_intrinsic_dimensions(); print "resolve_dimensions() " . $frame->get_style()->width . " " . $frame->get_style()->height . ";" . $frame->get_parent()->get_style()->width . " " . $frame->get_parent()->get_style()->height . ";" . $frame->get_parent()->get_parent()->get_style()->width . " " . $frame->get_parent()->get_parent()->get_style()->height . ";" . $img_width . " " . $img_height . "|"; } [, , $cbw, $cbh] = $frame->get_containing_block(); [$width, $height] = $this->calculate_size($cbw, $cbh); if ($debug_png) { print $width . " " . $height . ";"; } $style->set_used("width", $width); $style->set_used("height", $height); } protected function resolve_margins(): void { // Only handle the inline case for now // https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width // https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height $style = $this->_frame->get_style(); if ($style->margin_left === "auto") { $style->set_used("margin_left", 0.0); } if ($style->margin_right === "auto") { $style->set_used("margin_right", 0.0); } if ($style->margin_top === "auto") { $style->set_used("margin_top", 0.0); } if ($style->margin_bottom === "auto") { $style->set_used("margin_bottom", 0.0); } } } dompdf/src/FrameReflower/ListBullet.php 0000644 00000002256 15024772104 0014147 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\ListBullet as ListBulletFrameDecorator; /** * Reflows list bullets * * @package dompdf */ class ListBullet extends AbstractFrameReflower { /** * ListBullet constructor. * @param ListBulletFrameDecorator $frame */ function __construct(ListBulletFrameDecorator $frame) { parent::__construct($frame); } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { if ($block === null) { return; } /** @var ListBulletFrameDecorator */ $frame = $this->_frame; $style = $frame->get_style(); $style->set_used("width", $frame->get_width()); $frame->position(); if ($style->list_style_position === "inside") { $block->add_frame_to_line($frame); } else { $block->add_dangling_marker($frame); } } } dompdf/src/FrameReflower/Table.php 0000644 00000041612 15024772104 0013112 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Table as TableFrameDecorator; use Dompdf\Helpers; /** * Reflows tables * * @package dompdf */ class Table extends AbstractFrameReflower { /** * Frame for this reflower * * @var TableFrameDecorator */ protected $_frame; /** * Cache of results between call to get_min_max_width and assign_widths * * @var array */ protected $_state; /** * Table constructor. * @param TableFrameDecorator $frame */ function __construct(TableFrameDecorator $frame) { $this->_state = null; parent::__construct($frame); } /** * State is held here so it needs to be reset along with the decorator */ public function reset(): void { parent::reset(); $this->_state = null; } protected function _assign_widths() { $style = $this->_frame->get_style(); // Find the min/max width of the table and sort the columns into // absolute/percent/auto arrays $delta = $this->_state["width_delta"]; $min_width = $this->_state["min_width"]; $max_width = $this->_state["max_width"]; $percent_used = $this->_state["percent_used"]; $absolute_used = $this->_state["absolute_used"]; $auto_min = $this->_state["auto_min"]; $absolute =& $this->_state["absolute"]; $percent =& $this->_state["percent"]; $auto =& $this->_state["auto"]; // Determine the actual width of the table (excluding borders and // padding) $cb = $this->_frame->get_containing_block(); $columns =& $this->_frame->get_cellmap()->get_columns(); $width = $style->width; $min_table_width = $this->resolve_min_width($cb["w"]) - $delta; if ($width !== "auto") { $preferred_width = (float) $style->length_in_pt($width, $cb["w"]) - $delta; if ($preferred_width < $min_table_width) { $preferred_width = $min_table_width; } if ($preferred_width > $min_width) { $width = $preferred_width; } else { $width = $min_width; } } else { if ($max_width + $delta < $cb["w"]) { $width = $max_width; } elseif ($cb["w"] - $delta > $min_width) { $width = $cb["w"] - $delta; } else { $width = $min_width; } if ($width < $min_table_width) { $width = $min_table_width; } } // Store our resolved width $style->set_used("width", $width); $cellmap = $this->_frame->get_cellmap(); if ($cellmap->is_columns_locked()) { return; } // If the whole table fits on the page, then assign each column it's max width if ($width == $max_width) { foreach ($columns as $i => $col) { $cellmap->set_column_width($i, $col["max-width"]); } return; } // Determine leftover and assign it evenly to all columns if ($width > $min_width) { // We have three cases to deal with: // // 1. All columns are auto or absolute width. In this case we // distribute extra space across all auto columns weighted by the // difference between their max and min width, or by max width only // if the width of the table is larger than the max width for all // columns. // // 2. Only absolute widths have been specified, no auto columns. In // this case we distribute extra space across all columns weighted // by their absolute width. // // 3. Percentage widths have been specified. In this case we normalize // the percentage values and try to assign widths as fractions of // the table width. Absolute column widths are fully satisfied and // any remaining space is evenly distributed among all auto columns. // Case 1: if ($percent_used == 0 && count($auto)) { foreach ($absolute as $i) { $w = $columns[$i]["min-width"]; $cellmap->set_column_width($i, $w); } if ($width < $max_width) { $increment = $width - $min_width; $table_delta = $max_width - $min_width; foreach ($auto as $i) { $min = $columns[$i]["min-width"]; $max = $columns[$i]["max-width"]; $col_delta = $max - $min; $w = $min + $increment * ($col_delta / $table_delta); $cellmap->set_column_width($i, $w); } } else { $increment = $width - $max_width; $auto_max = $max_width - $absolute_used; foreach ($auto as $i) { $max = $columns[$i]["max-width"]; $f = $auto_max > 0 ? $max / $auto_max : 1 / count($auto); $w = $max + $increment * $f; $cellmap->set_column_width($i, $w); } } return; } // Case 2: if ($percent_used == 0 && !count($auto)) { $increment = $width - $absolute_used; foreach ($absolute as $i) { $abs = $columns[$i]["min-width"]; $f = $absolute_used > 0 ? $abs / $absolute_used : 1 / count($absolute); $w = $abs + $increment * $f; $cellmap->set_column_width($i, $w); } return; } // Case 3: if ($percent_used > 0) { // Scale percent values if the total percentage is > 100 or // there are no auto values to take up slack if ($percent_used > 100 || count($auto) == 0) { $scale = 100 / $percent_used; } else { $scale = 1; } // Account for the minimum space used by the unassigned auto // columns, by the columns with absolute widths, and the // percentage columns following the current one $used_width = $auto_min + $absolute_used; foreach ($absolute as $i) { $w = $columns[$i]["min-width"]; $cellmap->set_column_width($i, $w); } $percent_min = 0; foreach ($percent as $i) { $percent_min += $columns[$i]["min-width"]; } // First-come, first served foreach ($percent as $i) { $min = $columns[$i]["min-width"]; $percent_min -= $min; $slack = $width - $used_width - $percent_min; $columns[$i]["percent"] *= $scale; $w = min($columns[$i]["percent"] * $width / 100, $slack); if ($w < $min) { $w = $min; } $cellmap->set_column_width($i, $w); $used_width += $w; } // This works because $used_width includes the min-width of each // unassigned column if (count($auto) > 0) { $increment = ($width - $used_width) / count($auto); foreach ($auto as $i) { $w = $columns[$i]["min-width"] + $increment; $cellmap->set_column_width($i, $w); } } return; } } else { // We are over-constrained: // Each column gets its minimum width foreach ($columns as $i => $col) { $cellmap->set_column_width($i, $col["min-width"]); } } } /** * Determine the frame's height based on min/max height * * @return float */ protected function _calculate_height() { $frame = $this->_frame; $style = $frame->get_style(); $cb = $frame->get_containing_block(); $height = $style->length_in_pt($style->height, $cb["h"]); $cellmap = $frame->get_cellmap(); $cellmap->assign_frame_heights(); $rows = $cellmap->get_rows(); // Determine our content height $content_height = 0.0; foreach ($rows as $r) { $content_height += $r["height"]; } if ($height === "auto") { $height = $content_height; } // Handle min/max height // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights $min_height = $this->resolve_min_height($cb["h"]); $max_height = $this->resolve_max_height($cb["h"]); $height = Helpers::clamp($height, $min_height, $max_height); // Use the content height or the height value, whichever is greater if ($height <= $content_height) { $height = $content_height; } else { // FIXME: Borders and row positions are not properly updated by this // $cellmap->set_frame_heights($height, $content_height); } return $height; } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { /** @var TableFrameDecorator */ $frame = $this->_frame; // Check if a page break is forced $page = $frame->get_root(); $page->check_forced_page_break($frame); // Bail if the page is full if ($page->is_full()) { return; } // Let the page know that we're reflowing a table so that splits // are suppressed (simply setting page-break-inside: avoid won't // work because we may have an arbitrary number of block elements // inside tds.) $page->table_reflow_start(); $this->determine_absolute_containing_block(); // Counters and generated content $this->_set_content(); // Collapse vertical margins, if required $this->_collapse_margins(); // Table layout algorithm: // http://www.w3.org/TR/CSS21/tables.html#auto-table-layout if (is_null($this->_state)) { $this->get_min_max_width(); } $cb = $frame->get_containing_block(); $style = $frame->get_style(); // This is slightly inexact, but should be okay. Add half the // border-spacing to the table as padding. The other half is added to // the cells themselves. if ($style->border_collapse === "separate") { [$h, $v] = $style->border_spacing; $v = $v / 2; $h = $h / 2; $style->set_used("padding_left", (float)$style->length_in_pt($style->padding_left, $cb["w"]) + $h); $style->set_used("padding_right", (float)$style->length_in_pt($style->padding_right, $cb["w"]) + $h); $style->set_used("padding_top", (float)$style->length_in_pt($style->padding_top, $cb["w"]) + $v); $style->set_used("padding_bottom", (float)$style->length_in_pt($style->padding_bottom, $cb["w"]) + $v); } $this->_assign_widths(); // Adjust left & right margins, if they are auto $delta = $this->_state["width_delta"]; $width = $style->width; $left = $style->length_in_pt($style->margin_left, $cb["w"]); $right = $style->length_in_pt($style->margin_right, $cb["w"]); $diff = (float) $cb["w"] - (float) $width - $delta; if ($left === "auto" && $right === "auto") { if ($diff < 0) { $left = 0; $right = $diff; } else { $left = $right = $diff / 2; } } else { if ($left === "auto") { $left = max($diff - $right, 0); } if ($right === "auto") { $right = max($diff - $left, 0); } } $style->set_used("margin_left", $left); $style->set_used("margin_right", $right); $frame->position(); [$x, $y] = $frame->get_position(); // Determine the content edge $offset_x = (float)$left + (float)$style->length_in_pt([ $style->padding_left, $style->border_left_width ], $cb["w"]); $offset_y = (float)$style->length_in_pt([ $style->margin_top, $style->border_top_width, $style->padding_top ], $cb["w"]); $content_x = $x + $offset_x; $content_y = $y + $offset_y; if (isset($cb["h"])) { $h = $cb["h"]; } else { $h = null; } $cellmap = $frame->get_cellmap(); $col =& $cellmap->get_column(0); $col["x"] = $offset_x; $row =& $cellmap->get_row(0); $row["y"] = $offset_y; $cellmap->assign_x_positions(); // Set the containing block of each child & reflow foreach ($frame->get_children() as $child) { $child->set_containing_block($content_x, $content_y, $width, $h); $child->reflow(); if (!$page->in_nested_table()) { // Check if a split has occurred $page->check_page_break($child); if ($page->is_full()) { break; } } } // Stop reflow if a page break has occurred before the frame, in which // case it has been reset, including its position if ($page->is_full() && $frame->get_position("x") === null) { $page->table_reflow_end(); return; } // Assign heights to our cells: $style->set_used("height", $this->_calculate_height()); $page->table_reflow_end(); if ($block && $frame->is_in_flow()) { $block->add_frame_to_line($frame); if ($frame->is_block_level()) { $block->add_line(); } } } public function get_min_max_width(): array { if (!is_null($this->_min_max_cache)) { return $this->_min_max_cache; } $style = $this->_frame->get_style(); $cellmap = $this->_frame->get_cellmap(); $this->_frame->normalize(); // Add the cells to the cellmap (this will calculate column widths as // frames are added) $cellmap->add_frame($this->_frame); // Find the min/max width of the table and sort the columns into // absolute/percent/auto arrays $this->_state = []; $this->_state["min_width"] = 0; $this->_state["max_width"] = 0; $this->_state["percent_used"] = 0; $this->_state["absolute_used"] = 0; $this->_state["auto_min"] = 0; $this->_state["absolute"] = []; $this->_state["percent"] = []; $this->_state["auto"] = []; $columns =& $cellmap->get_columns(); foreach ($columns as $i => $col) { $this->_state["min_width"] += $col["min-width"]; $this->_state["max_width"] += $col["max-width"]; if ($col["absolute"] > 0) { $this->_state["absolute"][] = $i; $this->_state["absolute_used"] += $col["min-width"]; } elseif ($col["percent"] > 0) { $this->_state["percent"][] = $i; $this->_state["percent_used"] += $col["percent"]; } else { $this->_state["auto"][] = $i; $this->_state["auto_min"] += $col["min-width"]; } } // Account for margins, borders, padding, and border spacing $cb_w = $this->_frame->get_containing_block("w"); $lm = (float) $style->length_in_pt($style->margin_left, $cb_w); $rm = (float) $style->length_in_pt($style->margin_right, $cb_w); $dims = [ $style->border_left_width, $style->border_right_width, $style->padding_left, $style->padding_right ]; if ($style->border_collapse !== "collapse") { list($dims[]) = $style->border_spacing; } $delta = (float) $style->length_in_pt($dims, $cb_w); $this->_state["width_delta"] = $delta; $min_width = $this->_state["min_width"] + $delta + $lm + $rm; $max_width = $this->_state["max_width"] + $delta + $lm + $rm; return $this->_min_max_cache = [ $min_width, $max_width, "min" => $min_width, "max" => $max_width ]; } } dompdf/src/FrameReflower/TableCell.php 0000644 00000011573 15024772104 0013715 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Table as TableFrameDecorator; use Dompdf\Helpers; /** * Reflows table cells * * @package dompdf */ class TableCell extends Block { /** * TableCell constructor. * @param BlockFrameDecorator $frame */ function __construct(BlockFrameDecorator $frame) { parent::__construct($frame); } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { // Counters and generated content $this->_set_content(); $style = $this->_frame->get_style(); $table = TableFrameDecorator::find_parent_table($this->_frame); $cellmap = $table->get_cellmap(); list($x, $y) = $cellmap->get_frame_position($this->_frame); $this->_frame->set_position($x, $y); $cells = $cellmap->get_spanned_cells($this->_frame); $w = 0; foreach ($cells["columns"] as $i) { $col = $cellmap->get_column($i); $w += $col["used-width"]; } //FIXME? $h = $this->_frame->get_containing_block("h"); $left_space = (float)$style->length_in_pt([$style->margin_left, $style->padding_left, $style->border_left_width], $w); $right_space = (float)$style->length_in_pt([$style->padding_right, $style->margin_right, $style->border_right_width], $w); $top_space = (float)$style->length_in_pt([$style->margin_top, $style->padding_top, $style->border_top_width], $h); $bottom_space = (float)$style->length_in_pt([$style->margin_bottom, $style->padding_bottom, $style->border_bottom_width], $h); $cb_w = $w - $left_space - $right_space; $style->set_used("width", $cb_w); $content_x = $x + $left_space; $content_y = $line_y = $y + $top_space; // Adjust the first line based on the text-indent property $indent = (float)$style->length_in_pt($style->text_indent, $w); $this->_frame->increase_line_width($indent); $page = $this->_frame->get_root(); // Set the y position of the first line in the cell $line_box = $this->_frame->get_current_line_box(); $line_box->y = $line_y; // Set the containing blocks and reflow each child foreach ($this->_frame->get_children() as $child) { $child->set_containing_block($content_x, $content_y, $cb_w, $h); $this->process_clear($child); $child->reflow($this->_frame); $this->process_float($child, $content_x, $cb_w); if ($page->is_full()) { break; } } // Determine our height $style_height = (float)$style->length_in_pt($style->height, $h); /** @var FrameDecorator\TableCell */ $frame = $this->_frame; $frame->set_content_height($this->_calculate_content_height()); $height = max($style_height, (float)$frame->get_content_height()); // Let the cellmap know our height $cell_height = $height / count($cells["rows"]); if ($style_height <= $height) { $cell_height += $top_space + $bottom_space; } foreach ($cells["rows"] as $i) { $cellmap->set_row_height($i, $cell_height); } $style->set_used("height", $height); $this->_text_align(); $this->vertical_align(); // Handle relative positioning foreach ($this->_frame->get_children() as $child) { $this->position_relative($child); } } public function get_min_max_content_width(): array { // Ignore percentage values for a specified width here, as they are // relative to the table width, which is not determined yet $style = $this->_frame->get_style(); $width = $style->width; $fixed_width = $width !== "auto" && !Helpers::is_percent($width); [$min, $max] = $this->get_min_max_child_width(); // For table cells: Use specified width if it is greater than the // minimum defined by the content if ($fixed_width) { $width = (float) $style->length_in_pt($width, 0); $min = max($width, $min); $max = $min; } // Handle min/max width style properties $min_width = $this->resolve_min_width(null); $max_width = $this->resolve_max_width(null); $min = Helpers::clamp($min, $min_width, $max_width); $max = Helpers::clamp($max, $min_width, $max_width); return [$min, $max]; } } dompdf/src/FrameReflower/Page.php 0000644 00000013526 15024772104 0012742 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\Frame; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Page as PageFrameDecorator; /** * Reflows pages * * @package dompdf */ class Page extends AbstractFrameReflower { /** * Cache of the callbacks array * * @var array */ private $_callbacks; /** * Cache of the canvas * * @var \Dompdf\Canvas */ private $_canvas; /** * Page constructor. * @param PageFrameDecorator $frame */ function __construct(PageFrameDecorator $frame) { parent::__construct($frame); } /** * @param PageFrameDecorator $frame * @param int $page_number */ function apply_page_style(Frame $frame, $page_number) { $style = $frame->get_style(); $page_styles = $style->get_stylesheet()->get_page_styles(); // http://www.w3.org/TR/CSS21/page.html#page-selectors if (count($page_styles) > 1) { $odd = $page_number % 2 == 1; $first = $page_number == 1; $style = clone $page_styles["base"]; // FIXME RTL if ($odd && isset($page_styles[":right"])) { $style->merge($page_styles[":right"]); } if ($odd && isset($page_styles[":odd"])) { $style->merge($page_styles[":odd"]); } // FIXME RTL if (!$odd && isset($page_styles[":left"])) { $style->merge($page_styles[":left"]); } if (!$odd && isset($page_styles[":even"])) { $style->merge($page_styles[":even"]); } if ($first && isset($page_styles[":first"])) { $style->merge($page_styles[":first"]); } $frame->set_style($style); } $frame->calculate_bottom_page_edge(); } /** * Paged layout: * http://www.w3.org/TR/CSS21/page.html * * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { /** @var PageFrameDecorator $frame */ $frame = $this->_frame; $child = $frame->get_first_child(); $fixed_children = []; $prev_child = null; $current_page = 0; while ($child) { $this->apply_page_style($frame, $current_page + 1); $style = $frame->get_style(); // Pages are only concerned with margins $cb = $frame->get_containing_block(); $left = (float)$style->length_in_pt($style->margin_left, $cb["w"]); $right = (float)$style->length_in_pt($style->margin_right, $cb["w"]); $top = (float)$style->length_in_pt($style->margin_top, $cb["h"]); $bottom = (float)$style->length_in_pt($style->margin_bottom, $cb["h"]); $content_x = $cb["x"] + $left; $content_y = $cb["y"] + $top; $content_width = $cb["w"] - $left - $right; $content_height = $cb["h"] - $top - $bottom; // Only if it's the first page, we save the nodes with a fixed position if ($current_page == 0) { foreach ($child->get_children() as $onechild) { if ($onechild->get_style()->position === "fixed") { $fixed_children[] = $onechild->deep_copy(); } } $fixed_children = array_reverse($fixed_children); } $child->set_containing_block($content_x, $content_y, $content_width, $content_height); // Check for begin reflow callback $this->_check_callbacks("begin_page_reflow", $child); //Insert a copy of each node which have a fixed position if ($current_page >= 1) { foreach ($fixed_children as $fixed_child) { $child->insert_child_before($fixed_child->deep_copy(), $child->get_first_child()); } } $child->reflow(); $next_child = $child->get_next_sibling(); // Check for begin render callback $this->_check_callbacks("begin_page_render", $child); // Render the page $frame->get_renderer()->render($child); // Check for end render callback $this->_check_callbacks("end_page_render", $child); if ($next_child) { $frame->next_page(); } // Wait to dispose of all frames on the previous page // so callback will have access to them if ($prev_child) { $prev_child->dispose(true); } $prev_child = $child; $child = $next_child; $current_page++; } // Dispose of previous page if it still exists if ($prev_child) { $prev_child->dispose(true); } } /** * Check for callbacks that need to be performed when a given event * gets triggered on a page * * @param string $event The type of event * @param Frame $frame The frame that event is triggered on */ protected function _check_callbacks(string $event, Frame $frame): void { if (!isset($this->_callbacks)) { $dompdf = $this->get_dompdf(); $this->_callbacks = $dompdf->getCallbacks(); $this->_canvas = $dompdf->getCanvas(); } if (isset($this->_callbacks[$event])) { $fs = $this->_callbacks[$event]; $canvas = $this->_canvas; $fontMetrics = $this->get_dompdf()->getFontMetrics(); foreach ($fs as $f) { $f($frame, $canvas, $fontMetrics); } } } } dompdf/src/FrameReflower/TableRow.php 0000644 00000004137 15024772104 0013603 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Table as TableFrameDecorator; use Dompdf\FrameDecorator\TableRow as TableRowFrameDecorator; use Dompdf\Exception; /** * Reflows table rows * * @package dompdf */ class TableRow extends AbstractFrameReflower { /** * TableRow constructor. * @param TableRowFrameDecorator $frame */ function __construct(TableRowFrameDecorator $frame) { parent::__construct($frame); } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { /** @var TableRowFrameDecorator */ $frame = $this->_frame; // Check if a page break is forced $page = $frame->get_root(); $page->check_forced_page_break($frame); // Bail if the page is full if ($page->is_full()) { return; } // Counters and generated content $this->_set_content(); $this->_frame->position(); $style = $this->_frame->get_style(); $cb = $this->_frame->get_containing_block(); foreach ($this->_frame->get_children() as $child) { $child->set_containing_block($cb); $child->reflow(); if ($page->is_full()) { break; } } if ($page->is_full()) { return; } $table = TableFrameDecorator::find_parent_table($this->_frame); $cellmap = $table->get_cellmap(); $style->set_used("width", $cellmap->get_frame_width($this->_frame)); $style->set_used("height", $cellmap->get_frame_height($this->_frame)); $this->_frame->set_position($cellmap->get_frame_position($this->_frame)); } /** * @throws Exception */ public function get_min_max_width(): array { throw new Exception("Min/max width is undefined for table rows"); } } dompdf/src/FrameReflower/NullFrameReflower.php 0000644 00000001267 15024772104 0015460 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\Frame; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; /** * Dummy reflower * * @package dompdf */ class NullFrameReflower extends AbstractFrameReflower { /** * NullFrameReflower constructor. * @param Frame $frame */ function __construct(Frame $frame) { parent::__construct($frame); } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { return; } } dompdf/src/FrameReflower/TableRowGroup.php 0000644 00000003772 15024772104 0014624 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Table as TableFrameDecorator; use Dompdf\FrameDecorator\TableRowGroup as TableRowGroupFrameDecorator; /** * Reflows table row groups (e.g. tbody tags) * * @package dompdf */ class TableRowGroup extends AbstractFrameReflower { /** * TableRowGroup constructor. * @param TableRowGroupFrameDecorator $frame */ function __construct(TableRowGroupFrameDecorator $frame) { parent::__construct($frame); } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { /** @var TableRowGroupFrameDecorator */ $frame = $this->_frame; $page = $frame->get_root(); // Counters and generated content $this->_set_content(); $style = $frame->get_style(); $cb = $frame->get_containing_block(); foreach ($frame->get_children() as $child) { $child->set_containing_block($cb["x"], $cb["y"], $cb["w"], $cb["h"]); $child->reflow(); // Check if a split has occurred $page->check_page_break($child); if ($page->is_full()) { break; } } $table = TableFrameDecorator::find_parent_table($frame); $cellmap = $table->get_cellmap(); // Stop reflow if a page break has occurred before the frame, in which // case it is not part of its parent table's cell map yet if ($page->is_full() && !$cellmap->frame_exists_in_cellmap($frame)) { return; } $style->set_used("width", $cellmap->get_frame_width($frame)); $style->set_used("height", $cellmap->get_frame_height($frame)); $frame->set_position($cellmap->get_frame_position($frame)); } } dompdf/src/FrameReflower/Inline.php 0000644 00000013660 15024772104 0013303 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Inline as InlineFrameDecorator; use Dompdf\FrameDecorator\Text as TextFrameDecorator; /** * Reflows inline frames * * @package dompdf */ class Inline extends AbstractFrameReflower { /** * Inline constructor. * @param InlineFrameDecorator $frame */ function __construct(InlineFrameDecorator $frame) { parent::__construct($frame); } /** * Handle reflow of empty inline frames. * * Regular inline frames are positioned together with their text (or inline) * children after child reflow. Empty inline frames have no children that * could determine the positioning, so they need to be handled separately. * * @param BlockFrameDecorator $block */ protected function reflow_empty(BlockFrameDecorator $block): void { /** @var InlineFrameDecorator */ $frame = $this->_frame; $style = $frame->get_style(); // Resolve width, so the margin width can be checked $style->set_used("width", 0.0); $cb = $frame->get_containing_block(); $line = $block->get_current_line_box(); $width = $frame->get_margin_width(); if ($width > ($cb["w"] - $line->left - $line->w - $line->right)) { $block->add_line(); // Find the appropriate inline ancestor to split $child = $frame; $p = $child->get_parent(); while ($p instanceof InlineFrameDecorator && !$child->get_prev_sibling()) { $child = $p; $p = $p->get_parent(); } if ($p instanceof InlineFrameDecorator) { // Split parent and stop current reflow. Reflow continues // via child-reflow loop of split parent $p->split($child); return; } } $frame->position(); $block->add_frame_to_line($frame); } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { /** @var InlineFrameDecorator */ $frame = $this->_frame; // Check if a page break is forced $page = $frame->get_root(); $page->check_forced_page_break($frame); if ($page->is_full()) { return; } // Counters and generated content $this->_set_content(); $style = $frame->get_style(); // Resolve auto margins // https://www.w3.org/TR/CSS21/visudet.html#inline-width // https://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced if ($style->margin_left === "auto") { $style->set_used("margin_left", 0.0); } if ($style->margin_right === "auto") { $style->set_used("margin_right", 0.0); } if ($style->margin_top === "auto") { $style->set_used("margin_top", 0.0); } if ($style->margin_bottom === "auto") { $style->set_used("margin_bottom", 0.0); } // Handle line breaks if ($frame->get_node()->nodeName === "br") { if ($block) { $line = $block->get_current_line_box(); $frame->set_containing_line($line); $block->maximize_line_height($frame->get_margin_height(), $frame); $block->add_line(true); $next = $frame->get_next_sibling(); $p = $frame->get_parent(); if ($next && $p instanceof InlineFrameDecorator) { $p->split($next); } } return; } // Handle empty inline frames if (!$frame->get_first_child()) { if ($block) { $this->reflow_empty($block); } return; } // Add our margin, padding & border to the first and last children if (($f = $frame->get_first_child()) && $f instanceof TextFrameDecorator) { $f_style = $f->get_style(); $f_style->margin_left = $style->margin_left; $f_style->padding_left = $style->padding_left; $f_style->border_left_width = $style->border_left_width; $f_style->border_left_style = $style->border_left_style; $f_style->border_left_color = $style->border_left_color; } if (($l = $frame->get_last_child()) && $l instanceof TextFrameDecorator) { $l_style = $l->get_style(); $l_style->margin_right = $style->margin_right; $l_style->padding_right = $style->padding_right; $l_style->border_right_width = $style->border_right_width; $l_style->border_right_style = $style->border_right_style; $l_style->border_right_color = $style->border_right_color; } $cb = $frame->get_containing_block(); // Set the containing blocks and reflow each child. The containing // block is not changed by line boxes. foreach ($frame->get_children() as $child) { $child->set_containing_block($cb); $child->reflow($block); // Stop reflow if the frame has been reset by a line or page break // due to child reflow if (!$frame->content_set) { return; } } if (!$frame->get_first_child()) { return; } // Assume the position of the first child [$x, $y] = $frame->get_first_child()->get_position(); $frame->set_position($x, $y); // Handle relative positioning foreach ($frame->get_children() as $child) { $this->position_relative($child); } if ($block) { $block->add_frame_to_line($frame); } } } dompdf/src/FrameReflower/Block.php 0000644 00000103364 15024772104 0013120 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator; use Dompdf\FrameDecorator\Text as TextFrameDecorator; use Dompdf\Exception; use Dompdf\Css\Style; use Dompdf\Helpers; /** * Reflows block frames * * @package dompdf */ class Block extends AbstractFrameReflower { // Minimum line width to justify, as fraction of available width const MIN_JUSTIFY_WIDTH = 0.80; /** * Frame for this reflower * * @var BlockFrameDecorator */ protected $_frame; function __construct(BlockFrameDecorator $frame) { parent::__construct($frame); } /** * Calculate the ideal used value for the width property as per: * http://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins * * @param float $width * * @return array */ protected function _calculate_width($width) { $frame = $this->_frame; $style = $frame->get_style(); $absolute = $frame->is_absolute(); $cb = $frame->get_containing_block(); $w = $cb["w"]; $rm = $style->length_in_pt($style->margin_right, $w); $lm = $style->length_in_pt($style->margin_left, $w); $left = $style->length_in_pt($style->left, $w); $right = $style->length_in_pt($style->right, $w); // Handle 'auto' values $dims = [$style->border_left_width, $style->border_right_width, $style->padding_left, $style->padding_right, $width !== "auto" ? $width : 0, $rm !== "auto" ? $rm : 0, $lm !== "auto" ? $lm : 0]; // absolutely positioned boxes take the 'left' and 'right' properties into account if ($absolute) { $dims[] = $left !== "auto" ? $left : 0; $dims[] = $right !== "auto" ? $right : 0; } $sum = (float)$style->length_in_pt($dims, $w); // Compare to the containing block $diff = $w - $sum; if ($absolute) { // Absolutely positioned // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width if ($width === "auto" || $left === "auto" || $right === "auto") { // "all of the three are 'auto'" logic + otherwise case if ($lm === "auto") { $lm = 0; } if ($rm === "auto") { $rm = 0; } $block_parent = $frame->find_block_parent(); $parent_content = $block_parent->get_content_box(); $line = $block_parent->get_current_line_box(); // TODO: This is the in-flow inline position. Use the in-flow // block position if the original display type is block-level $inflow_x = $parent_content["x"] - $cb["x"] + $line->left + $line->w; if ($width === "auto" && $left === "auto" && $right === "auto") { // rule 3, per instruction preceding rule set // shrink-to-fit width $left = $inflow_x; [$min, $max] = $this->get_min_max_child_width(); $width = min(max($min, $diff - $left), $max); $right = $diff - $left - $width; } elseif ($width === "auto" && $left === "auto") { // rule 1 // shrink-to-fit width [$min, $max] = $this->get_min_max_child_width(); $width = min(max($min, $diff), $max); $left = $diff - $width; } elseif ($width === "auto" && $right === "auto") { // rule 3 // shrink-to-fit width [$min, $max] = $this->get_min_max_child_width(); $width = min(max($min, $diff), $max); $right = $diff - $width; } elseif ($left === "auto" && $right === "auto") { // rule 2 $left = $inflow_x; $right = $diff - $left; } elseif ($left === "auto") { // rule 4 $left = $diff; } elseif ($width === "auto") { // rule 5 $width = max($diff, 0); } else { // $right === "auto" // rule 6 $right = $diff; } } else { // "none of the three are 'auto'" logic described in paragraph preceding the rules if ($diff >= 0) { if ($lm === "auto" && $rm === "auto") { $lm = $rm = $diff / 2; } elseif ($lm === "auto") { $lm = $diff; } elseif ($rm === "auto") { $rm = $diff; } } else { // over-constrained, solve for right $right = $right + $diff; if ($lm === "auto") { $lm = 0; } if ($rm === "auto") { $rm = 0; } } } } elseif ($style->float !== "none" || $style->display === "inline-block") { // Shrink-to-fit width for float and inline block // https://www.w3.org/TR/CSS21/visudet.html#float-width // https://www.w3.org/TR/CSS21/visudet.html#inlineblock-width if ($width === "auto") { [$min, $max] = $this->get_min_max_child_width(); $width = min(max($min, $diff), $max); } if ($lm === "auto") { $lm = 0; } if ($rm === "auto") { $rm = 0; } } else { // Block-level, normal flow // https://www.w3.org/TR/CSS21/visudet.html#blockwidth if ($diff >= 0) { // Find auto properties and get them to take up the slack if ($width === "auto") { $width = $diff; if ($lm === "auto") { $lm = 0; } if ($rm === "auto") { $rm = 0; } } elseif ($lm === "auto" && $rm === "auto") { $lm = $rm = $diff / 2; } elseif ($lm === "auto") { $lm = $diff; } elseif ($rm === "auto") { $rm = $diff; } } else { // We are over constrained--set margin-right to the difference $rm = (float) $rm + $diff; if ($width === "auto") { $width = 0; } if ($lm === "auto") { $lm = 0; } } } return [ "width" => $width, "margin_left" => $lm, "margin_right" => $rm, "left" => $left, "right" => $right, ]; } /** * Call the above function, but resolve max/min widths * * @throws Exception * @return array */ protected function _calculate_restricted_width() { $frame = $this->_frame; $style = $frame->get_style(); $cb = $frame->get_containing_block(); if (!isset($cb["w"])) { throw new Exception("Box property calculation requires containing block width"); } $width = $style->length_in_pt($style->width, $cb["w"]); $values = $this->_calculate_width($width); $margin_left = $values["margin_left"]; $margin_right = $values["margin_right"]; $width = $values["width"]; $left = $values["left"]; $right = $values["right"]; // Handle min/max width // https://www.w3.org/TR/CSS21/visudet.html#min-max-widths $min_width = $this->resolve_min_width($cb["w"]); $max_width = $this->resolve_max_width($cb["w"]); if ($width > $max_width) { $values = $this->_calculate_width($max_width); $margin_left = $values["margin_left"]; $margin_right = $values["margin_right"]; $width = $values["width"]; $left = $values["left"]; $right = $values["right"]; } if ($width < $min_width) { $values = $this->_calculate_width($min_width); $margin_left = $values["margin_left"]; $margin_right = $values["margin_right"]; $width = $values["width"]; $left = $values["left"]; $right = $values["right"]; } return [$width, $margin_left, $margin_right, $left, $right]; } /** * Determine the unrestricted height of content within the block * not by adding each line's height, but by getting the last line's position. * This because lines could have been pushed lower by a clearing element. * * @return float */ protected function _calculate_content_height() { $height = 0; $lines = $this->_frame->get_line_boxes(); if (count($lines) > 0) { $last_line = end($lines); $content_box = $this->_frame->get_content_box(); $height = $last_line->y + $last_line->h - $content_box["y"]; } return $height; } /** * Determine the frame's restricted height * * @return array */ protected function _calculate_restricted_height() { $frame = $this->_frame; $style = $frame->get_style(); $content_height = $this->_calculate_content_height(); $cb = $frame->get_containing_block(); $height = $style->length_in_pt($style->height, $cb["h"]); $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]); $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]); $top = $style->length_in_pt($style->top, $cb["h"]); $bottom = $style->length_in_pt($style->bottom, $cb["h"]); if ($frame->is_absolute()) { // Absolutely positioned // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height $h_dims = [ $top !== "auto" ? $top : 0, $height !== "auto" ? $height : 0, $bottom !== "auto" ? $bottom : 0 ]; $w_dims = [ $style->margin_top !== "auto" ? $style->margin_top : 0, $style->padding_top, $style->border_top_width, $style->border_bottom_width, $style->padding_bottom, $style->margin_bottom !== "auto" ? $style->margin_bottom : 0 ]; $sum = (float)$style->length_in_pt($h_dims, $cb["h"]) + (float)$style->length_in_pt($w_dims, $cb["w"]); $diff = $cb["h"] - $sum; if ($height === "auto" || $top === "auto" || $bottom === "auto") { // "all of the three are 'auto'" logic + otherwise case if ($margin_top === "auto") { $margin_top = 0; } if ($margin_bottom === "auto") { $margin_bottom = 0; } $block_parent = $frame->find_block_parent(); $current_line = $block_parent->get_current_line_box(); // TODO: This is the in-flow inline position. Use the in-flow // block position if the original display type is block-level $inflow_y = $current_line->y - $cb["y"]; if ($height === "auto" && $top === "auto" && $bottom === "auto") { // rule 3, per instruction preceding rule set $top = $inflow_y; $height = $content_height; $bottom = $diff - $top - $height; } elseif ($height === "auto" && $top === "auto") { // rule 1 $height = $content_height; $top = $diff - $height; } elseif ($height === "auto" && $bottom === "auto") { // rule 3 $height = $content_height; $bottom = $diff - $height; } elseif ($top === "auto" && $bottom === "auto") { // rule 2 $top = $inflow_y; $bottom = $diff - $top; } elseif ($top === "auto") { // rule 4 $top = $diff; } elseif ($height === "auto") { // rule 5 $height = max($diff, 0); } else { // $bottom === "auto" // rule 6 $bottom = $diff; } } else { // "none of the three are 'auto'" logic described in paragraph preceding the rules if ($diff >= 0) { if ($margin_top === "auto" && $margin_bottom === "auto") { $margin_top = $margin_bottom = $diff / 2; } elseif ($margin_top === "auto") { $margin_top = $diff; } elseif ($margin_bottom === "auto") { $margin_bottom = $diff; } } else { // over-constrained, solve for bottom $bottom = $bottom + $diff; if ($margin_top === "auto") { $margin_top = 0; } if ($margin_bottom === "auto") { $margin_bottom = 0; } } } } else { // https://www.w3.org/TR/CSS21/visudet.html#normal-block // https://www.w3.org/TR/CSS21/visudet.html#block-root-margin if ($height === "auto") { $height = $content_height; } if ($margin_top === "auto") { $margin_top = 0; } if ($margin_bottom === "auto") { $margin_bottom = 0; } // Handle min/max height // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights $min_height = $this->resolve_min_height($cb["h"]); $max_height = $this->resolve_max_height($cb["h"]); $height = Helpers::clamp($height, $min_height, $max_height); } // TODO: Need to also take min/max height into account for absolute // positioning, using similar logic to the `_calculate_width`/ // `calculate_restricted_width` split above. The non-absolute case // can simply clamp height within min/max, as margins and offsets are // not affected return [$height, $margin_top, $margin_bottom, $top, $bottom]; } /** * Adjust the justification of each of our lines. * http://www.w3.org/TR/CSS21/text.html#propdef-text-align */ protected function _text_align() { $style = $this->_frame->get_style(); $w = $this->_frame->get_containing_block("w"); $width = (float)$style->length_in_pt($style->width, $w); $text_indent = (float)$style->length_in_pt($style->text_indent, $w); switch ($style->text_align) { default: case "left": foreach ($this->_frame->get_line_boxes() as $line) { if (!$line->inline) { continue; } $line->trim_trailing_ws(); if ($line->left) { foreach ($line->frames_to_align() as $frame) { $frame->move($line->left, 0); } } } break; case "right": foreach ($this->_frame->get_line_boxes() as $i => $line) { if (!$line->inline) { continue; } $line->trim_trailing_ws(); $indent = $i === 0 ? $text_indent : 0; $dx = $width - $line->w - $line->right - $indent; foreach ($line->frames_to_align() as $frame) { $frame->move($dx, 0); } } break; case "justify": // We justify all lines except the last one, unless the frame // has been split, in which case the actual last line is part of // the split-off frame $lines = $this->_frame->get_line_boxes(); $last_line_index = $this->_frame->is_split ? null : count($lines) - 1; foreach ($lines as $i => $line) { if (!$line->inline) { continue; } $line->trim_trailing_ws(); if ($line->left) { foreach ($line->frames_to_align() as $frame) { $frame->move($line->left, 0); } } if ($line->br || $i === $last_line_index) { continue; } $frames = $line->get_frames(); $other_frame_count = 0; foreach ($frames as $frame) { if (!($frame instanceof TextFrameDecorator)) { $other_frame_count++; } } $word_count = $line->wc + $other_frame_count; // Set the spacing for each child if ($word_count > 1) { $indent = $i === 0 ? $text_indent : 0; $spacing = ($width - $line->get_width() - $indent) / ($word_count - 1); } else { $spacing = 0; } $dx = 0; foreach ($frames as $frame) { if ($frame instanceof TextFrameDecorator) { $text = $frame->get_text(); $spaces = mb_substr_count($text, " "); $frame->move($dx, 0); $frame->set_text_spacing($spacing); $dx += $spaces * $spacing; } else { $frame->move($dx, 0); } } // The line (should) now occupy the entire width $line->w = $width; } break; case "center": case "centre": foreach ($this->_frame->get_line_boxes() as $i => $line) { if (!$line->inline) { continue; } $line->trim_trailing_ws(); $indent = $i === 0 ? $text_indent : 0; $dx = ($width + $line->left - $line->w - $line->right - $indent) / 2; foreach ($line->frames_to_align() as $frame) { $frame->move($dx, 0); } } break; } } /** * Align inline children vertically. * Aligns each child vertically after each line is reflowed */ function vertical_align() { $fontMetrics = $this->get_dompdf()->getFontMetrics(); foreach ($this->_frame->get_line_boxes() as $line) { $height = $line->h; // Move all markers to the top of the line box foreach ($line->get_list_markers() as $marker) { $x = $marker->get_position("x"); $marker->set_position($x, $line->y); } foreach ($line->frames_to_align() as $frame) { $style = $frame->get_style(); $isInlineBlock = $style->display !== "inline" && $style->display !== "-dompdf-list-bullet"; $baseline = $fontMetrics->getFontBaseline($style->font_family, $style->font_size); $y_offset = 0; //FIXME: The 0.8 ratio applied to the height is arbitrary (used to accommodate descenders?) if ($isInlineBlock) { // Workaround: Skip vertical alignment if the frame is the // only one one the line, excluding empty text frames, which // may be the result of trailing white space // FIXME: This special case should be removed once vertical // alignment is properly fixed $skip = true; foreach ($line->get_frames() as $other) { if ($other !== $frame && !($other->is_text_node() && $other->get_node()->nodeValue === "") ) { $skip = false; break; } } if ($skip) { continue; } $marginHeight = $frame->get_margin_height(); $imageHeightDiff = $height * 0.8 - $marginHeight; $align = $frame->get_style()->vertical_align; if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) { switch ($align) { case "middle": $y_offset = $imageHeightDiff / 2; break; case "sub": $y_offset = 0.3 * $height + $imageHeightDiff; break; case "super": $y_offset = -0.2 * $height + $imageHeightDiff; break; case "text-top": // FIXME: this should be the height of the frame minus the height of the text $y_offset = $height - $style->line_height; break; case "top": break; case "text-bottom": // FIXME: align bottom of image with the descender? case "bottom": $y_offset = 0.3 * $height + $imageHeightDiff; break; case "baseline": default: $y_offset = $imageHeightDiff; break; } } else { $y_offset = $baseline - (float)$style->length_in_pt($align, $style->font_size) - $marginHeight; } } else { $parent = $frame->get_parent(); if ($parent instanceof TableCellFrameDecorator) { $align = "baseline"; } else { $align = $parent->get_style()->vertical_align; } if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) { switch ($align) { case "middle": $y_offset = ($height * 0.8 - $baseline) / 2; break; case "sub": $y_offset = $height * 0.8 - $baseline * 0.5; break; case "super": $y_offset = $height * 0.8 - $baseline * 1.4; break; case "text-top": case "top": // Not strictly accurate, but good enough for now break; case "text-bottom": case "bottom": $y_offset = $height * 0.8 - $baseline; break; case "baseline": default: $y_offset = $height * 0.8 - $baseline; break; } } else { $y_offset = $height * 0.8 - $baseline - (float)$style->length_in_pt($align, $style->font_size); } } if ($y_offset !== 0) { $frame->move(0, $y_offset); } } } } /** * @param AbstractFrameDecorator $child */ function process_clear(AbstractFrameDecorator $child) { $child_style = $child->get_style(); $root = $this->_frame->get_root(); // Handle "clear" if ($child_style->clear !== "none") { //TODO: this is a WIP for handling clear/float frames that are in between inline frames if ($child->get_prev_sibling() !== null) { $this->_frame->add_line(); } if ($child_style->float !== "none" && $child->get_next_sibling()) { $this->_frame->set_current_line_number($this->_frame->get_current_line_number() - 1); } $lowest_y = $root->get_lowest_float_offset($child); // If a float is still applying, we handle it if ($lowest_y) { if ($child->is_in_flow()) { $line_box = $this->_frame->get_current_line_box(); $line_box->y = $lowest_y + $child->get_margin_height(); $line_box->left = 0; $line_box->right = 0; } $child->move(0, $lowest_y - $child->get_position("y")); } } } /** * @param AbstractFrameDecorator $child * @param float $cb_x * @param float $cb_w */ function process_float(AbstractFrameDecorator $child, $cb_x, $cb_w) { $child_style = $child->get_style(); $root = $this->_frame->get_root(); // Handle "float" if ($child_style->float !== "none") { $root->add_floating_frame($child); // Remove next frame's beginning whitespace $next = $child->get_next_sibling(); if ($next && $next instanceof TextFrameDecorator) { $next->set_text(ltrim($next->get_text())); } $line_box = $this->_frame->get_current_line_box(); list($old_x, $old_y) = $child->get_position(); $float_x = $cb_x; $float_y = $old_y; $float_w = $child->get_margin_width(); if ($child_style->clear === "none") { switch ($child_style->float) { case "left": $float_x += $line_box->left; break; case "right": $float_x += ($cb_w - $line_box->right - $float_w); break; } } else { if ($child_style->float === "right") { $float_x += ($cb_w - $float_w); } } if ($cb_w < $float_x + $float_w - $old_x) { // TODO handle when floating elements don't fit } $line_box->get_float_offsets(); if ($child->_float_next_line) { $float_y += $line_box->h; } $child->set_position($float_x, $float_y); $child->move($float_x - $old_x, $float_y - $old_y, true); } } /** * @param BlockFrameDecorator $block */ function reflow(BlockFrameDecorator $block = null) { // Check if a page break is forced $page = $this->_frame->get_root(); $page->check_forced_page_break($this->_frame); // Bail if the page is full if ($page->is_full()) { return; } $this->determine_absolute_containing_block(); // Counters and generated content $this->_set_content(); // Inherit any dangling list markers if ($block && $this->_frame->is_in_flow()) { $this->_frame->inherit_dangling_markers($block); } // Collapse margins if required $this->_collapse_margins(); $style = $this->_frame->get_style(); $cb = $this->_frame->get_containing_block(); // Determine the constraints imposed by this frame: calculate the width // of the content area: [$width, $margin_left, $margin_right, $left, $right] = $this->_calculate_restricted_width(); // Store the calculated properties $style->set_used("width", $width); $style->set_used("margin_left", $margin_left); $style->set_used("margin_right", $margin_right); $style->set_used("left", $left); $style->set_used("right", $right); $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]); $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]); $auto_top = $style->top === "auto"; $auto_margin_top = $margin_top === "auto"; // Update the position $this->_frame->position(); [$x, $y] = $this->_frame->get_position(); // Adjust the first line based on the text-indent property $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]); $this->_frame->increase_line_width($indent); // Determine the content edge $top = (float)$style->length_in_pt([ $margin_top !== "auto" ? $margin_top : 0, $style->border_top_width, $style->padding_top ], $cb["w"]); $bottom = (float)$style->length_in_pt([ $margin_bottom !== "auto" ? $margin_bottom : 0, $style->border_bottom_width, $style->padding_bottom ], $cb["w"]); $cb_x = $x + (float)$margin_left + (float)$style->length_in_pt([$style->border_left_width, $style->padding_left], $cb["w"]); $cb_y = $y + $top; $height = $style->length_in_pt($style->height, $cb["h"]); if ($height === "auto") { $height = ($cb["h"] + $cb["y"]) - $bottom - $cb_y; } // Set the y position of the first line in this block $line_box = $this->_frame->get_current_line_box(); $line_box->y = $cb_y; $line_box->get_float_offsets(); // Set the containing blocks and reflow each child foreach ($this->_frame->get_children() as $child) { $child->set_containing_block($cb_x, $cb_y, $width, $height); $this->process_clear($child); $child->reflow($this->_frame); // Check for a page break before the child $page->check_page_break($child); // Don't add the child to the line if a page break has occurred // before it (possibly via a descendant), in which case it has been // reset, including its position if ($page->is_full() && $child->get_position("x") === null) { break; } $this->process_float($child, $cb_x, $width); } // Stop reflow if a page break has occurred before the frame, in which // case it has been reset, including its position if ($page->is_full() && $this->_frame->get_position("x") === null) { return; } // Determine our height [$height, $margin_top, $margin_bottom, $top, $bottom] = $this->_calculate_restricted_height(); $style->set_used("height", $height); $style->set_used("margin_top", $margin_top); $style->set_used("margin_bottom", $margin_bottom); $style->set_used("top", $top); $style->set_used("bottom", $bottom); if ($this->_frame->is_absolute()) { if ($auto_top) { $this->_frame->move(0, $top); } if ($auto_margin_top) { $this->_frame->move(0, $margin_top, true); } } $this->_text_align(); $this->vertical_align(); // Handle relative positioning foreach ($this->_frame->get_children() as $child) { $this->position_relative($child); } if ($block && $this->_frame->is_in_flow()) { $block->add_frame_to_line($this->_frame); if ($this->_frame->is_block_level()) { $block->add_line(); } } } public function get_min_max_content_width(): array { // TODO: While the containing block is not set yet on the frame, it can // already be determined in some cases due to fixed dimensions on the // ancestor forming the containing block. In such cases, percentage // values could be resolved here $style = $this->_frame->get_style(); $width = $style->width; $fixed_width = $width !== "auto" && !Helpers::is_percent($width); // If the frame has a specified width, then we don't need to check // its children if ($fixed_width) { $min = (float) $style->length_in_pt($width, 0); $max = $min; } else { [$min, $max] = $this->get_min_max_child_width(); } // Handle min/max width style properties $min_width = $this->resolve_min_width(null); $max_width = $this->resolve_max_width(null); $min = Helpers::clamp($min, $min_width, $max_width); $max = Helpers::clamp($max, $min_width, $max_width); return [$min, $max]; } } dompdf/src/FrameReflower/Text.php 0000644 00000050262 15024772104 0013010 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameReflower; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Inline as InlineFrameDecorator; use Dompdf\FrameDecorator\Text as TextFrameDecorator; use Dompdf\FontMetrics; use Dompdf\Helpers; /** * Reflows text frames. * * @package dompdf */ class Text extends AbstractFrameReflower { /** * PHP string representation of HTML entity <shy> */ const SOFT_HYPHEN = "\xC2\xAD"; /** * The regex splits on everything that's a separator (^\S double negative), * excluding the following non-breaking space characters: * * nbsp (\xA0) * * narrow nbsp (\x{202F}) * * figure space (\x{2007}) */ public static $_whitespace_pattern = '/([^\S\xA0\x{202F}\x{2007}]+)/u'; /** * The regex splits on everything that's a separator (^\S double negative) * plus dashes, excluding the following non-breaking space characters: * * nbsp (\xA0) * * narrow nbsp (\x{202F}) * * figure space (\x{2007}) */ public static $_wordbreak_pattern = '/([^\S\xA0\x{202F}\x{2007}\n]+|\R|\-+|\xAD+)/u'; /** * Frame for this reflower * * @var TextFrameDecorator */ protected $_frame; /** * Saves trailing whitespace trimmed after a line break, so it can be * restored when needed. * * @var string|null */ protected $trailingWs = null; /** * @var FontMetrics */ private $fontMetrics; /** * @param TextFrameDecorator $frame * @param FontMetrics $fontMetrics */ public function __construct(TextFrameDecorator $frame, FontMetrics $fontMetrics) { parent::__construct($frame); $this->setFontMetrics($fontMetrics); } /** * Apply text transform and white-space collapse according to style. * * * http://www.w3.org/TR/CSS21/text.html#propdef-text-transform * * http://www.w3.org/TR/CSS21/text.html#propdef-white-space * * @param string $text * @return string */ protected function pre_process_text(string $text): string { $style = $this->_frame->get_style(); // Handle text transform switch ($style->text_transform) { case "capitalize": $text = Helpers::mb_ucwords($text); break; case "uppercase": $text = mb_convert_case($text, MB_CASE_UPPER); break; case "lowercase": $text = mb_convert_case($text, MB_CASE_LOWER); break; default: break; } // Handle white-space collapse switch ($style->white_space) { default: case "normal": case "nowrap": $text = preg_replace(self::$_whitespace_pattern, " ", $text) ?? ""; break; case "pre-line": // Collapse white space except for line breaks $text = preg_replace('/([^\S\xA0\x{202F}\x{2007}\n]+)/u', " ", $text) ?? ""; break; case "pre": case "pre-wrap": break; } return $text; } /** * @param string $text * @param BlockFrameDecorator $block * @param bool $nowrap * * @return bool|int */ protected function line_break(string $text, BlockFrameDecorator $block, bool $nowrap = false) { $fontMetrics = $this->getFontMetrics(); $frame = $this->_frame; $style = $frame->get_style(); $font = $style->font_family; $size = $style->font_size; $word_spacing = $style->word_spacing; $letter_spacing = $style->letter_spacing; // Determine the available width $current_line = $block->get_current_line_box(); $line_width = $frame->get_containing_block("w"); $current_line_width = $current_line->left + $current_line->w + $current_line->right; $available_width = $line_width - $current_line_width; // Determine the frame width including margin, padding & border $visible_text = preg_replace('/\xAD/u', "", $text); $text_width = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing); $mbp_width = (float) $style->length_in_pt([ $style->margin_left, $style->border_left_width, $style->padding_left, $style->padding_right, $style->border_right_width, $style->margin_right ], $line_width); $frame_width = $text_width + $mbp_width; if (Helpers::lengthLessOrEqual($frame_width, $available_width)) { return false; } if ($nowrap) { return $current_line_width == 0 ? false : 0; } // Split the text into words $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE); $wc = count($words); // Determine the split point $width = 0.0; $str = ""; $space_width = $fontMetrics->getTextWidth(" ", $font, $size, $word_spacing, $letter_spacing); $shy_width = $fontMetrics->getTextWidth(self::SOFT_HYPHEN, $font, $size); // @todo support <wbr> for ($i = 0; $i < $wc; $i += 2) { // Allow trailing white space to overflow. White space is always // collapsed to the standard space character currently, so only // handle that for now $sep = $words[$i + 1] ?? ""; $word = $sep === " " ? $words[$i] : $words[$i] . $sep; $word_width = $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing); $used_width = $width + $word_width + $mbp_width; if (Helpers::lengthGreater($used_width, $available_width)) { // If the previous split happened by soft hyphen, we have to // append its width again because the last hyphen of a line // won't be removed if (isset($words[$i - 1]) && self::SOFT_HYPHEN === $words[$i - 1]) { $width += $shy_width; } break; } // If the word is splitted by soft hyphen, but no line break is needed // we have to reduce the width. But the str is not modified, otherwise // the wrong offset is calculated at the end of this method. if ($sep === self::SOFT_HYPHEN) { $width += $word_width - $shy_width; $str .= $word; } elseif ($sep === " ") { $width += $word_width + $space_width; $str .= $word . $sep; } else { $width += $word_width; $str .= $word; } } // The first word has overflowed. Force it onto the line, or as many // characters as fit if breaking words is allowed if ($current_line_width == 0 && $width === 0.0) { if ($sep === " ") { $word .= $sep; } // https://www.w3.org/TR/css-text-3/#overflow-wrap-property $wrap = $style->overflow_wrap; $break_word = $wrap === "anywhere" || $wrap === "break-word"; if ($break_word) { $s = ""; for ($j = 0; $j < mb_strlen($word); $j++) { $c = mb_substr($word, $j, 1); $w = $fontMetrics->getTextWidth($s . $c, $font, $size, $word_spacing, $letter_spacing); if (Helpers::lengthGreater($w, $available_width)) { break; } $s .= $c; } // Always force the first character onto the line $str = $j === 0 ? $s . $c : $s; } else { $str = $word; } } $offset = mb_strlen($str); return $offset; } /** * @param string $text * @return bool|int */ protected function newline_break(string $text) { if (($i = mb_strpos($text, "\n")) === false) { return false; } return $i + 1; } /** * @param BlockFrameDecorator $block * @return bool|null Whether to add a new line at the end. `null` if reflow * should be stopped. */ protected function layout_line(BlockFrameDecorator $block): ?bool { $frame = $this->_frame; $style = $frame->get_style(); $current_line = $block->get_current_line_box(); $text = $frame->get_text(); // Trim leading white space if this is the first text on the line if ($current_line->w === 0.0 && !$frame->is_pre()) { $text = ltrim($text, " "); } if ($text === "") { $frame->set_text(""); $style->set_used("width", 0.0); return false; } // Determine the next line break // http://www.w3.org/TR/CSS21/text.html#propdef-white-space $white_space = $style->white_space; $nowrap = $white_space === "nowrap" || $white_space === "pre"; switch ($white_space) { default: case "normal": case "nowrap": $split = $this->line_break($text, $block, $nowrap); $add_line = false; break; case "pre": case "pre-line": case "pre-wrap": $hard_split = $this->newline_break($text); $first_line = $hard_split !== false ? mb_substr($text, 0, $hard_split) : $text; $soft_split = $this->line_break($first_line, $block, $nowrap); $split = $soft_split !== false ? $soft_split : $hard_split; $add_line = $hard_split !== false; break; } if ($split === 0) { // Make sure to move text when floating frames leave no space to // place anything onto the line // TODO: Would probably be better to move just below the current // floating frame instead of trying to place text in line-height // increments if ($current_line->h === 0.0) { // Line height might be 0 $h = max($frame->get_margin_height(), 1.0); $block->maximize_line_height($h, $frame); } // Break line and repeat layout $block->add_line(); // Find the appropriate inline ancestor to split $child = $frame; $p = $child->get_parent(); while ($p instanceof InlineFrameDecorator && !$child->get_prev_sibling()) { $child = $p; $p = $p->get_parent(); } if ($p instanceof InlineFrameDecorator) { // Split parent and stop current reflow. Reflow continues // via child-reflow loop of split parent $p->split($child); return null; } return $this->layout_line($block); } // Final split point is determined if ($split !== false && $split < mb_strlen($text)) { // Split the line $frame->set_text($text); $frame->split_text($split); $add_line = true; // Remove inner soft hyphens $t = $frame->get_text(); $shyPosition = mb_strpos($t, self::SOFT_HYPHEN); if (false !== $shyPosition && $shyPosition < mb_strlen($t) - 1) { $t = str_replace(self::SOFT_HYPHEN, "", mb_substr($t, 0, -1)) . mb_substr($t, -1); $frame->set_text($t); } } else { // No split required // Remove soft hyphens $text = str_replace(self::SOFT_HYPHEN, "", $text); $frame->set_text($text); } // Set our new width $frame->recalculate_width(); return $add_line; } /** * @param BlockFrameDecorator|null $block */ function reflow(BlockFrameDecorator $block = null) { $frame = $this->_frame; $page = $frame->get_root(); $page->check_forced_page_break($frame); if ($page->is_full()) { return; } // Determine the text height $style = $frame->get_style(); $size = $style->font_size; $font = $style->font_family; $font_height = $this->getFontMetrics()->getFontHeight($font, $size); $style->set_used("height", $font_height); // Handle text transform and white space $text = $this->pre_process_text($frame->get_text()); $frame->set_text($text); if ($block === null) { return; } $add_line = $this->layout_line($block); if ($add_line === null) { return; } $frame->position(); // Skip wrapped white space between block-level elements in case white // space is collapsed if ($frame->get_text() === "" && $frame->get_margin_width() === 0.0) { return; } $line = $block->add_frame_to_line($frame); $trimmed = trim($frame->get_text()); // Split the text into words (used to determine spacing between // words on justified lines) if ($trimmed !== "") { $words = preg_split(self::$_whitespace_pattern, $trimmed); $line->wc += count($words); } if ($add_line) { $block->add_line(); } } /** * Trim trailing white space from the frame text. */ public function trim_trailing_ws(): void { $frame = $this->_frame; $text = $frame->get_text(); $trailing = mb_substr($text, -1); // White space is always collapsed to the standard space character // currently, so only handle that for now if ($trailing === " ") { $this->trailingWs = $trailing; $frame->set_text(mb_substr($text, 0, -1)); $frame->recalculate_width(); } } public function reset(): void { parent::reset(); // Restore trimmed trailing white space, as the frame will go through // another reflow and line breaks might be different after a split if ($this->trailingWs !== null) { $text = $this->_frame->get_text(); $this->_frame->set_text($text . $this->trailingWs); $this->trailingWs = null; } } //........................................................................ public function get_min_max_width(): array { $fontMetrics = $this->getFontMetrics(); $frame = $this->_frame; $style = $frame->get_style(); $text = $frame->get_text(); $font = $style->font_family; $size = $style->font_size; $word_spacing = $style->word_spacing; $letter_spacing = $style->letter_spacing; // Handle text transform and white space $text = $this->pre_process_text($frame->get_text()); if (!$frame->is_pre()) { // Determine whether the frame is at the start of its parent block. // Trim leading white space in that case $child = $frame; $p = $frame->get_parent(); while (!$p->is_block() && !$child->get_prev_sibling()) { $child = $p; $p = $p->get_parent(); } if (!$child->get_prev_sibling()) { $text = ltrim($text, " "); } // Determine whether the frame is at the end of its parent block. // Trim trailing white space in that case $child = $frame; $p = $frame->get_parent(); while (!$p->is_block() && !$child->get_next_sibling()) { $child = $p; $p = $p->get_parent(); } if (!$child->get_next_sibling()) { $text = rtrim($text, " "); } } // Strip soft hyphens for max-line-width calculations $visible_text = preg_replace('/\xAD/u', "", $text); // Determine minimum text width switch ($style->white_space) { default: case "normal": case "pre-line": case "pre-wrap": // The min width is the longest word or, if breaking words is // allowed with the `anywhere` keyword, the widest character. // For performance reasons, we only check the first character in // the latter case. // https://www.w3.org/TR/css-text-3/#overflow-wrap-property if ($style->overflow_wrap === "anywhere") { $char = mb_substr($visible_text, 0, 1); $min = $fontMetrics->getTextWidth($char, $font, $size, $word_spacing, $letter_spacing); } else { // Find the longest word $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE); $lengths = array_map(function ($chunk) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) { // Allow trailing white space to overflow. As in actual // layout above, only handle a single space for now $sep = $chunk[1] ?? ""; $word = $sep === " " ? $chunk[0] : $chunk[0] . $sep; return $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing); }, array_chunk($words, 2)); $min = max($lengths); } break; case "pre": // Find the longest line $lines = array_flip(preg_split("/\R/u", $visible_text)); array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) { $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing); }); arsort($lines); $min = reset($lines); break; case "nowrap": $min = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing); break; } // Determine maximum text width switch ($style->white_space) { default: case "normal": $max = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing); break; case "pre-line": case "pre-wrap": // Find the longest line $lines = array_flip(preg_split("/\R/u", $visible_text)); array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) { $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing); }); arsort($lines); $max = reset($lines); break; case "pre": case "nowrap": $max = $min; break; } // Account for margins, borders, and padding $dims = [ $style->padding_left, $style->padding_right, $style->border_left_width, $style->border_right_width, $style->margin_left, $style->margin_right ]; // The containing block is not defined yet, treat percentages as 0 $delta = (float) $style->length_in_pt($dims, 0); $min += $delta; $max += $delta; return [$min, $max, "min" => $min, "max" => $max]; } /** * @param FontMetrics $fontMetrics * @return $this */ public function setFontMetrics(FontMetrics $fontMetrics) { $this->fontMetrics = $fontMetrics; return $this; } /** * @return FontMetrics */ public function getFontMetrics() { return $this->fontMetrics; } } dompdf/src/FrameDecorator/Image.php 0000644 00000005765 15024772104 0013253 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; use Dompdf\Helpers; use Dompdf\Image\Cache; /** * Decorates frames for image layout and rendering * * @package dompdf */ class Image extends AbstractFrameDecorator { /** * The path to the image file (note that remote images are * downloaded locally to Options:tempDir). * * @var string */ protected $_image_url; /** * The image's file error message * * @var string */ protected $_image_msg; /** * Class constructor * * @param Frame $frame the frame to decorate * @param DOMPDF $dompdf the document's dompdf object (required to resolve relative & remote urls) */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); $url = $frame->get_node()->getAttribute("src"); $debug_png = $dompdf->getOptions()->getDebugPng(); if ($debug_png) { print '[__construct ' . $url . ']'; } list($this->_image_url, /*$type*/, $this->_image_msg) = Cache::resolve_url( $url, $dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $dompdf->getOptions() ); if (Cache::is_broken($this->_image_url) && $alt = $frame->get_node()->getAttribute("alt") ) { $fontMetrics = $dompdf->getFontMetrics(); $style = $frame->get_style(); $font = $style->font_family; $size = $style->font_size; $word_spacing = $style->word_spacing; $letter_spacing = $style->letter_spacing; $style->width = (4 / 3) * $fontMetrics->getTextWidth($alt, $font, $size, $word_spacing, $letter_spacing); $style->height = $fontMetrics->getFontHeight($font, $size); } } /** * Get the intrinsic pixel dimensions of the image. * * @return array Width and height as `float|int`. */ public function get_intrinsic_dimensions(): array { [$width, $height] = Helpers::dompdf_getimagesize($this->_image_url, $this->_dompdf->getHttpContext()); return [$width, $height]; } /** * Resample the given pixel length according to dpi. * * @param float|int $length * @return float */ public function resample($length): float { $dpi = $this->_dompdf->getOptions()->getDpi(); return ($length * 72) / $dpi; } /** * Return the image's url * * @return string The url of this image */ function get_image_url() { return $this->_image_url; } /** * Return the image's error message * * @return string The image's error message */ function get_image_msg() { return $this->_image_msg; } } dompdf/src/FrameDecorator/ListBullet.php 0000644 00000005407 15024772104 0014305 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; /** * Decorates frames for list bullet rendering * * @package dompdf */ class ListBullet extends AbstractFrameDecorator { /** * Bullet diameter as fraction of font size. */ public const BULLET_SIZE = 0.35; /** * Bullet offset from font baseline as fraction of font size. */ public const BULLET_OFFSET = 0.1; /** * Thickness of bullet outline as fraction of font size. * See also `DECO_THICKNESS`. Screen: 0.08, print: better less, e.g. 0.04. */ public const BULLET_THICKNESS = 0.04; /** * Indentation from the start of the line as fraction of font size. */ public const MARKER_INDENT = 0.52; /** * ListBullet constructor. * @param Frame $frame * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); } /** * Get the width of the bullet symbol. * * @return float */ public function get_width(): float { $style = $this->_frame->get_style(); if ($style->list_style_type === "none") { return 0.0; } return $style->font_size * self::BULLET_SIZE; } /** * Get the height of the bullet symbol. * * @return float */ public function get_height(): float { $style = $this->_frame->get_style(); if ($style->list_style_type === "none") { return 0.0; } return $style->font_size * self::BULLET_SIZE; } /** * Get the width of the bullet, including indentation. */ public function get_margin_width(): float { $style = $this->get_style(); if ($style->list_style_type === "none") { return 0.0; } return $style->font_size * (self::BULLET_SIZE + self::MARKER_INDENT); } /** * Get the line height for the bullet. * * This increases the height of the corresponding line box when necessary. */ public function get_margin_height(): float { $style = $this->get_style(); if ($style->list_style_type === "none") { return 0.0; } // TODO: This is a copy of `FrameDecorator\Text::get_margin_height()` // Would be nice to properly refactor that at some point $font = $style->font_family; $size = $style->font_size; $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size); return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight; } } dompdf/src/FrameDecorator/Table.php 0000644 00000023522 15024772104 0013247 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Cellmap; use DOMNode; use Dompdf\Css\Style; use Dompdf\Dompdf; use Dompdf\Frame; /** * Decorates Frames for table layout * * @package dompdf */ class Table extends AbstractFrameDecorator { public const VALID_CHILDREN = Style::TABLE_INTERNAL_TYPES; /** * List of all row-group display types. */ public const ROW_GROUPS = [ "table-row-group", "table-header-group", "table-footer-group" ]; /** * The Cellmap object for this table. The cellmap maps table cells * to rows and columns, and aids in calculating column widths. * * @var Cellmap */ protected $_cellmap; /** * Table header rows. Each table header is duplicated when a table * spans pages. * * @var TableRowGroup[] */ protected $_headers; /** * Table footer rows. Each table footer is duplicated when a table * spans pages. * * @var TableRowGroup[] */ protected $_footers; /** * Class constructor * * @param Frame $frame the frame to decorate * @param Dompdf $dompdf */ public function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); $this->_cellmap = new Cellmap($this); if ($frame->get_style()->table_layout === "fixed") { $this->_cellmap->set_layout_fixed(true); } $this->_headers = []; $this->_footers = []; } public function reset() { parent::reset(); $this->_cellmap->reset(); $this->_headers = []; $this->_footers = []; $this->_reflower->reset(); } //........................................................................ /** * Split the table at $row. $row and all subsequent rows will be * added to the clone. This method is overridden in order to remove * frames from the cellmap properly. */ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void { if (is_null($child)) { parent::split($child, $page_break, $forced); return; } // If $child is a header or if it is the first non-header row, do // not duplicate headers, simply move the table to the next page. if (count($this->_headers) && !in_array($child, $this->_headers, true) && !in_array($child->get_prev_sibling(), $this->_headers, true) ) { $first_header = null; // Insert copies of the table headers before $child foreach ($this->_headers as $header) { $new_header = $header->deep_copy(); if (is_null($first_header)) { $first_header = $new_header; } $this->insert_child_before($new_header, $child); } parent::split($first_header, $page_break, $forced); } elseif (in_array($child->get_style()->display, self::ROW_GROUPS, true)) { // Individual rows should have already been handled parent::split($child, $page_break, $forced); } else { $iter = $child; while ($iter) { $this->_cellmap->remove_row($iter); $iter = $iter->get_next_sibling(); } parent::split($child, $page_break, $forced); } } public function copy(DOMNode $node) { $deco = parent::copy($node); // In order to keep columns' widths through pages $deco->_cellmap->set_columns($this->_cellmap->get_columns()); $deco->_cellmap->lock_columns(); return $deco; } /** * Static function to locate the parent table of a frame * * @param Frame $frame * * @return Table the table that is an ancestor of $frame */ public static function find_parent_table(Frame $frame) { while ($frame = $frame->get_parent()) { if ($frame->is_table()) { break; } } return $frame; } /** * Return this table's Cellmap * * @return Cellmap */ public function get_cellmap() { return $this->_cellmap; } //........................................................................ /** * Check for text nodes between valid table children that only contain white * space, except if white space is to be preserved. * * @param AbstractFrameDecorator $frame * * @return bool */ private function isEmptyTextNode(AbstractFrameDecorator $frame): bool { // This is based on the white-space pattern in `FrameReflower\Text`, // i.e. only match on collapsible white space $wsPattern = '/^[^\S\xA0\x{202F}\x{2007}]*$/u'; $validChildOrNull = function ($frame) { return $frame === null || in_array($frame->get_style()->display, self::VALID_CHILDREN, true); }; return $frame instanceof Text && !$frame->is_pre() && preg_match($wsPattern, $frame->get_text()) && $validChildOrNull($frame->get_prev_sibling()) && $validChildOrNull($frame->get_next_sibling()); } /** * Restructure tree so that the table has the correct structure. Misplaced * children are appropriately wrapped in anonymous row groups, rows, and * cells. * * https://www.w3.org/TR/CSS21/tables.html#anonymous-boxes */ public function normalize(): void { $column_caption = ["table-column-group", "table-column", "table-caption"]; $children = iterator_to_array($this->get_children()); $tbody = null; foreach ($children as $child) { $display = $child->get_style()->display; if (in_array($display, self::ROW_GROUPS, true)) { // Reset anonymous tbody $tbody = null; // Add headers and footers if ($display === "table-header-group") { $this->_headers[] = $child; } elseif ($display === "table-footer-group") { $this->_footers[] = $child; } continue; } if (in_array($display, $column_caption, true)) { continue; } // Remove empty text nodes between valid children if ($this->isEmptyTextNode($child)) { $this->remove_child($child); continue; } // Catch consecutive misplaced frames within a single anonymous group if ($tbody === null) { $tbody = $this->create_anonymous_child("tbody", "table-row-group"); $this->insert_child_before($tbody, $child); } $tbody->append_child($child); } // Handle empty table: Make sure there is at least one row group if (!$this->get_first_child()) { $tbody = $this->create_anonymous_child("tbody", "table-row-group"); $this->append_child($tbody); } foreach ($this->get_children() as $child) { $display = $child->get_style()->display; if (in_array($display, self::ROW_GROUPS, true)) { $this->normalizeRowGroup($child); } } } private function normalizeRowGroup(AbstractFrameDecorator $frame): void { $children = iterator_to_array($frame->get_children()); $tr = null; foreach ($children as $child) { $display = $child->get_style()->display; if ($display === "table-row") { // Reset anonymous tr $tr = null; continue; } // Remove empty text nodes between valid children if ($this->isEmptyTextNode($child)) { $frame->remove_child($child); continue; } // Catch consecutive misplaced frames within a single anonymous row if ($tr === null) { $tr = $frame->create_anonymous_child("tr", "table-row"); $frame->insert_child_before($tr, $child); } $tr->append_child($child); } // Handle empty row group: Make sure there is at least one row if (!$frame->get_first_child()) { $tr = $frame->create_anonymous_child("tr", "table-row"); $frame->append_child($tr); } foreach ($frame->get_children() as $child) { $this->normalizeRow($child); } } private function normalizeRow(AbstractFrameDecorator $frame): void { $children = iterator_to_array($frame->get_children()); $td = null; foreach ($children as $child) { $display = $child->get_style()->display; if ($display === "table-cell") { // Reset anonymous td $td = null; continue; } // Remove empty text nodes between valid children if ($this->isEmptyTextNode($child)) { $frame->remove_child($child); continue; } // Catch consecutive misplaced frames within a single anonymous cell if ($td === null) { $td = $frame->create_anonymous_child("td", "table-cell"); $frame->insert_child_before($td, $child); } $td->append_child($child); } // Handle empty row: Make sure there is at least one cell if (!$frame->get_first_child()) { $td = $frame->create_anonymous_child("td", "table-cell"); $frame->append_child($td); } } } dompdf/src/FrameDecorator/TableCell.php 0000644 00000006467 15024772104 0014060 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; use Dompdf\FrameDecorator\Block as BlockFrameDecorator; /** * Decorates table cells for layout * * @package dompdf */ class TableCell extends BlockFrameDecorator { protected $_resolved_borders; protected $_content_height; //........................................................................ /** * TableCell constructor. * @param Frame $frame * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); $this->_resolved_borders = []; $this->_content_height = 0; } //........................................................................ function reset() { parent::reset(); $this->_resolved_borders = []; $this->_content_height = 0; $this->_frame->reset(); } /** * @return int */ function get_content_height() { return $this->_content_height; } /** * @param $height */ function set_content_height($height) { $this->_content_height = $height; } /** * @param $height */ function set_cell_height($height) { $style = $this->get_style(); $v_space = (float)$style->length_in_pt( [ $style->margin_top, $style->padding_top, $style->border_top_width, $style->border_bottom_width, $style->padding_bottom, $style->margin_bottom ], (float)$style->length_in_pt($style->height) ); $new_height = $height - $v_space; $style->set_used("height", $new_height); if ($new_height > $this->_content_height) { $y_offset = 0; // Adjust our vertical alignment switch ($style->vertical_align) { default: case "baseline": // FIXME: this isn't right case "top": // Don't need to do anything return; case "middle": $y_offset = ($new_height - $this->_content_height) / 2; break; case "bottom": $y_offset = $new_height - $this->_content_height; break; } if ($y_offset) { // Move our children foreach ($this->get_line_boxes() as $line) { foreach ($line->get_frames() as $frame) { $frame->move(0, $y_offset); } } } } } /** * @param $side * @param $border_spec */ function set_resolved_border($side, $border_spec) { $this->_resolved_borders[$side] = $border_spec; } /** * @param $side * @return mixed */ function get_resolved_border($side) { return $this->_resolved_borders[$side]; } /** * @return array */ function get_resolved_borders() { return $this->_resolved_borders; } } dompdf/src/FrameDecorator/NullFrameDecorator.php 0000644 00000001326 15024772104 0015746 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; /** * Dummy decorator * * @package dompdf */ class NullFrameDecorator extends AbstractFrameDecorator { /** * NullFrameDecorator constructor. * @param Frame $frame * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); $style = $this->_frame->get_style(); $style->width = 0; $style->height = 0; $style->margin = 0; $style->padding = 0; } } dompdf/src/FrameDecorator/Page.php 0000644 00000060444 15024772104 0013100 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Helpers; use Dompdf\Frame; use Dompdf\Renderer; /** * Decorates frames for page layout * * @package dompdf */ class Page extends AbstractFrameDecorator { /** * The y value of the bottom edge of the page area. * * https://www.w3.org/TR/CSS21/page.html#page-margins * * @var float */ protected $bottom_page_edge; /** * Flag indicating page is full. * * @var bool */ protected $_page_full; /** * Number of tables currently being reflowed * * @var int */ protected $_in_table; /** * The pdf renderer * * @var Renderer */ protected $_renderer; /** * This page's floating frames * * @var array */ protected $_floating_frames = []; //........................................................................ /** * Class constructor * * @param Frame $frame the frame to decorate * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); $this->_page_full = false; $this->_in_table = 0; $this->bottom_page_edge = null; } /** * Set the renderer used for this pdf * * @param Renderer $renderer the renderer to use */ function set_renderer($renderer) { $this->_renderer = $renderer; } /** * Return the renderer used for this pdf * * @return Renderer */ function get_renderer() { return $this->_renderer; } /** * Calculate the bottom edge of the page area after margins have been * applied for the current page. */ public function calculate_bottom_page_edge(): void { [, , , $cbh] = $this->get_containing_block(); $style = $this->get_style(); $margin_bottom = (float) $style->length_in_pt($style->margin_bottom, $cbh); $this->bottom_page_edge = $cbh - $margin_bottom; } /** * Returns true if the page is full and is no longer accepting frames. * * @return bool */ function is_full() { return $this->_page_full; } /** * Start a new page by resetting the full flag. */ function next_page() { $this->_floating_frames = []; $this->_renderer->new_page(); $this->_page_full = false; } /** * Indicate to the page that a table is currently being reflowed. */ function table_reflow_start() { $this->_in_table++; } /** * Indicate to the page that table reflow is finished. */ function table_reflow_end() { $this->_in_table--; } /** * Return whether we are currently in a nested table or not * * @return bool */ function in_nested_table() { return $this->_in_table > 1; } /** * Check if a forced page break is required before $frame. This uses the * frame's page_break_before property as well as the preceding frame's * page_break_after property. * * @link http://www.w3.org/TR/CSS21/page.html#forced * * @param AbstractFrameDecorator $frame the frame to check * * @return bool true if a page break occurred */ function check_forced_page_break(Frame $frame) { // Skip check if page is already split and for the body if ($this->_page_full || $frame->get_node()->nodeName === "body") { return false; } $page_breaks = ["always", "left", "right"]; $style = $frame->get_style(); if (($frame->is_block_level() || $style->display === "table-row") && in_array($style->page_break_before, $page_breaks, true) ) { // Prevent cascading splits $frame->split(null, true, true); $style->page_break_before = "auto"; $this->_page_full = true; $frame->_already_pushed = true; return true; } // Find the preceding block-level sibling (or table row). Inline // elements are treated as if wrapped in an anonymous block container // here. See https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level $prev = $frame->get_prev_sibling(); while ($prev && (($prev->is_text_node() && $prev->get_node()->nodeValue === "") || $prev->get_node()->nodeName === "bullet") ) { $prev = $prev->get_prev_sibling(); } if ($prev && ($prev->is_block_level() || $prev->get_style()->display === "table-row")) { if (in_array($prev->get_style()->page_break_after, $page_breaks, true)) { // Prevent cascading splits $frame->split(null, true, true); $prev->get_style()->page_break_after = "auto"; $this->_page_full = true; $frame->_already_pushed = true; return true; } $prev_last_child = $prev->get_last_child(); while ($prev_last_child && (($prev_last_child->is_text_node() && $prev_last_child->get_node()->nodeValue === "") || $prev_last_child->get_node()->nodeName === "bullet") ) { $prev_last_child = $prev_last_child->get_prev_sibling(); } if ($prev_last_child && $prev_last_child->is_block_level() && in_array($prev_last_child->get_style()->page_break_after, $page_breaks, true) ) { $frame->split(null, true, true); $prev_last_child->get_style()->page_break_after = "auto"; $this->_page_full = true; $frame->_already_pushed = true; return true; } } return false; } /** * Check for a gap between the top content edge of a frame and its child * content. * * Additionally, the top margin, border, and padding of the frame must fit * on the current page. * * @param float $childPos The top margin or line-box edge of the child content. * @param Frame $frame The parent frame to check. * @return bool */ protected function hasGap(float $childPos, Frame $frame): bool { $style = $frame->get_style(); $cbw = $frame->get_containing_block("w"); $contentEdge = $frame->get_position("y") + (float) $style->length_in_pt([ $style->margin_top, $style->border_top_width, $style->padding_top ], $cbw); return Helpers::lengthGreater($childPos, $contentEdge) && Helpers::lengthLessOrEqual($contentEdge, $this->bottom_page_edge); } /** * Determine if a page break is allowed before $frame * http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks * * In the normal flow, page breaks can occur at the following places: * * 1. In the vertical margin between block boxes. When an * unforced page break occurs here, the used values of the * relevant 'margin-top' and 'margin-bottom' properties are set * to '0'. When a forced page break occurs here, the used value * of the relevant 'margin-bottom' property is set to '0'; the * relevant 'margin-top' used value may either be set to '0' or * retained. * 2. Between line boxes inside a block container box. * 3. Between the content edge of a block container box and the * outer edges of its child content (margin edges of block-level * children or line box edges for inline-level children) if there * is a (non-zero) gap between them. * * These breaks are subject to the following rules: * * * Rule A: Breaking at (1) is allowed only if the * 'page-break-after' and 'page-break-before' properties of all * the elements generating boxes that meet at this margin allow * it, which is when at least one of them has the value * 'always', 'left', or 'right', or when all of them are 'auto'. * * * Rule B: However, if all of them are 'auto' and a common * ancestor of all the elements has a 'page-break-inside' value * of 'avoid', then breaking here is not allowed. * * * Rule C: Breaking at (2) is allowed only if the number of line * boxes between the break and the start of the enclosing block * box is the value of 'orphans' or more, and the number of line * boxes between the break and the end of the box is the value * of 'widows' or more. * * * Rule D: In addition, breaking at (2) or (3) is allowed only * if the 'page-break-inside' property of the element and all * its ancestors is 'auto'. * * If the above does not provide enough break points to keep content * from overflowing the page boxes, then rules A, B and D are * dropped in order to find additional breakpoints. * * If that still does not lead to sufficient break points, rule C is * dropped as well, to find still more break points. * * We also allow breaks between table rows. * * @param AbstractFrameDecorator $frame the frame to check * * @return bool true if a break is allowed, false otherwise */ protected function _page_break_allowed(Frame $frame) { Helpers::dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName . ")"); $display = $frame->get_style()->display; // Block Frames (1): if ($frame->is_block_level() || $display === "-dompdf-image") { // Avoid breaks within table-cells if ($this->_in_table > ($display === "table" ? 1 : 0)) { Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table); return false; } // Rule A if ($frame->get_style()->page_break_before === "avoid") { Helpers::dompdf_debug("page-break", "before: avoid"); return false; } // Find the preceding block-level sibling. Inline elements are // treated as if wrapped in an anonymous block container here. See // https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level $prev = $frame->get_prev_sibling(); while ($prev && (($prev->is_text_node() && $prev->get_node()->nodeValue === "") || $prev->get_node()->nodeName === "bullet") ) { $prev = $prev->get_prev_sibling(); } // Does the previous element allow a page break after? if ($prev && ($prev->is_block_level() || $prev->get_style()->display === "-dompdf-image") && $prev->get_style()->page_break_after === "avoid" ) { Helpers::dompdf_debug("page-break", "after: avoid"); return false; } // Rules B & D $parent = $frame->get_parent(); $p = $parent; while ($p) { if ($p->get_style()->page_break_inside === "avoid") { Helpers::dompdf_debug("page-break", "parent->inside: avoid"); return false; } $p = $p->find_block_parent(); } // To prevent cascading page breaks when a top-level element has // page-break-inside: avoid, ensure that at least one frame is // on the page before splitting. if ($parent->get_node()->nodeName === "body" && !$prev) { // We are the body's first child Helpers::dompdf_debug("page-break", "Body's first child."); return false; } // Check for a possible type (3) break if (!$prev && $parent && !$this->hasGap($frame->get_position("y"), $parent)) { Helpers::dompdf_debug("page-break", "First block-level frame, no gap"); return false; } Helpers::dompdf_debug("page-break", "block: break allowed"); return true; } // Inline frames (2): else { if ($frame->is_inline_level()) { // Avoid breaks within table-cells if ($this->_in_table) { Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table); return false; } // Rule C $block_parent = $frame->find_block_parent(); $parent_style = $block_parent->get_style(); $line = $block_parent->get_current_line_box(); $line_count = count($block_parent->get_line_boxes()); $line_number = $frame->get_containing_line() && empty($line->get_frames()) ? $line_count - 1 : $line_count; // The line number of the frame can be less than the current // number of line boxes, in case we are backtracking. As long as // we are not checking for widows yet, just checking against the // number of line boxes is sufficient in most cases, though. if ($line_number <= $parent_style->orphans) { Helpers::dompdf_debug("page-break", "orphans"); return false; } // FIXME: Checking widows is tricky without having laid out the // remaining line boxes. Just ignore it for now... // Rule D $p = $block_parent; while ($p) { if ($p->get_style()->page_break_inside === "avoid") { Helpers::dompdf_debug("page-break", "parent->inside: avoid"); return false; } $p = $p->find_block_parent(); } // To prevent cascading page breaks when a top-level element has // page-break-inside: avoid, ensure that at least one frame with // some content is on the page before splitting. $prev = $frame->get_prev_sibling(); while ($prev && ($prev->is_text_node() && trim($prev->get_node()->nodeValue) == "")) { $prev = $prev->get_prev_sibling(); } if ($block_parent->get_node()->nodeName === "body" && !$prev) { // We are the body's first child Helpers::dompdf_debug("page-break", "Body's first child."); return false; } Helpers::dompdf_debug("page-break", "inline: break allowed"); return true; // Table-rows } else { if ($display === "table-row") { // If this is a nested table, prevent the page from breaking if ($this->_in_table > 1) { Helpers::dompdf_debug("page-break", "table: nested table"); return false; } // Rule A (table row) if ($frame->get_style()->page_break_before === "avoid") { Helpers::dompdf_debug("page-break", "before: avoid"); return false; } // Find the preceding row $prev = $frame->get_prev_sibling(); if (!$prev) { $prev_group = $frame->get_parent()->get_prev_sibling(); if ($prev_group && in_array($prev_group->get_style()->display, Table::ROW_GROUPS, true) ) { $prev = $prev_group->get_last_child(); } } // Check if a page break is allowed after the preceding row if ($prev && $prev->get_style()->page_break_after === "avoid") { Helpers::dompdf_debug("page-break", "after: avoid"); return false; } // Avoid breaking before the first row of a table if (!$prev) { Helpers::dompdf_debug("page-break", "table: first-row"); return false; } // Rule B (table row) // Check if the page_break_inside property is not 'avoid' // for the parent table or any of its ancestors $table = Table::find_parent_table($frame); $p = $table; while ($p) { if ($p->get_style()->page_break_inside === "avoid") { Helpers::dompdf_debug("page-break", "parent->inside: avoid"); return false; } $p = $p->find_block_parent(); } Helpers::dompdf_debug("page-break", "table-row: break allowed"); return true; } else { if (in_array($display, Table::ROW_GROUPS, true)) { // Disallow breaks at row-groups: only split at row boundaries return false; } else { Helpers::dompdf_debug("page-break", "? " . $display); return false; } } } } } /** * Check if $frame will fit on the page. If the frame does not fit, * the frame tree is modified so that a page break occurs in the * correct location. * * @param AbstractFrameDecorator $frame the frame to check * * @return bool */ function check_page_break(Frame $frame) { if ($this->_page_full || $frame->_already_pushed // Never check for breaks on empty text nodes || ($frame->is_text_node() && $frame->get_node()->nodeValue === "") ) { return false; } $p = $frame; do { $display = $p->get_style()->display; if ($display == "table-row") { if ($p->_already_pushed) { return false; } } } while ($p = $p->get_parent()); // If the frame is absolute or fixed it shouldn't break $p = $frame; do { if ($p->is_absolute()) { return false; } } while ($p = $p->get_parent()); $margin_height = $frame->get_margin_height(); // Determine the frame's maximum y value $max_y = (float)$frame->get_position("y") + $margin_height; // If a split is to occur here, then the bottom margins & paddings of all // parents of $frame must fit on the page as well: $p = $frame->get_parent(); while ($p && $p !== $this) { $cbw = $p->get_containing_block("w"); $max_y += (float) $p->get_style()->computed_bottom_spacing($cbw); $p = $p->get_parent(); } // Check if $frame flows off the page if (Helpers::lengthLessOrEqual($max_y, $this->bottom_page_edge)) { // no: do nothing return false; } Helpers::dompdf_debug("page-break", "check_page_break"); Helpers::dompdf_debug("page-break", "in_table: " . $this->_in_table); // yes: determine page break location $iter = $frame; $flg = false; $pushed_flg = false; $in_table = $this->_in_table; Helpers::dompdf_debug("page-break", "Starting search"); while ($iter) { // echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".spl_object_hash($iter->get_node()). ""; if ($iter === $this) { Helpers::dompdf_debug("page-break", "reached root."); // We've reached the root in our search. Just split at $frame. break; } if ($iter->_already_pushed) { $pushed_flg = true; } elseif ($this->_page_break_allowed($iter)) { Helpers::dompdf_debug("page-break", "break allowed, splitting."); $iter->split(null, true); $this->_page_full = true; $this->_in_table = $in_table; $iter->_already_pushed = true; $frame->_already_pushed = true; return true; } if (!$flg && $next = $iter->get_last_child()) { Helpers::dompdf_debug("page-break", "following last child."); if ($next->is_table()) { $this->_in_table++; } $iter = $next; $pushed_flg = false; continue; } if ($pushed_flg) { // The frame was already pushed, avoid breaking on a previous page break; } $next = $iter->get_prev_sibling(); // Skip empty text nodes while ($next && $next->is_text_node() && $next->get_node()->nodeValue === "") { $next = $next->get_prev_sibling(); } if ($next) { Helpers::dompdf_debug("page-break", "following prev sibling."); if ($next->is_table() && !$iter->is_table()) { $this->_in_table++; } elseif (!$next->is_table() && $iter->is_table()) { $this->_in_table--; } $iter = $next; $flg = false; continue; } if ($next = $iter->get_parent()) { Helpers::dompdf_debug("page-break", "following parent."); if ($iter->is_table()) { $this->_in_table--; } $iter = $next; $flg = true; continue; } break; } $this->_in_table = $in_table; // No valid page break found. Just break at $frame. Helpers::dompdf_debug("page-break", "no valid break found, just splitting."); // If we are in a table, backtrack to the nearest top-level table row if ($this->_in_table) { $iter = $frame; while ($iter && $iter->get_style()->display !== "table-row" && $iter->get_style()->display !== 'table-row-group' && $iter->_already_pushed === false) { $iter = $iter->get_parent(); } if ($iter) { $iter->split(null, true); $iter->_already_pushed = true; } else { return false; } } else { $frame->split(null, true); } $this->_page_full = true; $frame->_already_pushed = true; return true; } //........................................................................ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void { // Do nothing } /** * Add a floating frame * * @param Frame $frame * * @return void */ function add_floating_frame(Frame $frame) { array_unshift($this->_floating_frames, $frame); } /** * @return Frame[] */ function get_floating_frames() { return $this->_floating_frames; } /** * @param $key */ public function remove_floating_frame($key) { unset($this->_floating_frames[$key]); } /** * @param Frame $child * @return int|mixed */ public function get_lowest_float_offset(Frame $child) { $style = $child->get_style(); $side = $style->clear; $float = $style->float; $y = 0; if ($float === "none") { foreach ($this->_floating_frames as $key => $frame) { if ($side === "both" || $frame->get_style()->float === $side) { $y = max($y, $frame->get_position("y") + $frame->get_margin_height()); } $this->remove_floating_frame($key); } } if ($y > 0) { $y++; // add 1px buffer from float } return $y; } } dompdf/src/FrameDecorator/TableRow.php 0000644 00000001073 15024772104 0013734 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; /** * Decorates Frames for table row layout * * @package dompdf */ class TableRow extends AbstractFrameDecorator { /** * TableRow constructor. * @param Frame $frame * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); } } dompdf/src/FrameDecorator/TableRowGroup.php 0000644 00000003777 15024772104 0014766 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; /** * Table row group decorator * * Overrides split() method for tbody, thead & tfoot elements * * @package dompdf */ class TableRowGroup extends AbstractFrameDecorator { /** * Class constructor * * @param Frame $frame Frame to decorate * @param Dompdf $dompdf Current dompdf instance */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); } /** * Split the row group at the given child and remove all subsequent child * rows and all subsequent row groups from the cellmap. */ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void { if (is_null($child)) { parent::split($child, $page_break, $forced); return; } // Remove child & all subsequent rows from the cellmap /** @var Table $parent */ $parent = $this->get_parent(); $cellmap = $parent->get_cellmap(); $iter = $child; while ($iter) { $cellmap->remove_row($iter); $iter = $iter->get_next_sibling(); } // Remove all subsequent row groups from the cellmap $iter = $this->get_next_sibling(); while ($iter) { $cellmap->remove_row_group($iter); $iter = $iter->get_next_sibling(); } // If we are splitting at the first child remove the // table-row-group from the cellmap as well if ($child === $this->get_first_child()) { $cellmap->remove_row_group($this); parent::split(null, $page_break, $forced); return; } $cellmap->update_row_group($this, $child->get_prev_sibling()); parent::split($child, $page_break, $forced); } } dompdf/src/FrameDecorator/ListBulletImage.php 0000644 00000005346 15024772104 0015252 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; use Dompdf\Helpers; use Dompdf\Image\Cache; /** * Decorates frames for list bullets with custom images * * @package dompdf */ class ListBulletImage extends ListBullet { /** * The underlying image frame * * @var Image */ protected $_img; /** * The image's width in pixels * * @var float */ protected $_width; /** * The image's height in pixels * * @var float */ protected $_height; /** * ListBulletImage constructor. * @param Frame $frame * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { $style = $frame->get_style(); $url = $style->list_style_image; $frame->get_node()->setAttribute("src", $url); $this->_img = new Image($frame, $dompdf); parent::__construct($this->_img, $dompdf); $url = $this->_img->get_image_url(); if (Cache::is_broken($url)) { $this->_width = parent::get_width(); $this->_height = parent::get_height(); } else { // Resample the bullet image to be consistent with 'auto' sized images [$width, $height] = $this->_img->get_intrinsic_dimensions(); $this->_width = $this->_img->resample($width); $this->_height = $this->_img->resample($height); } } public function get_width(): float { return $this->_width; } public function get_height(): float { return $this->_height; } public function get_margin_width(): float { $style = $this->get_style(); return $this->_width + $style->font_size * self::MARKER_INDENT; } public function get_margin_height(): float { $fontMetrics = $this->_dompdf->getFontMetrics(); $style = $this->get_style(); $font = $style->font_family; $size = $style->font_size; $fontHeight = $fontMetrics->getFontHeight($font, $size); $baseline = $fontMetrics->getFontBaseline($font, $size); // This is the same factor as used in // `FrameDecorator\Text::get_margin_height()` $f = $style->line_height / ($size > 0 ? $size : 1); // FIXME: Tries to approximate replacing the space above the font // baseline with the image return $f * ($fontHeight - $baseline) + $this->_height; } /** * Return image url * * @return string */ function get_image_url() { return $this->_img->get_image_url(); } } dompdf/src/FrameDecorator/AbstractFrameDecorator.php 0000644 00000054035 15024772104 0016604 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use DOMElement; use DOMNode; use Dompdf\Helpers; use Dompdf\Dompdf; use Dompdf\Exception; use Dompdf\Frame; use Dompdf\Frame\Factory; use Dompdf\Frame\FrameListIterator; use Dompdf\Frame\FrameTreeIterator; use Dompdf\FrameReflower\AbstractFrameReflower; use Dompdf\Css\Style; use Dompdf\Positioner\AbstractPositioner; /** * Base AbstractFrameDecorator class * * @package dompdf */ abstract class AbstractFrameDecorator extends Frame { const DEFAULT_COUNTER = "-dompdf-default-counter"; /** * array([id] => counter_value) (for generated content) * * @var array */ public $_counters = []; /** * The root node of the DOM tree * * @var Frame */ protected $_root; /** * The decorated frame * * @var Frame */ protected $_frame; /** * AbstractPositioner object used to position this frame (Strategy pattern) * * @var AbstractPositioner */ protected $_positioner; /** * Reflower object used to calculate frame dimensions (Strategy pattern) * * @var AbstractFrameReflower */ protected $_reflower; /** * Reference to the current dompdf instance * * @var Dompdf */ protected $_dompdf; /** * First block parent * * @var Block */ private $_block_parent; /** * First positioned parent (position: relative | absolute | fixed) * * @var AbstractFrameDecorator */ private $_positioned_parent; /** * Cache for the get_parent while loop results * * @var Frame */ private $_cached_parent; /** * Whether generated content and counters have been set. * * @var bool */ public $content_set = false; /** * Whether the frame has been split * * @var bool */ public $is_split = false; /** * Whether the frame is a split-off frame * * @var bool */ public $is_split_off = false; /** * Class constructor * * @param Frame $frame The decoration target * @param Dompdf $dompdf The Dompdf object */ function __construct(Frame $frame, Dompdf $dompdf) { $this->_frame = $frame; $this->_root = null; $this->_dompdf = $dompdf; $frame->set_decorator($this); } /** * "Destructor": forcibly free all references held by this object * * @param bool $recursive if true, call dispose on all children */ function dispose($recursive = false) { if ($recursive) { while ($child = $this->get_first_child()) { $child->dispose(true); } } $this->_root = null; unset($this->_root); $this->_frame->dispose(true); $this->_frame = null; unset($this->_frame); $this->_positioner = null; unset($this->_positioner); $this->_reflower = null; unset($this->_reflower); } /** * Return a copy of this frame with $node as its node * * @param DOMNode $node * * @return AbstractFrameDecorator */ function copy(DOMNode $node) { $frame = new Frame($node); $style = clone $this->_frame->get_style(); $style->reset(); $frame->set_style($style); if ($node instanceof DOMElement && $node->hasAttribute("id")) { $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id")); $node->removeAttribute("id"); } return Factory::decorate_frame($frame, $this->_dompdf, $this->_root); } /** * Create a deep copy: copy this node and all children * * @return AbstractFrameDecorator */ function deep_copy() { $node = $this->_frame->get_node()->cloneNode(); $frame = new Frame($node); $style = clone $this->_frame->get_style(); $style->reset(); $frame->set_style($style); if ($node instanceof DOMElement && $node->hasAttribute("id")) { $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id")); $node->removeAttribute("id"); } $deco = Factory::decorate_frame($frame, $this->_dompdf, $this->_root); foreach ($this->get_children() as $child) { $deco->append_child($child->deep_copy()); } return $deco; } /** * Create an anonymous child frame, inheriting styles from this frame. * * @param string $node_name * @param string $display * * @return AbstractFrameDecorator */ public function create_anonymous_child(string $node_name, string $display): AbstractFrameDecorator { $style = $this->get_style(); $child_style = $style->get_stylesheet()->create_style(); $child_style->set_prop("display", $display); $child_style->inherit($style); $node = $this->get_node()->ownerDocument->createElement($node_name); $frame = new Frame($node); $frame->set_style($child_style); return Factory::decorate_frame($frame, $this->_dompdf, $this->_root); } function reset() { $this->_frame->reset(); $this->_reflower->reset(); $this->reset_generated_content(); $this->revert_counter_increment(); $this->content_set = false; $this->_counters = []; // clear parent lookup caches $this->_cached_parent = null; $this->_block_parent = null; $this->_positioned_parent = null; // Reset all children foreach ($this->get_children() as $child) { $child->reset(); } } /** * If this represents a generated node then child nodes represent generated * content. Remove the children since the content will be generated next * time this frame is reflowed. */ protected function reset_generated_content(): void { if ($this->content_set && $this->get_node()->nodeName === "dompdf_generated" ) { $content = $this->get_style()->content; if ($content !== "normal" && $content !== "none") { foreach ($this->get_children() as $child) { $this->remove_child($child); } } } } /** * Decrement any counters that were incremented on the current node, unless * that node is the body. */ protected function revert_counter_increment(): void { if ($this->content_set && $this->get_node()->nodeName !== "body" && ($decrement = $this->get_style()->counter_increment) !== "none" ) { $this->decrement_counters($decrement); } } // Getters ----------- function get_id() { return $this->_frame->get_id(); } /** * @return Frame */ function get_frame() { return $this->_frame; } function get_node() { return $this->_frame->get_node(); } function get_style() { return $this->_frame->get_style(); } /** * @deprecated */ function get_original_style() { return $this->_frame->get_style(); } function get_containing_block($i = null) { return $this->_frame->get_containing_block($i); } function get_position($i = null) { return $this->_frame->get_position($i); } /** * @return Dompdf */ function get_dompdf() { return $this->_dompdf; } public function get_margin_width(): float { return $this->_frame->get_margin_width(); } public function get_margin_height(): float { return $this->_frame->get_margin_height(); } public function get_content_box(): array { return $this->_frame->get_content_box(); } public function get_padding_box(): array { return $this->_frame->get_padding_box(); } public function get_border_box(): array { return $this->_frame->get_border_box(); } function set_id($id) { $this->_frame->set_id($id); } public function set_style(Style $style): void { $this->_frame->set_style($style); } function set_containing_block($x = null, $y = null, $w = null, $h = null) { $this->_frame->set_containing_block($x, $y, $w, $h); } function set_position($x = null, $y = null) { $this->_frame->set_position($x, $y); } function is_auto_height() { return $this->_frame->is_auto_height(); } function is_auto_width() { return $this->_frame->is_auto_width(); } function __toString() { return $this->_frame->__toString(); } function prepend_child(Frame $child, $update_node = true) { while ($child instanceof AbstractFrameDecorator) { $child = $child->_frame; } $this->_frame->prepend_child($child, $update_node); } function append_child(Frame $child, $update_node = true) { while ($child instanceof AbstractFrameDecorator) { $child = $child->_frame; } $this->_frame->append_child($child, $update_node); } function insert_child_before(Frame $new_child, Frame $ref, $update_node = true) { while ($new_child instanceof AbstractFrameDecorator) { $new_child = $new_child->_frame; } if ($ref instanceof AbstractFrameDecorator) { $ref = $ref->_frame; } $this->_frame->insert_child_before($new_child, $ref, $update_node); } function insert_child_after(Frame $new_child, Frame $ref, $update_node = true) { $insert_frame = $new_child; while ($insert_frame instanceof AbstractFrameDecorator) { $insert_frame = $insert_frame->_frame; } $reference_frame = $ref; while ($reference_frame instanceof AbstractFrameDecorator) { $reference_frame = $reference_frame->_frame; } $this->_frame->insert_child_after($insert_frame, $reference_frame, $update_node); } function remove_child(Frame $child, $update_node = true) { while ($child instanceof AbstractFrameDecorator) { $child = $child->_frame; } return $this->_frame->remove_child($child, $update_node); } /** * @param bool $use_cache * @return AbstractFrameDecorator */ function get_parent($use_cache = true) { if ($use_cache && $this->_cached_parent) { return $this->_cached_parent; } $p = $this->_frame->get_parent(); if ($p && $deco = $p->get_decorator()) { while ($tmp = $deco->get_decorator()) { $deco = $tmp; } return $this->_cached_parent = $deco; } else { return $this->_cached_parent = $p; } } /** * @return AbstractFrameDecorator */ function get_first_child() { $c = $this->_frame->get_first_child(); if ($c && $deco = $c->get_decorator()) { while ($tmp = $deco->get_decorator()) { $deco = $tmp; } return $deco; } else { if ($c) { return $c; } } return null; } /** * @return AbstractFrameDecorator */ function get_last_child() { $c = $this->_frame->get_last_child(); if ($c && $deco = $c->get_decorator()) { while ($tmp = $deco->get_decorator()) { $deco = $tmp; } return $deco; } else { if ($c) { return $c; } } return null; } /** * @return AbstractFrameDecorator */ function get_prev_sibling() { $s = $this->_frame->get_prev_sibling(); if ($s && $deco = $s->get_decorator()) { while ($tmp = $deco->get_decorator()) { $deco = $tmp; } return $deco; } else { if ($s) { return $s; } } return null; } /** * @return AbstractFrameDecorator */ function get_next_sibling() { $s = $this->_frame->get_next_sibling(); if ($s && $deco = $s->get_decorator()) { while ($tmp = $deco->get_decorator()) { $deco = $tmp; } return $deco; } else { if ($s) { return $s; } } return null; } /** * @return FrameListIterator<AbstractFrameDecorator> */ public function get_children(): FrameListIterator { return new FrameListIterator($this); } /** * @return FrameTreeIterator<AbstractFrameDecorator> */ function get_subtree(): FrameTreeIterator { return new FrameTreeIterator($this); } function set_positioner(AbstractPositioner $posn) { $this->_positioner = $posn; if ($this->_frame instanceof AbstractFrameDecorator) { $this->_frame->set_positioner($posn); } } function set_reflower(AbstractFrameReflower $reflower) { $this->_reflower = $reflower; if ($this->_frame instanceof AbstractFrameDecorator) { $this->_frame->set_reflower($reflower); } } /** * @return AbstractPositioner */ function get_positioner() { return $this->_positioner; } /** * @return AbstractFrameReflower */ function get_reflower() { return $this->_reflower; } /** * @param Frame $root */ function set_root(Frame $root) { $this->_root = $root; if ($this->_frame instanceof AbstractFrameDecorator) { $this->_frame->set_root($root); } } /** * @return Page */ function get_root() { return $this->_root; } /** * @return Block */ function find_block_parent() { // Find our nearest block level parent if (isset($this->_block_parent)) { return $this->_block_parent; } $p = $this->get_parent(); while ($p) { if ($p->is_block()) { break; } $p = $p->get_parent(); } return $this->_block_parent = $p; } /** * @return AbstractFrameDecorator */ function find_positioned_parent() { // Find our nearest relative positioned parent if (isset($this->_positioned_parent)) { return $this->_positioned_parent; } $p = $this->get_parent(); while ($p) { if ($p->is_positioned()) { break; } $p = $p->get_parent(); } if (!$p) { $p = $this->_root; } return $this->_positioned_parent = $p; } /** * Split this frame at $child. * The current frame is cloned and $child and all children following * $child are added to the clone. The clone is then passed to the * current frame's parent->split() method. * * @param Frame|null $child * @param bool $page_break * @param bool $forced Whether the page break is forced. * * @throws Exception */ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void { if (is_null($child)) { $this->get_parent()->split($this, $page_break, $forced); return; } if ($child->get_parent() !== $this) { throw new Exception("Unable to split: frame is not a child of this one."); } $this->revert_counter_increment(); $node = $this->_frame->get_node(); $split = $this->copy($node->cloneNode()); $style = $this->_frame->get_style(); $split_style = $split->get_style(); // Truncate the box decoration at the split, except for the body if ($node->nodeName !== "body") { // Clear bottom decoration of original frame $style->margin_bottom = 0.0; $style->padding_bottom = 0.0; $style->border_bottom_width = 0.0; $style->border_bottom_left_radius = 0.0; $style->border_bottom_right_radius = 0.0; // Clear top decoration of split frame $split_style->margin_top = 0.0; $split_style->padding_top = 0.0; $split_style->border_top_width = 0.0; $split_style->border_top_left_radius = 0.0; $split_style->border_top_right_radius = 0.0; $split_style->page_break_before = "auto"; } $split_style->text_indent = 0.0; $split_style->counter_reset = "none"; $this->is_split = true; $split->is_split_off = true; $split->_already_pushed = true; $this->get_parent()->insert_child_after($split, $this); if ($this instanceof Block) { // Remove the frames that will be moved to the new split node from // the line boxes $this->remove_frames_from_line($child); // recalculate the float offsets after paging foreach ($this->get_line_boxes() as $line_box) { $line_box->get_float_offsets(); } } if (!$forced) { // Reset top margin in case of an unforced page break // https://www.w3.org/TR/CSS21/page.html#allowed-page-breaks $child->get_style()->margin_top = 0.0; } // Add $child and all following siblings to the new split node $iter = $child; while ($iter) { $frame = $iter; $iter = $iter->get_next_sibling(); $frame->reset(); $split->append_child($frame); } $this->get_parent()->split($split, $page_break, $forced); // Preserve the current counter values. This must be done after the // parent split, as counters get reset on frame reset $split->_counters = $this->_counters; } /** * @param array $counters */ public function reset_counters(array $counters): void { foreach ($counters as $id => $value) { $this->reset_counter($id, $value); } } /** * @param string $id * @param int $value */ public function reset_counter(string $id = self::DEFAULT_COUNTER, int $value = 0): void { $this->get_parent()->_counters[$id] = $value; } /** * @param array $counters */ public function decrement_counters(array $counters): void { foreach ($counters as $id => $increment) { $this->increment_counter($id, $increment * -1); } } /** * @param array $counters */ public function increment_counters(array $counters): void { foreach ($counters as $id => $increment) { $this->increment_counter($id, $increment); } } /** * @param string $id * @param int $increment */ public function increment_counter(string $id = self::DEFAULT_COUNTER, int $increment = 1): void { $counter_frame = $this->lookup_counter_frame($id); if ($counter_frame) { if (!isset($counter_frame->_counters[$id])) { $counter_frame->_counters[$id] = 0; } $counter_frame->_counters[$id] += $increment; } } /** * @param string $id * @return AbstractFrameDecorator|null */ function lookup_counter_frame($id = self::DEFAULT_COUNTER) { $f = $this->get_parent(); while ($f) { if (isset($f->_counters[$id])) { return $f; } $fp = $f->get_parent(); if (!$fp) { return $f; } $f = $fp; } return null; } /** * @param string $id * @param string $type * @return bool|string * * TODO: What version is the best : this one or the one in ListBullet ? */ function counter_value(string $id = self::DEFAULT_COUNTER, string $type = "decimal") { $type = mb_strtolower($type); if (!isset($this->_counters[$id])) { $this->_counters[$id] = 0; } $value = $this->_counters[$id]; switch ($type) { default: case "decimal": return $value; case "decimal-leading-zero": return str_pad($value, 2, "0", STR_PAD_LEFT); case "lower-roman": return Helpers::dec2roman($value); case "upper-roman": return mb_strtoupper(Helpers::dec2roman($value)); case "lower-latin": case "lower-alpha": return chr((($value - 1) % 26) + ord('a')); case "upper-latin": case "upper-alpha": return chr((($value - 1) % 26) + ord('A')); case "lower-greek": return Helpers::unichr($value + 944); case "upper-greek": return Helpers::unichr($value + 912); } } final function position() { $this->_positioner->position($this); } /** * @param float $offset_x * @param float $offset_y * @param bool $ignore_self */ final function move(float $offset_x, float $offset_y, bool $ignore_self = false): void { $this->_positioner->move($this, $offset_x, $offset_y, $ignore_self); } /** * @param Block|null $block */ final function reflow(Block $block = null) { // Uncomment this to see the frames before they're laid out, instead of // during rendering. //echo $this->_frame; flush(); $this->_reflower->reflow($block); } /** * @return array */ final public function get_min_max_width(): array { return $this->_reflower->get_min_max_width(); } } dompdf/src/FrameDecorator/Inline.php 0000644 00000007363 15024772104 0013443 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; use Dompdf\Exception; /** * Decorates frames for inline layout * * @package dompdf */ class Inline extends AbstractFrameDecorator { /** * Inline constructor. * @param Frame $frame * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); } /** * Vertical padding, border, and margin do not apply when determining the * height for inline frames. * * http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced * * The vertical padding, border and margin of an inline, non-replaced box * start at the top and bottom of the content area, not the * 'line-height'. But only the 'line-height' is used to calculate the * height of the line box. * * @return float */ public function get_margin_height(): float { $style = $this->get_style(); $font = $style->font_family; $size = $style->font_size; $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size); return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight; } public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void { if (is_null($child)) { $this->get_parent()->split($this, $page_break, $forced); return; } if ($child->get_parent() !== $this) { throw new Exception("Unable to split: frame is not a child of this one."); } $this->revert_counter_increment(); $node = $this->_frame->get_node(); $split = $this->copy($node->cloneNode()); $style = $this->_frame->get_style(); $split_style = $split->get_style(); // Unset the current node's right style properties $style->margin_right = 0.0; $style->padding_right = 0.0; $style->border_right_width = 0.0; $style->border_top_right_radius = 0.0; $style->border_bottom_right_radius = 0.0; // Unset the split node's left style properties since we don't want them // to propagate $split_style->margin_left = 0.0; $split_style->padding_left = 0.0; $split_style->border_left_width = 0.0; $split_style->border_top_left_radius = 0.0; $split_style->border_bottom_left_radius = 0.0; // If this is a generated node don't propagate the content style if ($split->get_node()->nodeName == "dompdf_generated") { $split_style->content = "normal"; } //On continuation of inline element on next line, //don't repeat non-horizontally repeatable background images //See e.g. in testcase image_variants, long descriptions if (($url = $style->background_image) && $url !== "none" && ($repeat = $style->background_repeat) && $repeat !== "repeat" && $repeat !== "repeat-x" ) { $split_style->background_image = "none"; } $this->get_parent()->insert_child_after($split, $this); // Add $child and all following siblings to the new split node $iter = $child; while ($iter) { $frame = $iter; $iter = $iter->get_next_sibling(); $frame->reset(); $split->append_child($frame); } $parent = $this->get_parent(); if ($page_break) { $parent->split($split, $page_break, $forced); } elseif ($parent instanceof Inline) { $parent->split($split); } } } dompdf/src/FrameDecorator/Block.php 0000644 00000013753 15024772104 0013257 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; use Dompdf\LineBox; /** * Decorates frames for block layout * * @package dompdf */ class Block extends AbstractFrameDecorator { /** * Current line index * * @var int */ protected $_cl; /** * The block's line boxes * * @var LineBox[] */ protected $_line_boxes; /** * List of markers that have not found their line box to vertically align * with yet. Markers are collected by nested block containers until an * inline line box is found at the start of the block. * * @var ListBullet[] */ protected $dangling_markers; /** * Block constructor. * @param Frame $frame * @param Dompdf $dompdf */ function __construct(Frame $frame, Dompdf $dompdf) { parent::__construct($frame, $dompdf); $this->_line_boxes = [new LineBox($this)]; $this->_cl = 0; $this->dangling_markers = []; } function reset() { parent::reset(); $this->_line_boxes = [new LineBox($this)]; $this->_cl = 0; $this->dangling_markers = []; } /** * @return LineBox */ function get_current_line_box() { return $this->_line_boxes[$this->_cl]; } /** * @return int */ function get_current_line_number() { return $this->_cl; } /** * @return LineBox[] */ function get_line_boxes() { return $this->_line_boxes; } /** * @param int $line_number * @return int */ function set_current_line_number($line_number) { $line_boxes_count = count($this->_line_boxes); $cl = max(min($line_number, $line_boxes_count), 0); return ($this->_cl = $cl); } /** * @param int $i */ function clear_line($i) { if (isset($this->_line_boxes[$i])) { unset($this->_line_boxes[$i]); } } /** * @param Frame $frame * @return LineBox|null */ public function add_frame_to_line(Frame $frame): ?LineBox { $current_line = $this->_line_boxes[$this->_cl]; $frame->set_containing_line($current_line); // Inline frames are currently treated as wrappers, and are not actually // added to the line if ($frame instanceof Inline) { return null; } $current_line->add_frame($frame); $this->increase_line_width($frame->get_margin_width()); $this->maximize_line_height($frame->get_margin_height(), $frame); // Add any dangling list markers to the first line box if it is inline if ($this->_cl === 0 && $current_line->inline && $this->dangling_markers !== [] ) { foreach ($this->dangling_markers as $marker) { $current_line->add_list_marker($marker); $this->maximize_line_height($marker->get_margin_height(), $marker); } $this->dangling_markers = []; } return $current_line; } /** * Remove the given frame and all following frames and lines from the block. * * @param Frame $frame */ public function remove_frames_from_line(Frame $frame): void { // Inline frames are not added to line boxes themselves, only their // text frame children $actualFrame = $frame; while ($actualFrame !== null && $actualFrame instanceof Inline) { $actualFrame = $actualFrame->get_first_child(); } if ($actualFrame === null) { return; } // Search backwards through the lines for $frame $frame = $actualFrame; $i = $this->_cl; $j = null; while ($i > 0) { $line = $this->_line_boxes[$i]; foreach ($line->get_frames() as $index => $f) { if ($frame === $f) { $j = $index; break 2; } } $i--; } if ($j === null) { return; } // Remove all lines that follow for ($k = $this->_cl; $k > $i; $k--) { unset($this->_line_boxes[$k]); } // Remove the line, if it is empty if ($j > 0) { $line->remove_frames($j); } else { unset($this->_line_boxes[$i]); } // Reset array indices $this->_line_boxes = array_values($this->_line_boxes); $this->_cl = count($this->_line_boxes) - 1; } /** * @param float $w */ public function increase_line_width(float $w): void { $this->_line_boxes[$this->_cl]->w += $w; } /** * @param float $val * @param Frame $frame */ public function maximize_line_height(float $val, Frame $frame): void { if ($val > $this->_line_boxes[$this->_cl]->h) { $this->_line_boxes[$this->_cl]->tallest_frame = $frame; $this->_line_boxes[$this->_cl]->h = $val; } } /** * @param bool $br */ public function add_line(bool $br = false): void { $line = $this->_line_boxes[$this->_cl]; $line->br = $br; $y = $line->y + $line->h; $new_line = new LineBox($this, $y); $this->_line_boxes[++$this->_cl] = $new_line; } /** * @param ListBullet $marker */ public function add_dangling_marker(ListBullet $marker): void { $this->dangling_markers[] = $marker; } /** * Inherit any dangling markers from the parent block. * * @param Block $block */ public function inherit_dangling_markers(self $block): void { if ($block->dangling_markers !== []) { $this->dangling_markers = $block->dangling_markers; $block->dangling_markers = []; } } } dompdf/src/FrameDecorator/Text.php 0000644 00000011714 15024772104 0013144 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\FrameDecorator; use Dompdf\Dompdf; use Dompdf\Frame; use Dompdf\Exception; /** * Decorates Frame objects for text layout * * @package dompdf */ class Text extends AbstractFrameDecorator { /** * @var float */ protected $text_spacing; /** * Text constructor. * @param Frame $frame * @param Dompdf $dompdf * @throws Exception */ function __construct(Frame $frame, Dompdf $dompdf) { if (!$frame->is_text_node()) { throw new Exception("Text_Decorator can only be applied to #text nodes."); } parent::__construct($frame, $dompdf); $this->text_spacing = 0.0; } function reset() { parent::reset(); $this->text_spacing = 0.0; } // Accessor methods /** * @return float */ public function get_text_spacing(): float { return $this->text_spacing; } /** * @return string */ function get_text() { // FIXME: this should be in a child class (and is incorrect) // if ( $this->_frame->get_style()->content !== "normal" ) { // $this->_frame->get_node()->data = $this->_frame->get_style()->content; // $this->_frame->get_style()->content = "normal"; // } // Helpers::pre_r("---"); // $style = $this->_frame->get_style(); // var_dump($text = $this->_frame->get_node()->data); // var_dump($asc = utf8_decode($text)); // for ($i = 0; $i < strlen($asc); $i++) // Helpers::pre_r("$i: " . $asc[$i] . " - " . ord($asc[$i])); // Helpers::pre_r("width: " . $this->_dompdf->getFontMetrics()->getTextWidth($text, $style->font_family, $style->font_size)); return $this->_frame->get_node()->data; } //........................................................................ /** * Vertical padding, border, and margin do not apply when determining the * height for inline frames. * * http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced * * The vertical padding, border and margin of an inline, non-replaced box * start at the top and bottom of the content area, not the * 'line-height'. But only the 'line-height' is used to calculate the * height of the line box. * * @return float */ public function get_margin_height(): float { // This function is also called in add_frame_to_line() and is used to // determine the line height $style = $this->get_style(); $font = $style->font_family; $size = $style->font_size; $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size); return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight; } public function get_padding_box(): array { $style = $this->_frame->get_style(); $pb = $this->_frame->get_padding_box(); $pb[3] = $pb["h"] = (float) $style->length_in_pt($style->height); return $pb; } /** * @param float $spacing */ public function set_text_spacing(float $spacing): void { $this->text_spacing = $spacing; $this->recalculate_width(); } /** * Recalculate the text width * * @return float */ public function recalculate_width(): float { $fontMetrics = $this->_dompdf->getFontMetrics(); $style = $this->get_style(); $text = $this->get_text(); $font = $style->font_family; $size = $style->font_size; $word_spacing = $this->text_spacing + $style->word_spacing; $letter_spacing = $style->letter_spacing; $text_width = $fontMetrics->getTextWidth($text, $font, $size, $word_spacing, $letter_spacing); $style->set_used("width", $text_width); return $text_width; } // Text manipulation methods /** * Split the text in this frame at the offset specified. The remaining * text is added as a sibling frame following this one and is returned. * * @param int $offset * @return Frame|null */ function split_text($offset) { if ($offset == 0) { return null; } $split = $this->_frame->get_node()->splitText($offset); if ($split === false) { return null; } $deco = $this->copy($split); $p = $this->get_parent(); $p->insert_child_after($deco, $this, false); if ($p instanceof Inline) { $p->split($deco); } return $deco; } /** * @param int $offset * @param int $count */ function delete_text($offset, $count) { $this->_frame->get_node()->deleteData($offset, $count); } /** * @param string $text */ function set_text($text) { $this->_frame->get_node()->data = $text; } } dompdf/src/Css/Color.php 0000644 00000024712 15024772104 0011133 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use Dompdf\Helpers; class Color { static $cssColorNames = [ "aliceblue" => "F0F8FF", "antiquewhite" => "FAEBD7", "aqua" => "00FFFF", "aquamarine" => "7FFFD4", "azure" => "F0FFFF", "beige" => "F5F5DC", "bisque" => "FFE4C4", "black" => "000000", "blanchedalmond" => "FFEBCD", "blue" => "0000FF", "blueviolet" => "8A2BE2", "brown" => "A52A2A", "burlywood" => "DEB887", "cadetblue" => "5F9EA0", "chartreuse" => "7FFF00", "chocolate" => "D2691E", "coral" => "FF7F50", "cornflowerblue" => "6495ED", "cornsilk" => "FFF8DC", "crimson" => "DC143C", "cyan" => "00FFFF", "darkblue" => "00008B", "darkcyan" => "008B8B", "darkgoldenrod" => "B8860B", "darkgray" => "A9A9A9", "darkgreen" => "006400", "darkgrey" => "A9A9A9", "darkkhaki" => "BDB76B", "darkmagenta" => "8B008B", "darkolivegreen" => "556B2F", "darkorange" => "FF8C00", "darkorchid" => "9932CC", "darkred" => "8B0000", "darksalmon" => "E9967A", "darkseagreen" => "8FBC8F", "darkslateblue" => "483D8B", "darkslategray" => "2F4F4F", "darkslategrey" => "2F4F4F", "darkturquoise" => "00CED1", "darkviolet" => "9400D3", "deeppink" => "FF1493", "deepskyblue" => "00BFFF", "dimgray" => "696969", "dimgrey" => "696969", "dodgerblue" => "1E90FF", "firebrick" => "B22222", "floralwhite" => "FFFAF0", "forestgreen" => "228B22", "fuchsia" => "FF00FF", "gainsboro" => "DCDCDC", "ghostwhite" => "F8F8FF", "gold" => "FFD700", "goldenrod" => "DAA520", "gray" => "808080", "green" => "008000", "greenyellow" => "ADFF2F", "grey" => "808080", "honeydew" => "F0FFF0", "hotpink" => "FF69B4", "indianred" => "CD5C5C", "indigo" => "4B0082", "ivory" => "FFFFF0", "khaki" => "F0E68C", "lavender" => "E6E6FA", "lavenderblush" => "FFF0F5", "lawngreen" => "7CFC00", "lemonchiffon" => "FFFACD", "lightblue" => "ADD8E6", "lightcoral" => "F08080", "lightcyan" => "E0FFFF", "lightgoldenrodyellow" => "FAFAD2", "lightgray" => "D3D3D3", "lightgreen" => "90EE90", "lightgrey" => "D3D3D3", "lightpink" => "FFB6C1", "lightsalmon" => "FFA07A", "lightseagreen" => "20B2AA", "lightskyblue" => "87CEFA", "lightslategray" => "778899", "lightslategrey" => "778899", "lightsteelblue" => "B0C4DE", "lightyellow" => "FFFFE0", "lime" => "00FF00", "limegreen" => "32CD32", "linen" => "FAF0E6", "magenta" => "FF00FF", "maroon" => "800000", "mediumaquamarine" => "66CDAA", "mediumblue" => "0000CD", "mediumorchid" => "BA55D3", "mediumpurple" => "9370DB", "mediumseagreen" => "3CB371", "mediumslateblue" => "7B68EE", "mediumspringgreen" => "00FA9A", "mediumturquoise" => "48D1CC", "mediumvioletred" => "C71585", "midnightblue" => "191970", "mintcream" => "F5FFFA", "mistyrose" => "FFE4E1", "moccasin" => "FFE4B5", "navajowhite" => "FFDEAD", "navy" => "000080", "oldlace" => "FDF5E6", "olive" => "808000", "olivedrab" => "6B8E23", "orange" => "FFA500", "orangered" => "FF4500", "orchid" => "DA70D6", "palegoldenrod" => "EEE8AA", "palegreen" => "98FB98", "paleturquoise" => "AFEEEE", "palevioletred" => "DB7093", "papayawhip" => "FFEFD5", "peachpuff" => "FFDAB9", "peru" => "CD853F", "pink" => "FFC0CB", "plum" => "DDA0DD", "powderblue" => "B0E0E6", "purple" => "800080", "red" => "FF0000", "rosybrown" => "BC8F8F", "royalblue" => "4169E1", "saddlebrown" => "8B4513", "salmon" => "FA8072", "sandybrown" => "F4A460", "seagreen" => "2E8B57", "seashell" => "FFF5EE", "sienna" => "A0522D", "silver" => "C0C0C0", "skyblue" => "87CEEB", "slateblue" => "6A5ACD", "slategray" => "708090", "slategrey" => "708090", "snow" => "FFFAFA", "springgreen" => "00FF7F", "steelblue" => "4682B4", "tan" => "D2B48C", "teal" => "008080", "thistle" => "D8BFD8", "tomato" => "FF6347", "turquoise" => "40E0D0", "violet" => "EE82EE", "wheat" => "F5DEB3", "white" => "FFFFFF", "whitesmoke" => "F5F5F5", "yellow" => "FFFF00", "yellowgreen" => "9ACD32", ]; /** * @param array|string|null $color * @return array|string|null */ static function parse($color) { if ($color === null) { return null; } if (is_array($color)) { // Assume the array has the right format... // FIXME: should/could verify this. return $color; } static $cache = []; $color = strtolower($color); if (isset($cache[$color])) { return $cache[$color]; } if ($color === "transparent") { return $cache[$color] = $color; } if (isset(self::$cssColorNames[$color])) { return $cache[$color] = self::getArray(self::$cssColorNames[$color]); } // https://www.w3.org/TR/css-color-4/#hex-notation if (mb_substr($color, 0, 1) === "#") { $length = mb_strlen($color); $alpha = 1.0; // #rgb format if ($length === 4) { return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]); } // #rgba format if ($length === 5) { if (ctype_xdigit($color[4])) { $alpha = round(hexdec($color[4] . $color[4])/255, 2); } return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3], $alpha); } // #rrggbb format if ($length === 7) { return $cache[$color] = self::getArray(mb_substr($color, 1, 6)); } // #rrggbbaa format if ($length === 9) { if (ctype_xdigit(mb_substr($color, 7, 2))) { $alpha = round(hexdec(mb_substr($color, 7, 2))/255, 2); } return $cache[$color] = self::getArray(mb_substr($color, 1, 6), $alpha); } return null; } // rgb( r g b [/α] ) / rgb( r,g,b[,α] ) format and alias rgba() // https://www.w3.org/TR/css-color-4/#rgb-functions if (mb_substr($color, 0, 4) === "rgb(" || mb_substr($color, 0, 5) === "rgba(") { $i = mb_strpos($color, "("); $j = mb_strpos($color, ")"); // Bad color value if ($i === false || $j === false) { return null; } $value_decl = trim(mb_substr($color, $i + 1, $j - $i - 1)); if (mb_strpos($value_decl, ",") === false) { // Space-separated values syntax `r g b` or `r g b / α` $parts = preg_split("/\s*\/\s*/", $value_decl); $triplet = preg_split("/\s+/", $parts[0]); $alpha = $parts[1] ?? 1.0; } else { // Comma-separated values syntax `r, g, b` or `r, g, b, α` $parts = preg_split("/\s*,\s*/", $value_decl); $triplet = array_slice($parts, 0, 3); $alpha = $parts[3] ?? 1.0; } if (count($triplet) !== 3) { return null; } // Parse alpha value if (Helpers::is_percent($alpha)) { $alpha = (float) $alpha / 100; } else { $alpha = (float) $alpha; } $alpha = max(0.0, min($alpha, 1.0)); foreach ($triplet as &$c) { if (Helpers::is_percent($c)) { $c = round((float) $c * 2.55); } } return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet), $alpha); } // cmyk( c,m,y,k ) format // http://www.w3.org/TR/css3-gcpm/#cmyk-colors if (mb_substr($color, 0, 5) === "cmyk(") { $i = mb_strpos($color, "("); $j = mb_strpos($color, ")"); // Bad color value if ($i === false || $j === false) { return null; } $values = explode(",", mb_substr($color, $i + 1, $j - $i - 1)); if (count($values) != 4) { return null; } $values = array_map(function ($c) { return min(1.0, max(0.0, floatval(trim($c)))); }, $values); return $cache[$color] = self::getArray($values); } // Invalid or unsupported color format return null; } /** * @param array|string $color * @param float $alpha * @return array */ static function getArray($color, $alpha = 1.0) { $c = [null, null, null, null, "alpha" => $alpha, "hex" => null]; if (is_array($color)) { $c = $color; $c["c"] = $c[0]; $c["m"] = $c[1]; $c["y"] = $c[2]; $c["k"] = $c[3]; $c["alpha"] = $alpha; $c["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])"; } else { if (ctype_xdigit($color) === false || mb_strlen($color) !== 6) { // invalid color value ... expected 6-character hex return $c; } $c[0] = hexdec(mb_substr($color, 0, 2)) / 0xff; $c[1] = hexdec(mb_substr($color, 2, 2)) / 0xff; $c[2] = hexdec(mb_substr($color, 4, 2)) / 0xff; $c["r"] = $c[0]; $c["g"] = $c[1]; $c["b"] = $c[2]; $c["alpha"] = $alpha; $c["hex"] = sprintf("#%s%02X", $color, round($alpha * 255)); } return $c; } } dompdf/src/Css/Style.php 0000644 00000340370 15024772104 0011156 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use Dompdf\Adapter\CPDF; use Dompdf\Exception; use Dompdf\FontMetrics; use Dompdf\Frame; /** * Represents CSS properties. * * The Style class is responsible for handling and storing CSS properties. * It includes methods to resolve colors and lengths, as well as getters & * setters for many CSS properties. * * Access to the different CSS properties is provided by the methods * {@link Style::set_prop()} and {@link Style::get_specified()}, and the * property overload methods {@link Style::__set()} and {@link Style::__get()}, * as well as {@link Style::set_used()}. The latter methods operate on used * values and permit access to any (CSS) property using the following syntax: * * ``` * $style->margin_top = 10.0; * echo $style->margin_top; // Returns `10.0` * ``` * * To declare a property from a string, use {@link Style::set_prop()}: * * ``` * $style->set_prop("margin_top", "1em"); * echo $style->get_specified("margin_top"); // Returns `1em` * echo $style->margin_top; // Returns `12.0`, assuming the default font size * ``` * * Actual CSS parsing is performed in the {@link Stylesheet} class. * * @property string $azimuth * @property string $background_attachment * @property array|string $background_color * @property string $background_image Image URL or `none` * @property string $background_image_resolution * @property array $background_position * @property string $background_repeat * @property array|string $background_size `cover`, `contain`, or `[width, height]`, each being a length, percentage, or `auto` * @property string $border_collapse * @property string $border_color Only use for setting all sides to the same color * @property float[] $border_spacing Pair of `[horizontal, vertical]` spacing * @property string $border_style Only use for setting all sides to the same style * @property array|string $border_top_color * @property array|string $border_right_color * @property array|string $border_bottom_color * @property array|string $border_left_color * @property string $border_top_style Valid border style * @property string $border_right_style Valid border style * @property string $border_bottom_style Valid border style * @property string $border_left_style Valid border style * @property float $border_top_width Length in pt * @property float $border_right_width Length in pt * @property float $border_bottom_width Length in pt * @property float $border_left_width Length in pt * @property string $border_width Only use for setting all sides to the same width * @property float|string $border_bottom_left_radius Radius in pt or a percentage value * @property float|string $border_bottom_right_radius Radius in pt or a percentage value * @property float|string $border_top_left_radius Radius in pt or a percentage value * @property float|string $border_top_right_radius Radius in pt or a percentage value * @property string $border_radius Only use for setting all corners to the same radius * @property float|string $bottom Length in pt, a percentage value, or `auto` * @property string $caption_side * @property string $clear * @property string $clip * @property array|string $color * @property string[]|string $content List of content components, `normal`, or `none` * @property array|string $counter_increment Array defining the counters to increment or `none` * @property array|string $counter_reset Array defining the counters to reset or `none` * @property string $cue_after * @property string $cue_before * @property string $cue * @property string $cursor * @property string $direction * @property string $display * @property string $elevation * @property string $empty_cells * @property string $float * @property string $font_family * @property float $font_size Length in pt * @property string $font_style * @property string $font_variant * @property string $font_weight * @property float|string $height Length in pt, a percentage value, or `auto` * @property string $image_resolution * @property string $inset Only use for setting all box insets to the same length * @property float|string $left Length in pt, a percentage value, or `auto` * @property float $letter_spacing Length in pt * @property float $line_height Length in pt * @property string $list_style_image Image URL or `none` * @property string $list_style_position * @property string $list_style_type * @property float|string $margin_right Length in pt, a percentage value, or `auto` * @property float|string $margin_left Length in pt, a percentage value, or `auto` * @property float|string $margin_top Length in pt, a percentage value, or `auto` * @property float|string $margin_bottom Length in pt, a percentage value, or `auto` * @property string $margin Only use for setting all sides to the same length * @property float|string $max_height Length in pt, a percentage value, or `none` * @property float|string $max_width Length in pt, a percentage value, or `none` * @property float|string $min_height Length in pt, a percentage value, or `auto` * @property float|string $min_width Length in pt, a percentage value, or `auto` * @property float $opacity Number in the range [0, 1] * @property int $orphans * @property array|string $outline_color * @property string $outline_style Valid border style, except for `hidden` * @property float $outline_width Length in pt * @property float $outline_offset Length in pt * @property string $overflow * @property string $overflow_wrap * @property float|string $padding_top Length in pt or a percentage value * @property float|string $padding_right Length in pt or a percentage value * @property float|string $padding_bottom Length in pt or a percentage value * @property float|string $padding_left Length in pt or a percentage value * @property string $padding Only use for setting all sides to the same length * @property string $page_break_after * @property string $page_break_before * @property string $page_break_inside * @property string $pause_after * @property string $pause_before * @property string $pause * @property string $pitch_range * @property string $pitch * @property string $play_during * @property string $position * @property string $quotes * @property string $richness * @property float|string $right Length in pt, a percentage value, or `auto` * @property float[]|string $size Pair of `[width, height]` or `auto` * @property string $speak_header * @property string $speak_numeral * @property string $speak_punctuation * @property string $speak * @property string $speech_rate * @property string $src * @property string $stress * @property string $table_layout * @property string $text_align * @property string $text_decoration * @property float|string $text_indent Length in pt or a percentage value * @property string $text_transform * @property float|string $top Length in pt, a percentage value, or `auto` * @property array $transform List of transforms * @property array $transform_origin * @property string $unicode_bidi * @property string $unicode_range * @property string $vertical_align * @property string $visibility * @property string $voice_family * @property string $volume * @property string $white_space * @property int $widows * @property float|string $width Length in pt, a percentage value, or `auto` * @property string $word_break * @property float $word_spacing Length in pt * @property int|string $z_index Integer value or `auto` * @property string $_dompdf_keep * * @package dompdf */ class Style { protected const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*"; protected const CSS_INTEGER = "[+-]?\d+"; protected const CSS_NUMBER = "[+-]?\d*\.?\d+(?:[eE][+-]?\d+)?"; /** * Default font size, in points. * * @var float */ public static $default_font_size = 12; /** * Default line height, as a fraction of the font size. * * @var float */ public static $default_line_height = 1.2; /** * Default "absolute" font sizes relative to the default font-size * https://www.w3.org/TR/css-fonts-3/#absolute-size-value * * @var array<float> */ public static $font_size_keywords = [ "xx-small" => 0.6, // 3/5 "x-small" => 0.75, // 3/4 "small" => 0.889, // 8/9 "medium" => 1, // 1 "large" => 1.2, // 6/5 "x-large" => 1.5, // 3/2 "xx-large" => 2.0, // 2/1 ]; /** * List of valid text-align keywords. */ public const TEXT_ALIGN_KEYWORDS = ["left", "right", "center", "justify"]; /** * List of valid vertical-align keywords. */ public const VERTICAL_ALIGN_KEYWORDS = ["baseline", "bottom", "middle", "sub", "super", "text-bottom", "text-top", "top"]; /** * List of all block-level (outer) display types. * * https://www.w3.org/TR/css-display-3/#display-type * * https://www.w3.org/TR/css-display-3/#block-level */ public const BLOCK_LEVEL_TYPES = [ "block", // "flow-root", "list-item", // "flex", // "grid", "table" ]; /** * List of all inline-level (outer) display types. * * https://www.w3.org/TR/css-display-3/#display-type * * https://www.w3.org/TR/css-display-3/#inline-level */ public const INLINE_LEVEL_TYPES = [ "inline", "inline-block", // "inline-flex", // "inline-grid", "inline-table" ]; /** * List of all table-internal (outer) display types. * * https://www.w3.org/TR/css-display-3/#layout-specific-display */ public const TABLE_INTERNAL_TYPES = [ "table-row-group", "table-header-group", "table-footer-group", "table-row", "table-cell", "table-column-group", "table-column", "table-caption" ]; /** * List of all inline (inner) display types. */ public const INLINE_TYPES = ["inline"]; /** * List of all block (inner) display types. */ public const BLOCK_TYPES = ["block", "inline-block", "table-cell", "list-item"]; /** * List of all table (inner) display types. */ public const TABLE_TYPES = ["table", "inline-table"]; /** * Lookup table for valid display types. Initially computed from the * different constants. * * @var array */ protected static $valid_display_types = []; /** * List of all positioned types. */ public const POSITIONED_TYPES = ["relative", "absolute", "fixed"]; /** * List of valid border styles. */ public const BORDER_STYLES = [ "none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset" ]; /** * List of valid outline-style values. * Same as the border styles, except `auto` is allowed, `hidden` is not. * * @link https://www.w3.org/TR/css-ui-4/#typedef-outline-line-style */ protected const OUTLINE_STYLES = [ "auto", "none", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset" ]; /** * Map of CSS shorthand properties and their corresponding sub-properties. * The order of the sub-properties is relevant for the fallback getter, * which is used in case no specific getter method is defined. * * @var array */ protected static $_props_shorthand = [ "background" => [ "background_image", "background_position", "background_size", "background_repeat", // "background_origin", // "background_clip", "background_attachment", "background_color" ], "border" => [ "border_top_width", "border_right_width", "border_bottom_width", "border_left_width", "border_top_style", "border_right_style", "border_bottom_style", "border_left_style", "border_top_color", "border_right_color", "border_bottom_color", "border_left_color" ], "border_top" => [ "border_top_width", "border_top_style", "border_top_color" ], "border_right" => [ "border_right_width", "border_right_style", "border_right_color" ], "border_bottom" => [ "border_bottom_width", "border_bottom_style", "border_bottom_color" ], "border_left" => [ "border_left_width", "border_left_style", "border_left_color" ], "border_width" => [ "border_top_width", "border_right_width", "border_bottom_width", "border_left_width" ], "border_style" => [ "border_top_style", "border_right_style", "border_bottom_style", "border_left_style" ], "border_color" => [ "border_top_color", "border_right_color", "border_bottom_color", "border_left_color" ], "border_radius" => [ "border_top_left_radius", "border_top_right_radius", "border_bottom_right_radius", "border_bottom_left_radius" ], "font" => [ "font_family", "font_size", // "font_stretch", "font_style", "font_variant", "font_weight", "line_height" ], "inset" => [ "top", "right", "bottom", "left" ], "list_style" => [ "list_style_image", "list_style_position", "list_style_type" ], "margin" => [ "margin_top", "margin_right", "margin_bottom", "margin_left" ], "padding" => [ "padding_top", "padding_right", "padding_bottom", "padding_left" ], "outline" => [ "outline_width", "outline_style", "outline_color" ] ]; /** * Maps legacy property names to actual property names. * * @var array */ protected static $_props_alias = [ "word_wrap" => "overflow_wrap", "_dompdf_background_image_resolution" => "background_image_resolution", "_dompdf_image_resolution" => "image_resolution", "_webkit_transform" => "transform", "_webkit_transform_origin" => "transform_origin" ]; /** * Default style values. * * @link https://www.w3.org/TR/CSS21/propidx.html * * @var array */ protected static $_defaults = null; /** * List of inherited properties * * @link https://www.w3.org/TR/CSS21/propidx.html * * @var array */ protected static $_inherited = null; /** * Caches method_exists result * * @var array<bool> */ protected static $_methods_cache = []; /** * The stylesheet this style belongs to * * @var Stylesheet */ protected $_stylesheet; /** * Media queries attached to the style * * @var array */ protected $_media_queries; /** * Properties set by an `!important` declaration. * * @var array */ protected $_important_props = []; /** * Specified (or declared) values of the CSS properties. * * https://www.w3.org/TR/css-cascade-3/#value-stages * * @var array */ protected $_props = []; /** * Computed values of the CSS properties. * * @var array */ protected $_props_computed = []; /** * Used values of the CSS properties. * * @var array */ protected $_props_used = []; /** * Marks properties with non-final used values that should be cleared on * style reset. * * @var array */ protected $non_final_used = []; protected static $_dependency_map = [ "border_top_style" => [ "border_top_width" ], "border_bottom_style" => [ "border_bottom_width" ], "border_left_style" => [ "border_left_width" ], "border_right_style" => [ "border_right_width" ], "direction" => [ "text_align" ], "font_size" => [ "background_position", "background_size", "border_top_width", "border_right_width", "border_bottom_width", "border_left_width", "border_top_left_radius", "border_top_right_radius", "border_bottom_right_radius", "border_bottom_left_radius", "letter_spacing", "line_height", "margin_top", "margin_right", "margin_bottom", "margin_left", "outline_width", "outline_offset", "padding_top", "padding_right", "padding_bottom", "padding_left", "word_spacing", "width", "height", "min-width", "min-height", "max-width", "max-height" ], "float" => [ "display" ], "position" => [ "display" ], "outline_style" => [ "outline_width" ] ]; /** * Lookup table for dependent properties. Initially computed from the * dependency map. * * @var array */ protected static $_dependent_props = []; /** * Style of the parent element in document tree. * * @var Style */ protected $parent_style; /** * @var Frame|null */ protected $_frame; /** * The origin of the style * * @var int */ protected $_origin = Stylesheet::ORIG_AUTHOR; /** * The computed bottom spacing * * @var float|string|null */ private $_computed_bottom_spacing = null; /** * @var bool|null */ private $has_border_radius_cache = null; /** * @var array|null */ private $resolved_border_radius = null; /** * @var FontMetrics */ private $fontMetrics; /** * @param Stylesheet $stylesheet The stylesheet the style is associated with. * @param int $origin */ public function __construct(Stylesheet $stylesheet, int $origin = Stylesheet::ORIG_AUTHOR) { $this->fontMetrics = $stylesheet->getFontMetrics(); $this->_stylesheet = $stylesheet; $this->_media_queries = []; $this->_origin = $origin; $this->parent_style = null; if (!isset(self::$_defaults)) { // Shorthand $d =& self::$_defaults; // All CSS 2.1 properties, and their default values // Some properties are specified with their computed value for // efficiency; this only works if the computed value is not // dependent on another property $d["azimuth"] = "center"; $d["background_attachment"] = "scroll"; $d["background_color"] = "transparent"; $d["background_image"] = "none"; $d["background_image_resolution"] = "normal"; $d["background_position"] = ["0%", "0%"]; $d["background_repeat"] = "repeat"; $d["background"] = ""; $d["border_collapse"] = "separate"; $d["border_color"] = ""; $d["border_spacing"] = [0.0, 0.0]; $d["border_style"] = ""; $d["border_top"] = ""; $d["border_right"] = ""; $d["border_bottom"] = ""; $d["border_left"] = ""; $d["border_top_color"] = "currentcolor"; $d["border_right_color"] = "currentcolor"; $d["border_bottom_color"] = "currentcolor"; $d["border_left_color"] = "currentcolor"; $d["border_top_style"] = "none"; $d["border_right_style"] = "none"; $d["border_bottom_style"] = "none"; $d["border_left_style"] = "none"; $d["border_top_width"] = "medium"; $d["border_right_width"] = "medium"; $d["border_bottom_width"] = "medium"; $d["border_left_width"] = "medium"; $d["border_width"] = ""; $d["border_bottom_left_radius"] = 0.0; $d["border_bottom_right_radius"] = 0.0; $d["border_top_left_radius"] = 0.0; $d["border_top_right_radius"] = 0.0; $d["border_radius"] = ""; $d["border"] = ""; $d["bottom"] = "auto"; $d["caption_side"] = "top"; $d["clear"] = "none"; $d["clip"] = "auto"; $d["color"] = "#000000"; $d["content"] = "normal"; $d["counter_increment"] = "none"; $d["counter_reset"] = "none"; $d["cue_after"] = "none"; $d["cue_before"] = "none"; $d["cue"] = ""; $d["cursor"] = "auto"; $d["direction"] = "ltr"; $d["display"] = "inline"; $d["elevation"] = "level"; $d["empty_cells"] = "show"; $d["float"] = "none"; $d["font_family"] = $stylesheet->get_dompdf()->getOptions()->getDefaultFont(); $d["font_size"] = "medium"; $d["font_style"] = "normal"; $d["font_variant"] = "normal"; $d["font_weight"] = "normal"; $d["font"] = ""; $d["height"] = "auto"; $d["image_resolution"] = "normal"; $d["inset"] = ""; $d["left"] = "auto"; $d["letter_spacing"] = "normal"; $d["line_height"] = "normal"; $d["list_style_image"] = "none"; $d["list_style_position"] = "outside"; $d["list_style_type"] = "disc"; $d["list_style"] = ""; $d["margin_right"] = 0.0; $d["margin_left"] = 0.0; $d["margin_top"] = 0.0; $d["margin_bottom"] = 0.0; $d["margin"] = ""; $d["max_height"] = "none"; $d["max_width"] = "none"; $d["min_height"] = "auto"; $d["min_width"] = "auto"; $d["orphans"] = 2; $d["outline_color"] = "currentcolor"; // "invert" special color is not supported $d["outline_style"] = "none"; $d["outline_width"] = "medium"; $d["outline_offset"] = 0.0; $d["outline"] = ""; $d["overflow"] = "visible"; $d["overflow_wrap"] = "normal"; $d["padding_top"] = 0.0; $d["padding_right"] = 0.0; $d["padding_bottom"] = 0.0; $d["padding_left"] = 0.0; $d["padding"] = ""; $d["page_break_after"] = "auto"; $d["page_break_before"] = "auto"; $d["page_break_inside"] = "auto"; $d["pause_after"] = "0"; $d["pause_before"] = "0"; $d["pause"] = ""; $d["pitch_range"] = "50"; $d["pitch"] = "medium"; $d["play_during"] = "auto"; $d["position"] = "static"; $d["quotes"] = "auto"; $d["richness"] = "50"; $d["right"] = "auto"; $d["size"] = "auto"; // @page $d["speak_header"] = "once"; $d["speak_numeral"] = "continuous"; $d["speak_punctuation"] = "none"; $d["speak"] = "normal"; $d["speech_rate"] = "medium"; $d["stress"] = "50"; $d["table_layout"] = "auto"; $d["text_align"] = ""; $d["text_decoration"] = "none"; $d["text_indent"] = 0.0; $d["text_transform"] = "none"; $d["top"] = "auto"; $d["unicode_bidi"] = "normal"; $d["vertical_align"] = "baseline"; $d["visibility"] = "visible"; $d["voice_family"] = ""; $d["volume"] = "medium"; $d["white_space"] = "normal"; $d["widows"] = 2; $d["width"] = "auto"; $d["word_break"] = "normal"; $d["word_spacing"] = "normal"; $d["z_index"] = "auto"; // CSS3 $d["opacity"] = 1.0; $d["background_size"] = ["auto", "auto"]; $d["transform"] = "none"; $d["transform_origin"] = "50% 50%"; // for @font-face $d["src"] = ""; $d["unicode_range"] = ""; // vendor-prefixed properties $d["_dompdf_keep"] = ""; // Properties that inherit by default self::$_inherited = [ "azimuth", "background_image_resolution", "border_collapse", "border_spacing", "caption_side", "color", "cursor", "direction", "elevation", "empty_cells", "font_family", "font_size", "font_style", "font_variant", "font_weight", "font", "image_resolution", "letter_spacing", "line_height", "list_style_image", "list_style_position", "list_style_type", "list_style", "orphans", "overflow_wrap", "pitch_range", "pitch", "quotes", "richness", "speak_header", "speak_numeral", "speak_punctuation", "speak", "speech_rate", "stress", "text_align", "text_indent", "text_transform", "visibility", "voice_family", "volume", "white_space", "widows", "word_break", "word_spacing", ]; // Compute dependent props from dependency map foreach (self::$_dependency_map as $props) { foreach ($props as $prop) { self::$_dependent_props[$prop] = true; } } // Compute valid display-type lookup table self::$valid_display_types = [ "none" => true, "-dompdf-br" => true, "-dompdf-image" => true, "-dompdf-list-bullet" => true, "-dompdf-page" => true ]; foreach (self::BLOCK_LEVEL_TYPES as $val) { self::$valid_display_types[$val] = true; } foreach (self::INLINE_LEVEL_TYPES as $val) { self::$valid_display_types[$val] = true; } foreach (self::TABLE_INTERNAL_TYPES as $val) { self::$valid_display_types[$val] = true; } } } /** * Clear all non-final used values. * * @return void */ public function reset(): void { foreach (array_keys($this->non_final_used) as $prop) { unset($this->_props_used[$prop]); } $this->non_final_used = []; } /** * @param array $media_queries */ public function set_media_queries(array $media_queries): void { $this->_media_queries = $media_queries; } /** * @return array */ public function get_media_queries(): array { return $this->_media_queries; } /** * @param Frame $frame */ public function set_frame(Frame $frame): void { $this->_frame = $frame; } /** * @return Frame|null */ public function get_frame(): ?Frame { return $this->_frame; } /** * @param int $origin */ public function set_origin(int $origin): void { $this->_origin = $origin; } /** * @return int */ public function get_origin(): int { return $this->_origin; } /** * Returns the {@link Stylesheet} the style is associated with. * * @return Stylesheet */ public function get_stylesheet(): Stylesheet { return $this->_stylesheet; } public function is_absolute(): bool { $position = $this->__get("position"); return $position === "absolute" || $position === "fixed"; } public function is_in_flow(): bool { $float = $this->__get("float"); return $float === "none" && !$this->is_absolute(); } /** * Converts any CSS length value into an absolute length in points. * * length_in_pt() takes a single length (e.g. '1em') or an array of * lengths and returns an absolute length. If an array is passed, then * the return value is the sum of all elements. If any of the lengths * provided are "auto" or "none" then that value is returned. * * If a reference size is not provided, the current font size is used. * * @param float|string|array $length The numeric length (or string measurement) or array of lengths to resolve. * @param float|null $ref_size An absolute reference size to resolve percentage lengths. * * @return float|string */ public function length_in_pt($length, ?float $ref_size = null) { $font_size = $this->__get("font_size"); $ref_size = $ref_size ?? $font_size; if (!\is_array($length)) { $length = [$length]; } $ret = 0.0; foreach ($length as $l) { if ($l === "auto" || $l === "none") { return $l; } // Assume numeric values are already in points if (is_numeric($l)) { $ret += (float) $l; continue; } $val = $this->single_length_in_pt((string) $l, $ref_size, $font_size); $ret += $val ?? 0; } return $ret; } /** * Convert a length declaration to pt. * * @param string $l The length declaration. * @param float $ref_size Reference size for percentage declarations. * @param float|null $font_size Font size for resolving font-size relative units. * * @return float|null The length in pt, or `null` for invalid declarations. */ protected function single_length_in_pt(string $l, float $ref_size = 0, ?float $font_size = null): ?float { static $cache = []; $font_size = $font_size ?? $this->__get("font_size"); $key = "$l/$ref_size/$font_size"; if (\array_key_exists($key, $cache)) { return $cache[$key]; } $number = self::CSS_NUMBER; $pattern = "/^($number)(.*)?$/"; if (!preg_match($pattern, $l, $matches)) { return null; } $v = (float) $matches[1]; $unit = mb_strtolower($matches[2]); if ($unit === "") { // Legacy support for unitless values, not covered by spec. Might // want to restrict this to unitless `0` in the future $value = $v; } elseif ($unit === "%") { $value = $v / 100 * $ref_size; } elseif ($unit === "px") { $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi(); $value = ($v * 72) / $dpi; } elseif ($unit === "pt") { $value = $v; } elseif ($unit === "rem") { $tree = $this->_stylesheet->get_dompdf()->getTree(); $root_style = $tree !== null ? $tree->get_root()->get_style() : null; $root_font_size = $root_style === null || $root_style === $this ? $font_size : $root_style->__get("font_size"); $value = $v * $root_font_size; // Skip caching if the root style is not available yet, as to avoid // incorrectly cached values if the root font size is different from // the default if ($root_style === null) { return $value; } } elseif ($unit === "em") { $value = $v * $font_size; } elseif ($unit === "cm") { $value = $v * 72 / 2.54; } elseif ($unit === "mm") { $value = $v * 72 / 25.4; } elseif ($unit === "ex") { // FIXME: em:ex ratio? $value = $v * $font_size / 2; } elseif ($unit === "in") { $value = $v * 72; } elseif ($unit === "pc") { $value = $v * 12; } else { // Invalid or unsupported declaration $value = null; } return $cache[$key] = $value; } /** * Resolve inherited property values using the provided parent style or the * default values, in case no parent style exists. * * https://www.w3.org/TR/css-cascade-3/#inheriting * * @param Style|null $parent */ public function inherit(?Style $parent = null): void { $this->parent_style = $parent; // Clear the computed font size, as it might depend on the parent // font size unset($this->_props_computed["font_size"]); unset($this->_props_used["font_size"]); if ($parent) { foreach (self::$_inherited as $prop) { // For properties that inherit by default: When the cascade did // not result in a value, inherit the parent value. Inheritance // is handled via the specific sub-properties for shorthands if (isset($this->_props[$prop]) || isset(self::$_props_shorthand[$prop])) { continue; } if (isset($parent->_props[$prop])) { $parent_val = $parent->computed($prop); $this->_props[$prop] = $parent_val; $this->_props_computed[$prop] = $parent_val; $this->_props_used[$prop] = null; } } } foreach ($this->_props as $prop => $val) { if ($val === "inherit") { if ($parent && isset($parent->_props[$prop])) { $parent_val = $parent->computed($prop); $this->_props[$prop] = $parent_val; $this->_props_computed[$prop] = $parent_val; $this->_props_used[$prop] = null; } else { // Parent prop not set, use default $this->_props[$prop] = self::$_defaults[$prop]; unset($this->_props_computed[$prop]); unset($this->_props_used[$prop]); } } } } /** * Override properties in this style with those in $style * * @param Style $style */ public function merge(Style $style): void { foreach ($style->_props as $prop => $val) { $important = isset($style->_important_props[$prop]); // `!important` declarations take precedence over normal ones if (!$important && isset($this->_important_props[$prop])) { continue; } if ($important) { $this->_important_props[$prop] = true; } $this->_props[$prop] = $val; // Copy an existing computed value only for non-dependent // properties; otherwise it may be invalid for the current style if (!isset(self::$_dependent_props[$prop]) && \array_key_exists($prop, $style->_props_computed) ) { $this->_props_computed[$prop] = $style->_props_computed[$prop]; $this->_props_used[$prop] = null; } else { unset($this->_props_computed[$prop]); unset($this->_props_used[$prop]); } } } /** * Clear information about important declarations after the style has been * finalized during stylesheet loading. */ public function clear_important(): void { $this->_important_props = []; } /** * Clear border-radius and bottom-spacing cache as necessary when a given * property is set. * * @param string $prop The property that is set. */ protected function clear_cache(string $prop): void { // Clear border-radius cache on setting any border-radius // property if ($prop === "border_top_left_radius" || $prop === "border_top_right_radius" || $prop === "border_bottom_left_radius" || $prop === "border_bottom_right_radius" ) { $this->has_border_radius_cache = null; $this->resolved_border_radius = null; } // Clear bottom-spacing cache if necessary. Border style can // disable/enable border calculations if ($prop === "margin_bottom" || $prop === "padding_bottom" || $prop === "border_bottom_width" || $prop === "border_bottom_style" ) { $this->_computed_bottom_spacing = null; } } /** * Set a style property from a value declaration. * * Setting `$clear_dependencies` to `false` is useful for saving a bit of * unnecessary work while loading stylesheets. * * @param string $prop The property to set. * @param mixed $val The value declaration or computed value. * @param bool $important Whether the declaration is important. * @param bool $clear_dependencies Whether to clear computed values of dependent properties. */ public function set_prop(string $prop, $val, bool $important = false, bool $clear_dependencies = true): void { $prop = str_replace("-", "_", $prop); // Legacy property aliases if (isset(self::$_props_alias[$prop])) { $prop = self::$_props_alias[$prop]; } if (!isset(self::$_defaults[$prop])) { global $_dompdf_warnings; $_dompdf_warnings[] = "'$prop' is not a recognized CSS property."; return; } if ($prop !== "content" && \is_string($val) && mb_strpos($val, "url") === false && mb_strlen($val) > 1) { $val = mb_strtolower(trim(str_replace(["\n", "\t"], [" "], $val))); } if (isset(self::$_props_shorthand[$prop])) { // Shorthand properties directly set their respective sub-properties // https://www.w3.org/TR/css-cascade-3/#shorthand if ($val === "initial" || $val === "inherit" || $val === "unset") { foreach (self::$_props_shorthand[$prop] as $sub_prop) { $this->set_prop($sub_prop, $val, $important, $clear_dependencies); } } else { $method = "_set_$prop"; if (!isset(self::$_methods_cache[$method])) { self::$_methods_cache[$method] = method_exists($this, $method); } if (self::$_methods_cache[$method]) { $values = $this->$method($val); if ($values === []) { return; } // Each missing sub-property is assigned its initial value // https://www.w3.org/TR/css-cascade-3/#shorthand foreach (self::$_props_shorthand[$prop] as $sub_prop) { $sub_val = $values[$sub_prop] ?? self::$_defaults[$sub_prop]; $this->set_prop($sub_prop, $sub_val, $important, $clear_dependencies); } } } } else { // Legacy support for `word-break: break-word` // https://www.w3.org/TR/css-text-3/#valdef-word-break-break-word if ($prop === "word_break" && $val === "break-word") { $val = "normal"; $this->set_prop("overflow_wrap", "anywhere", $important, $clear_dependencies); } // `!important` declarations take precedence over normal ones if (!$important && isset($this->_important_props[$prop])) { return; } if ($important) { $this->_important_props[$prop] = true; } // https://www.w3.org/TR/css-cascade-3/#inherit-initial if ($val === "unset") { $val = \in_array($prop, self::$_inherited, true) ? "inherit" : "initial"; } // https://www.w3.org/TR/css-cascade-3/#valdef-all-initial if ($val === "initial") { $val = self::$_defaults[$prop]; } $computed = $this->compute_prop($prop, $val); // Skip invalid declarations if ($computed === null) { return; } $this->_props[$prop] = $val; $this->_props_computed[$prop] = $computed; $this->_props_used[$prop] = null; if ($clear_dependencies) { // Clear the computed values of any dependent properties, so // they can be re-computed if (isset(self::$_dependency_map[$prop])) { foreach (self::$_dependency_map[$prop] as $dependent) { unset($this->_props_computed[$dependent]); unset($this->_props_used[$dependent]); } } $this->clear_cache($prop); } } } /** * Get the specified value of a style property. * * @param string $prop * * @return mixed * @throws Exception */ public function get_specified(string $prop) { // Legacy property aliases if (isset(self::$_props_alias[$prop])) { $prop = self::$_props_alias[$prop]; } if (!isset(self::$_defaults[$prop])) { throw new Exception("'$prop' is not a recognized CSS property."); } return $this->_props[$prop] ?? self::$_defaults[$prop]; } /** * Set a style property to its final value. * * This sets the specified and used value of the style property to the given * value, meaning the value is not parsed and thus should have a type * compatible with the property. * * If a shorthand property is specified, all of its sub-properties are set * to the given value. * * @param string $prop The property to set. * @param mixed $val The final value of the property. * * @throws Exception */ public function __set(string $prop, $val) { // Legacy property aliases if (isset(self::$_props_alias[$prop])) { $prop = self::$_props_alias[$prop]; } if (!isset(self::$_defaults[$prop])) { throw new Exception("'$prop' is not a recognized CSS property."); } if (isset(self::$_props_shorthand[$prop])) { foreach (self::$_props_shorthand[$prop] as $sub_prop) { $this->__set($sub_prop, $val); } } else { $this->_props[$prop] = $val; $this->_props_computed[$prop] = $val; $this->_props_used[$prop] = $val; $this->clear_cache($prop); } } /** * Set the used value of a style property. * * Used values are cleared on style reset. * * If a shorthand property is specified, all of its sub-properties are set * to the given value. * * @param string $prop The property to set. * @param mixed $val The used value of the property. * * @throws Exception */ public function set_used(string $prop, $val): void { // Legacy property aliases if (isset(self::$_props_alias[$prop])) { $prop = self::$_props_alias[$prop]; } if (!isset(self::$_defaults[$prop])) { throw new Exception("'$prop' is not a recognized CSS property."); } if (isset(self::$_props_shorthand[$prop])) { foreach (self::$_props_shorthand[$prop] as $sub_prop) { $this->set_used($sub_prop, $val); } } else { $this->_props_used[$prop] = $val; $this->non_final_used[$prop] = true; } } /** * Get the used or computed value of a style property, depending on whether * the used value has been determined yet. * * @param string $prop * * @return mixed * @throws Exception */ public function __get(string $prop) { // Legacy property aliases if (isset(self::$_props_alias[$prop])) { $prop = self::$_props_alias[$prop]; } if (!isset(self::$_defaults[$prop])) { throw new Exception("'$prop' is not a recognized CSS property."); } if (isset($this->_props_used[$prop])) { return $this->_props_used[$prop]; } $method = "_get_$prop"; if (!isset(self::$_methods_cache[$method])) { self::$_methods_cache[$method] = method_exists($this, $method); } if (isset(self::$_props_shorthand[$prop])) { // Don't cache shorthand values, always use getter. If no dedicated // getter exists, use a simple fallback getter concatenating all // sub-property values if (self::$_methods_cache[$method]) { return $this->$method(); } else { return implode(" ", array_map(function ($sub_prop) { $val = $this->__get($sub_prop); return \is_array($val) ? implode(" ", $val) : $val; }, self::$_props_shorthand[$prop])); } } else { $computed = $this->computed($prop); $used = self::$_methods_cache[$method] ? $this->$method($computed) : $computed; $this->_props_used[$prop] = $used; return $used; } } /** * @param string $prop The property to compute. * @param mixed $val The value to compute. Non-string values are treated as already computed. * * @return mixed The computed value. */ protected function compute_prop(string $prop, $val) { // During style merge, the parent style is not available yet, so // temporarily use the initial value for `inherit` properties. The // keyword is properly resolved during inheritance if ($val === "inherit") { $val = self::$_defaults[$prop]; } // Check for values which are already computed if (!\is_string($val)) { return $val; } $method = "_compute_$prop"; if (!isset(self::$_methods_cache[$method])) { self::$_methods_cache[$method] = method_exists($this, $method); } if (self::$_methods_cache[$method]) { return $this->$method($val); } elseif ($val !== "") { return $val; } else { return null; } } /** * Get the computed value for the given property. * * @param string $prop The property to get the computed value of. * * @return mixed The computed value. */ protected function computed(string $prop) { if (!\array_key_exists($prop, $this->_props_computed)) { $val = $this->_props[$prop] ?? self::$_defaults[$prop]; $computed = $this->compute_prop($prop, $val); $this->_props_computed[$prop] = $computed; } return $this->_props_computed[$prop]; } /** * @param float $cbw The width of the containing block. * @return float|string|null */ public function computed_bottom_spacing(float $cbw) { // Caching the bottom spacing independently of the given width is a bit // iffy, but should be okay, as the containing block should only // potentially change after a page break, and the style is reset in that // case if ($this->_computed_bottom_spacing !== null) { return $this->_computed_bottom_spacing; } return $this->_computed_bottom_spacing = $this->length_in_pt( [ $this->margin_bottom, $this->padding_bottom, $this->border_bottom_width ], $cbw ); } /** * Returns an `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")` * based on the provided CSS color value. * * @param string|null $color * @return array|string|null */ public function munge_color($color) { return Color::parse($color); } /** * @return string */ public function get_font_family_raw(): string { return trim($this->_props["font_family"], " \t\n\r\x0B\"'"); } /** * Getter for the `font-family` CSS property. * * Uses the {@link FontMetrics} class to resolve the font family into an * actual font file. * * @param string $computed * @return string * @throws Exception * * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-family */ protected function _get_font_family($computed): string { //TODO: we should be using the calculated prop rather than perform the entire family parsing operation again $fontMetrics = $this->getFontMetrics(); $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss(); // Select the appropriate font. First determine the subtype, then check // the specified font-families for a candidate. // Resolve font-weight $weight = $this->__get("font_weight"); if ($weight === 'bold') { $weight = 700; } elseif (preg_match('/^[0-9]+$/', $weight, $match)) { $weight = (int)$match[0]; } else { $weight = 400; } // Resolve font-style $font_style = $this->__get("font_style"); $subtype = $fontMetrics->getType($weight . ' ' . $font_style); $families = preg_split("/\s*,\s*/", $computed); $font = null; foreach ($families as $family) { //remove leading and trailing string delimiters, e.g. on font names with spaces; //remove leading and trailing whitespace $family = trim($family, " \t\n\r\x0B\"'"); if ($DEBUGCSS) { print '(' . $family . ')'; } $font = $fontMetrics->getFont($family, $subtype); if ($font) { if ($DEBUGCSS) { print "<pre>[get_font_family:"; print '(' . $computed . '.' . $font_style . '.' . $weight . '.' . $subtype . ')'; print '(' . $font . ")get_font_family]\n</pre>"; } return $font; } } $family = null; if ($DEBUGCSS) { print '(default)'; } $font = $fontMetrics->getFont($family, $subtype); if ($font) { if ($DEBUGCSS) { print '(' . $font . ")get_font_family]\n</pre>"; } return $font; } throw new Exception("Unable to find a suitable font replacement for: '" . $computed . "'"); } /** * @param float|string $computed * @return float * * @link https://www.w3.org/TR/css-text-4/#word-spacing-property */ protected function _get_word_spacing($computed) { if (\is_float($computed)) { return $computed; } // Resolve percentage values $font_size = $this->__get("font_size"); return $this->single_length_in_pt($computed, $font_size); } /** * @param float|string $computed * @return float * * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property */ protected function _get_letter_spacing($computed) { if (\is_float($computed)) { return $computed; } // Resolve percentage values $font_size = $this->__get("font_size"); return $this->single_length_in_pt($computed, $font_size); } /** * @param float|string $computed * @return float * * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height */ protected function _get_line_height($computed) { // Lengths have been computed to float, number values to string if (\is_float($computed)) { return $computed; } $font_size = $this->__get("font_size"); $factor = $computed === "normal" ? self::$default_line_height : (float) $computed; return $factor * $font_size; } /** * @param string $computed * @param bool $current_is_parent * * @return array|string */ protected function get_color_value($computed, bool $current_is_parent = false) { if ($computed === "currentcolor") { // https://www.w3.org/TR/css-color-4/#resolving-other-colors if ($current_is_parent) { // Use the `color` value from the parent for the `color` // property itself return isset($this->parent_style) ? $this->parent_style->__get("color") : $this->munge_color(self::$_defaults["color"]); } return $this->__get("color"); } return $this->munge_color($computed) ?? "transparent"; } /** * Returns the color as an array * * The array has the following format: * `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")` * * @param string $computed * @return array|string * * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color */ protected function _get_color($computed) { return $this->get_color_value($computed, true); } /** * Returns the background color as an array * * See {@link Style::_get_color()} for format of the color array. * * @param string $computed * @return array|string * * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color */ protected function _get_background_color($computed) { return $this->get_color_value($computed); } /** * Returns the background image URI, or "none" * * @param string $computed * @return string * * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image */ protected function _get_background_image($computed): string { return $this->_stylesheet->resolve_url($computed); } /** * Returns the border color as an array * * See {@link Style::_get_color()} for format of the color array. * * @param string $computed * @return array|string * * @link https://www.w3.org/TR/CSS21/box.html#border-color-properties */ protected function _get_border_top_color($computed) { return $this->get_color_value($computed); } /** * @param string $computed * @return array|string */ protected function _get_border_right_color($computed) { return $this->get_color_value($computed); } /** * @param string $computed * @return array|string */ protected function _get_border_bottom_color($computed) { return $this->get_color_value($computed); } /** * @param string $computed * @return array|string */ protected function _get_border_left_color($computed) { return $this->get_color_value($computed); } /** * Return an array of all border properties. * * The returned array has the following structure: * * ``` * array("top" => array("width" => [border-width], * "style" => [border-style], * "color" => [border-color (array)]), * "bottom" ... ) * ``` * * @return array */ public function get_border_properties(): array { return [ "top" => [ "width" => $this->__get("border_top_width"), "style" => $this->__get("border_top_style"), "color" => $this->__get("border_top_color"), ], "bottom" => [ "width" => $this->__get("border_bottom_width"), "style" => $this->__get("border_bottom_style"), "color" => $this->__get("border_bottom_color"), ], "right" => [ "width" => $this->__get("border_right_width"), "style" => $this->__get("border_right_style"), "color" => $this->__get("border_right_color"), ], "left" => [ "width" => $this->__get("border_left_width"), "style" => $this->__get("border_left_style"), "color" => $this->__get("border_left_color"), ], ]; } /** * Return a single border-side property * * @param string $side * @return string */ protected function get_border_side(string $side): string { $color = $this->__get("border_{$side}_color"); return $this->__get("border_{$side}_width") . " " . $this->__get("border_{$side}_style") . " " . (\is_array($color) ? $color["hex"] : $color); } /** * Return full border properties as a string * * Border properties are returned just as specified in CSS: * `[width] [style] [color]` * e.g. "1px solid blue" * * @return string * * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties */ protected function _get_border_top(): string { return $this->get_border_side("top"); } /** * @return string */ protected function _get_border_right(): string { return $this->get_border_side("right"); } /** * @return string */ protected function _get_border_bottom(): string { return $this->get_border_side("bottom"); } /** * @return string */ protected function _get_border_left(): string { return $this->get_border_side("left"); } public function has_border_radius(): bool { if (isset($this->has_border_radius_cache)) { return $this->has_border_radius_cache; } // Use a fixed ref size here. We don't know the border-box width here // and font size might be 0. Since we are only interested in whether // there is any border radius at all, this should do $tl = (float) $this->length_in_pt($this->border_top_left_radius, 12); $tr = (float) $this->length_in_pt($this->border_top_right_radius, 12); $br = (float) $this->length_in_pt($this->border_bottom_right_radius, 12); $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, 12); $this->has_border_radius_cache = $tl + $tr + $br + $bl > 0; return $this->has_border_radius_cache; } /** * Get the final border-radius values to use. * * Percentage values are resolved relative to the width of the border box. * The border radius is additionally scaled for the given render box, and * constrained by its width and height. * * @param float[] $border_box The border box of the frame. * @param float[]|null $render_box The box to resolve the border radius for. * * @return float[] A 4-tuple of top-left, top-right, bottom-right, and bottom-left radius. */ public function resolve_border_radius( array $border_box, ?array $render_box = null ): array { $render_box = $render_box ?? $border_box; $use_cache = $render_box === $border_box; if ($use_cache && isset($this->resolved_border_radius)) { return $this->resolved_border_radius; } [$x, $y, $w, $h] = $border_box; // Resolve percentages relative to width, as long as we have no support // for per-axis radii $tl = (float) $this->length_in_pt($this->border_top_left_radius, $w); $tr = (float) $this->length_in_pt($this->border_top_right_radius, $w); $br = (float) $this->length_in_pt($this->border_bottom_right_radius, $w); $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, $w); if ($tl + $tr + $br + $bl > 0) { [$rx, $ry, $rw, $rh] = $render_box; $t_offset = $y - $ry; $r_offset = $rx + $rw - $x - $w; $b_offset = $ry + $rh - $y - $h; $l_offset = $x - $rx; if ($tl > 0) { $tl = max($tl + ($t_offset + $l_offset) / 2, 0); } if ($tr > 0) { $tr = max($tr + ($t_offset + $r_offset) / 2, 0); } if ($br > 0) { $br = max($br + ($b_offset + $r_offset) / 2, 0); } if ($bl > 0) { $bl = max($bl + ($b_offset + $l_offset) / 2, 0); } if ($tl + $bl > $rh) { $f = $rh / ($tl + $bl); $tl = $f * $tl; $bl = $f * $bl; } if ($tr + $br > $rh) { $f = $rh / ($tr + $br); $tr = $f * $tr; $br = $f * $br; } if ($tl + $tr > $rw) { $f = $rw / ($tl + $tr); $tl = $f * $tl; $tr = $f * $tr; } if ($bl + $br > $rw) { $f = $rw / ($bl + $br); $bl = $f * $bl; $br = $f * $br; } } $values = [$tl, $tr, $br, $bl]; if ($use_cache) { $this->resolved_border_radius = $values; } return $values; } /** * Returns the outline color as an array * * See {@link Style::_get_color()} for format of the color array. * * @param string $computed * @return array|string * * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-color */ protected function _get_outline_color($computed) { return $this->get_color_value($computed); } /** * @param string $computed * @return string * * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-style */ protected function _get_outline_style($computed): string { return $computed === "auto" ? "solid" : $computed; } /** * Return full outline properties as a string * * Outline properties are returned just as specified in CSS: * `[width] [style] [color]` * e.g. "1px solid blue" * * @return string * * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties */ protected function _get_outline(): string { $color = $this->__get("outline_color"); return $this->__get("outline_width") . " " . $this->__get("outline_style") . " " . (\is_array($color) ? $color["hex"] : $color); } /** * Returns the list style image URI, or "none" * * @param string $computed * @return string * * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image */ protected function _get_list_style_image($computed): string { return $this->_stylesheet->resolve_url($computed); } /** * @param string $value * @param int $default * * @return array|string */ protected function parse_counter_prop(string $value, int $default) { $ident = self::CSS_IDENTIFIER; $integer = self::CSS_INTEGER; $pattern = "/($ident)(?:\s+($integer))?/"; if (!preg_match_all($pattern, $value, $matches, PREG_SET_ORDER)) { return "none"; } $counters = []; foreach ($matches as $match) { $counter = $match[1]; $value = isset($match[2]) ? (int) $match[2] : $default; $counters[$counter] = $value; } return $counters; } /** * @param string $computed * @return array|string * * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment */ protected function _get_counter_increment($computed) { if ($computed === "none") { return $computed; } return $this->parse_counter_prop($computed, 1); } /** * @param string $computed * @return array|string * * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-reset */ protected function _get_counter_reset($computed) { if ($computed === "none") { return $computed; } return $this->parse_counter_prop($computed, 0); } /** * @param string $computed * @return string[]|string * * @link https://www.w3.org/TR/CSS21/generate.html#propdef-content */ protected function _get_content($computed) { if ($computed === "normal" || $computed === "none") { return $computed; } return $this->parse_property_value($computed); } /*==============================*/ /** * Parse a property value into its components. * * @param string $value * * @return string[] */ protected function parse_property_value(string $value): array { $ident = self::CSS_IDENTIFIER; $number = self::CSS_NUMBER; $pattern = "/\n" . "\s* \" ( (?:[^\"]|\\\\[\"])* ) (?<!\\\\)\" |\n" . // String "" "\s* ' ( (?:[^']|\\\\['])* ) (?<!\\\\)' |\n" . // String '' "\s* ($ident \\([^)]*\\) ) |\n" . // Functional "\s* ($ident) |\n" . // Keyword "\s* (\#[0-9a-fA-F]*) |\n" . // Hex value "\s* ($number [a-zA-Z%]*) |\n" . // Number (+ unit/percentage) "\s* ([\/,;]) \n" . // Delimiter "/Sx"; if (!preg_match_all($pattern, $value, $matches)) { return []; } return array_map("trim", $matches[0]); } protected function is_color_value(string $val): bool { return $val === "currentcolor" || $val === "transparent" || isset(Color::$cssColorNames[$val]) || preg_match("/^#|rgb\(|rgba\(|cmyk\(/", $val); } /** * @param string $val * @return string|null */ protected function compute_color_value(string $val): ?string { // https://www.w3.org/TR/css-color-4/#resolving-other-colors $munged_color = $val !== "currentcolor" ? $this->munge_color($val) : $val; if ($munged_color === null) { return null; } return \is_array($munged_color) ? $munged_color["hex"] : $munged_color; } /** * @param string $val * @return int|null */ protected function compute_integer(string $val): ?int { $integer = self::CSS_INTEGER; return preg_match("/^$integer$/", $val) ? (int) $val : null; } /** * @param string $val * @return float|null */ protected function compute_length(string $val): ?float { return mb_strpos($val, "%") === false ? $this->single_length_in_pt($val) : null; } /** * @param string $val * @return float|null */ protected function compute_length_positive(string $val): ?float { $computed = $this->compute_length($val); return $computed !== null && $computed >= 0 ? $computed : null; } /** * @param string $val * @return float|string|null */ protected function compute_length_percentage(string $val) { // Compute with a fixed ref size to decide whether percentage values // are valid $computed = $this->single_length_in_pt($val, 12); if ($computed === null) { return null; } // Retain valid percentage declarations return mb_strpos($val, "%") === false ? $computed : $val; } /** * @param string $val * @return float|string|null */ protected function compute_length_percentage_positive(string $val) { // Compute with a fixed ref size to decide whether percentage values // are valid $computed = $this->single_length_in_pt($val, 12); if ($computed === null || $computed < 0) { return null; } // Retain valid percentage declarations return mb_strpos($val, "%") === false ? $computed : $val; } /** * @param string $val * @param string $style_prop The corresponding border-/outline-style property. * * @return float|null * * @link https://www.w3.org/TR/css-backgrounds-3/#typedef-line-width */ protected function compute_line_width(string $val, string $style_prop): ?float { // Border-width keywords if ($val === "thin") { $computed = 0.5; } elseif ($val === "medium") { $computed = 1.5; } elseif ($val === "thick") { $computed = 2.5; } else { $computed = $this->compute_length_positive($val); } if ($computed === null) { return null; } // Computed width is 0 if the line style is `none` or `hidden` // https://www.w3.org/TR/css-backgrounds-3/#border-width // https://www.w3.org/TR/css-ui-4/#outline-width $lineStyle = $this->__get($style_prop); $hasLineStyle = $lineStyle !== "none" && $lineStyle !== "hidden"; return $hasLineStyle ? $computed : 0.0; } /** * @param string $val * @return string|null */ protected function compute_border_style(string $val): ?string { return \in_array($val, self::BORDER_STYLES, true) ? $val : null; } /** * Parse a property value with 1 to 4 components into 4 values, as required * by shorthand properties such as `margin`, `padding`, and `border-radius`. * * @param string $prop The shorthand property with exactly 4 sub-properties to handle. * @param string $value The property value to parse. * * @return string[] */ protected function set_quad_shorthand(string $prop, string $value): array { $v = $this->parse_property_value($value); switch (\count($v)) { case 1: $values = [$v[0], $v[0], $v[0], $v[0]]; break; case 2: $values = [$v[0], $v[1], $v[0], $v[1]]; break; case 3: $values = [$v[0], $v[1], $v[2], $v[1]]; break; case 4: $values = [$v[0], $v[1], $v[2], $v[3]]; break; default: return []; } return array_combine(self::$_props_shorthand[$prop], $values); } /*======================*/ /** * @link https://www.w3.org/TR/CSS21/visuren.html#display-prop */ protected function _compute_display(string $val) { // Make sure that common valid, but unsupported display types have an // appropriate fallback display type switch ($val) { case "flow-root": case "flex": case "grid": case "table-caption": $val = "block"; break; case "inline-flex": case "inline-grid": $val = "inline-block"; break; } if (!isset(self::$valid_display_types[$val])) { return null; } // https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo if ($this->is_in_flow()) { return $val; } else { switch ($val) { case "inline": case "inline-block": // case "table-row-group": // case "table-header-group": // case "table-footer-group": // case "table-row": // case "table-cell": // case "table-column-group": // case "table-column": // case "table-caption": return "block"; case "inline-table": return "table"; default: return $val; } } } /** * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color */ protected function _compute_color(string $color) { return $this->compute_color_value($color); } /** * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color */ protected function _compute_background_color(string $color) { return $this->compute_color_value($color); } /** * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image */ protected function _compute_background_image(string $val) { $parsed_val = $this->_stylesheet->resolve_url($val); if ($parsed_val === "none") { return "none"; } else { return "url($parsed_val)"; } } /** * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat */ protected function _compute_background_repeat(string $val) { $keywords = ["repeat", "repeat-x", "repeat-y", "no-repeat"]; return \in_array($val, $keywords, true) ? $val : null; } /** * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment */ protected function _compute_background_attachment(string $val) { $keywords = ["scroll", "fixed"]; return \in_array($val, $keywords, true) ? $val : null; } /** * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-position */ protected function _compute_background_position(string $val) { $parts = preg_split("/\s+/", $val); if (\count($parts) > 2) { return null; } switch ($parts[0]) { case "left": $x = "0%"; break; case "right": $x = "100%"; break; case "top": $y = "0%"; break; case "bottom": $y = "100%"; break; case "center": $x = "50%"; $y = "50%"; break; default: $x = $parts[0]; break; } if (isset($parts[1])) { switch ($parts[1]) { case "left": $x = "0%"; break; case "right": $x = "100%"; break; case "top": $y = "0%"; break; case "bottom": $y = "100%"; break; case "center": if ($parts[0] === "left" || $parts[0] === "right" || $parts[0] === "center") { $y = "50%"; } else { $x = "50%"; } break; default: $y = $parts[1]; break; } } else { $y = "50%"; } if (!isset($x)) { $x = "0%"; } if (!isset($y)) { $y = "0%"; } return [$x, $y]; } /** * Compute `background-size`. * * Computes to one of the following values: * * `cover` * * `contain` * * `[width, height]`, each being a length, percentage, or `auto` * * @link https://www.w3.org/TR/css-backgrounds-3/#background-size */ protected function _compute_background_size(string $val) { if ($val === "cover" || $val === "contain") { return $val; } $parts = preg_split("/\s+/", $val); if (\count($parts) > 2) { return null; } $width = $parts[0]; if ($width !== "auto") { $width = $this->compute_length_percentage_positive($width); } $height = $parts[1] ?? "auto"; if ($height !== "auto") { $height = $this->compute_length_percentage_positive($height); } if ($width === null || $height === null) { return null; } return [$width, $height]; } /** * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-background */ protected function _set_background(string $value): array { $components = $this->parse_property_value($value); $props = []; $pos_size = []; foreach ($components as $val) { if ($val === "none" || mb_substr($val, 0, 4) === "url(") { $props["background_image"] = $val; } elseif ($val === "scroll" || $val === "fixed") { $props["background_attachment"] = $val; } elseif ($val === "repeat" || $val === "repeat-x" || $val === "repeat-y" || $val === "no-repeat") { $props["background_repeat"] = $val; } elseif ($this->is_color_value($val)) { $props["background_color"] = $val; } else { $pos_size[] = $val; } } if (\count($pos_size)) { // Split value list at "/" $index = array_search("/", $pos_size, true); if ($index !== false) { $pos = \array_slice($pos_size, 0, $index); $size = \array_slice($pos_size, $index + 1); } else { $pos = $pos_size; $size = []; } $props["background_position"] = implode(" ", $pos); if (\count($size)) { $props["background_size"] = implode(" ", $size); } } return $props; } /** * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-size */ protected function _compute_font_size(string $size) { $parent_font_size = isset($this->parent_style) ? $this->parent_style->__get("font_size") : self::$default_font_size; switch ($size) { case "xx-small": case "x-small": case "small": case "medium": case "large": case "x-large": case "xx-large": $fs = self::$default_font_size * self::$font_size_keywords[$size]; break; case "smaller": $fs = 8 / 9 * $parent_font_size; break; case "larger": $fs = 6 / 5 * $parent_font_size; break; default: $fs = $this->single_length_in_pt($size, $parent_font_size, $parent_font_size); break; } return $fs; } /** * @link https://www.w3.org/TR/CSS21/fonts.html#font-boldness */ protected function _compute_font_weight(string $weight) { $computed_weight = $weight; if ($weight === "bolder") { //TODO: One font weight heavier than the parent element (among the available weights of the font). $computed_weight = "bold"; } elseif ($weight === "lighter") { //TODO: One font weight lighter than the parent element (among the available weights of the font). $computed_weight = "normal"; } return $computed_weight; } /** * Handle the `font` shorthand property. * * `[ font-style || font-variant || font-weight ] font-size [ / line-height ] font-family` * * @link https://www.w3.org/TR/CSS21/fonts.html#font-shorthand */ protected function _set_font(string $value): array { $components = $this->parse_property_value($value); $props = []; $number = self::CSS_NUMBER; $unit = "pt|px|pc|rem|em|ex|in|cm|mm|%"; $sizePattern = "/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|$number(?:$unit))$/"; $sizeIndex = null; // Find index of font-size to split the component list foreach ($components as $i => $val) { if (preg_match($sizePattern, $val)) { $sizeIndex = $i; $props["font_size"] = $val; break; } } // `font-size` is mandatory if ($sizeIndex === null) { return []; } // `font-style`, `font-variant`, `font-weight` in any order $styleVariantWeight = \array_slice($components, 0, $sizeIndex); $stylePattern = "/^(italic|oblique)$/"; $variantPattern = "/^(small-caps)$/"; $weightPattern = "/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900)$/"; if (\count($styleVariantWeight) > 3) { return []; } foreach ($styleVariantWeight as $val) { if ($val === "normal") { // Ignore any `normal` value, as it is valid and the initial // value for all three properties } elseif (!isset($props["font_style"]) && preg_match($stylePattern, $val)) { $props["font_style"] = $val; } elseif (!isset($props["font_variant"]) && preg_match($variantPattern, $val)) { $props["font_variant"] = $val; } elseif (!isset($props["font_weight"]) && preg_match($weightPattern, $val)) { $props["font_weight"] = $val; } else { // Duplicates and other values disallowed here return []; } } // Optional slash + `line-height` followed by mandatory `font-family` $lineFamily = \array_slice($components, $sizeIndex + 1); $hasLineHeight = $lineFamily !== [] && $lineFamily[0] === "/"; $lineHeight = $hasLineHeight ? \array_slice($lineFamily, 1, 1) : []; $fontFamily = $hasLineHeight ? \array_slice($lineFamily, 2) : $lineFamily; $lineHeightPattern = "/^(normal|$number(?:$unit)?)$/"; // Missing `font-family` or `line-height` after slash if ($fontFamily === [] || ($hasLineHeight && !preg_match($lineHeightPattern, $lineHeight[0])) ) { return []; } if ($hasLineHeight) { $props["line_height"] = $lineHeight[0]; } $props["font_family"] = implode("", $fontFamily); return $props; } /** * Compute `text-align`. * * If no alignment is set on the element and the direction is rtl then * the property is set to "right", otherwise it is set to "left". * * @link https://www.w3.org/TR/CSS21/text.html#propdef-text-align */ protected function _compute_text_align(string $val) { $alignment = $val; if ($alignment === "") { $alignment = "left"; if ($this->__get("direction") === "rtl") { $alignment = "right"; } } if (!\in_array($alignment, self::TEXT_ALIGN_KEYWORDS, true)) { return null; } return $alignment; } /** * @link https://www.w3.org/TR/css-text-4/#word-spacing-property */ protected function _compute_word_spacing(string $val) { if ($val === "normal") { return 0.0; } return $this->compute_length_percentage($val); } /** * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property */ protected function _compute_letter_spacing(string $val) { if ($val === "normal") { return 0.0; } return $this->compute_length_percentage($val); } /** * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height */ protected function _compute_line_height(string $val) { if ($val === "normal") { return $val; } // Compute number values to string and lengths to float (in pt) if (is_numeric($val)) { return (string) $val; } $font_size = $this->__get("font_size"); $computed = $this->single_length_in_pt($val, $font_size); return $computed !== null && $computed >= 0 ? $computed : null; } /** * @link https://www.w3.org/TR/css-text-3/#text-indent-property */ protected function _compute_text_indent(string $val) { return $this->compute_length_percentage($val); } /** * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-before */ protected function _compute_page_break_before(string $break) { if ($break === "left" || $break === "right") { $break = "always"; } return $break; } /** * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-after */ protected function _compute_page_break_after(string $break) { if ($break === "left" || $break === "right") { $break = "always"; } return $break; } /** * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-width */ protected function _compute_width(string $val) { if ($val === "auto") { return $val; } return $this->compute_length_percentage_positive($val); } /** * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-height */ protected function _compute_height(string $val) { if ($val === "auto") { return $val; } return $this->compute_length_percentage_positive($val); } /** * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-width */ protected function _compute_min_width(string $val) { // Legacy support for `none`, not covered by spec if ($val === "auto" || $val === "none") { return "auto"; } return $this->compute_length_percentage_positive($val); } /** * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-height */ protected function _compute_min_height(string $val) { // Legacy support for `none`, not covered by spec if ($val === "auto" || $val === "none") { return "auto"; } return $this->compute_length_percentage_positive($val); } /** * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-width */ protected function _compute_max_width(string $val) { // Legacy support for `auto`, not covered by spec if ($val === "none" || $val === "auto") { return "none"; } return $this->compute_length_percentage_positive($val); } /** * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-height */ protected function _compute_max_height(string $val) { // Legacy support for `auto`, not covered by spec if ($val === "none" || $val === "auto") { return "none"; } return $this->compute_length_percentage_positive($val); } /** * @link https://www.w3.org/TR/css-position-3/#inset-properties * @link https://www.w3.org/TR/css-position-3/#propdef-inset */ protected function _set_inset(string $val): array { return $this->set_quad_shorthand("inset", $val); } /** * @param string $val * @return float|string|null */ protected function compute_box_inset(string $val) { if ($val === "auto") { return $val; } return $this->compute_length_percentage($val); } protected function _compute_top(string $val) { return $this->compute_box_inset($val); } protected function _compute_right(string $val) { return $this->compute_box_inset($val); } protected function _compute_bottom(string $val) { return $this->compute_box_inset($val); } protected function _compute_left(string $val) { return $this->compute_box_inset($val); } /** * @link https://www.w3.org/TR/CSS21/box.html#margin-properties * @link https://www.w3.org/TR/CSS21/box.html#propdef-margin */ protected function _set_margin(string $val): array { return $this->set_quad_shorthand("margin", $val); } /** * @param string $val * @return float|string|null */ protected function compute_margin(string $val) { // Legacy support for `none` keyword, not covered by spec if ($val === "none") { return 0.0; } if ($val === "auto") { return $val; } return $this->compute_length_percentage($val); } protected function _compute_margin_top(string $val) { return $this->compute_margin($val); } protected function _compute_margin_right(string $val) { return $this->compute_margin($val); } protected function _compute_margin_bottom(string $val) { return $this->compute_margin($val); } protected function _compute_margin_left(string $val) { return $this->compute_margin($val); } /** * @link https://www.w3.org/TR/CSS21/box.html#padding-properties * @link https://www.w3.org/TR/CSS21/box.html#propdef-padding */ protected function _set_padding(string $val): array { return $this->set_quad_shorthand("padding", $val); } /** * @param string $val * @return float|string|null */ protected function compute_padding(string $val) { // Legacy support for `none` keyword, not covered by spec if ($val === "none") { return 0.0; } return $this->compute_length_percentage_positive($val); } protected function _compute_padding_top(string $val) { return $this->compute_padding($val); } protected function _compute_padding_right(string $val) { return $this->compute_padding($val); } protected function _compute_padding_bottom(string $val) { return $this->compute_padding($val); } protected function _compute_padding_left(string $val) { return $this->compute_padding($val); } /** * @param string $value `width || style || color` * @param string[] $styles The list of border styles to accept. * * @return array Array of `[width, style, color]`, or `null` if the declaration is invalid. */ protected function parse_border_side(string $value, array $styles = self::BORDER_STYLES): ?array { $components = $this->parse_property_value($value); $width = null; $style = null; $color = null; foreach ($components as $val) { if ($style === null && \in_array($val, $styles, true)) { $style = $val; } elseif ($color === null && $this->is_color_value($val)) { $color = $val; } elseif ($width === null) { // Assume width $width = $val; } else { // Duplicates are not allowed return null; } } return [$width, $style, $color]; } /** * @link https://www.w3.org/TR/CSS21/box.html#border-properties * @link https://www.w3.org/TR/CSS21/box.html#propdef-border */ protected function _set_border(string $value): array { $values = $this->parse_border_side($value); if ($values === null) { return []; } return array_merge( array_combine(self::$_props_shorthand["border_top"], $values), array_combine(self::$_props_shorthand["border_right"], $values), array_combine(self::$_props_shorthand["border_bottom"], $values), array_combine(self::$_props_shorthand["border_left"], $values) ); } /** * @param string $prop * @param string $value * @return array */ protected function set_border_side(string $prop, string $value): array { $values = $this->parse_border_side($value); if ($values === null) { return []; } return array_combine(self::$_props_shorthand[$prop], $values); } protected function _set_border_top(string $val): array { return $this->set_border_side("border_top", $val); } protected function _set_border_right(string $val): array { return $this->set_border_side("border_right", $val); } protected function _set_border_bottom(string $val): array { return $this->set_border_side("border_bottom", $val); } protected function _set_border_left(string $val): array { return $this->set_border_side("border_left", $val); } /** * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-color */ protected function _set_border_color(string $val): array { return $this->set_quad_shorthand("border_color", $val); } protected function _compute_border_top_color(string $val) { return $this->compute_color_value($val); } protected function _compute_border_right_color(string $val) { return $this->compute_color_value($val); } protected function _compute_border_bottom_color(string $val) { return $this->compute_color_value($val); } protected function _compute_border_left_color(string $val) { return $this->compute_color_value($val); } /** * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-style */ protected function _set_border_style(string $val): array { return $this->set_quad_shorthand("border_style", $val); } protected function _compute_border_top_style(string $val) { return $this->compute_border_style($val); } protected function _compute_border_right_style(string $val) { return $this->compute_border_style($val); } protected function _compute_border_bottom_style(string $val) { return $this->compute_border_style($val); } protected function _compute_border_left_style(string $val) { return $this->compute_border_style($val); } /** * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-width */ protected function _set_border_width(string $val): array { return $this->set_quad_shorthand("border_width", $val); } protected function _compute_border_top_width(string $val) { return $this->compute_line_width($val, "border_top_style"); } protected function _compute_border_right_width(string $val) { return $this->compute_line_width($val, "border_right_style"); } protected function _compute_border_bottom_width(string $val) { return $this->compute_line_width($val, "border_bottom_style"); } protected function _compute_border_left_width(string $val) { return $this->compute_line_width($val, "border_left_style"); } /** * @link https://www.w3.org/TR/css-backgrounds-3/#corners * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-border-radius */ protected function _set_border_radius(string $val): array { return $this->set_quad_shorthand("border_radius", $val); } protected function _compute_border_top_left_radius(string $val) { return $this->compute_length_percentage_positive($val); } protected function _compute_border_top_right_radius(string $val) { return $this->compute_length_percentage_positive($val); } protected function _compute_border_bottom_right_radius(string $val) { return $this->compute_length_percentage_positive($val); } protected function _compute_border_bottom_left_radius(string $val) { return $this->compute_length_percentage_positive($val); } /** * @link https://www.w3.org/TR/css-ui-4/#outline-props * @link https://www.w3.org/TR/css-ui-4/#propdef-outline */ protected function _set_outline(string $value): array { $values = $this->parse_border_side($value, self::OUTLINE_STYLES); if ($values === null) { return []; } return array_combine(self::$_props_shorthand["outline"], $values); } protected function _compute_outline_color(string $val) { return $this->compute_color_value($val); } protected function _compute_outline_style(string $val) { return \in_array($val, self::OUTLINE_STYLES, true) ? $val : null; } protected function _compute_outline_width(string $val) { return $this->compute_line_width($val, "outline_style"); } /** * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-offset */ protected function _compute_outline_offset(string $val) { return $this->compute_length($val); } /** * Compute `border-spacing` to two lengths of the form * `[horizontal, vertical]`. * * @link https://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing */ protected function _compute_border_spacing(string $val) { $parts = preg_split("/\s+/", $val); if (\count($parts) > 2) { return null; } $h = $this->compute_length_positive($parts[0]); $v = isset($parts[1]) ? $this->compute_length_positive($parts[1]) : $h; if ($h === null || $v === null) { return null; } return [$h, $v]; } /** * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image */ protected function _compute_list_style_image(string $val) { $parsed_val = $this->_stylesheet->resolve_url($val); if ($parsed_val === "none") { return "none"; } else { return "url($parsed_val)"; } } /** * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style */ protected function _set_list_style(string $value): array { static $positions = ["inside", "outside"]; static $types = [ "disc", "circle", "square", "decimal-leading-zero", "decimal", "1", "lower-roman", "upper-roman", "a", "A", "lower-greek", "lower-latin", "upper-latin", "lower-alpha", "upper-alpha", "armenian", "georgian", "hebrew", "cjk-ideographic", "hiragana", "katakana", "hiragana-iroha", "katakana-iroha", "none" ]; $components = $this->parse_property_value($value); $props = []; foreach ($components as $val) { /* https://www.w3.org/TR/CSS21/generate.html#list-style * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none' */ if ($val === "none") { $props["list_style_type"] = $val; $props["list_style_image"] = $val; continue; } //On setting or merging or inheriting list_style_image as well as list_style_type, //and url exists, then url has precedence, otherwise fall back to list_style_type //Firefox is wrong here (list_style_image gets overwritten on explicit list_style_type) //Internet Explorer 7/8 and dompdf is right. if (mb_substr($val, 0, 4) === "url(") { $props["list_style_image"] = $val; continue; } if (\in_array($val, $types, true)) { $props["list_style_type"] = $val; } elseif (\in_array($val, $positions, true)) { $props["list_style_position"] = $val; } } return $props; } /** * @link https://www.w3.org/TR/css-page-3/#page-size-prop */ protected function _compute_size(string $val) { if ($val === "auto") { return $val; } $parts = $this->parse_property_value($val); $count = \count($parts); if ($count === 0 || $count > 3) { return null; } $size = null; $orientation = null; $lengths = []; foreach ($parts as $part) { if ($size === null && isset(CPDF::$PAPER_SIZES[$part])) { $size = $part; } elseif ($orientation === null && ($part === "portrait" || $part === "landscape")) { $orientation = $part; } else { $lengths[] = $part; } } if ($size !== null && $lengths !== []) { return null; } if ($size !== null) { // Standard paper size [$l1, $l2] = \array_slice(CPDF::$PAPER_SIZES[$size], 2, 2); } elseif ($lengths === []) { // Orientation only, use default paper size $dims = $this->_stylesheet->get_dompdf()->getPaperSize(); [$l1, $l2] = \array_slice($dims, 2, 2); } else { // Custom paper size $l1 = $this->compute_length_positive($lengths[0]); $l2 = isset($lengths[1]) ? $this->compute_length_positive($lengths[1]) : $l1; if ($l1 === null || $l2 === null) { return null; } } if (($orientation === "portrait" && $l1 > $l2) || ($orientation === "landscape" && $l2 > $l1) ) { return [$l2, $l1]; } return [$l1, $l2]; } /** * @param string $computed * @return array * * @link https://www.w3.org/TR/css-transforms-1/#transform-property */ protected function _get_transform($computed) { //TODO: should be handled in setter (lengths set to absolute) $number = "\s*([^,\s]+)\s*"; $tr_value = "\s*([^,\s]+)\s*"; $angle = "\s*([^,\s]+(?:deg|rad)?)\s*"; if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $computed, $parts, PREG_SET_ORDER)) { return []; } $functions = [ //"matrix" => "\($number,$number,$number,$number,$number,$number\)", "translate" => "\($tr_value(?:,$tr_value)?\)", "translateX" => "\($tr_value\)", "translateY" => "\($tr_value\)", "scale" => "\($number(?:,$number)?\)", "scaleX" => "\($number\)", "scaleY" => "\($number\)", "rotate" => "\($angle\)", "skew" => "\($angle(?:,$angle)?\)", "skewX" => "\($angle\)", "skewY" => "\($angle\)", ]; $transforms = []; foreach ($parts as $part) { $t = $part[0]; foreach ($functions as $name => $pattern) { if (preg_match("/$name\s*$pattern/i", $t, $matches)) { $values = \array_slice($matches, 1); switch ($name) { // <angle> units case "rotate": case "skew": case "skewX": case "skewY": foreach ($values as $i => $value) { if (strpos($value, "rad")) { $values[$i] = rad2deg((float) $value); } else { $values[$i] = (float) $value; } } switch ($name) { case "skew": if (!isset($values[1])) { $values[1] = 0; } break; case "skewX": $name = "skew"; $values = [$values[0], 0]; break; case "skewY": $name = "skew"; $values = [0, $values[0]]; break; } break; // <translation-value> units case "translate": $values[0] = $this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)); if (isset($values[1])) { $values[1] = $this->length_in_pt($values[1], (float)$this->length_in_pt($this->height)); } else { $values[1] = 0; } break; case "translateX": $name = "translate"; $values = [$this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)), 0]; break; case "translateY": $name = "translate"; $values = [0, $this->length_in_pt($values[0], (float)$this->length_in_pt($this->height))]; break; // <number> units case "scale": if (!isset($values[1])) { $values[1] = $values[0]; } break; case "scaleX": $name = "scale"; $values = [$values[0], 1.0]; break; case "scaleY": $name = "scale"; $values = [1.0, $values[0]]; break; } $transforms[] = [ $name, $values, ]; } } } return $transforms; } /** * @param string $computed * @return array * * @link https://www.w3.org/TR/css-transforms-1/#transform-origin-property */ protected function _get_transform_origin($computed) { //TODO: should be handled in setter $values = preg_split("/\s+/", $computed); $values = array_map(function ($value) { if (\in_array($value, ["top", "left"], true)) { return 0; } elseif (\in_array($value, ["bottom", "right"], true)) { return "100%"; } else { return $value; } }, $values); if (!isset($values[1])) { $values[1] = $values[0]; } return $values; } /** * @param string $val * @return string|null */ protected function parse_image_resolution(string $val): ?string { // If exif data could be get: // $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/'; $re = '/^\s*(\d+|normal|auto)\s*$/'; if (!preg_match($re, $val, $matches)) { return null; } return $matches[1]; } /** * auto | normal | dpi */ protected function _compute_background_image_resolution(string $val) { return $this->parse_image_resolution($val); } /** * auto | normal | dpi */ protected function _compute_image_resolution(string $val) { return $this->parse_image_resolution($val); } /** * @link https://www.w3.org/TR/css-break-3/#propdef-orphans */ protected function _compute_orphans(string $val) { return $this->compute_integer($val); } /** * @link https://www.w3.org/TR/css-break-3/#propdef-widows */ protected function _compute_widows(string $val) { return $this->compute_integer($val); } /** * @link https://www.w3.org/TR/css-color-4/#propdef-opacity */ protected function _compute_opacity(string $val) { $number = self::CSS_NUMBER; $pattern = "/^($number)(%?)$/"; if (!preg_match($pattern, $val, $matches)) { return null; } $v = (float) $matches[1]; $percent = $matches[2] === "%"; $opacity = $percent ? ($v / 100) : $v; return max(0.0, min($opacity, 1.0)); } /** * @link https://www.w3.org/TR/CSS21//visuren.html#propdef-z-index */ protected function _compute_z_index(string $val) { if ($val === "auto") { return $val; } return $this->compute_integer($val); } /** * @param FontMetrics $fontMetrics * @return $this */ public function setFontMetrics(FontMetrics $fontMetrics) { $this->fontMetrics = $fontMetrics; return $this; } /** * @return FontMetrics */ public function getFontMetrics() { return $this->fontMetrics; } /** * Generate a string representation of the Style * * This dumps the entire property array into a string via print_r. Useful * for debugging. * * @return string */ /*DEBUGCSS print: see below additional debugging util*/ public function __toString(): string { $parent_font_size = $this->parent_style ? $this->parent_style->font_size : self::$default_font_size; return print_r(array_merge(["parent_font_size" => $parent_font_size], $this->_props), true); } /*DEBUGCSS*/ public function debug_print(): void { $parent_font_size = $this->parent_style ? $this->parent_style->font_size : self::$default_font_size; print " parent_font_size:" . $parent_font_size . ";\n"; print " Props [\n"; print " specified [\n"; foreach ($this->_props as $prop => $val) { print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true)); if (isset($this->_important_props[$prop])) { print ' !important'; } print ";\n"; } print " ]\n"; print " computed [\n"; foreach ($this->_props_computed as $prop => $val) { print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true)); print ";\n"; } print " ]\n"; print " cached [\n"; foreach ($this->_props_used as $prop => $val) { print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true)); print ";\n"; } print " ]\n"; print " ]\n"; } } dompdf/src/Css/AttributeTranslator.php 0000644 00000042510 15024772104 0014066 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use Dompdf\Frame; use Dompdf\Helpers; /** * Translates HTML 4.0 attributes into CSS rules * * @package dompdf */ class AttributeTranslator { static $_style_attr = "_html_style_attribute"; // Munged data originally from // http://www.w3.org/TR/REC-html40/index/attributes.html // http://www.cs.tut.fi/~jkorpela/html2css.html private static $__ATTRIBUTE_LOOKUP = [ //'caption' => array ( 'align' => '', ), 'img' => [ 'align' => [ 'bottom' => 'vertical-align: baseline;', 'middle' => 'vertical-align: middle;', 'top' => 'vertical-align: top;', 'left' => 'float: left;', 'right' => 'float: right;' ], 'border' => 'border: %0.2Fpx solid;', 'height' => '_set_px_height', 'hspace' => 'padding-left: %1$0.2Fpx; padding-right: %1$0.2Fpx;', 'vspace' => 'padding-top: %1$0.2Fpx; padding-bottom: %1$0.2Fpx;', 'width' => '_set_px_width', ], 'table' => [ 'align' => [ 'left' => 'margin-left: 0; margin-right: auto;', 'center' => 'margin-left: auto; margin-right: auto;', 'right' => 'margin-left: auto; margin-right: 0;' ], 'bgcolor' => 'background-color: %s;', 'border' => '_set_table_border', 'cellpadding' => '_set_table_cellpadding', //'border-spacing: %0.2F; border-collapse: separate;', 'cellspacing' => '_set_table_cellspacing', 'frame' => [ 'void' => 'border-style: none;', 'above' => 'border-top-style: solid;', 'below' => 'border-bottom-style: solid;', 'hsides' => 'border-left-style: solid; border-right-style: solid;', 'vsides' => 'border-top-style: solid; border-bottom-style: solid;', 'lhs' => 'border-left-style: solid;', 'rhs' => 'border-right-style: solid;', 'box' => 'border-style: solid;', 'border' => 'border-style: solid;' ], 'rules' => '_set_table_rules', 'width' => 'width: %s;', ], 'hr' => [ 'align' => '_set_hr_align', // Need to grab width to set 'left' & 'right' correctly 'noshade' => 'border-style: solid;', 'size' => '_set_hr_size', //'border-width: %0.2F px;', 'width' => 'width: %s;', ], 'div' => [ 'align' => 'text-align: %s;', ], 'h1' => [ 'align' => 'text-align: %s;', ], 'h2' => [ 'align' => 'text-align: %s;', ], 'h3' => [ 'align' => 'text-align: %s;', ], 'h4' => [ 'align' => 'text-align: %s;', ], 'h5' => [ 'align' => 'text-align: %s;', ], 'h6' => [ 'align' => 'text-align: %s;', ], //TODO: translate more form element attributes 'input' => [ 'size' => '_set_input_width' ], 'p' => [ 'align' => 'text-align: %s;', ], // 'col' => array( // 'align' => '', // 'valign' => '', // ), // 'colgroup' => array( // 'align' => '', // 'valign' => '', // ), 'tbody' => [ 'align' => '_set_table_row_align', 'valign' => '_set_table_row_valign', ], 'td' => [ 'align' => 'text-align: %s;', 'bgcolor' => '_set_background_color', 'height' => 'height: %s;', 'nowrap' => 'white-space: nowrap;', 'valign' => 'vertical-align: %s;', 'width' => 'width: %s;', ], 'tfoot' => [ 'align' => '_set_table_row_align', 'valign' => '_set_table_row_valign', ], 'th' => [ 'align' => 'text-align: %s;', 'bgcolor' => '_set_background_color', 'height' => 'height: %s;', 'nowrap' => 'white-space: nowrap;', 'valign' => 'vertical-align: %s;', 'width' => 'width: %s;', ], 'thead' => [ 'align' => '_set_table_row_align', 'valign' => '_set_table_row_valign', ], 'tr' => [ 'align' => '_set_table_row_align', 'bgcolor' => '_set_table_row_bgcolor', 'valign' => '_set_table_row_valign', ], 'body' => [ 'background' => 'background-image: url(%s);', 'bgcolor' => '_set_background_color', 'link' => '_set_body_link', 'text' => '_set_color', ], 'br' => [ 'clear' => 'clear: %s;', ], 'basefont' => [ 'color' => '_set_color', 'face' => 'font-family: %s;', 'size' => '_set_basefont_size', ], 'font' => [ 'color' => '_set_color', 'face' => 'font-family: %s;', 'size' => '_set_font_size', ], 'dir' => [ 'compact' => 'margin: 0.5em 0;', ], 'dl' => [ 'compact' => 'margin: 0.5em 0;', ], 'menu' => [ 'compact' => 'margin: 0.5em 0;', ], 'ol' => [ 'compact' => 'margin: 0.5em 0;', 'start' => 'counter-reset: -dompdf-default-counter %d;', 'type' => 'list-style-type: %s;', ], 'ul' => [ 'compact' => 'margin: 0.5em 0;', 'type' => 'list-style-type: %s;', ], 'li' => [ 'type' => 'list-style-type: %s;', 'value' => 'counter-reset: -dompdf-default-counter %d;', ], 'pre' => [ 'width' => 'width: %s;', ], ]; protected static $_last_basefont_size = 3; protected static $_font_size_lookup = [ // For basefont support -3 => "4pt", -2 => "5pt", -1 => "6pt", 0 => "7pt", 1 => "8pt", 2 => "10pt", 3 => "12pt", 4 => "14pt", 5 => "18pt", 6 => "24pt", 7 => "34pt", // For basefont support 8 => "48pt", 9 => "44pt", 10 => "52pt", 11 => "60pt", ]; /** * @param Frame $frame */ static function translate_attributes(Frame $frame) { $node = $frame->get_node(); $tag = $node->nodeName; if (!isset(self::$__ATTRIBUTE_LOOKUP[$tag])) { return; } $valid_attrs = self::$__ATTRIBUTE_LOOKUP[$tag]; $attrs = $node->attributes; $style = rtrim($node->getAttribute(self::$_style_attr), "; "); if ($style != "") { $style .= ";"; } foreach ($attrs as $attr => $attr_node) { if (!isset($valid_attrs[$attr])) { continue; } $value = $attr_node->value; $target = $valid_attrs[$attr]; // Look up $value in $target, if $target is an array: if (is_array($target)) { if (isset($target[$value])) { $style .= " " . self::_resolve_target($node, $target[$value], $value); } } else { // otherwise use target directly $style .= " " . self::_resolve_target($node, $target, $value); } } if (!is_null($style)) { $style = ltrim($style); $node->setAttribute(self::$_style_attr, $style); } } /** * @param \DOMNode $node * @param string $target * @param string $value * * @return string */ protected static function _resolve_target(\DOMNode $node, $target, $value) { if ($target[0] === "_") { return self::$target($node, $value); } return $value ? sprintf($target, $value) : ""; } /** * @param \DOMElement $node * @param string $new_style */ static function append_style(\DOMElement $node, $new_style) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $style .= $new_style; $style = ltrim($style, ";"); $node->setAttribute(self::$_style_attr, $style); } /** * @param \DOMNode $node * * @return \DOMNodeList|\DOMElement[] */ protected static function get_cell_list(\DOMNode $node) { $xpath = new \DOMXpath($node->ownerDocument); switch ($node->nodeName) { default: case "table": $query = "tr/td | thead/tr/td | tbody/tr/td | tfoot/tr/td | tr/th | thead/tr/th | tbody/tr/th | tfoot/tr/th"; break; case "tbody": case "tfoot": case "thead": $query = "tr/td | tr/th"; break; case "tr": $query = "td | th"; break; } return $xpath->query($query, $node); } /** * @param string $value * * @return string */ protected static function _get_valid_color($value) { if (preg_match('/^#?([0-9A-F]{6})$/i', $value, $matches)) { $value = "#$matches[1]"; } return $value; } /** * @param \DOMElement $node * @param string $value * * @return string */ protected static function _set_color(\DOMElement $node, $value) { $value = self::_get_valid_color($value); return "color: $value;"; } /** * @param \DOMElement $node * @param string $value * * @return string */ protected static function _set_background_color(\DOMElement $node, $value) { $value = self::_get_valid_color($value); return "background-color: $value;"; } protected static function _set_px_width(\DOMElement $node, string $value): string { $v = trim($value); if (Helpers::is_percent($v)) { return sprintf("width: %s;", $v); } if (is_numeric(mb_substr($v, 0, 1))) { return sprintf("width: %spx;", (float) $v); } return ""; } protected static function _set_px_height(\DOMElement $node, string $value): string { $v = trim($value); if (Helpers::is_percent($v)) { return sprintf("height: %s;", $v); } if (is_numeric(mb_substr($v, 0, 1))) { return sprintf("height: %spx;", (float) $v); } return ""; } /** * @param \DOMElement $node * @param string $value * * @return null */ protected static function _set_table_cellpadding(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { self::append_style($cell, "; padding: {$value}px;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return string */ protected static function _set_table_border(\DOMElement $node, $value) { return "border-width: $value" . "px;"; } /** * @param \DOMElement $node * @param string $value * * @return string */ protected static function _set_table_cellspacing(\DOMElement $node, $value) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); if ($value == 0) { $style .= "; border-collapse: collapse;"; } else { $style .= "; border-spacing: {$value}px; border-collapse: separate;"; } return ltrim($style, ";"); } /** * @param \DOMElement $node * @param string $value * * @return null|string */ protected static function _set_table_rules(\DOMElement $node, $value) { $new_style = "; border-collapse: collapse;"; switch ($value) { case "none": $new_style .= "border-style: none;"; break; case "groups": // FIXME: unsupported return null; case "rows": $new_style .= "border-style: solid none solid none; border-width: 1px; "; break; case "cols": $new_style .= "border-style: none solid none solid; border-width: 1px; "; break; case "all": $new_style .= "border-style: solid; border-width: 1px; "; break; default: // Invalid value return null; } $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { $style = $cell->getAttribute(self::$_style_attr); $style .= $new_style; $cell->setAttribute(self::$_style_attr, $style); } $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $style .= "; border-collapse: collapse; "; return ltrim($style, "; "); } /** * @param \DOMElement $node * @param string $value * * @return string */ protected static function _set_hr_size(\DOMElement $node, $value) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $style .= "; border-width: " . max(0, $value - 2) . "; "; return ltrim($style, "; "); } /** * @param \DOMElement $node * @param string $value * * @return null|string */ protected static function _set_hr_align(\DOMElement $node, $value) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $width = $node->getAttribute("width"); if ($width == "") { $width = "100%"; } $remainder = 100 - (double)rtrim($width, "% "); switch ($value) { case "left": $style .= "; margin-right: $remainder %;"; break; case "right": $style .= "; margin-left: $remainder %;"; break; case "center": $style .= "; margin-left: auto; margin-right: auto;"; break; default: return null; } return ltrim($style, "; "); } /** * @param \DOMElement $node * @param string $value * * @return null|string */ protected static function _set_input_width(\DOMElement $node, $value) { if (empty($value)) { return null; } if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), ["text","password"])) { return sprintf("width: %Fem", (((int)$value * .65)+2)); } else { return sprintf("width: %upx;", (int)$value); } } /** * @param \DOMElement $node * @param string $value * * @return null */ protected static function _set_table_row_align(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { self::append_style($cell, "; text-align: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ protected static function _set_table_row_valign(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { self::append_style($cell, "; vertical-align: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ protected static function _set_table_row_bgcolor(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); $value = self::_get_valid_color($value); foreach ($cell_list as $cell) { self::append_style($cell, "; background-color: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ protected static function _set_body_link(\DOMElement $node, $value) { $a_list = $node->getElementsByTagName("a"); $value = self::_get_valid_color($value); foreach ($a_list as $a) { self::append_style($a, "; color: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ protected static function _set_basefont_size(\DOMElement $node, $value) { // FIXME: ? we don't actually set the font size of anything here, just // the base size for later modification by <font> tags. self::$_last_basefont_size = $value; return null; } /** * @param \DOMElement $node * @param string $value * * @return string */ protected static function _set_font_size(\DOMElement $node, $value) { $style = $node->getAttribute(self::$_style_attr); if ($value[0] === "-" || $value[0] === "+") { $value = self::$_last_basefont_size + (int)$value; } if (isset(self::$_font_size_lookup[$value])) { $style .= "; font-size: " . self::$_font_size_lookup[$value] . ";"; } else { $style .= "; font-size: $value;"; } return ltrim($style, "; "); } } dompdf/src/Css/Stylesheet.php 0000644 00000170407 15024772104 0012211 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use DOMElement; use DOMXPath; use Dompdf\Dompdf; use Dompdf\Helpers; use Dompdf\Exception; use Dompdf\FontMetrics; use Dompdf\Frame\FrameTree; /** * The master stylesheet class * * The Stylesheet class is responsible for parsing stylesheets and style * tags/attributes. It also acts as a registry of the individual Style * objects generated by the current set of loaded CSS files and style * elements. * * @see Style * @package dompdf */ class Stylesheet { /** * The location of the default built-in CSS file. */ const DEFAULT_STYLESHEET = "/lib/res/html.css"; /** * User agent stylesheet origin * * @var int */ const ORIG_UA = 1; /** * User normal stylesheet origin * * @var int */ const ORIG_USER = 2; /** * Author normal stylesheet origin * * @var int */ const ORIG_AUTHOR = 3; /* * The highest possible specificity is 0x01000000 (and that is only for author * stylesheets, as it is for inline styles). Origin precedence can be achieved by * adding multiples of 0x10000000 to the actual specificity. Important * declarations are handled in Style; though technically they should be handled * here so that user important declarations can be made to take precedence over * user important declarations, this doesn't matter in practice as Dompdf does * not support user stylesheets, and user agent stylesheets can not include * important declarations. */ private static $_stylesheet_origins = [ self::ORIG_UA => 0x00000000, // user agent declarations self::ORIG_USER => 0x10000000, // user normal declarations self::ORIG_AUTHOR => 0x30000000, // author normal declarations ]; /** * Non-CSS presentational hints (i.e. HTML 4 attributes) are handled as if added * to the beginning of an author stylesheet, i.e. anything in author stylesheets * should override them. */ const SPEC_NON_CSS = 0x20000000; /** * Current dompdf instance * * @var Dompdf */ private $_dompdf; /** * Array of currently defined styles * * @var Style[][] */ private $_styles; /** * Base protocol of the document being parsed * Used to handle relative urls. * * @var string */ private $_protocol = ""; /** * Base hostname of the document being parsed * Used to handle relative urls. * * @var string */ private $_base_host = ""; /** * Base path of the document being parsed * Used to handle relative urls. * * @var string */ private $_base_path = ""; /** * The styles defined by @page rules * * @var array<Style> */ private $_page_styles; /** * List of loaded files, used to prevent recursion * * @var array */ private $_loaded_files; /** * Current stylesheet origin * * @var int */ private $_current_origin = self::ORIG_UA; /** * Accepted CSS media types * List of types and parsing rules for future extensions: * http://www.w3.org/TR/REC-html40/types.html * screen, tty, tv, projection, handheld, print, braille, aural, all * The following are non standard extensions for undocumented specific environments. * static, visual, bitmap, paged, dompdf * Note, even though the generated pdf file is intended for print output, * the desired content might be different (e.g. screen or projection view of html file). * Therefore allow specification of content by dompdf setting Options::defaultMediaType. * If given, replace media "print" by Options::defaultMediaType. * (Previous version $ACCEPTED_MEDIA_TYPES = $ACCEPTED_GENERIC_MEDIA_TYPES + $ACCEPTED_DEFAULT_MEDIA_TYPE) */ static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print"; static $ACCEPTED_GENERIC_MEDIA_TYPES = ["all", "static", "visual", "bitmap", "paged", "dompdf"]; static $VALID_MEDIA_TYPES = ["all", "aural", "bitmap", "braille", "dompdf", "embossed", "handheld", "paged", "print", "projection", "screen", "speech", "static", "tty", "tv", "visual"]; /** * @var FontMetrics */ private $fontMetrics; /** * The class constructor. * * The base protocol, host & path are initialized to those of * the current script. */ function __construct(Dompdf $dompdf) { $this->_dompdf = $dompdf; $this->setFontMetrics($dompdf->getFontMetrics()); $this->_styles = []; $this->_loaded_files = []; $script = __FILE__; if (isset($_SERVER["SCRIPT_FILENAME"])) { $script = $_SERVER["SCRIPT_FILENAME"]; } list($this->_protocol, $this->_base_host, $this->_base_path) = Helpers::explode_url($script); $this->_page_styles = ["base" => new Style($this)]; } /** * Set the base protocol * * @param string $protocol */ function set_protocol(string $protocol) { $this->_protocol = $protocol; } /** * Set the base host * * @param string $host */ function set_host(string $host) { $this->_base_host = $host; } /** * Set the base path * * @param string $path */ function set_base_path(string $path) { $this->_base_path = $path; } /** * Return the Dompdf object * * @return Dompdf */ function get_dompdf() { return $this->_dompdf; } /** * Return the base protocol for this stylesheet * * @return string */ function get_protocol() { return $this->_protocol; } /** * Return the base host for this stylesheet * * @return string */ function get_host() { return $this->_base_host; } /** * Return the base path for this stylesheet * * @return string */ function get_base_path() { return $this->_base_path; } /** * Return the array of page styles * * @return Style[] */ function get_page_styles() { return $this->_page_styles; } /** * Create a new Style object associated with this stylesheet * * @return Style */ function create_style(): Style { return new Style($this, $this->_current_origin); } /** * Add a new Style object to the stylesheet * * The style's origin is changed to the current origin of the stylesheet. * * @param string $key the Style's selector * @param Style $style the Style to be added */ function add_style(string $key, Style $style): void { if (!isset($this->_styles[$key])) { $this->_styles[$key] = []; } $style->set_origin($this->_current_origin); $this->_styles[$key][] = $style; } /** * load and parse a CSS string * * @param string $css * @param int $origin */ function load_css(&$css, $origin = self::ORIG_AUTHOR) { if ($origin) { $this->_current_origin = $origin; } $this->_parse_css($css); } /** * load and parse a CSS file * * @param string $file * @param int $origin */ function load_css_file($file, $origin = self::ORIG_AUTHOR) { if ($origin) { $this->_current_origin = $origin; } // Prevent circular references if (isset($this->_loaded_files[$file])) { return; } $this->_loaded_files[$file] = true; if (strpos($file, "data:") === 0) { $parsed = Helpers::parse_data_uri($file); $css = $parsed["data"]; } else { $options = $this->_dompdf->getOptions(); $parsed_url = Helpers::explode_url($file); $protocol = $parsed_url["protocol"]; if ($file !== $this->getDefaultStylesheet()) { $allowed_protocols = $options->getAllowedProtocols(); if (!array_key_exists($protocol, $allowed_protocols)) { Helpers::record_warnings(E_USER_WARNING, "Permission denied on $file. The communication protocol is not supported.", __FILE__, __LINE__); return; } foreach ($allowed_protocols[$protocol]["rules"] as $rule) { [$result, $message] = $rule($file); if (!$result) { Helpers::record_warnings(E_USER_WARNING, "Error loading $file: $message", __FILE__, __LINE__); return; } } } [$css, $http_response_header] = Helpers::getFileContent($file, $this->_dompdf->getHttpContext()); $good_mime_type = true; // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/ if (isset($http_response_header) && !$this->_dompdf->getQuirksmode()) { foreach ($http_response_header as $_header) { if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) && ($matches[1] !== "text/css") ) { $good_mime_type = false; } } } if (!$good_mime_type || $css === null) { Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__); return; } [$this->_protocol, $this->_base_host, $this->_base_path] = $parsed_url; } $this->_parse_css($css); } /** * @link https://www.w3.org/TR/CSS21/cascade.html#specificity * * @param string $selector * @param int $origin * - Stylesheet::ORIG_UA: user agent style sheet * - Stylesheet::ORIG_USER: user style sheet * - Stylesheet::ORIG_AUTHOR: author style sheet * * @return int */ protected function specificity(string $selector, int $origin = self::ORIG_AUTHOR): int { $a = ($selector === "!attr") ? 1 : 0; $b = min(mb_substr_count($selector, "#"), 255); $c = min(mb_substr_count($selector, ".") + mb_substr_count($selector, "[") + mb_substr_count($selector, ":") - 2 * mb_substr_count($selector, "::"), 255); $d = min(mb_substr_count($selector, " ") + mb_substr_count($selector, ">") + mb_substr_count($selector, "+") + mb_substr_count($selector, "~") - mb_substr_count($selector, "~=") + mb_substr_count($selector, "::"), 255); //If a normal element name is at the beginning of the string, //a leading whitespace might have been removed on whitespace collapsing and removal //therefore there might be one whitespace less as selected element names //this can lead to a too small specificity //see selectorToXpath if (!in_array($selector[0], [" ", ">", ".", "#", "+", "~", ":", "["], true) && $selector !== "*") { $d++; } if ($this->_dompdf->getOptions()->getDebugCss()) { /*DEBUGCSS*/ print "<pre>\n"; /*DEBUGCSS*/ printf("specificity(): 0x%08x \"%s\"\n", self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)), $selector); /*DEBUGCSS*/ print "</pre>"; } return self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)); } /** * Converts a CSS selector to an XPath query. * * @param string $selector * @param bool $firstPass * * @return array|null */ protected function selectorToXpath(string $selector, bool $firstPass = false): ?array { // Collapse white space and strip whitespace around delimiters //$search = array("/\\s+/", "/\\s+([.>#+:])\\s+/"); //$replace = array(" ", "\\1"); //$selector = preg_replace($search, $replace, trim($selector)); // Initial query, always expanded to // below (non-absolute) $query = "/"; // Will contain :before and :after $pseudo_elements = []; // Parse the selector //$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE); $delimiters = [" ", ">", ".", "#", "+", "~", ":", "[", "("]; // Add an implicit space at the beginning of the selector if there is no // delimiter there already. if (!in_array($selector[0], $delimiters, true)) { $selector = " $selector"; } $name = "*"; $len = mb_strlen($selector); $i = 0; while ($i < $len) { $s = $selector[$i]; $i++; // Eat characters up to the next delimiter $tok = ""; $in_attr = false; $in_func = false; while ($i < $len) { $c = $selector[$i]; $c_prev = $selector[$i - 1]; if (!$in_func && !$in_attr && in_array($c, $delimiters, true) && !($c === $c_prev && $c === ":")) { break; } if ($c_prev === "[") { $in_attr = true; } if ($c_prev === "(") { $in_func = true; } $tok .= $selector[$i++]; if ($in_attr && $c === "]") { $in_attr = false; break; } if ($in_func && $c === ")") { $in_func = false; break; } } switch ($s) { case " ": case ">": // All elements matching the next token that are descendants // or children of the current token // https://www.w3.org/TR/selectors-3/#descendant-combinators // https://www.w3.org/TR/selectors-3/#child-combinators $expr = $s === " " ? "descendant" : "child"; // Tag names are case-insensitive $name = $tok === "" ? "*" : strtolower($tok); $query .= "/$expr::$name"; break; case "+": // Next-sibling combinator // https://www.w3.org/TR/selectors-3/#sibling-combinators // Tag names are case-insensitive $name = $tok === "" ? "*" : strtolower($tok); $query .= "/following-sibling::*[1]"; if ($name !== "*") { $query .= "[name() = '$name']"; } break; case "~": // Subsequent-sibling combinator // https://www.w3.org/TR/selectors-3/#sibling-combinators // Tag names are case-insensitive $name = $tok === "" ? "*" : strtolower($tok); $query .= "/following-sibling::$name"; break; case "#": // All elements matching the current token with id equal // to the _next_ token // https://www.w3.org/TR/selectors-3/#id-selectors if ($query === "/") { $query .= "/*"; } $query .= "[@id=\"$tok\"]"; break; case ".": // All elements matching the current token with a class // equal to the _next_ token // https://www.w3.org/TR/selectors-3/#class-html if ($query === "/") { $query .= "/*"; } // Match multiple classes: $tok contains the current selected // class. Search for class attributes with class="$tok", // class=".* $tok .*" and class=".* $tok" // This doesn't work because libxml only supports XPath 1.0... //$query .= "[matches(@$attr,\"^{$tok}\$|^{$tok}[ ]+|[ ]+{$tok}\$|[ ]+{$tok}[ ]+\")]"; $query .= "[contains(concat(' ', normalize-space(@class), ' '), concat(' ', '$tok', ' '))]"; break; case ":": if ($query === "/") { $query .= "/*"; } $last = false; // Pseudo-classes switch ($tok) { case "root": $query .= "[not(parent::*)]"; break; case "first-child": $query .= "[not(preceding-sibling::*)]"; break; case "last-child": $query .= "[not(following-sibling::*)]"; break; case "only-child": $query .= "[not(preceding-sibling::*) and not(following-sibling::*)]"; break; // https://www.w3.org/TR/selectors-3/#nth-child-pseudo /** @noinspection PhpMissingBreakStatementInspection */ case "nth-last-child": $last = true; case "nth-child": $p = $i + 1; $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p)); $position = $last ? "(count(following-sibling::*) + 1)" : "(count(preceding-sibling::*) + 1)"; $condition = $this->selectorAnPlusB($nth, $position); $query .= "[$condition]"; break; // TODO: `*:first-of-type`, `*:nth-of-type` etc. // (without fixed element name) are treated equivalent // to their `:*-child` counterparts here. They might // not be properly expressible in XPath 1.0 case "first-of-type": $query .= "[not(preceding-sibling::$name)]"; break; case "last-of-type": $query .= "[not(following-sibling::$name)]"; break; case "only-of-type": $query .= "[not(preceding-sibling::$name) and not(following-sibling::$name)]"; break; // https://www.w3.org/TR/selectors-3/#nth-of-type-pseudo /** @noinspection PhpMissingBreakStatementInspection */ case "nth-last-of-type": $last = true; case "nth-of-type": $p = $i + 1; $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p)); $position = $last ? "(count(following-sibling::$name) + 1)" : "(count(preceding-sibling::$name) + 1)"; $condition = $this->selectorAnPlusB($nth, $position); $query .= "[$condition]"; break; // https://www.w3.org/TR/selectors-4/#empty-pseudo case "empty": $query .= "[not(*) and not(normalize-space())]"; break; // TODO: bit of a hack attempt at matches support, currently only matches against elements case "matches": $p = $i + 1; $matchList = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p)); // Tag names are case-insensitive $elements = array_map("trim", explode(",", strtolower($matchList))); foreach ($elements as &$element) { $element = "name() = '$element'"; } $query .= "[" . implode(" or ", $elements) . "]"; break; // https://www.w3.org/TR/selectors-3/#UIstates case "disabled": case "checked": $query .= "[@$tok]"; break; case "enabled": $query .= "[not(@disabled)]"; break; // https://www.w3.org/TR/selectors-3/#dynamic-pseudos // https://www.w3.org/TR/selectors-4/#the-any-link-pseudo case "link": case "any-link": $query .= "[@href]"; break; // N/A case "visited": case "hover": case "active": case "focus": case "focus-visible": case "focus-within": $query .= "[false()]"; break; // https://www.w3.org/TR/selectors-3/#first-line // https://www.w3.org/TR/selectors-3/#first-letter case "first-line": case ":first-line": case "first-letter": case ":first-letter": // TODO $el = ltrim($tok, ":"); $pseudo_elements[$el] = true; break; // https://www.w3.org/TR/selectors-3/#gen-content case "before": case ":before": case "after": case ":after": $pos = ltrim($tok, ":"); $pseudo_elements[$pos] = true; if (!$firstPass) { $query .= "/*[@$pos]"; } break; // Invalid or unsupported pseudo-class or pseudo-element default: return null; } break; case "[": // Attribute selectors. All with an attribute matching the // following token(s) // https://www.w3.org/TR/selectors-3/#attribute-selectors if ($query === "/") { $query .= "/*"; } $attr_delimiters = ["=", "]", "~", "|", "$", "^", "*"]; $tok_len = mb_strlen($tok); $j = 0; $attr = ""; $op = ""; $value = ""; while ($j < $tok_len) { if (in_array($tok[$j], $attr_delimiters, true)) { break; } $attr .= $tok[$j++]; } if ($attr === "") { // Selector invalid: Missing attribute name return null; } if (!isset($tok[$j])) { // Selector invalid: Missing ] or operator return null; } switch ($tok[$j]) { case "~": case "|": case "^": case "$": case "*": $op .= $tok[$j++]; if (!isset($tok[$j]) || $tok[$j] !== "=") { // Selector invalid: Incomplete attribute operator return null; } $op .= $tok[$j]; break; case "=": $op = "="; break; } // Read the attribute value, if required if ($op !== "") { $j++; while ($j < $tok_len) { if ($tok[$j] === "]") { break; } $value .= $tok[$j++]; } } if (!isset($tok[$j])) { // Selector invalid: Missing ] return null; } $value = trim($value, "\"'"); switch ($op) { case "": $query .= "[@$attr]"; break; case "=": $query .= "[@$attr=\"$value\"]"; break; case "~=": // FIXME: this will break if $value contains quoted strings // (e.g. [type~="a b c" "d e f"]) $query .= $value !== "" && !preg_match("/\s+/", $value) ? "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', \"$value\", ' '))]" : "[false()]"; break; case "|=": $values = explode("-", $value); $query .= "["; foreach ($values as $val) { $query .= "starts-with(@$attr, \"$val\") or "; } $query = rtrim($query, " or ") . "]"; break; case "^=": $query .= $value !== "" ? "[starts-with(@$attr,\"$value\")]" : "[false()]"; break; case "$=": $query .= $value !== "" ? "[substring(@$attr, string-length(@$attr)-" . (strlen($value) - 1) . ")=\"$value\"]" : "[false()]"; break; case "*=": $query .= $value !== "" ? "[contains(@$attr,\"$value\")]" : "[false()]"; break; } break; } } return ["query" => $query, "pseudo_elements" => $pseudo_elements]; } /** * Parse an `nth-child` expression of the form `an+b`, `odd`, or `even`. * * @param string $expr * @param string $position * * @return string * * @link https://www.w3.org/TR/selectors-3/#nth-child-pseudo */ protected function selectorAnPlusB(string $expr, string $position): string { // odd if ($expr === "odd") { return "($position mod 2) = 1"; } // even elseif ($expr === "even") { return "($position mod 2) = 0"; } // b elseif (preg_match("/^\d+$/", $expr)) { return "$position = $expr"; } // an+b // https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb $expr = preg_replace("/\s/", "", $expr); if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) { return "false()"; } $a = (isset($matches["a"]) && $matches["a"] !== "") ? ($matches["a"] !== "-" ? intval($matches["a"]) : -1) : 1; $b = (isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0; if ($b === 0) { return "($position mod $a) = 0"; } else { $compare = ($a < 0) ? "<=" : ">="; $b2 = -$b; if ($b2 >= 0) { $b2 = "+$b2"; } return "($position $compare $b) and ((($position $b2) mod " . abs($a) . ") = 0)"; } } /** * applies all current styles to a particular document tree * * apply_styles() applies all currently loaded styles to the provided * {@link FrameTree}. Aside from parsing CSS, this is the main purpose * of this class. * * @param FrameTree $tree */ function apply_styles(FrameTree $tree) { // Use XPath to select nodes. This would be easier if we could attach // Frame objects directly to DOMNodes using the setUserData() method, but // we can't do that just yet. Instead, we set a _node attribute_ in // Frame->set_id() and use that as a handle on the Frame object via // FrameTree::$_registry. // We create a scratch array of styles indexed by frame id. Once all // styles have been assigned, we order the cached styles by specificity // and create a final style object to assign to the frame. // FIXME: this is not particularly robust... $styles = []; $xp = new DOMXPath($tree->get_dom()); $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); // Add generated content foreach ($this->_styles as $selector => $selector_styles) { if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) { continue; } $query = $this->selectorToXpath($selector, true); if ($query === null) { Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); continue; } // Retrieve the nodes, limit to body for generated content // TODO: If we use a context node can we remove the leading dot? $nodes = @$xp->query('.' . $query["query"]); if ($nodes === false) { Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); continue; } foreach ($selector_styles as $style) { foreach ($nodes as $node) { // Only DOMElements get styles if (!($node instanceof DOMElement)) { continue; } foreach (array_keys($query["pseudo_elements"], true, true) as $pos) { // Do not add a new pseudo element if another one already matched if ($node->hasAttribute("dompdf_{$pos}_frame_id")) { continue; } $content = $style->get_specified("content"); // Do not create non-displayed before/after pseudo elements // https://www.w3.org/TR/CSS21/generate.html#content // https://www.w3.org/TR/CSS21/generate.html#undisplayed-counters if ($content === "normal" || $content === "none") { continue; } if (($src = $this->resolve_url($content)) !== "none") { $new_node = $node->ownerDocument->createElement("img_generated"); $new_node->setAttribute("src", $src); } else { $new_node = $node->ownerDocument->createElement("dompdf_generated"); } $new_node->setAttribute($pos, $pos); $new_frame_id = $tree->insert_node($node, $new_node, $pos); $node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id); } } } } // Apply all styles in stylesheet foreach ($this->_styles as $selector => $selector_styles) { $query = $this->selectorToXpath($selector); if ($query === null) { Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); continue; } // Retrieve the nodes $nodes = @$xp->query($query["query"]); if ($nodes === false) { Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); continue; } foreach ($selector_styles as $style) { $spec = $this->specificity($selector, $style->get_origin()); foreach ($nodes as $node) { // Only DOMElements get styles if (!($node instanceof DOMElement)) { continue; } $id = $node->getAttribute("frame_id"); // Assign the current style to the scratch array $styles[$id][$spec][] = $style; } } } // Set the page width, height, and orientation based on the canvas paper size $canvas = $this->_dompdf->getCanvas(); $paper_width = $canvas->get_width(); $paper_height = $canvas->get_height(); $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait"); if ($this->_page_styles["base"] && is_array($this->_page_styles["base"]->size)) { $paper_width = $this->_page_styles['base']->size[0]; $paper_height = $this->_page_styles['base']->size[1]; $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait"); } // Now create the styles and assign them to the appropriate frames. (We // iterate over the tree using an implicit FrameTree iterator.) $root_flg = false; foreach ($tree as $frame) { // Helpers::pre_r($frame->get_node()->nodeName . ":"); if (!$root_flg && $this->_page_styles["base"]) { $style = $this->_page_styles["base"]; } else { $style = $this->create_style(); } // Find nearest DOMElement parent $p = $frame; while ($p = $p->get_parent()) { if ($p->get_node()->nodeType === XML_ELEMENT_NODE) { break; } } // Styles can only be applied directly to DOMElements; anonymous // frames inherit from their parent if ($frame->get_node()->nodeType !== XML_ELEMENT_NODE) { $style->inherit($p ? $p->get_style() : null); $frame->set_style($style); continue; } $id = $frame->get_id(); // Handle HTML 4.0 attributes AttributeTranslator::translate_attributes($frame); if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") { $styles[$id][self::SPEC_NON_CSS][] = $this->_parse_properties($str); } // Locate any additional style attributes if (($str = $frame->get_node()->getAttribute("style")) !== "") { // Destroy CSS comments $str = preg_replace("'/\*.*?\*/'si", "", $str); $spec = $this->specificity("!attr", self::ORIG_AUTHOR); $styles[$id][$spec][] = $this->_parse_properties($str); } // Grab the applicable styles if (isset($styles[$id])) { /** @var array[][] $applied_styles */ $applied_styles = $styles[$id]; // Sort by specificity ksort($applied_styles); if ($DEBUGCSS) { $debug_nodename = $frame->get_node()->nodeName; print "<pre>\n$debug_nodename [\n"; foreach ($applied_styles as $spec => $arr) { printf(" specificity 0x%08x\n", $spec); /** @var Style $s */ foreach ($arr as $s) { print " [\n"; $s->debug_print(); print " ]\n"; } } } // Merge the new styles with the inherited styles $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType(); foreach ($applied_styles as $arr) { /** @var Style $s */ foreach ($arr as $s) { $media_queries = $s->get_media_queries(); foreach ($media_queries as $media_query) { list($media_query_feature, $media_query_value) = $media_query; // if any of the Style's media queries fail then do not apply the style //TODO: When the media query logic is fully developed we should not apply the Style when any of the media queries fail or are bad, per https://www.w3.org/TR/css3-mediaqueries/#error-handling if (in_array($media_query_feature, self::$VALID_MEDIA_TYPES)) { if ((strlen($media_query_feature) === 0 && !in_array($media_query, $acceptedmedia)) || (in_array($media_query, $acceptedmedia) && $media_query_value == "not")) { continue (3); } } else { switch ($media_query_feature) { case "height": if ($paper_height !== (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "min-height": if ($paper_height < (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "max-height": if ($paper_height > (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "width": if ($paper_width !== (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "min-width": //if (min($paper_width, $media_query_width) === $paper_width) { if ($paper_width < (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "max-width": //if (max($paper_width, $media_query_width) === $paper_width) { if ($paper_width > (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "orientation": if ($paper_orientation !== $media_query_value) { continue (3); } break; default: Helpers::record_warnings(E_USER_WARNING, "Unknown media query: $media_query_feature", __FILE__, __LINE__); break; } } } $style->merge($s); } } } // Handle inheritance if ($p && $DEBUGCSS) { print " inherit [\n"; $p->get_style()->debug_print(); print " ]\n"; } $style->inherit($p ? $p->get_style() : null); if ($DEBUGCSS) { print " DomElementStyle [\n"; $style->debug_print(); print " ]\n"; print "]\n</pre>"; } $style->clear_important(); $frame->set_style($style); if (!$root_flg && $this->_page_styles["base"]) { $root_flg = true; // set the page width, height, and orientation based on the parsed page style if ($style->size !== "auto") { list($paper_width, $paper_height) = $style->size; } $paper_width = $paper_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->margin_right); $paper_height = $paper_height - (float)$style->length_in_pt($style->margin_top) - (float)$style->length_in_pt($style->margin_bottom); $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait"); } } // We're done! Clean out the registry of all styles since we // won't be needing this later. foreach (array_keys($this->_styles) as $key) { $this->_styles[$key] = null; unset($this->_styles[$key]); } } /** * parse a CSS string using a regex parser * Called by {@link Stylesheet::parse_css()} * * @param string $str * * @throws Exception */ private function _parse_css($str) { $str = trim($str); // Destroy comments and remove HTML comments $css = preg_replace([ "'/\*.*?\*/'si", "/^<!--/", "/-->$/" ], "", $str); // FIXME: handle '{' within strings, e.g. [attr="string {}"] // Something more legible: $re = "/\s* # Skip leading whitespace \n" . "( @([^\s{]+)\s*([^{;]*) (?:;|({)) )? # Match @rules followed by ';' or '{' \n" . "(?(1) # Only parse sub-sections if we're in an @rule... \n" . " (?(4) # ...and if there was a leading '{' \n" . " \s*( (?:(?>[^{}]+) ({)? # Parse rulesets and individual @page rules \n" . " (?(6) (?>[^}]*) }) \s*)+? \n" . " ) \n" . " }) # Balancing '}' \n" . "| # Branch to match regular rules (not preceded by '@') \n" . "([^{]*{[^}]*})) # Parse normal rulesets \n" . "/xs"; if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) { // An error occurred throw new Exception("Error parsing css file: preg_match_all() failed."); } // After matching, the array indices are set as follows: // // [0] => complete text of match // [1] => contains '@import ...;' or '@media {' if applicable // [2] => text following @ for cases where [1] is set // [3] => media types or full text following '@import ...;' // [4] => '{', if present // [5] => rulesets within media rules // [6] => '{', within media rules // [7] => individual rules, outside of media rules // $media_query_regex = "/(?:((only|not)?\s*(" . implode("|", self::$VALID_MEDIA_TYPES) . "))|(\s*\(\s*((?:(min|max)-)?([\w\-]+))\s*(?:\:\s*(.*?)\s*)?\)))/isx"; //Helpers::pre_r($matches); foreach ($matches as $match) { $match[2] = trim($match[2]); if ($match[2] !== "") { // Handle @rules switch ($match[2]) { case "import": $this->_parse_import($match[3]); break; case "media": $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType(); $media_queries = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3]))); foreach ($media_queries as $media_query) { if (in_array($media_query, $acceptedmedia)) { //if we have a media type match go ahead and parse the stylesheet $this->_parse_sections($match[5]); break; } elseif (!in_array($media_query, self::$VALID_MEDIA_TYPES)) { // otherwise conditionally parse the stylesheet assuming there are parseable media queries if (preg_match_all($media_query_regex, $media_query, $media_query_matches, PREG_SET_ORDER) !== false) { $mq = []; foreach ($media_query_matches as $media_query_match) { if (empty($media_query_match[1]) === false) { $media_query_feature = strtolower($media_query_match[3]); $media_query_value = strtolower($media_query_match[2]); $mq[] = [$media_query_feature, $media_query_value]; } elseif (empty($media_query_match[4]) === false) { $media_query_feature = strtolower($media_query_match[5]); $media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null); $mq[] = [$media_query_feature, $media_query_value]; } } $this->_parse_sections($match[5], $mq); break; } } } break; case "page": //This handles @page to be applied to page oriented media //Note: This has a reduced syntax: //@page { margin:1cm; color:blue; } //Not a sequence of styles like a full.css, but only the properties //of a single style, which is applied to the very first "root" frame before //processing other styles of the frame. //Working properties: // margin (for margin around edge of paper) // font-family (default font of pages) // color (default text color of pages) //Non working properties: // border // padding // background-color //Todo:Reason is unknown //Other properties (like further font or border attributes) not tested. //If a border or background color around each paper sheet is desired, //assign it to the <body> tag, possibly only for the css of the correct media type. // If the page has a name, skip the style. $page_selector = trim($match[3]); $key = null; switch ($page_selector) { case "": $key = "base"; break; case ":left": case ":right": case ":odd": case ":even": /** @noinspection PhpMissingBreakStatementInspection */ case ":first": $key = $page_selector; break; default: break 2; } // Store the style for later... if (empty($this->_page_styles[$key])) { $this->_page_styles[$key] = $this->_parse_properties($match[5]); } else { $this->_page_styles[$key]->merge($this->_parse_properties($match[5])); } break; case "font-face": $this->_parse_font_face($match[5]); break; default: // ignore everything else break; } continue; } if ($match[7] !== "") { $this->_parse_sections($match[7]); } } } /** * Resolve the given `url()` declaration to an absolute URL. * * @param string|null $val The declaration to resolve in the context of the stylesheet. * @return string The resolved URL, or `none`, if the value is `none`, * invalid, or points to a non-existent local file. */ public function resolve_url($val): string { $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); $parsed_url = "none"; if (empty($val) || $val === "none") { $path = "none"; } elseif (mb_strpos($val, "url") === false) { $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none } else { $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val)); // Resolve the url now in the context of the current stylesheet $path = Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $val); if ($path === null) { $path = "none"; } } if ($DEBUGCSS) { $parsed_url = Helpers::explode_url($path); print "<pre>[_image\n"; print_r($parsed_url); print $this->_protocol . "\n" . $this->_base_path . "\n" . $path . "\n"; print "_image]</pre>"; } return $path; } /** * parse @import{} sections * * @param string $url the url of the imported CSS file */ private function _parse_import($url) { $arr = preg_split("/[\s\n,]/", $url, -1, PREG_SPLIT_NO_EMPTY); $url = array_shift($arr); $accept = false; if (count($arr) > 0) { $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType(); // @import url media_type [media_type...] foreach ($arr as $type) { if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) { $accept = true; break; } } } else { // unconditional import $accept = true; } if ($accept) { // Store our current base url properties in case the new url is elsewhere $protocol = $this->_protocol; $host = $this->_base_host; $path = $this->_base_path; // $url = str_replace(array('"',"url", "(", ")"), "", $url); // If the protocol is php, assume that we will import using file:// // $url = Helpers::build_url($protocol === "php://" ? "file://" : $protocol, $host, $path, $url); // Above does not work for subfolders and absolute urls. // Todo: As above, do we need to replace php or file to an empty protocol for local files? if (($url = $this->resolve_url($url)) !== "none") { $this->load_css_file($url); } // Restore the current base url $this->_protocol = $protocol; $this->_base_host = $host; $this->_base_path = $path; } } /** * parse @font-face{} sections * http://www.w3.org/TR/css3-fonts/#the-font-face-rule * * @param string $str CSS @font-face rules */ private function _parse_font_face($str) { $descriptors = $this->_parse_properties($str); preg_match_all("/(url|local)\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\)\s*(format\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\))?/i", $descriptors->src, $src); $valid_sources = []; foreach ($src[0] as $i => $value) { $source = [ "local" => strtolower($src[1][$i]) === "local", "uri" => $src[2][$i], "format" => strtolower($src[4][$i]), "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]), ]; if (!$source["local"] && in_array($source["format"], ["", "truetype"]) && $source["path"] !== null) { $valid_sources[] = $source; } } // No valid sources if (empty($valid_sources)) { return; } $style = [ "family" => $descriptors->get_font_family_raw(), "weight" => $descriptors->font_weight, "style" => $descriptors->font_style, ]; $this->getFontMetrics()->registerFont($style, $valid_sources[0]["path"], $this->_dompdf->getHttpContext()); } /** * parse regular CSS blocks * * _parse_properties() creates a new Style object based on the provided * CSS rules. * * @param string $str CSS rules * @return Style */ private function _parse_properties($str) { $properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str); $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); if ($DEBUGCSS) { print '[_parse_properties'; } // Create the style $style = new Style($this, Stylesheet::ORIG_AUTHOR); foreach ($properties as $prop) { // If the $prop contains an url, the regex may be wrong // @todo: fix the regex so that it works every time /*if (strpos($prop, "url(") === false) { if (preg_match("/([a-z-]+)\s*:\s*[^:]+$/i", $prop, $m)) $prop = $m[0]; }*/ //A css property can have " ! important" appended (whitespace optional) //strip this off to decode core of the property correctly. /* Instead of short code, prefer the typical case with fast code $important = preg_match("/(.*?)!\s*important/",$prop,$match); if ( $important ) { $prop = $match[1]; } $prop = trim($prop); */ if ($DEBUGCSS) print '('; $important = false; $prop = trim($prop); if (substr($prop, -9) === 'important') { $prop_tmp = rtrim(substr($prop, 0, -9)); if (substr($prop_tmp, -1) === '!') { $prop = rtrim(substr($prop_tmp, 0, -1)); $important = true; } } if ($prop === "") { if ($DEBUGCSS) print 'empty)'; continue; } $i = mb_strpos($prop, ":"); if ($i === false) { if ($DEBUGCSS) print 'novalue' . $prop . ')'; continue; } $prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i))); $value = ltrim(mb_substr($prop, $i + 1)); if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')'; $style->set_prop($prop_name, $value, $important, false); } if ($DEBUGCSS) print '_parse_properties]'; return $style; } /** * parse selector + rulesets * * @param string $str CSS selectors and rulesets * @param array $media_queries */ private function _parse_sections($str, $media_queries = []) { // Pre-process selectors: collapse all whitespace and strip whitespace // around '>', '.', ':', '+', '~', '#' $patterns = ["/\s+/", "/\s+([>.:+~#])\s+/"]; $replacements = [" ", "\\1"]; $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); $sections = explode("}", $str); if ($DEBUGCSS) print '[_parse_sections'; foreach ($sections as $sect) { $i = mb_strpos($sect, "{"); if ($i === false) { continue; } if ($DEBUGCSS) print '[section'; $selector_str = preg_replace($patterns, $replacements, mb_substr($sect, 0, $i)); $selectors = preg_split("/,(?![^\(]*\))/", $selector_str, 0, PREG_SPLIT_NO_EMPTY); $style = $this->_parse_properties(trim(mb_substr($sect, $i + 1))); // Assign it to the selected elements foreach ($selectors as $selector) { $selector = trim($selector); if ($selector === "") { if ($DEBUGCSS) print '#empty#'; continue; } if ($DEBUGCSS) print '#' . $selector . '#'; //if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; } //FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here if (count($media_queries) > 0) { $style->set_media_queries($media_queries); } $this->add_style($selector, $style); } if ($DEBUGCSS) { print 'section]'; } } if ($DEBUGCSS) { print "_parse_sections]\n"; } } /** * @return string */ public function getDefaultStylesheet() { $options = $this->_dompdf->getOptions(); $rootDir = realpath($options->getRootDir()); return Helpers::build_url("file://", "", $rootDir, $rootDir . self::DEFAULT_STYLESHEET); } /** * @param FontMetrics $fontMetrics * @return $this */ public function setFontMetrics(FontMetrics $fontMetrics) { $this->fontMetrics = $fontMetrics; return $this; } /** * @return FontMetrics */ public function getFontMetrics() { return $this->fontMetrics; } /** * dumps the entire stylesheet as a string * * Generates a string of each selector and associated style in the * Stylesheet. Useful for debugging. * * @return string */ function __toString() { $str = ""; foreach ($this->_styles as $selector => $selector_styles) { foreach ($selector_styles as $style) { $str .= "$selector => " . $style->__toString() . "\n"; } } return $str; } } dompdf/src/Canvas.php 0000644 00000034374 15024772104 0010545 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; /** * Main rendering interface * * Currently {@link Dompdf\Adapter\CPDF}, {@link Dompdf\Adapter\PDFLib}, and {@link Dompdf\Adapter\GD} * implement this interface. * * Implementations should measure x and y increasing to the left and down, * respectively, with the origin in the top left corner. Implementations * are free to use a unit other than points for length, but I can't * guarantee that the results will look any good. * * @package dompdf */ interface Canvas { /** * @param string|float[] $paper The paper size to use as either a standard paper size (see {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}) * or an array of the form `[x1, y1, x2, y2]` (typically `[0, 0, width, height]`). * @param string $orientation The paper orientation, either `portrait` or `landscape`. * @param Dompdf|null $dompdf The Dompdf instance. */ public function __construct($paper = "letter", string $orientation = "portrait", ?Dompdf $dompdf = null); /** * @return Dompdf */ function get_dompdf(); /** * Returns the current page number * * @return int */ function get_page_number(); /** * Returns the total number of pages in the document * * @return int */ function get_page_count(); /** * Sets the total number of pages * * @param int $count */ function set_page_count($count); /** * Draws a line from x1,y1 to x2,y2 * * See {@link Cpdf::setLineStyle()} for a description of the format of the * $style and $cap parameters (aka dash and cap). * * @param float $x1 * @param float $y1 * @param float $x2 * @param float $y2 * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $width * @param array $style * @param string $cap `butt`, `round`, or `square` */ function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt"); /** * Draws an arc * * See {@link Cpdf::setLineStyle()} for a description of the format of the * $style and $cap parameters (aka dash and cap). * * @param float $x X coordinate of the arc * @param float $y Y coordinate of the arc * @param float $r1 Radius 1 * @param float $r2 Radius 2 * @param float $astart Start angle in degrees * @param float $aend End angle in degrees * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $width * @param array $style * @param string $cap `butt`, `round`, or `square` */ function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt"); /** * Draws a rectangle at x1,y1 with width w and height h * * See {@link Cpdf::setLineStyle()} for a description of the format of the * $style and $cap parameters (aka dash and cap). * * @param float $x1 * @param float $y1 * @param float $w * @param float $h * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $width * @param array $style * @param string $cap `butt`, `round`, or `square` */ function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt"); /** * Draws a filled rectangle at x1,y1 with width w and height h * * @param float $x1 * @param float $y1 * @param float $w * @param float $h * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 */ function filled_rectangle($x1, $y1, $w, $h, $color); /** * Starts a clipping rectangle at x1,y1 with width w and height h * * @param float $x1 * @param float $y1 * @param float $w * @param float $h */ function clipping_rectangle($x1, $y1, $w, $h); /** * Starts a rounded clipping rectangle at x1,y1 with width w and height h * * @param float $x1 * @param float $y1 * @param float $w * @param float $h * @param float $tl * @param float $tr * @param float $br * @param float $bl */ function clipping_roundrectangle($x1, $y1, $w, $h, $tl, $tr, $br, $bl); /** * Starts a clipping polygon * * @param float[] $points */ public function clipping_polygon(array $points): void; /** * Ends the last clipping shape */ function clipping_end(); /** * Processes a callback on every page. * * The callback function receives the four parameters `int $pageNumber`, * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in * that order. * * This function can be used to add page numbers to all pages after the * first one, for example. * * @param callable $callback The callback function to process on every page */ public function page_script($callback): void; /** * Writes text at the specified x and y coordinates on every page. * * The strings '{PAGE_NUM}' and '{PAGE_COUNT}' are automatically replaced * with their current values. * * @param float $x * @param float $y * @param string $text The text to write * @param string $font The font file to use * @param float $size The font size, in points * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $word_space Word spacing adjustment * @param float $char_space Char spacing adjustment * @param float $angle Angle to write the text at, measured clockwise starting from the x-axis */ public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0); /** * Draws a line at the specified coordinates on every page. * * @param float $x1 * @param float $y1 * @param float $x2 * @param float $y2 * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $width * @param array $style */ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = []); /** * Save current state */ function save(); /** * Restore last state */ function restore(); /** * Rotate * * @param float $angle angle in degrees for counter-clockwise rotation * @param float $x Origin abscissa * @param float $y Origin ordinate */ function rotate($angle, $x, $y); /** * Skew * * @param float $angle_x * @param float $angle_y * @param float $x Origin abscissa * @param float $y Origin ordinate */ function skew($angle_x, $angle_y, $x, $y); /** * Scale * * @param float $s_x scaling factor for width as percent * @param float $s_y scaling factor for height as percent * @param float $x Origin abscissa * @param float $y Origin ordinate */ function scale($s_x, $s_y, $x, $y); /** * Translate * * @param float $t_x movement to the right * @param float $t_y movement to the bottom */ function translate($t_x, $t_y); /** * Transform * * @param float $a * @param float $b * @param float $c * @param float $d * @param float $e * @param float $f */ function transform($a, $b, $c, $d, $e, $f); /** * Draws a polygon * * The polygon is formed by joining all the points stored in the $points * array. $points has the following structure: * ``` * array(0 => x1, * 1 => y1, * 2 => x2, * 3 => y2, * ... * ); * ``` * * See {@link Cpdf::setLineStyle()} for a description of the format of the * $style parameter (aka dash). * * @param array $points * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $width * @param array $style * @param bool $fill Fills the polygon if true */ function polygon($points, $color, $width = null, $style = [], $fill = false); /** * Draws a circle at $x,$y with radius $r * * See {@link Cpdf::setLineStyle()} for a description of the format of the * $style parameter (aka dash). * * @param float $x * @param float $y * @param float $r * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $width * @param array $style * @param bool $fill Fills the circle if true */ function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false); /** * Add an image to the pdf. * * The image is placed at the specified x and y coordinates with the * given width and height. * * @param string $img The path to the image * @param float $x X position * @param float $y Y position * @param float $w Width * @param float $h Height * @param string $resolution The resolution of the image */ function image($img, $x, $y, $w, $h, $resolution = "normal"); /** * Writes text at the specified x and y coordinates * * @param float $x * @param float $y * @param string $text The text to write * @param string $font The font file to use * @param float $size The font size, in points * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]` * where r, g, b, and alpha are float values between 0 and 1 * @param float $word_space Word spacing adjustment * @param float $char_space Char spacing adjustment * @param float $angle Angle to write the text at, measured clockwise starting from the x-axis */ function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0); /** * Add a named destination (similar to <a name="foo">...</a> in html) * * @param string $anchorname The name of the named destination */ function add_named_dest($anchorname); /** * Add a link to the pdf * * @param string $url The url to link to * @param float $x The x position of the link * @param float $y The y position of the link * @param float $width The width of the link * @param float $height The height of the link */ function add_link($url, $x, $y, $width, $height); /** * Add meta information to the PDF. * * @param string $label Label of the value (Creator, Producer, etc.) * @param string $value The text to set */ public function add_info(string $label, string $value): void; /** * Calculates text size, in points * * @param string $text The text to be sized * @param string $font The font file to use * @param float $size The font size, in points * @param float $word_spacing Word spacing, if any * @param float $char_spacing Char spacing, if any * * @return float */ function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0); /** * Calculates font height, in points * * @param string $font The font file to use * @param float $size The font size, in points * * @return float */ function get_font_height($font, $size); /** * Returns the font x-height, in points * * @param string $font The font file to use * @param float $size The font size, in points * * @return float */ //function get_font_x_height($font, $size); /** * Calculates font baseline, in points * * @param string $font The font file to use * @param float $size The font size, in points * * @return float */ function get_font_baseline($font, $size); /** * Returns the PDF's width in points * * @return float */ function get_width(); /** * Returns the PDF's height in points * * @return float */ function get_height(); /** * Sets the opacity * * @param float $opacity * @param string $mode */ public function set_opacity(float $opacity, string $mode = "Normal"): void; /** * Sets the default view * * @param string $view * 'XYZ' left, top, zoom * 'Fit' * 'FitH' top * 'FitV' left * 'FitR' left,bottom,right * 'FitB' * 'FitBH' top * 'FitBV' left * @param array $options */ function set_default_view($view, $options = []); /** * @param string $code */ function javascript($code); /** * Starts a new page * * Subsequent drawing operations will appear on the new page. */ function new_page(); /** * Streams the PDF to the client. * * @param string $filename The filename to present to the client. * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1). */ function stream($filename, $options = []); /** * Returns the PDF as a string. * * @param array $options Associative array: 'compress' => 1 or 0 (default 1). * * @return string */ function output($options = []); } dompdf/src/Dompdf.php 0000644 00000116601 15024772104 0010535 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; use DOMDocument; use DOMNode; use Dompdf\Adapter\CPDF; use DOMXPath; use Dompdf\Frame\Factory; use Dompdf\Frame\FrameTree; use Dompdf\Image\Cache; use Dompdf\Css\Stylesheet; use Dompdf\Helpers; use Masterminds\HTML5; /** * Dompdf - PHP5 HTML to PDF renderer * * Dompdf loads HTML and does its best to render it as a PDF. It gets its * name from the new DomDocument PHP5 extension. Source HTML is first * parsed by a DomDocument object. Dompdf takes the resulting DOM tree and * attaches a {@link Frame} object to each node. {@link Frame} objects store * positioning and layout information and each has a reference to a {@link * Style} object. * * Style information is loaded and parsed (see {@link Stylesheet}) and is * applied to the frames in the tree by using XPath. CSS selectors are * converted into XPath queries, and the computed {@link Style} objects are * applied to the {@link Frame}s. * * {@link Frame}s are then decorated (in the design pattern sense of the * word) based on their CSS display property ({@link * http://www.w3.org/TR/CSS21/visuren.html#propdef-display}). * Frame_Decorators augment the basic {@link Frame} class by adding * additional properties and methods specific to the particular type of * {@link Frame}. For example, in the CSS layout model, block frames * (display: block;) contain line boxes that are usually filled with text or * other inline frames. The Block therefore adds a $lines * property as well as methods to add {@link Frame}s to lines and to add * additional lines. {@link Frame}s also are attached to specific * AbstractPositioner and {@link AbstractFrameReflower} objects that contain the * positioining and layout algorithm for a specific type of frame, * respectively. This is an application of the Strategy pattern. * * Layout, or reflow, proceeds recursively (post-order) starting at the root * of the document. Space constraints (containing block width & height) are * pushed down, and resolved positions and sizes bubble up. Thus, every * {@link Frame} in the document tree is traversed once (except for tables * which use a two-pass layout algorithm). If you are interested in the * details, see the reflow() method of the Reflower classes. * * Rendering is relatively straightforward once layout is complete. {@link * Frame}s are rendered using an adapted {@link Cpdf} class, originally * written by Wayne Munro, http://www.ros.co.nz/pdf/. (Some performance * related changes have been made to the original {@link Cpdf} class, and * the {@link Dompdf\Adapter\CPDF} class provides a simple, stateless interface to * PDF generation.) PDFLib support has now also been added, via the {@link * Dompdf\Adapter\PDFLib}. * * * @package dompdf */ class Dompdf { /** * Version string for dompdf * * @var string */ private $version = 'dompdf'; /** * DomDocument representing the HTML document * * @var DOMDocument */ private $dom; /** * FrameTree derived from the DOM tree * * @var FrameTree */ private $tree; /** * Stylesheet for the document * * @var Stylesheet */ private $css; /** * Actual PDF renderer * * @var Canvas */ private $canvas; /** * Desired paper size ('letter', 'legal', 'A4', etc.) * * @var string|float[] */ private $paperSize; /** * Paper orientation ('portrait' or 'landscape') * * @var string */ private $paperOrientation = "portrait"; /** * Callbacks on new page and new element * * @var array */ private $callbacks = []; /** * Experimental caching capability * * @var string */ private $cacheId; /** * Base hostname * * Used for relative paths/urls * @var string */ private $baseHost = ""; /** * Absolute base path * * Used for relative paths/urls * @var string */ private $basePath = ""; /** * Protocol used to request file (file://, http://, etc) * * @var string */ private $protocol = ""; /** * The system's locale * * @var string */ private $systemLocale = null; /** * The system's mbstring internal encoding * * @var string */ private $mbstringEncoding = null; /** * The system's PCRE JIT configuration * * @var string */ private $pcreJit = null; /** * The default view of the PDF in the viewer * * @var string */ private $defaultView = "Fit"; /** * The default view options of the PDF in the viewer * * @var array */ private $defaultViewOptions = []; /** * Tells whether the DOM document is in quirksmode (experimental) * * @var bool */ private $quirksmode = false; /** * Local file extension whitelist * * File extensions supported by dompdf for local files. * * @var array */ private $allowedLocalFileExtensions = ["htm", "html"]; /** * @var array */ private $messages = []; /** * @var Options */ private $options; /** * @var FontMetrics */ private $fontMetrics; /** * The list of built-in fonts * * @var array * @deprecated */ public static $native_fonts = [ "courier", "courier-bold", "courier-oblique", "courier-boldoblique", "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique", "times-roman", "times-bold", "times-italic", "times-bolditalic", "symbol", "zapfdinbats" ]; /** * The list of built-in fonts * * @var array */ public static $nativeFonts = [ "courier", "courier-bold", "courier-oblique", "courier-boldoblique", "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique", "times-roman", "times-bold", "times-italic", "times-bolditalic", "symbol", "zapfdinbats" ]; /** * Class constructor * * @param Options|array|null $options */ public function __construct($options = null) { if (isset($options) && $options instanceof Options) { $this->setOptions($options); } elseif (is_array($options)) { $this->setOptions(new Options($options)); } else { $this->setOptions(new Options()); } $versionFile = realpath(__DIR__ . '/../VERSION'); if (($version = file_get_contents($versionFile)) !== false) { $version = trim($version); if ($version !== '$Format:<%h>$') { $this->version = sprintf('dompdf %s', $version); } } $this->setPhpConfig(); $this->paperSize = $this->options->getDefaultPaperSize(); $this->paperOrientation = $this->options->getDefaultPaperOrientation(); $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation); $this->fontMetrics = new FontMetrics($this->canvas, $this->options); $this->css = new Stylesheet($this); $this->restorePhpConfig(); } /** * Save the system's existing locale, PCRE JIT, and MBString encoding * configuration and configure the system for Dompdf processing */ private function setPhpConfig() { if (sprintf('%.1f', 1.0) !== '1.0') { $this->systemLocale = setlocale(LC_NUMERIC, "0"); setlocale(LC_NUMERIC, "C"); } $this->pcreJit = @ini_get('pcre.jit'); @ini_set('pcre.jit', '0'); $this->mbstringEncoding = mb_internal_encoding(); mb_internal_encoding('UTF-8'); } /** * Restore the system's locale configuration */ private function restorePhpConfig() { if ($this->systemLocale !== null) { setlocale(LC_NUMERIC, $this->systemLocale); $this->systemLocale = null; } if ($this->pcreJit !== null) { @ini_set('pcre.jit', $this->pcreJit); $this->pcreJit = null; } if ($this->mbstringEncoding !== null) { mb_internal_encoding($this->mbstringEncoding); $this->mbstringEncoding = null; } } /** * @param $file * @deprecated */ public function load_html_file($file) { $this->loadHtmlFile($file); } /** * Loads an HTML file * Parse errors are stored in the global array _dompdf_warnings. * * @param string $file a filename or url to load * @param string $encoding Encoding of $file * * @throws Exception */ public function loadHtmlFile($file, $encoding = null) { $this->setPhpConfig(); if (!$this->protocol && !$this->baseHost && !$this->basePath) { [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file); } $protocol = strtolower($this->protocol); $uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file); $allowed_protocols = $this->options->getAllowedProtocols(); if (!array_key_exists($protocol, $allowed_protocols)) { throw new Exception("Permission denied on $file. The communication protocol is not supported."); } if ($protocol === "file://") { $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION)); if (!in_array($ext, $this->allowedLocalFileExtensions)) { throw new Exception("Permission denied on $file: The file extension is forbidden."); } } foreach ($allowed_protocols[$protocol]["rules"] as $rule) { [$result, $message] = $rule($uri); if (!$result) { throw new Exception("Error loading $file: $message"); } } [$contents, $http_response_header] = Helpers::getFileContent($uri, $this->options->getHttpContext()); if ($contents === null) { throw new Exception("File '$file' not found."); } // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/ if (isset($http_response_header)) { foreach ($http_response_header as $_header) { if (preg_match("@Content-Type:\s*[\w/]+;\s*?charset=([^\s]+)@i", $_header, $matches)) { $encoding = strtoupper($matches[1]); break; } } } $this->restorePhpConfig(); $this->loadHtml($contents, $encoding); } /** * @param string $str * @param string $encoding * @deprecated */ public function load_html($str, $encoding = null) { $this->loadHtml($str, $encoding); } public function loadDOM($doc, $quirksmode = false) { // Remove #text children nodes in nodes that shouldn't have $tag_names = ["html", "head", "table", "tbody", "thead", "tfoot", "tr"]; foreach ($tag_names as $tag_name) { $nodes = $doc->getElementsByTagName($tag_name); foreach ($nodes as $node) { self::removeTextNodes($node); } } $this->dom = $doc; $this->quirksmode = $quirksmode; $this->tree = new FrameTree($this->dom); } /** * Loads an HTML string * Parse errors are stored in the global array _dompdf_warnings. * * @param string $str HTML text to load * @param string $encoding Encoding of $str */ public function loadHtml($str, $encoding = null) { $this->setPhpConfig(); // Determine character encoding when $encoding parameter not used if ($encoding === null) { mb_detect_order('auto'); if (($encoding = mb_detect_encoding($str, null, true)) === false) { //"auto" is expanded to "ASCII,JIS,UTF-8,EUC-JP,SJIS" $encoding = "auto"; } } if (in_array(strtoupper($encoding), array('UTF-8','UTF8')) === false) { $str = mb_convert_encoding($str, 'UTF-8', $encoding); //Update encoding after converting $encoding = 'UTF-8'; } $metatags = [ '@<meta\s+http-equiv="Content-Type"\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))?@i', '@<meta\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))"?\s+http-equiv="Content-Type"@i', '@<meta [^>]*charset\s*=\s*["\']?\s*([^"\' ]+)@i', ]; foreach ($metatags as $metatag) { if (preg_match($metatag, $str, $matches)) { if (isset($matches[1]) && in_array($matches[1], mb_list_encodings())) { $document_encoding = $matches[1]; break; } } } if (isset($document_encoding) && in_array(strtoupper($document_encoding), ['UTF-8','UTF8']) === false) { $str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str); } elseif (isset($document_encoding) === false && strpos($str, '<head>') !== false) { $str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str); } elseif (isset($document_encoding) === false) { $str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' . $str; } // remove BOM mark from UTF-8, it's treated as document text by DOMDocument // FIXME: roll this into the encoding detection using UTF-8/16/32 BOM (http://us2.php.net/manual/en/function.mb-detect-encoding.php#91051)? if (substr($str, 0, 3) == chr(0xEF) . chr(0xBB) . chr(0xBF)) { $str = substr($str, 3); } // Store parsing warnings as messages set_error_handler([Helpers::class, 'record_warnings']); try { // @todo Take the quirksmode into account // https://quirks.spec.whatwg.org/ // http://hsivonen.iki.fi/doctype/ $quirksmode = false; $html5 = new HTML5(['encoding' => $encoding, 'disable_html_ns' => true]); $dom = $html5->loadHTML($str); // extra step to normalize the HTML document structure // see Masterminds/html5-php#166 $doc = new DOMDocument("1.0", $encoding); $doc->preserveWhiteSpace = true; $doc->loadHTML($html5->saveHTML($dom), LIBXML_NOWARNING | LIBXML_NOERROR); $this->loadDOM($doc, $quirksmode); } finally { restore_error_handler(); $this->restorePhpConfig(); } } /** * @param DOMNode $node * @deprecated */ public static function remove_text_nodes(DOMNode $node) { self::removeTextNodes($node); } /** * @param DOMNode $node */ public static function removeTextNodes(DOMNode $node) { $children = []; for ($i = 0; $i < $node->childNodes->length; $i++) { $child = $node->childNodes->item($i); if ($child->nodeName === "#text") { $children[] = $child; } } foreach ($children as $child) { $node->removeChild($child); } } /** * Builds the {@link FrameTree}, loads any CSS and applies the styles to * the {@link FrameTree} */ private function processHtml() { $this->tree->build_tree(); $this->css->load_css_file($this->css->getDefaultStylesheet(), Stylesheet::ORIG_UA); $acceptedmedia = Stylesheet::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->options->getDefaultMediaType(); // <base href="" /> /** @var \DOMElement|null */ $baseNode = $this->dom->getElementsByTagName("base")->item(0); $baseHref = $baseNode ? $baseNode->getAttribute("href") : ""; if ($baseHref !== "") { [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($baseHref); } // Set the base path of the Stylesheet to that of the file being processed $this->css->set_protocol($this->protocol); $this->css->set_host($this->baseHost); $this->css->set_base_path($this->basePath); // Get all the stylesheets so that they are processed in document order $xpath = new DOMXPath($this->dom); $stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']"); /** @var \DOMElement $tag */ foreach ($stylesheets as $tag) { switch (strtolower($tag->nodeName)) { // load <link rel="STYLESHEET" ... /> tags case "link": if (mb_strtolower(stripos($tag->getAttribute("rel"), "stylesheet") !== false) || // may be "appendix stylesheet" mb_strtolower($tag->getAttribute("type")) === "text/css" ) { //Check if the css file is for an accepted media type //media not given then always valid $formedialist = preg_split("/[\s\n,]/", $tag->getAttribute("media"), -1, PREG_SPLIT_NO_EMPTY); if (count($formedialist) > 0) { $accept = false; foreach ($formedialist as $type) { if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) { $accept = true; break; } } if (!$accept) { //found at least one mediatype, but none of the accepted ones //Skip this css file. break; } } $url = $tag->getAttribute("href"); $url = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $url); if ($url !== null) { $this->css->load_css_file($url, Stylesheet::ORIG_AUTHOR); } } break; // load <style> tags case "style": // Accept all <style> tags by default (note this is contrary to W3C // HTML 4.0 spec: // http://www.w3.org/TR/REC-html40/present/styles.html#adef-media // which states that the default media type is 'screen' if ($tag->hasAttributes() && ($media = $tag->getAttribute("media")) && !in_array($media, $acceptedmedia) ) { break; } $css = ""; if ($tag->hasChildNodes()) { $child = $tag->firstChild; while ($child) { $css .= $child->nodeValue; // Handle <style><!-- blah --></style> $child = $child->nextSibling; } } else { $css = $tag->nodeValue; } // Set the base path of the Stylesheet to that of the file being processed $this->css->set_protocol($this->protocol); $this->css->set_host($this->baseHost); $this->css->set_base_path($this->basePath); $this->css->load_css($css, Stylesheet::ORIG_AUTHOR); break; } // Set the base path of the Stylesheet to that of the file being processed $this->css->set_protocol($this->protocol); $this->css->set_host($this->baseHost); $this->css->set_base_path($this->basePath); } } /** * @param string $cacheId * @deprecated */ public function enable_caching($cacheId) { $this->enableCaching($cacheId); } /** * Enable experimental caching capability * * @param string $cacheId */ public function enableCaching($cacheId) { $this->cacheId = $cacheId; } /** * @param string $value * @return bool * @deprecated */ public function parse_default_view($value) { return $this->parseDefaultView($value); } /** * @param string $value * @return bool */ public function parseDefaultView($value) { $valid = ["XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"]; $options = preg_split("/\s*,\s*/", trim($value)); $defaultView = array_shift($options); if (!in_array($defaultView, $valid)) { return false; } $this->setDefaultView($defaultView, $options); return true; } /** * Renders the HTML to PDF */ public function render() { $this->setPhpConfig(); $logOutputFile = $this->options->getLogOutputFile(); if ($logOutputFile) { if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) { touch($logOutputFile); } $startTime = microtime(true); if (is_writable($logOutputFile)) { ob_start(); } } $this->processHtml(); $this->css->apply_styles($this->tree); // @page style rules : size, margins $pageStyles = $this->css->get_page_styles(); $basePageStyle = $pageStyles["base"]; unset($pageStyles["base"]); foreach ($pageStyles as $pageStyle) { $pageStyle->inherit($basePageStyle); } // Set paper size if defined via CSS if (is_array($basePageStyle->size)) { [$width, $height] = $basePageStyle->size; $this->setPaper([0, 0, $width, $height]); } // Create a new canvas instance if the current one does not match the // desired paper size $canvasWidth = $this->canvas->get_width(); $canvasHeight = $this->canvas->get_height(); $size = $this->getPaperSize(); if ($canvasWidth !== $size[2] || $canvasHeight !== $size[3]) { $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation); $this->fontMetrics->setCanvas($this->canvas); } $canvas = $this->canvas; $root_frame = $this->tree->get_root(); $root = Factory::decorate_root($root_frame, $this); foreach ($this->tree as $frame) { if ($frame === $root_frame) { continue; } Factory::decorate_frame($frame, $this, $root); } // Add meta information $title = $this->dom->getElementsByTagName("title"); if ($title->length) { $canvas->add_info("Title", trim($title->item(0)->nodeValue)); } $metas = $this->dom->getElementsByTagName("meta"); $labels = [ "author" => "Author", "keywords" => "Keywords", "description" => "Subject", ]; /** @var \DOMElement $meta */ foreach ($metas as $meta) { $name = mb_strtolower($meta->getAttribute("name")); $value = trim($meta->getAttribute("content")); if (isset($labels[$name])) { $canvas->add_info($labels[$name], $value); continue; } if ($name === "dompdf.view" && $this->parseDefaultView($value)) { $canvas->set_default_view($this->defaultView, $this->defaultViewOptions); } } $root->set_containing_block(0, 0, $canvas->get_width(), $canvas->get_height()); $root->set_renderer(new Renderer($this)); // This is where the magic happens: $root->reflow(); if (isset($this->callbacks["end_document"])) { $fs = $this->callbacks["end_document"]; foreach ($fs as $f) { $canvas->page_script($f); } } // Clean up cached images if (!$this->options->getDebugKeepTemp()) { Cache::clear($this->options->getDebugPng()); } global $_dompdf_warnings, $_dompdf_show_warnings; if ($_dompdf_show_warnings && isset($_dompdf_warnings)) { echo '<b>Dompdf Warnings</b><br><pre>'; foreach ($_dompdf_warnings as $msg) { echo $msg . "\n"; } if ($canvas instanceof CPDF) { echo $canvas->get_cpdf()->messages; } echo '</pre>'; flush(); } if ($logOutputFile && is_writable($logOutputFile)) { $this->writeLog($logOutputFile, $startTime); ob_end_clean(); } $this->restorePhpConfig(); } /** * Writes the output buffer in the log file * * @param string $logOutputFile * @param float $startTime */ private function writeLog(string $logOutputFile, float $startTime): void { $frames = Frame::$ID_COUNTER; $memory = memory_get_peak_usage(true) / 1024; $time = (microtime(true) - $startTime) * 1000; $out = sprintf( "<span style='color: #000' title='Frames'>%6d</span>" . "<span style='color: #009' title='Memory'>%10.2f KB</span>" . "<span style='color: #900' title='Time'>%10.2f ms</span>" . "<span title='Quirksmode'> " . ($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") . "</span><br />", $frames, $memory, $time); $out .= ob_get_contents(); ob_clean(); file_put_contents($logOutputFile, $out); } /** * Add meta information to the PDF after rendering. * * @deprecated */ public function add_info($label, $value) { $this->addInfo($label, $value); } /** * Add meta information to the PDF after rendering. * * @param string $label Label of the value (Creator, Producer, etc.) * @param string $value The text to set */ public function addInfo(string $label, string $value): void { $this->canvas->add_info($label, $value); } /** * Streams the PDF to the client. * * The file will open a download dialog by default. The options * parameter controls the output. Accepted options (array keys) are: * * 'compress' = > 1 (=default) or 0: * Apply content stream compression * * 'Attachment' => 1 (=default) or 0: * Set the 'Content-Disposition:' HTTP header to 'attachment' * (thereby causing the browser to open a download dialog) * * @param string $filename the name of the streamed file * @param array $options header options (see above) */ public function stream($filename = "document.pdf", $options = []) { $this->setPhpConfig(); $this->canvas->stream($filename, $options); $this->restorePhpConfig(); } /** * Returns the PDF as a string. * * The options parameter controls the output. Accepted options are: * * 'compress' = > 1 or 0 - apply content stream compression, this is * on (1) by default * * @param array $options options (see above) * * @return string|null */ public function output($options = []) { $this->setPhpConfig(); $output = $this->canvas->output($options); $this->restorePhpConfig(); return $output; } /** * @return string * @deprecated */ public function output_html() { return $this->outputHtml(); } /** * Returns the underlying HTML document as a string * * @return string */ public function outputHtml() { return $this->dom->saveHTML(); } /** * Get the dompdf option value * * @param string $key * @return mixed * @deprecated */ public function get_option($key) { return $this->options->get($key); } /** * @param string $key * @param mixed $value * @return $this * @deprecated */ public function set_option($key, $value) { $this->options->set($key, $value); return $this; } /** * @param array $options * @return $this * @deprecated */ public function set_options(array $options) { $this->options->set($options); return $this; } /** * @param string $size * @param string $orientation * @deprecated */ public function set_paper($size, $orientation = "portrait") { $this->setPaper($size, $orientation); } /** * Sets the paper size & orientation * * @param string|float[] $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES} * @param string $orientation 'portrait' or 'landscape' * @return $this */ public function setPaper($size, string $orientation = "portrait"): self { $this->paperSize = $size; $this->paperOrientation = $orientation; return $this; } /** * Gets the paper size * * @return float[] A four-element float array */ public function getPaperSize(): array { $paper = $this->paperSize; $orientation = $this->paperOrientation; if (is_array($paper)) { $size = array_map("floatval", $paper); } else { $paper = strtolower($paper); $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"]; } if (strtolower($orientation) === "landscape") { [$size[2], $size[3]] = [$size[3], $size[2]]; } return $size; } /** * Gets the paper orientation * * @return string Either "portrait" or "landscape" */ public function getPaperOrientation(): string { return $this->paperOrientation; } /** * @param FrameTree $tree * @return $this */ public function setTree(FrameTree $tree) { $this->tree = $tree; return $this; } /** * @return FrameTree * @deprecated */ public function get_tree() { return $this->getTree(); } /** * Returns the underlying {@link FrameTree} object * * @return FrameTree */ public function getTree() { return $this->tree; } /** * @param string $protocol * @return $this * @deprecated */ public function set_protocol($protocol) { return $this->setProtocol($protocol); } /** * Sets the protocol to use * FIXME validate these * * @param string $protocol * @return $this */ public function setProtocol(string $protocol) { $this->protocol = $protocol; return $this; } /** * @return string * @deprecated */ public function get_protocol() { return $this->getProtocol(); } /** * Returns the protocol in use * * @return string */ public function getProtocol() { return $this->protocol; } /** * @param string $host * @deprecated */ public function set_host($host) { $this->setBaseHost($host); } /** * Sets the base hostname * * @param string $baseHost * @return $this */ public function setBaseHost(string $baseHost) { $this->baseHost = $baseHost; return $this; } /** * @return string * @deprecated */ public function get_host() { return $this->getBaseHost(); } /** * Returns the base hostname * * @return string */ public function getBaseHost() { return $this->baseHost; } /** * Sets the base path * * @param string $path * @deprecated */ public function set_base_path($path) { $this->setBasePath($path); } /** * Sets the base path * * @param string $basePath * @return $this */ public function setBasePath(string $basePath) { $this->basePath = $basePath; return $this; } /** * @return string * @deprecated */ public function get_base_path() { return $this->getBasePath(); } /** * Returns the base path * * @return string */ public function getBasePath() { return $this->basePath; } /** * @param string $default_view The default document view * @param array $options The view's options * @return $this * @deprecated */ public function set_default_view($default_view, $options) { return $this->setDefaultView($default_view, $options); } /** * Sets the default view * * @param string $defaultView The default document view * @param array $options The view's options * @return $this */ public function setDefaultView($defaultView, $options) { $this->defaultView = $defaultView; $this->defaultViewOptions = $options; return $this; } /** * @param resource $http_context * @return $this * @deprecated */ public function set_http_context($http_context) { return $this->setHttpContext($http_context); } /** * Sets the HTTP context * * @param resource|array $httpContext * @return $this */ public function setHttpContext($httpContext) { $this->options->setHttpContext($httpContext); return $this; } /** * @return resource * @deprecated */ public function get_http_context() { return $this->getHttpContext(); } /** * Returns the HTTP context * * @return resource */ public function getHttpContext() { return $this->options->getHttpContext(); } /** * Set a custom `Canvas` instance to render the document to. * * Be aware that the instance will be replaced on render if the document * defines a paper size different from the canvas. * * @param Canvas $canvas * @return $this */ public function setCanvas(Canvas $canvas) { $this->canvas = $canvas; return $this; } /** * @return Canvas * @deprecated */ public function get_canvas() { return $this->getCanvas(); } /** * Return the underlying Canvas instance (e.g. Dompdf\Adapter\CPDF, Dompdf\Adapter\GD) * * @return Canvas */ public function getCanvas() { return $this->canvas; } /** * @param Stylesheet $css * @return $this */ public function setCss(Stylesheet $css) { $this->css = $css; return $this; } /** * @return Stylesheet * @deprecated */ public function get_css() { return $this->getCss(); } /** * Returns the stylesheet * * @return Stylesheet */ public function getCss() { return $this->css; } /** * @param DOMDocument $dom * @return $this */ public function setDom(DOMDocument $dom) { $this->dom = $dom; return $this; } /** * @return DOMDocument * @deprecated */ public function get_dom() { return $this->getDom(); } /** * @return DOMDocument */ public function getDom() { return $this->dom; } /** * @param Options $options * @return $this */ public function setOptions(Options $options) { // For backwards compatibility if ($this->options && $this->options->getHttpContext() && !$options->getHttpContext()) { $options->setHttpContext($this->options->getHttpContext()); } $this->options = $options; $fontMetrics = $this->fontMetrics; if (isset($fontMetrics)) { $fontMetrics->setOptions($options); } return $this; } /** * @return Options */ public function getOptions() { return $this->options; } /** * @return array * @deprecated */ public function get_callbacks() { return $this->getCallbacks(); } /** * Returns the callbacks array * * @return array */ public function getCallbacks() { return $this->callbacks; } /** * @param array $callbacks the set of callbacks to set * @return $this * @deprecated */ public function set_callbacks($callbacks) { return $this->setCallbacks($callbacks); } /** * Define callbacks that allow modifying the document during render. * * The callbacks array should contain arrays with `event` set to a callback * event name and `f` set to a function or any other callable. * * The available callback events are: * * `begin_page_reflow`: called before page reflow * * `begin_frame`: called before a frame is rendered * * `end_frame`: called after frame rendering is complete * * `begin_page_render`: called before a page is rendered * * `end_page_render`: called after page rendering is complete * * `end_document`: called for every page after rendering is complete * * The function `f` receives three arguments `Frame $frame`, `Canvas $canvas`, * and `FontMetrics $fontMetrics` for all events but `end_document`. For * `end_document`, the function receives four arguments `int $pageNumber`, * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics` instead. * * @param array $callbacks The set of callbacks to set. * @return $this */ public function setCallbacks(array $callbacks): self { $this->callbacks = []; foreach ($callbacks as $c) { if (is_array($c) && isset($c["event"]) && isset($c["f"])) { $event = $c["event"]; $f = $c["f"]; if (is_string($event) && is_callable($f)) { $this->callbacks[$event][] = $f; } } } return $this; } /** * @return boolean * @deprecated */ public function get_quirksmode() { return $this->getQuirksmode(); } /** * Get the quirks mode * * @return boolean true if quirks mode is active */ public function getQuirksmode() { return $this->quirksmode; } /** * @param FontMetrics $fontMetrics * @return $this */ public function setFontMetrics(FontMetrics $fontMetrics) { $this->fontMetrics = $fontMetrics; return $this; } /** * @return FontMetrics */ public function getFontMetrics() { return $this->fontMetrics; } /** * PHP5 overloaded getter * Along with {@link Dompdf::__set()} __get() provides access to all * properties directly. Typically __get() is not called directly outside * of this class. * * @param string $prop * * @throws Exception * @return mixed */ function __get($prop) { switch ($prop) { case 'version': return $this->version; default: throw new Exception('Invalid property: ' . $prop); } } } dompdf/src/Image/Cache.php 0000644 00000024343 15024772104 0011352 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Image; use Dompdf\Options; use Dompdf\Helpers; use Dompdf\Exception\ImageException; /** * Static class that resolves image urls and downloads and caches * remote images if required. * * @package dompdf */ class Cache { /** * Array of downloaded images. Cached so that identical images are * not needlessly downloaded. * * @var array */ protected static $_cache = []; /** * @var array */ protected static $tempImages = []; /** * The url to the "broken image" used when images can't be loaded * * @var string */ public static $broken_image = "data:image/svg+xml;charset=utf8,%3C?xml version='1.0'?%3E%3Csvg width='64' height='64' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Crect stroke='%23666666' id='svg_1' height='60.499994' width='60.166667' y='1.666669' x='1.999998' stroke-width='1.5' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_3' y2='59.333253' x2='59.749916' y1='4.333415' x1='4.250079' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_4' y2='59.999665' x2='4.062838' y1='3.750342' x1='60.062164' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3C/g%3E%3C/svg%3E"; public static $error_message = "Image not found or type unknown"; /** * Resolve and fetch an image for use. * * @param string $url The url of the image * @param string $protocol Default protocol if none specified in $url * @param string $host Default host if none specified in $url * @param string $base_path Default path if none specified in $url * @param Options $options An instance of Dompdf\Options * * @return array An array with three elements: The local path to the image, the image * extension, and an error message if the image could not be cached */ static function resolve_url($url, $protocol, $host, $base_path, Options $options) { $tempfile = null; $resolved_url = null; $type = null; $message = null; try { $full_url = Helpers::build_url($protocol, $host, $base_path, $url); if ($full_url === null) { throw new ImageException("Unable to parse image URL $url.", E_WARNING); } $parsed_url = Helpers::explode_url($full_url); $protocol = strtolower($parsed_url["protocol"]); $is_data_uri = strpos($protocol, "data:") === 0; if (!$is_data_uri) { $allowed_protocols = $options->getAllowedProtocols(); if (!array_key_exists($protocol, $allowed_protocols)) { throw new ImageException("Permission denied on $url. The communication protocol is not supported.", E_WARNING); } foreach ($allowed_protocols[$protocol]["rules"] as $rule) { [$result, $message] = $rule($full_url); if (!$result) { throw new ImageException("Error loading $url: $message", E_WARNING); } } } if ($protocol === "file://") { $resolved_url = $full_url; } elseif (isset(self::$_cache[$full_url])) { $resolved_url = self::$_cache[$full_url]; } else { $tmp_dir = $options->getTempDir(); if (($resolved_url = @tempnam($tmp_dir, "ca_dompdf_img_")) === false) { throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING); } $tempfile = $resolved_url; $image = null; if ($is_data_uri) { if (($parsed_data_uri = Helpers::parse_data_uri($url)) !== false) { $image = $parsed_data_uri["data"]; } } else { list($image, $http_response_header) = Helpers::getFileContent($full_url, $options->getHttpContext()); } // Image not found or invalid if ($image === null) { $msg = ($is_data_uri ? "Data-URI could not be parsed" : "Image not found"); throw new ImageException($msg, E_WARNING); } if (@file_put_contents($resolved_url, $image) === false) { throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING); } self::$_cache[$full_url] = $resolved_url; } // Check if the local file is readable if (!is_readable($resolved_url) || !filesize($resolved_url)) { throw new ImageException("Image not readable or empty", E_WARNING); } list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext()); if (($width && $height && in_array($type, ["gif", "png", "jpeg", "bmp", "svg","webp"], true)) === false) { throw new ImageException("Image type unknown", E_WARNING); } if ($type === "svg") { $parser = xml_parser_create("utf-8"); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false); xml_set_element_handler( $parser, function ($parser, $name, $attributes) use ($options, $parsed_url, $full_url) { if (strtolower($name) === "image") { $attributes = array_change_key_case($attributes, CASE_LOWER); $urls = []; $urls[] = $attributes["xlink:href"] ?? ""; $urls[] = $attributes["href"] ?? ""; foreach ($urls as $url) { if (!empty($url)) { $inner_full_url = Helpers::build_url($parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $url); if ($inner_full_url === $full_url) { throw new ImageException("SVG self-reference is not allowed", E_WARNING); } [$resolved_url, $type, $message] = self::resolve_url($url, $parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $options); if (!empty($message)) { throw new ImageException("This SVG document references a restricted resource. $message", E_WARNING); } } } } }, false ); if (($fp = fopen($resolved_url, "r")) !== false) { while ($line = fread($fp, 8192)) { xml_parse($parser, $line, false); } fclose($fp); xml_parse($parser, "", true); } xml_parser_free($parser); } } catch (ImageException $e) { if ($tempfile) { unlink($tempfile); } $resolved_url = self::$broken_image; list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext()); $message = self::$error_message; Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine()); self::$_cache[$full_url] = $resolved_url; } return [$resolved_url, $type, $message]; } /** * Register a temp file for the given original image file. * * @param string $filePath The path of the original image. * @param string $tempPath The path of the temp file to register. * @param string $key An optional key to register the temp file at. */ static function addTempImage(string $filePath, string $tempPath, string $key = "default"): void { if (!isset(self::$tempImages[$filePath])) { self::$tempImages[$filePath] = []; } self::$tempImages[$filePath][$key] = $tempPath; } /** * Get the path of a temp file registered for the given original image file. * * @param string $filePath The path of the original image. * @param string $key The key the temp file is registered at. */ static function getTempImage(string $filePath, string $key = "default"): ?string { return self::$tempImages[$filePath][$key] ?? null; } /** * Unlink all cached images (i.e. temporary images either downloaded * or converted) except for the bundled "broken image" */ static function clear(bool $debugPng = false) { foreach (self::$_cache as $file) { if ($file === self::$broken_image) { continue; } if ($debugPng) { print "[clear unlink $file]"; } if (file_exists($file)) { unlink($file); } } foreach (self::$tempImages as $versions) { foreach ($versions as $file) { if ($file === self::$broken_image) { continue; } if ($debugPng) { print "[unlink temp image $file]"; } if (file_exists($file)) { unlink($file); } } } self::$_cache = []; self::$tempImages = []; } static function detect_type($file, $context = null) { list(, , $type) = Helpers::dompdf_getimagesize($file, $context); return $type; } static function is_broken($url) { return $url === self::$broken_image; } } if (file_exists(realpath(__DIR__ . "/../../lib/res/broken_image.svg"))) { Cache::$broken_image = realpath(__DIR__ . "/../../lib/res/broken_image.svg"); } dompdf/src/Exception/ImageException.php 0000644 00000001073 15024772104 0014157 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Exception; use Dompdf\Exception; /** * Image exception thrown by DOMPDF * * @package dompdf */ class ImageException extends Exception { /** * Class constructor * * @param string $message Error message * @param int $code Error code */ function __construct($message = null, $code = 0) { parent::__construct($message, $code); } } dompdf/src/Frame/FrameTreeIterator.php 0000644 00000003035 15024772104 0013736 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Frame; use Iterator; use Dompdf\Frame; /** * Pre-order Iterator * * Returns frames in preorder traversal order (parent then children) * * @package dompdf */ class FrameTreeIterator implements Iterator { /** * @var Frame */ protected $_root; /** * @var Frame[] */ protected $_stack = []; /** * @var int */ protected $_num; /** * @param Frame $root */ public function __construct(Frame $root) { $this->_stack[] = $this->_root = $root; $this->_num = 0; } public function rewind(): void { $this->_stack = [$this->_root]; $this->_num = 0; } /** * @return bool */ public function valid(): bool { return count($this->_stack) > 0; } /** * @return int */ public function key(): int { return $this->_num; } /** * @return Frame */ public function current(): Frame { return end($this->_stack); } public function next(): void { $b = array_pop($this->_stack); $this->_num++; // Push all children onto the stack in reverse order if ($c = $b->get_last_child()) { $this->_stack[] = $c; while ($c = $c->get_prev_sibling()) { $this->_stack[] = $c; } } } } dompdf/src/Frame/FrameListIterator.php 0000644 00000003630 15024772104 0013753 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Frame; use Iterator; use Dompdf\Frame; /** * Linked-list Iterator * * Returns children in order and allows for the list to change during iteration, * provided the changes occur to or after the current element. * * @package dompdf */ class FrameListIterator implements Iterator { /** * @var Frame */ protected $parent; /** * @var Frame|null */ protected $cur; /** * @var Frame|null */ protected $prev; /** * @var int */ protected $num; /** * @param Frame $frame */ public function __construct(Frame $frame) { $this->parent = $frame; $this->rewind(); } public function rewind(): void { $this->cur = $this->parent->get_first_child(); $this->prev = null; $this->num = 0; } /** * @return bool */ public function valid(): bool { return $this->cur !== null; } /** * @return int */ public function key(): int { return $this->num; } /** * @return Frame|null */ public function current(): ?Frame { return $this->cur; } public function next(): void { if ($this->cur === null) { return; } if ($this->cur->get_parent() === $this->parent) { $this->prev = $this->cur; $this->cur = $this->cur->get_next_sibling(); $this->num++; } else { // Continue from the previous child if the current frame has been // moved to another parent $this->cur = $this->prev !== null ? $this->prev->get_next_sibling() : $this->parent->get_first_child(); } } } dompdf/src/Frame/FrameTree.php 0000644 00000021454 15024772104 0012231 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Frame; use DOMDocument; use DOMNode; use DOMElement; use DOMXPath; use Dompdf\Exception; use Dompdf\Frame; use IteratorAggregate; /** * Represents an entire document as a tree of frames * * The FrameTree consists of {@link Frame} objects each tied to specific * DOMNode objects in a specific DomDocument. The FrameTree has the same * structure as the DomDocument, but adds additional capabilities for * styling and layout. * * @package dompdf */ class FrameTree implements IteratorAggregate { /** * Tags to ignore while parsing the tree * * @var array */ protected static $HIDDEN_TAGS = [ "area", "base", "basefont", "head", "style", "meta", "title", "colgroup", "noembed", "param", "#comment" ]; /** * The main DomDocument * * @see http://ca2.php.net/manual/en/ref.dom.php * @var DOMDocument */ protected $_dom; /** * The root node of the FrameTree. * * @var Frame */ protected $_root; /** * Subtrees of absolutely positioned elements * * @var array of Frames */ protected $_absolute_frames; /** * A mapping of {@link Frame} objects to DOMNode objects * * @var array */ protected $_registry; /** * Class constructor * * @param DOMDocument $dom the main DomDocument object representing the current html document */ public function __construct(DomDocument $dom) { $this->_dom = $dom; $this->_root = null; $this->_registry = []; } /** * Returns the DOMDocument object representing the current html document * * @return DOMDocument */ public function get_dom() { return $this->_dom; } /** * Returns the root frame of the tree * * @return Frame */ public function get_root() { return $this->_root; } /** * Returns a specific frame given its id * * @param string $id * * @return Frame|null */ public function get_frame($id) { return isset($this->_registry[$id]) ? $this->_registry[$id] : null; } /** * Returns a post-order iterator for all frames in the tree * * @deprecated Iterate the tree directly instead * @return FrameTreeIterator */ public function get_frames(): FrameTreeIterator { return new FrameTreeIterator($this->_root); } /** * Returns a post-order iterator for all frames in the tree * * @return FrameTreeIterator */ public function getIterator(): FrameTreeIterator { return new FrameTreeIterator($this->_root); } /** * Builds the tree */ public function build_tree() { $html = $this->_dom->getElementsByTagName("html")->item(0); if (is_null($html)) { $html = $this->_dom->firstChild; } if (is_null($html)) { throw new Exception("Requested HTML document contains no data."); } $this->fix_tables(); $this->_root = $this->_build_tree_r($html); } /** * Adds missing TBODYs around TR */ protected function fix_tables() { $xp = new DOMXPath($this->_dom); // Move table caption before the table // FIXME find a better way to deal with it... $captions = $xp->query('//table/caption'); foreach ($captions as $caption) { $table = $caption->parentNode; $table->parentNode->insertBefore($caption, $table); } $firstRows = $xp->query('//table/tr[1]'); /** @var DOMElement $tableChild */ foreach ($firstRows as $tableChild) { $tbody = $this->_dom->createElement('tbody'); $tableNode = $tableChild->parentNode; do { if ($tableChild->nodeName === 'tr') { $tmpNode = $tableChild; $tableChild = $tableChild->nextSibling; $tableNode->removeChild($tmpNode); $tbody->appendChild($tmpNode); } else { if ($tbody->hasChildNodes() === true) { $tableNode->insertBefore($tbody, $tableChild); $tbody = $this->_dom->createElement('tbody'); } $tableChild = $tableChild->nextSibling; } } while ($tableChild); if ($tbody->hasChildNodes() === true) { $tableNode->appendChild($tbody); } } } // FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes /** * Remove a child from a node * * Remove a child from a node. If the removed node results in two * adjacent #text nodes then combine them. * * @param DOMNode $node the current DOMNode being considered * @param array $children an array of nodes that are the children of $node * @param int $index index from the $children array of the node to remove */ protected function _remove_node(DOMNode $node, array &$children, $index) { $child = $children[$index]; $previousChild = $child->previousSibling; $nextChild = $child->nextSibling; $node->removeChild($child); if (isset($previousChild, $nextChild)) { if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") { $previousChild->nodeValue .= $nextChild->nodeValue; $this->_remove_node($node, $children, $index+1); } } array_splice($children, $index, 1); } /** * Recursively adds {@link Frame} objects to the tree * * Recursively build a tree of Frame objects based on a dom tree. * No layout information is calculated at this time, although the * tree may be adjusted (i.e. nodes and frames for generated content * and images may be created). * * @param DOMNode $node the current DOMNode being considered * * @return Frame */ protected function _build_tree_r(DOMNode $node) { $frame = new Frame($node); $id = $frame->get_id(); $this->_registry[$id] = $frame; if (!$node->hasChildNodes()) { return $frame; } // Store the children in an array so that the tree can be modified $children = []; $length = $node->childNodes->length; for ($i = 0; $i < $length; $i++) { $children[] = $node->childNodes->item($i); } $index = 0; // INFO: We don't advance $index if a node is removed to avoid skipping nodes while ($index < count($children)) { $child = $children[$index]; $nodeName = strtolower($child->nodeName); // Skip non-displaying nodes if (in_array($nodeName, self::$HIDDEN_TAGS)) { if ($nodeName !== "head" && $nodeName !== "style") { $this->_remove_node($node, $children, $index); } else { $index++; } continue; } // Skip empty text nodes if ($nodeName === "#text" && $child->nodeValue === "") { $this->_remove_node($node, $children, $index); continue; } // Skip empty image nodes if ($nodeName === "img" && $child->getAttribute("src") === "") { $this->_remove_node($node, $children, $index); continue; } if (is_object($child)) { $frame->append_child($this->_build_tree_r($child), false); } $index++; } return $frame; } /** * @param DOMElement $node * @param DOMElement $new_node * @param string $pos * * @return mixed */ public function insert_node(DOMElement $node, DOMElement $new_node, $pos) { if ($pos === "after" || !$node->firstChild) { $node->appendChild($new_node); } else { $node->insertBefore($new_node, $node->firstChild); } $this->_build_tree_r($new_node); $frame_id = $new_node->getAttribute("frame_id"); $frame = $this->get_frame($frame_id); $parent_id = $node->getAttribute("frame_id"); $parent = $this->get_frame($parent_id); if ($parent) { if ($pos === "before") { $parent->prepend_child($frame, false); } else { $parent->append_child($frame, false); } } return $frame_id; } } dompdf/src/Frame/Factory.php 0000644 00000020055 15024772104 0011762 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Frame; use Dompdf\Dompdf; use Dompdf\Exception; use Dompdf\Frame; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\Page as PageFrameDecorator; use Dompdf\FrameReflower\Page as PageFrameReflower; use Dompdf\Positioner\AbstractPositioner; use DOMXPath; /** * Contains frame decorating logic * * This class is responsible for assigning the correct {@link AbstractFrameDecorator}, * {@link AbstractPositioner}, and {@link AbstractFrameReflower} objects to {@link Frame} * objects. This is determined primarily by the Frame's display type, but * also by the Frame's node's type (e.g. DomElement vs. #text) * * @package dompdf */ class Factory { /** * Array of positioners for specific frame types * * @var AbstractPositioner[] */ protected static $_positioners; /** * Decorate the root Frame * * @param Frame $root The frame to decorate * @param Dompdf $dompdf The dompdf instance * * @return PageFrameDecorator */ public static function decorate_root(Frame $root, Dompdf $dompdf): PageFrameDecorator { $frame = new PageFrameDecorator($root, $dompdf); $frame->set_reflower(new PageFrameReflower($frame)); $root->set_decorator($frame); return $frame; } /** * Decorate a Frame * * @param Frame $frame The frame to decorate * @param Dompdf $dompdf The dompdf instance * @param Frame|null $root The root of the frame * * @throws Exception * @return AbstractFrameDecorator|null * FIXME: this is admittedly a little smelly... */ public static function decorate_frame(Frame $frame, Dompdf $dompdf, ?Frame $root = null): ?AbstractFrameDecorator { $style = $frame->get_style(); $display = $style->display; switch ($display) { case "block": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "inline-block": $positioner = "Inline"; $decorator = "Block"; $reflower = "Block"; break; case "inline": $positioner = "Inline"; if ($frame->is_text_node()) { $decorator = "Text"; $reflower = "Text"; } else { $decorator = "Inline"; $reflower = "Inline"; } break; case "table": $positioner = "Block"; $decorator = "Table"; $reflower = "Table"; break; case "inline-table": $positioner = "Inline"; $decorator = "Table"; $reflower = "Table"; break; case "table-row-group": case "table-header-group": case "table-footer-group": $positioner = "NullPositioner"; $decorator = "TableRowGroup"; $reflower = "TableRowGroup"; break; case "table-row": $positioner = "NullPositioner"; $decorator = "TableRow"; $reflower = "TableRow"; break; case "table-cell": $positioner = "TableCell"; $decorator = "TableCell"; $reflower = "TableCell"; break; case "list-item": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "-dompdf-list-bullet": if ($style->list_style_position === "inside") { $positioner = "Inline"; } else { $positioner = "ListBullet"; } if ($style->list_style_image !== "none") { $decorator = "ListBulletImage"; } else { $decorator = "ListBullet"; } $reflower = "ListBullet"; break; case "-dompdf-image": $positioner = "Inline"; $decorator = "Image"; $reflower = "Image"; break; case "-dompdf-br": $positioner = "Inline"; $decorator = "Inline"; $reflower = "Inline"; break; default: case "none": if ($style->_dompdf_keep !== "yes") { // Remove the node and the frame $frame->get_parent()->remove_child($frame); return null; } $positioner = "NullPositioner"; $decorator = "NullFrameDecorator"; $reflower = "NullFrameReflower"; break; } // Handle CSS position $position = $style->position; if ($position === "absolute") { $positioner = "Absolute"; } elseif ($position === "fixed") { $positioner = "Fixed"; } $node = $frame->get_node(); // Handle nodeName if ($node->nodeName === "img") { $style->set_prop("display", "-dompdf-image"); $decorator = "Image"; $reflower = "Image"; } $decorator = "Dompdf\\FrameDecorator\\$decorator"; $reflower = "Dompdf\\FrameReflower\\$reflower"; /** @var AbstractFrameDecorator $deco */ $deco = new $decorator($frame, $dompdf); $deco->set_positioner(self::getPositionerInstance($positioner)); $deco->set_reflower(new $reflower($deco, $dompdf->getFontMetrics())); if ($root) { $deco->set_root($root); } if ($display === "list-item") { // Insert a list-bullet frame $xml = $dompdf->getDom(); $bullet_node = $xml->createElement("bullet"); // arbitrary choice $b_f = new Frame($bullet_node); $node = $frame->get_node(); $parent_node = $node->parentNode; if ($parent_node && $parent_node instanceof \DOMElement) { if (!$parent_node->hasAttribute("dompdf-children-count")) { $xpath = new DOMXPath($xml); $count = $xpath->query("li", $parent_node)->length; $parent_node->setAttribute("dompdf-children-count", $count); } if (is_numeric($node->getAttribute("value"))) { $index = intval($node->getAttribute("value")); } else { if (!$parent_node->hasAttribute("dompdf-counter")) { $index = ($parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1); } else { $index = (int)$parent_node->getAttribute("dompdf-counter") + 1; } } $parent_node->setAttribute("dompdf-counter", $index); $bullet_node->setAttribute("dompdf-counter", $index); } $new_style = $dompdf->getCss()->create_style(); $new_style->set_prop("display", "-dompdf-list-bullet"); $new_style->inherit($style); $b_f->set_style($new_style); $deco->prepend_child(Factory::decorate_frame($b_f, $dompdf, $root)); } return $deco; } /** * Creates Positioners * * @param string $type Type of positioner to use * * @return AbstractPositioner */ protected static function getPositionerInstance(string $type): AbstractPositioner { if (!isset(self::$_positioners[$type])) { $class = '\\Dompdf\\Positioner\\'.$type; self::$_positioners[$type] = new $class(); } return self::$_positioners[$type]; } } dompdf/src/Helpers.php 0000644 00000113223 15024772104 0010723 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; class Helpers { /** * print_r wrapper for html/cli output * * Wraps print_r() output in < pre > tags if the current sapi is not 'cli'. * Returns the output string instead of displaying it if $return is true. * * @param mixed $mixed variable or expression to display * @param bool $return * * @return string|null */ public static function pre_r($mixed, $return = false) { if ($return) { return "<pre>" . print_r($mixed, true) . "</pre>"; } if (php_sapi_name() !== "cli") { echo "<pre>"; } print_r($mixed); if (php_sapi_name() !== "cli") { echo "</pre>"; } else { echo "\n"; } flush(); return null; } /** * builds a full url given a protocol, hostname, base path and url * * @param string $protocol * @param string $host * @param string $base_path * @param string $url * @return string * * Initially the trailing slash of $base_path was optional, and conditionally appended. * However on dynamically created sites, where the page is given as url parameter, * the base path might not end with an url. * Therefore do not append a slash, and **require** the $base_url to ending in a slash * when needed. * Vice versa, on using the local file system path of a file, make sure that the slash * is appended (o.k. also for Windows) */ public static function build_url($protocol, $host, $base_path, $url) { $protocol = mb_strtolower($protocol); if (empty($protocol)) { $protocol = "file://"; } if ($url === "") { return null; } $url_lc = mb_strtolower($url); // Is the url already fully qualified, a Data URI, or a reference to a named anchor? // File-protocol URLs may require additional processing (e.g. for URLs with a relative path) if ( ( mb_strpos($url_lc, "://") !== false && !in_array(substr($url_lc, 0, 7), ["file://", "phar://"], true) ) || mb_substr($url_lc, 0, 1) === "#" || mb_strpos($url_lc, "data:") === 0 || mb_strpos($url_lc, "mailto:") === 0 || mb_strpos($url_lc, "tel:") === 0 ) { return $url; } $res = ""; if (strpos($url_lc, "file://") === 0) { $url = substr($url, 7); $protocol = "file://"; } elseif (strpos($url_lc, "phar://") === 0) { $res = substr($url, strpos($url_lc, ".phar")+5); $url = substr($url, 7, strpos($url_lc, ".phar")-2); $protocol = "phar://"; } $ret = ""; $is_local_path = in_array($protocol, ["file://", "phar://"], true); if ($is_local_path) { //On Windows local file, an abs path can begin also with a '\' or a drive letter and colon //drive: followed by a relative path would be a drive specific default folder. //not known in php app code, treat as abs path //($url[1] !== ':' || ($url[2]!=='\\' && $url[2]!=='/')) if ($url[0] !== '/' && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || (mb_strlen($url) > 1 && $url[0] !== '\\' && $url[1] !== ':'))) { // For rel path and local access we ignore the host, and run the path through realpath() $ret .= realpath($base_path) . '/'; } $ret .= $url; $ret = preg_replace('/\?(.*)$/', "", $ret); $filepath = realpath($ret); if ($filepath === false) { return null; } $ret = "$protocol$filepath$res"; return $ret; } $ret = $protocol; // Protocol relative urls (e.g. "//example.org/style.css") if (strpos($url, '//') === 0) { $ret .= substr($url, 2); //remote urls with backslash in html/css are not really correct, but lets be genereous } elseif ($url[0] === '/' || $url[0] === '\\') { // Absolute path $ret .= $host . $url; } else { // Relative path //$base_path = $base_path !== "" ? rtrim($base_path, "/\\") . "/" : ""; $ret .= $host . $base_path . $url; } // URL should now be complete, final cleanup $parsed_url = parse_url($ret); // reproduced from https://www.php.net/manual/en/function.parse-url.php#106731 $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; $host = isset($parsed_url['host']) ? $parsed_url['host'] : ''; $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; $user = isset($parsed_url['user']) ? $parsed_url['user'] : ''; $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : ''; $pass = ($user || $pass) ? "$pass@" : ''; $path = isset($parsed_url['path']) ? $parsed_url['path'] : ''; $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; // partially reproduced from https://stackoverflow.com/a/1243431/264628 /* replace '//' or '/./' or '/foo/../' with '/' */ $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); for ($n=1; $n>0; $path=preg_replace($re, '/', $path, -1, $n)) {} $ret = "$scheme$user$pass$host$port$path$query$fragment"; return $ret; } /** * Builds a HTTP Content-Disposition header string using `$dispositionType` * and `$filename`. * * If the filename contains any characters not in the ISO-8859-1 character * set, a fallback filename will be included for clients not supporting the * `filename*` parameter. * * @param string $dispositionType * @param string $filename * @return string */ public static function buildContentDispositionHeader($dispositionType, $filename) { $encoding = mb_detect_encoding($filename); $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding); $fallbackfilename = str_replace("\"", "", $fallbackfilename); $encodedfilename = rawurlencode($filename); $contentDisposition = "Content-Disposition: $dispositionType; filename=\"$fallbackfilename\""; if ($fallbackfilename !== $filename) { $contentDisposition .= "; filename*=UTF-8''$encodedfilename"; } return $contentDisposition; } /** * Converts decimal numbers to roman numerals. * * As numbers larger than 3999 (and smaller than 1) cannot be represented in * the standard form of roman numerals, those are left in decimal form. * * See https://en.wikipedia.org/wiki/Roman_numerals#Standard_form * * @param int|string $num * * @throws Exception * @return string */ public static function dec2roman($num): string { static $ones = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"]; static $tens = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"]; static $hund = ["", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"]; static $thou = ["", "m", "mm", "mmm"]; if (!is_numeric($num)) { throw new Exception("dec2roman() requires a numeric argument."); } if ($num >= 4000 || $num <= 0) { return (string) $num; } $num = strrev((string)$num); $ret = ""; switch (mb_strlen($num)) { /** @noinspection PhpMissingBreakStatementInspection */ case 4: $ret .= $thou[$num[3]]; /** @noinspection PhpMissingBreakStatementInspection */ case 3: $ret .= $hund[$num[2]]; /** @noinspection PhpMissingBreakStatementInspection */ case 2: $ret .= $tens[$num[1]]; /** @noinspection PhpMissingBreakStatementInspection */ case 1: $ret .= $ones[$num[0]]; default: break; } return $ret; } /** * Restrict a length to the given range. * * If min > max, the result is min. * * @param float $length * @param float $min * @param float $max * * @return float */ public static function clamp(float $length, float $min, float $max): float { return max($min, min($length, $max)); } /** * Determines whether $value is a percentage or not * * @param string|float|int $value * * @return bool */ public static function is_percent($value): bool { return is_string($value) && false !== mb_strpos($value, "%"); } /** * Parses a data URI scheme * http://en.wikipedia.org/wiki/Data_URI_scheme * * @param string $data_uri The data URI to parse * * @return array|bool The result with charset, mime type and decoded data */ public static function parse_data_uri($data_uri) { if (!preg_match('/^data:(?P<mime>[a-z0-9\/+-.]+)(;charset=(?P<charset>[a-z0-9-])+)?(?P<base64>;base64)?\,(?P<data>.*)?/is', $data_uri, $match)) { return false; } $match['data'] = rawurldecode($match['data']); $result = [ 'charset' => $match['charset'] ? $match['charset'] : 'US-ASCII', 'mime' => $match['mime'] ? $match['mime'] : 'text/plain', 'data' => $match['base64'] ? base64_decode($match['data']) : $match['data'], ]; return $result; } /** * Encodes a Uniform Resource Identifier (URI) by replacing non-alphanumeric * characters with a percent (%) sign followed by two hex digits, excepting * characters in the URI reserved character set. * * Assumes that the URI is a complete URI, so does not encode reserved * characters that have special meaning in the URI. * * Simulates the encodeURI function available in JavaScript * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI * * Source: http://stackoverflow.com/q/4929584/264628 * * @param string $uri The URI to encode * @return string The original URL with special characters encoded */ public static function encodeURI($uri) { $unescaped = [ '%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~', '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')' ]; $reserved = [ '%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':', '%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$' ]; $score = [ '%23'=>'#' ]; return strtr(rawurlencode(rawurldecode($uri)), array_merge($reserved, $unescaped, $score)); } /** * Decoder for RLE8 compression in windows bitmaps * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp * * @param string $str Data to decode * @param int $width Image width * * @return string */ public static function rle8_decode($str, $width) { $lineWidth = $width + (3 - ($width - 1) % 4); $out = ''; $cnt = strlen($str); for ($i = 0; $i < $cnt; $i++) { $o = ord($str[$i]); switch ($o) { case 0: # ESCAPE $i++; switch (ord($str[$i])) { case 0: # NEW LINE $padCnt = $lineWidth - strlen($out) % $lineWidth; if ($padCnt < $lineWidth) { $out .= str_repeat(chr(0), $padCnt); # pad line } break; case 1: # END OF FILE $padCnt = $lineWidth - strlen($out) % $lineWidth; if ($padCnt < $lineWidth) { $out .= str_repeat(chr(0), $padCnt); # pad line } break 3; case 2: # DELTA $i += 2; break; default: # ABSOLUTE MODE $num = ord($str[$i]); for ($j = 0; $j < $num; $j++) { $out .= $str[++$i]; } if ($num % 2) { $i++; } } break; default: $out .= str_repeat($str[++$i], $o); } } return $out; } /** * Decoder for RLE4 compression in windows bitmaps * see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp * * @param string $str Data to decode * @param int $width Image width * * @return string */ public static function rle4_decode($str, $width) { $w = floor($width / 2) + ($width % 2); $lineWidth = $w + (3 - (($width - 1) / 2) % 4); $pixels = []; $cnt = strlen($str); $c = 0; for ($i = 0; $i < $cnt; $i++) { $o = ord($str[$i]); switch ($o) { case 0: # ESCAPE $i++; switch (ord($str[$i])) { case 0: # NEW LINE while (count($pixels) % $lineWidth != 0) { $pixels[] = 0; } break; case 1: # END OF FILE while (count($pixels) % $lineWidth != 0) { $pixels[] = 0; } break 3; case 2: # DELTA $i += 2; break; default: # ABSOLUTE MODE $num = ord($str[$i]); for ($j = 0; $j < $num; $j++) { if ($j % 2 == 0) { $c = ord($str[++$i]); $pixels[] = ($c & 240) >> 4; } else { $pixels[] = $c & 15; } } if ($num % 2 == 0) { $i++; } } break; default: $c = ord($str[++$i]); for ($j = 0; $j < $o; $j++) { $pixels[] = ($j % 2 == 0 ? ($c & 240) >> 4 : $c & 15); } } } $out = ''; if (count($pixels) % 2) { $pixels[] = 0; } $cnt = count($pixels) / 2; for ($i = 0; $i < $cnt; $i++) { $out .= chr(16 * $pixels[2 * $i] + $pixels[2 * $i + 1]); } return $out; } /** * parse a full url or pathname and return an array(protocol, host, path, * file + query + fragment) * * @param string $url * @return array */ public static function explode_url($url) { $protocol = ""; $host = ""; $path = ""; $file = ""; $res = ""; $arr = parse_url($url); if ( isset($arr["scheme"]) ) { $arr["scheme"] = mb_strtolower($arr["scheme"]); } if (isset($arr["scheme"]) && $arr["scheme"] !== "file" && $arr["scheme"] !== "phar" && strlen($arr["scheme"]) > 1) { $protocol = $arr["scheme"] . "://"; if (isset($arr["user"])) { $host .= $arr["user"]; if (isset($arr["pass"])) { $host .= ":" . $arr["pass"]; } $host .= "@"; } if (isset($arr["host"])) { $host .= $arr["host"]; } if (isset($arr["port"])) { $host .= ":" . $arr["port"]; } if (isset($arr["path"]) && $arr["path"] !== "") { // Do we have a trailing slash? if ($arr["path"][mb_strlen($arr["path"]) - 1] === "/") { $path = $arr["path"]; $file = ""; } else { $path = rtrim(dirname($arr["path"]), '/\\') . "/"; $file = basename($arr["path"]); } } if (isset($arr["query"])) { $file .= "?" . $arr["query"]; } if (isset($arr["fragment"])) { $file .= "#" . $arr["fragment"]; } } else { $protocol = ""; $host = ""; // localhost, really $i = mb_stripos($url, "://"); if ($i !== false) { $protocol = mb_strtolower(mb_substr($url, 0, $i + 3)); $url = mb_substr($url, $i + 3); } else { $protocol = "file://"; } if ($protocol === "phar://") { $res = substr($url, stripos($url, ".phar")+5); $url = substr($url, 7, stripos($url, ".phar")-2); } $file = basename($url); $path = dirname($url) . "/"; } $ret = [$protocol, $host, $path, $file, "protocol" => $protocol, "host" => $host, "path" => $path, "file" => $file, "resource" => $res]; return $ret; } /** * Print debug messages * * @param string $type The type of debug messages to print * @param string $msg The message to show */ public static function dompdf_debug($type, $msg) { global $_DOMPDF_DEBUG_TYPES, $_dompdf_show_warnings, $_dompdf_debug; if (isset($_DOMPDF_DEBUG_TYPES[$type]) && ($_dompdf_show_warnings || $_dompdf_debug)) { $arr = debug_backtrace(); echo basename($arr[0]["file"]) . " (" . $arr[0]["line"] . "): " . $arr[1]["function"] . ": "; Helpers::pre_r($msg); } } /** * Stores warnings in an array for display later * This function allows warnings generated by the DomDocument parser * and CSS loader ({@link Stylesheet}) to be captured and displayed * later. Without this function, errors are displayed immediately and * PDF streaming is impossible. * @see http://www.php.net/manual/en/function.set-error_handler.php * * @param int $errno * @param string $errstr * @param string $errfile * @param string $errline * * @throws Exception */ public static function record_warnings($errno, $errstr, $errfile, $errline) { // Not a warning or notice if (!($errno & (E_WARNING | E_NOTICE | E_USER_NOTICE | E_USER_WARNING | E_STRICT | E_DEPRECATED | E_USER_DEPRECATED))) { throw new Exception($errstr . " $errno"); } global $_dompdf_warnings; global $_dompdf_show_warnings; if ($_dompdf_show_warnings) { echo $errstr . "\n"; } $_dompdf_warnings[] = $errstr; } /** * @param $c * @return bool|string */ public static function unichr($c) { if ($c <= 0x7F) { return chr($c); } elseif ($c <= 0x7FF) { return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F); } elseif ($c <= 0xFFFF) { return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F) . chr(0x80 | $c & 0x3F); } elseif ($c <= 0x10FFFF) { return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F) . chr(0x80 | $c >> 6 & 0x3F) . chr(0x80 | $c & 0x3F); } return false; } /** * Converts a CMYK color to RGB * * @param float|float[] $c * @param float $m * @param float $y * @param float $k * * @return float[] */ public static function cmyk_to_rgb($c, $m = null, $y = null, $k = null) { if (is_array($c)) { [$c, $m, $y, $k] = $c; } $c *= 255; $m *= 255; $y *= 255; $k *= 255; $r = (1 - round(2.55 * ($c + $k))); $g = (1 - round(2.55 * ($m + $k))); $b = (1 - round(2.55 * ($y + $k))); if ($r < 0) { $r = 0; } if ($g < 0) { $g = 0; } if ($b < 0) { $b = 0; } return [ $r, $g, $b, "r" => $r, "g" => $g, "b" => $b ]; } /** * getimagesize doesn't give a good size for 32bit BMP image v5 * * @param string $filename * @param resource $context * @return array An array of three elements: width and height as * `float|int`, and image type as `string|null`. */ public static function dompdf_getimagesize($filename, $context = null) { static $cache = []; if (isset($cache[$filename])) { return $cache[$filename]; } [$width, $height, $type] = getimagesize($filename); // Custom types $types = [ IMAGETYPE_JPEG => "jpeg", IMAGETYPE_GIF => "gif", IMAGETYPE_BMP => "bmp", IMAGETYPE_PNG => "png", IMAGETYPE_WEBP => "webp", ]; $type = $types[$type] ?? null; if ($width == null || $height == null) { [$data] = Helpers::getFileContent($filename, $context); if ($data !== null) { if (substr($data, 0, 2) === "BM") { $meta = unpack("vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight", $data); $width = (int) $meta["width"]; $height = (int) $meta["height"]; $type = "bmp"; } elseif (strpos($data, "<svg") !== false) { $doc = new \Svg\Document(); $doc->loadFile($filename); [$width, $height] = $doc->getDimensions(); $width = (float) $width; $height = (float) $height; $type = "svg"; } } } return $cache[$filename] = [$width ?? 0, $height ?? 0, $type]; } /** * Credit goes to mgutt * http://www.programmierer-forum.de/function-imagecreatefrombmp-welche-variante-laeuft-t143137.htm * Modified by Fabien Menager to support RGB555 BMP format */ public static function imagecreatefrombmp($filename, $context = null) { if (!function_exists("imagecreatetruecolor")) { trigger_error("The PHP GD extension is required, but is not installed.", E_ERROR); return false; } // version 1.00 if (!($fh = fopen($filename, 'rb'))) { trigger_error('imagecreatefrombmp: Can not open ' . $filename, E_USER_WARNING); return false; } $bytes_read = 0; // read file header $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14)); // check for bitmap if ($meta['type'] != 19778) { trigger_error('imagecreatefrombmp: ' . $filename . ' is not a bitmap!', E_USER_WARNING); return false; } // read image header $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40)); $bytes_read += 40; // read additional bitfield header if ($meta['compression'] == 3) { $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12)); $bytes_read += 12; } // set bytes and padding $meta['bytes'] = $meta['bits'] / 8; $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4))); if ($meta['decal'] == 4) { $meta['decal'] = 0; } // obtain imagesize if ($meta['imagesize'] < 1) { $meta['imagesize'] = $meta['filesize'] - $meta['offset']; // in rare cases filesize is equal to offset so we need to read physical size if ($meta['imagesize'] < 1) { $meta['imagesize'] = @filesize($filename) - $meta['offset']; if ($meta['imagesize'] < 1) { trigger_error('imagecreatefrombmp: Can not obtain filesize of ' . $filename . '!', E_USER_WARNING); return false; } } } // calculate colors $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors']; // read color palette $palette = []; if ($meta['bits'] < 16) { $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4)); // in rare cases the color value is signed if ($palette[1] < 0) { foreach ($palette as $i => $color) { $palette[$i] = $color + 16777216; } } } // ignore extra bitmap headers if ($meta['headersize'] > $bytes_read) { fread($fh, $meta['headersize'] - $bytes_read); } // create gd image $im = imagecreatetruecolor($meta['width'], $meta['height']); $data = fread($fh, $meta['imagesize']); // uncompress data switch ($meta['compression']) { case 1: $data = Helpers::rle8_decode($data, $meta['width']); break; case 2: $data = Helpers::rle4_decode($data, $meta['width']); break; } $p = 0; $vide = chr(0); $y = $meta['height'] - 1; $error = 'imagecreatefrombmp: ' . $filename . ' has not enough data!'; // loop through the image data beginning with the lower left corner while ($y >= 0) { $x = 0; while ($x < $meta['width']) { switch ($meta['bits']) { case 32: case 24: if (!($part = substr($data, $p, 3 /*$meta['bytes']*/))) { trigger_error($error, E_USER_WARNING); return $im; } $color = unpack('V', $part . $vide); break; case 16: if (!($part = substr($data, $p, 2 /*$meta['bytes']*/))) { trigger_error($error, E_USER_WARNING); return $im; } $color = unpack('v', $part); if (empty($meta['rMask']) || $meta['rMask'] != 0xf800) { $color[1] = (($color[1] & 0x7c00) >> 7) * 65536 + (($color[1] & 0x03e0) >> 2) * 256 + (($color[1] & 0x001f) << 3); // 555 } else { $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3); // 565 } break; case 8: $color = unpack('n', $vide . substr($data, $p, 1)); $color[1] = $palette[$color[1] + 1]; break; case 4: $color = unpack('n', $vide . substr($data, floor($p), 1)); $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F; $color[1] = $palette[$color[1] + 1]; break; case 1: $color = unpack('n', $vide . substr($data, floor($p), 1)); switch (($p * 8) % 8) { case 0: $color[1] = $color[1] >> 7; break; case 1: $color[1] = ($color[1] & 0x40) >> 6; break; case 2: $color[1] = ($color[1] & 0x20) >> 5; break; case 3: $color[1] = ($color[1] & 0x10) >> 4; break; case 4: $color[1] = ($color[1] & 0x8) >> 3; break; case 5: $color[1] = ($color[1] & 0x4) >> 2; break; case 6: $color[1] = ($color[1] & 0x2) >> 1; break; case 7: $color[1] = ($color[1] & 0x1); break; } $color[1] = $palette[$color[1] + 1]; break; default: trigger_error('imagecreatefrombmp: ' . $filename . ' has ' . $meta['bits'] . ' bits and this is not supported!', E_USER_WARNING); return false; } imagesetpixel($im, $x, $y, $color[1]); $x++; $p += $meta['bytes']; } $y--; $p += $meta['decal']; } fclose($fh); return $im; } /** * Gets the content of the file at the specified path using one of * the following methods, in preferential order: * - file_get_contents: if allow_url_fopen is true or the file is local * - curl: if allow_url_fopen is false and curl is available * * @param string $uri * @param resource $context * @param int $offset * @param int $maxlen * @return string[] */ public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null) { $content = null; $headers = null; [$protocol] = Helpers::explode_url($uri); $is_local_path = in_array(strtolower($protocol), ["", "file://", "phar://"], true); $can_use_curl = in_array(strtolower($protocol), ["http://", "https://"], true); set_error_handler([self::class, 'record_warnings']); try { if ($is_local_path || ini_get('allow_url_fopen') || !$can_use_curl) { if ($is_local_path === false) { $uri = Helpers::encodeURI($uri); } if (isset($maxlen)) { $result = file_get_contents($uri, false, $context, $offset, $maxlen); } else { $result = file_get_contents($uri, false, $context, $offset); } if ($result !== false) { $content = $result; } if (isset($http_response_header)) { $headers = $http_response_header; } } elseif ($can_use_curl && function_exists('curl_exec')) { $curl = curl_init($uri); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HEADER, true); if ($offset > 0) { curl_setopt($curl, CURLOPT_RESUME_FROM, $offset); } if ($maxlen > 0) { curl_setopt($curl, CURLOPT_BUFFERSIZE, 128); curl_setopt($curl, CURLOPT_NOPROGRESS, false); curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, function ($res, $download_size_total, $download_size, $upload_size_total, $upload_size) use ($maxlen) { return ($download_size > $maxlen) ? 1 : 0; }); } $context_options = []; if (!is_null($context)) { $context_options = stream_context_get_options($context); } foreach ($context_options as $stream => $options) { foreach ($options as $option => $value) { $key = strtolower($stream) . ":" . strtolower($option); switch ($key) { case "curl:curl_verify_ssl_host": curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, !$value ? 0 : 2); break; case "curl:max_redirects": curl_setopt($curl, CURLOPT_MAXREDIRS, $value); break; case "http:follow_location": curl_setopt($curl, CURLOPT_FOLLOWLOCATION, $value); break; case "http:header": if (is_string($value)) { curl_setopt($curl, CURLOPT_HTTPHEADER, [$value]); } else { curl_setopt($curl, CURLOPT_HTTPHEADER, $value); } break; case "http:timeout": curl_setopt($curl, CURLOPT_TIMEOUT, $value); break; case "http:user_agent": curl_setopt($curl, CURLOPT_USERAGENT, $value); break; case "curl:curl_verify_ssl_peer": case "ssl:verify_peer": curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $value); break; } } } $data = curl_exec($curl); if ($data !== false && !curl_errno($curl)) { switch ($http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE)) { case 200: $raw_headers = substr($data, 0, curl_getinfo($curl, CURLINFO_HEADER_SIZE)); $headers = preg_split("/[\n\r]+/", trim($raw_headers)); $content = substr($data, curl_getinfo($curl, CURLINFO_HEADER_SIZE)); break; } } curl_close($curl); } } finally { restore_error_handler(); } return [$content, $headers]; } /** * @param string $str * @return string */ public static function mb_ucwords(string $str): string { $max_len = mb_strlen($str); if ($max_len === 1) { return mb_strtoupper($str); } $str = mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1); foreach ([' ', '.', ',', '!', '?', '-', '+'] as $s) { $pos = 0; while (($pos = mb_strpos($str, $s, $pos)) !== false) { $pos++; // Nothing to do if the separator is the last char of the string if ($pos !== false && $pos < $max_len) { // If the char we want to upper is the last char there is nothing to append behind if ($pos + 1 < $max_len) { $str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1)) . mb_substr($str, $pos + 1); } else { $str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1)); } } } } return $str; } /** * Check whether two lengths should be considered equal, accounting for * inaccuracies in float computation. * * The implementation relies on the fact that we are neither dealing with * very large, nor with very small numbers in layout. Adapted from * https://floating-point-gui.de/errors/comparison/. * * @param float $a * @param float $b * * @return bool */ public static function lengthEqual(float $a, float $b): bool { // The epsilon results in a precision of at least: // * 7 decimal digits at around 1 // * 4 decimal digits at around 1000 (around the size of common paper formats) // * 2 decimal digits at around 100,000 (100,000pt ~ 35.28m) static $epsilon = 1e-8; static $almostZero = 1e-12; $diff = abs($a - $b); if ($a === $b || $diff < $almostZero) { return true; } return $diff < $epsilon * max(abs($a), abs($b)); } /** * Check `$a < $b`, accounting for inaccuracies in float computation. */ public static function lengthLess(float $a, float $b): bool { return $a < $b && !self::lengthEqual($a, $b); } /** * Check `$a <= $b`, accounting for inaccuracies in float computation. */ public static function lengthLessOrEqual(float $a, float $b): bool { return $a <= $b || self::lengthEqual($a, $b); } /** * Check `$a > $b`, accounting for inaccuracies in float computation. */ public static function lengthGreater(float $a, float $b): bool { return $a > $b && !self::lengthEqual($a, $b); } /** * Check `$a >= $b`, accounting for inaccuracies in float computation. */ public static function lengthGreaterOrEqual(float $a, float $b): bool { return $a >= $b || self::lengthEqual($a, $b); } } dompdf/src/JavascriptEmbedder.php 0000644 00000001641 15024772104 0013057 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; /** * Embeds Javascript into the PDF document * * @package dompdf */ class JavascriptEmbedder { /** * @var Dompdf */ protected $_dompdf; /** * JavascriptEmbedder constructor. * * @param Dompdf $dompdf */ public function __construct(Dompdf $dompdf) { $this->_dompdf = $dompdf; } /** * @param $script */ public function insert($script) { $this->_dompdf->getCanvas()->javascript($script); } /** * @param Frame $frame */ public function render(Frame $frame) { if (!$this->_dompdf->getOptions()->getIsJavascriptEnabled()) { return; } $this->insert($frame->get_node()->nodeValue); } } dompdf/src/Frame.php 0000644 00000074337 15024772104 0010367 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; use Dompdf\Css\Style; use Dompdf\Frame\FrameListIterator; /** * The main Frame class * * This class represents a single HTML element. This class stores * positioning information as well as containing block location and * dimensions. Style information for the element is stored in a {@link * Style} object. Tree structure is maintained via the parent & children * links. * * @package dompdf */ class Frame { const WS_TEXT = 1; const WS_SPACE = 2; /** * The DOMElement or DOMText object this frame represents * * @var \DOMElement|\DOMText */ protected $_node; /** * Unique identifier for this frame. Used to reference this frame * via the node. * * @var int */ protected $_id; /** * Unique id counter * * @var int */ public static $ID_COUNTER = 0; /*protected*/ /** * This frame's calculated style * * @var Style */ protected $_style; /** * This frame's parent in the document tree. * * @var Frame */ protected $_parent; /** * This frame's first child. All children are handled as a * doubly-linked list. * * @var Frame */ protected $_first_child; /** * This frame's last child. * * @var Frame */ protected $_last_child; /** * This frame's previous sibling in the document tree. * * @var Frame */ protected $_prev_sibling; /** * This frame's next sibling in the document tree. * * @var Frame */ protected $_next_sibling; /** * This frame's containing block (used in layout): array(x, y, w, h) * * @var float[] */ protected $_containing_block; /** * Position on the page of the top-left corner of the margin box of * this frame: array(x,y) * * @var float[] */ protected $_position; /** * Absolute opacity of this frame * * @var float */ protected $_opacity; /** * This frame's decorator * * @var FrameDecorator\AbstractFrameDecorator */ protected $_decorator; /** * This frame's containing line box * * @var LineBox|null */ protected $_containing_line; /** * @var array */ protected $_is_cache = []; /** * Tells whether the frame was already pushed to the next page * * @var bool */ public $_already_pushed = false; /** * @var bool */ public $_float_next_line = false; /** * @var int */ public static $_ws_state = self::WS_SPACE; /** * Class constructor * * @param \DOMNode $node the DOMNode this frame represents */ public function __construct(\DOMNode $node) { $this->_node = $node; $this->_parent = null; $this->_first_child = null; $this->_last_child = null; $this->_prev_sibling = $this->_next_sibling = null; $this->_style = null; $this->_containing_block = [ "x" => null, "y" => null, "w" => null, "h" => null, ]; $this->_containing_block[0] =& $this->_containing_block["x"]; $this->_containing_block[1] =& $this->_containing_block["y"]; $this->_containing_block[2] =& $this->_containing_block["w"]; $this->_containing_block[3] =& $this->_containing_block["h"]; $this->_position = [ "x" => null, "y" => null, ]; $this->_position[0] =& $this->_position["x"]; $this->_position[1] =& $this->_position["y"]; $this->_opacity = 1.0; $this->_decorator = null; $this->set_id(self::$ID_COUNTER++); } /** * WIP : preprocessing to remove all the unused whitespace */ protected function ws_trim() { if ($this->ws_keep()) { return; } if (self::$_ws_state === self::WS_SPACE) { $node = $this->_node; if ($node->nodeName === "#text" && !empty($node->nodeValue)) { $node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", trim($node->nodeValue)); self::$_ws_state = self::WS_TEXT; } } } /** * @return bool */ protected function ws_keep() { $whitespace = $this->get_style()->white_space; return in_array($whitespace, ["pre", "pre-wrap", "pre-line"]); } /** * @return bool */ protected function ws_is_text() { $node = $this->get_node(); if ($node->nodeName === "img") { return true; } if (!$this->is_in_flow()) { return false; } if ($this->is_text_node()) { return trim($node->nodeValue) !== ""; } return true; } /** * "Destructor": forcibly free all references held by this frame * * @param bool $recursive if true, call dispose on all children */ public function dispose($recursive = false) { if ($recursive) { while ($child = $this->_first_child) { $child->dispose(true); } } // Remove this frame from the tree if ($this->_prev_sibling) { $this->_prev_sibling->_next_sibling = $this->_next_sibling; } if ($this->_next_sibling) { $this->_next_sibling->_prev_sibling = $this->_prev_sibling; } if ($this->_parent && $this->_parent->_first_child === $this) { $this->_parent->_first_child = $this->_next_sibling; } if ($this->_parent && $this->_parent->_last_child === $this) { $this->_parent->_last_child = $this->_prev_sibling; } if ($this->_parent) { $this->_parent->get_node()->removeChild($this->_node); } $this->_style = null; unset($this->_style); } /** * Re-initialize the frame */ public function reset() { $this->_position["x"] = null; $this->_position["y"] = null; $this->_containing_block["x"] = null; $this->_containing_block["y"] = null; $this->_containing_block["w"] = null; $this->_containing_block["h"] = null; $this->_style->reset(); } /** * @return \DOMElement|\DOMText */ public function get_node() { return $this->_node; } /** * @return int */ public function get_id() { return $this->_id; } /** * @return Style */ public function get_style() { return $this->_style; } /** * @deprecated * @return Style */ public function get_original_style() { return $this->_style; } /** * @return Frame */ public function get_parent() { return $this->_parent; } /** * @return FrameDecorator\AbstractFrameDecorator */ public function get_decorator() { return $this->_decorator; } /** * @return Frame */ public function get_first_child() { return $this->_first_child; } /** * @return Frame */ public function get_last_child() { return $this->_last_child; } /** * @return Frame */ public function get_prev_sibling() { return $this->_prev_sibling; } /** * @return Frame */ public function get_next_sibling() { return $this->_next_sibling; } /** * @return FrameListIterator */ public function get_children(): FrameListIterator { return new FrameListIterator($this); } // Layout property accessors /** * Containing block dimensions * * @param string|null $i The key of the wanted containing block's dimension (x, y, w, h) * * @return float[]|float */ public function get_containing_block($i = null) { if (isset($i)) { return $this->_containing_block[$i]; } return $this->_containing_block; } /** * Block position * * @param string|null $i The key of the wanted position value (x, y) * * @return float[]|float */ public function get_position($i = null) { if (isset($i)) { return $this->_position[$i]; } return $this->_position; } //........................................................................ /** * Return the width of the margin box of the frame, in pt. Meaningless * unless the width has been calculated properly. * * @return float */ public function get_margin_width(): float { $style = $this->_style; return (float)$style->length_in_pt([ $style->width, $style->margin_left, $style->margin_right, $style->border_left_width, $style->border_right_width, $style->padding_left, $style->padding_right ], $this->_containing_block["w"]); } /** * Return the height of the margin box of the frame, in pt. Meaningless * unless the height has been calculated properly. * * @return float */ public function get_margin_height(): float { $style = $this->_style; return (float)$style->length_in_pt( [ $style->height, (float)$style->length_in_pt( [ $style->border_top_width, $style->border_bottom_width, $style->margin_top, $style->margin_bottom, $style->padding_top, $style->padding_bottom ], $this->_containing_block["w"] ) ], $this->_containing_block["h"] ); } /** * Return the content box (x,y,w,h) of the frame. * * Width and height might be reported as 0 if they have not been resolved * yet. * * @return float[] */ public function get_content_box(): array { $style = $this->_style; $cb = $this->_containing_block; $x = $this->_position["x"] + (float)$style->length_in_pt( [ $style->margin_left, $style->border_left_width, $style->padding_left ], $cb["w"] ); $y = $this->_position["y"] + (float)$style->length_in_pt( [ $style->margin_top, $style->border_top_width, $style->padding_top ], $cb["w"] ); $w = (float)$style->length_in_pt($style->width, $cb["w"]); $h = (float)$style->length_in_pt($style->height, $cb["h"]); return [0 => $x, "x" => $x, 1 => $y, "y" => $y, 2 => $w, "w" => $w, 3 => $h, "h" => $h]; } /** * Return the padding box (x,y,w,h) of the frame. * * Width and height might be reported as 0 if they have not been resolved * yet. * * @return float[] */ public function get_padding_box(): array { $style = $this->_style; $cb = $this->_containing_block; $x = $this->_position["x"] + (float)$style->length_in_pt( [ $style->margin_left, $style->border_left_width ], $cb["w"] ); $y = $this->_position["y"] + (float)$style->length_in_pt( [ $style->margin_top, $style->border_top_width ], $cb["h"] ); $w = (float)$style->length_in_pt( [ $style->padding_left, $style->width, $style->padding_right ], $cb["w"] ); $h = (float)$style->length_in_pt( [ $style->padding_top, $style->padding_bottom, $style->length_in_pt($style->height, $cb["h"]) ], $cb["w"] ); return [0 => $x, "x" => $x, 1 => $y, "y" => $y, 2 => $w, "w" => $w, 3 => $h, "h" => $h]; } /** * Return the border box of the frame. * * Width and height might be reported as 0 if they have not been resolved * yet. * * @return float[] */ public function get_border_box(): array { $style = $this->_style; $cb = $this->_containing_block; $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]); $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]); $w = (float)$style->length_in_pt( [ $style->border_left_width, $style->padding_left, $style->width, $style->padding_right, $style->border_right_width ], $cb["w"] ); $h = (float)$style->length_in_pt( [ $style->border_top_width, $style->padding_top, $style->padding_bottom, $style->border_bottom_width, $style->length_in_pt($style->height, $cb["h"]) ], $cb["w"] ); return [0 => $x, "x" => $x, 1 => $y, "y" => $y, 2 => $w, "w" => $w, 3 => $h, "h" => $h]; } /** * @param float|null $opacity * * @return float */ public function get_opacity(?float $opacity = null): float { if ($opacity !== null) { $this->set_opacity($opacity); } return $this->_opacity; } /** * @return LineBox|null */ public function &get_containing_line() { return $this->_containing_line; } //........................................................................ // Set methods /** * @param int $id */ public function set_id($id) { $this->_id = $id; // We can only set attributes of DOMElement objects (nodeType == 1). // Since these are the only objects that we can assign CSS rules to, // this shortcoming is okay. if ($this->_node->nodeType == XML_ELEMENT_NODE) { $this->_node->setAttribute("frame_id", $id); } } /** * @param Style $style */ public function set_style(Style $style): void { // $style->set_frame($this); $this->_style = $style; } /** * @param FrameDecorator\AbstractFrameDecorator $decorator */ public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator) { $this->_decorator = $decorator; } /** * @param float|float[]|null $x * @param float|null $y * @param float|null $w * @param float|null $h */ public function set_containing_block($x = null, $y = null, $w = null, $h = null) { if (is_array($x)) { foreach ($x as $key => $val) { $$key = $val; } } if (is_numeric($x)) { $this->_containing_block["x"] = $x; } if (is_numeric($y)) { $this->_containing_block["y"] = $y; } if (is_numeric($w)) { $this->_containing_block["w"] = $w; } if (is_numeric($h)) { $this->_containing_block["h"] = $h; } } /** * @param float|float[]|null $x * @param float|null $y */ public function set_position($x = null, $y = null) { if (is_array($x)) { list($x, $y) = [$x["x"], $x["y"]]; } if (is_numeric($x)) { $this->_position["x"] = $x; } if (is_numeric($y)) { $this->_position["y"] = $y; } } /** * @param float $opacity */ public function set_opacity(float $opacity): void { $parent = $this->get_parent(); $base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0; $this->_opacity = $base_opacity * $opacity; } /** * @param LineBox $line */ public function set_containing_line(LineBox $line) { $this->_containing_line = $line; } /** * Indicates if the margin height is auto sized * * @return bool */ public function is_auto_height() { $style = $this->_style; return in_array( "auto", [ $style->height, $style->margin_top, $style->margin_bottom, $style->border_top_width, $style->border_bottom_width, $style->padding_top, $style->padding_bottom, $this->_containing_block["h"] ], true ); } /** * Indicates if the margin width is auto sized * * @return bool */ public function is_auto_width() { $style = $this->_style; return in_array( "auto", [ $style->width, $style->margin_left, $style->margin_right, $style->border_left_width, $style->border_right_width, $style->padding_left, $style->padding_right, $this->_containing_block["w"] ], true ); } /** * Tells if the frame is a text node * * @return bool */ public function is_text_node(): bool { if (isset($this->_is_cache["text_node"])) { return $this->_is_cache["text_node"]; } return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text"); } /** * @return bool */ public function is_positioned(): bool { if (isset($this->_is_cache["positioned"])) { return $this->_is_cache["positioned"]; } $position = $this->get_style()->position; return $this->_is_cache["positioned"] = in_array($position, Style::POSITIONED_TYPES, true); } /** * @return bool */ public function is_absolute(): bool { if (isset($this->_is_cache["absolute"])) { return $this->_is_cache["absolute"]; } return $this->_is_cache["absolute"] = $this->get_style()->is_absolute(); } /** * Whether the frame is a block container. * * @return bool */ public function is_block(): bool { if (isset($this->_is_cache["block"])) { return $this->_is_cache["block"]; } return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::BLOCK_TYPES, true); } /** * Whether the frame has a block-level display type. * * @return bool */ public function is_block_level(): bool { if (isset($this->_is_cache["block_level"])) { return $this->_is_cache["block_level"]; } $display = $this->get_style()->display; return $this->_is_cache["block_level"] = in_array($display, Style::BLOCK_LEVEL_TYPES, true); } /** * Whether the frame has an inline-level display type. * * @return bool */ public function is_inline_level(): bool { if (isset($this->_is_cache["inline_level"])) { return $this->_is_cache["inline_level"]; } $display = $this->get_style()->display; return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true); } /** * @return bool */ public function is_in_flow(): bool { if (isset($this->_is_cache["in_flow"])) { return $this->_is_cache["in_flow"]; } return $this->_is_cache["in_flow"] = $this->get_style()->is_in_flow(); } /** * @return bool */ public function is_pre(): bool { if (isset($this->_is_cache["pre"])) { return $this->_is_cache["pre"]; } $white_space = $this->get_style()->white_space; return $this->_is_cache["pre"] = in_array($white_space, ["pre", "pre-wrap"], true); } /** * @return bool */ public function is_table(): bool { if (isset($this->_is_cache["table"])) { return $this->_is_cache["table"]; } $display = $this->get_style()->display; return $this->_is_cache["table"] = in_array($display, Style::TABLE_TYPES, true); } /** * Inserts a new child at the beginning of the Frame * * @param Frame $child The new Frame to insert * @param bool $update_node Whether or not to update the DOM */ public function prepend_child(Frame $child, $update_node = true) { if ($update_node) { $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null); } // Remove the child from its parent if ($child->_parent) { $child->_parent->remove_child($child, false); } $child->_parent = $this; $child->_prev_sibling = null; // Handle the first child if (!$this->_first_child) { $this->_first_child = $child; $this->_last_child = $child; $child->_next_sibling = null; } else { $this->_first_child->_prev_sibling = $child; $child->_next_sibling = $this->_first_child; $this->_first_child = $child; } } /** * Inserts a new child at the end of the Frame * * @param Frame $child The new Frame to insert * @param bool $update_node Whether or not to update the DOM */ public function append_child(Frame $child, $update_node = true) { if ($update_node) { $this->_node->appendChild($child->_node); } // Remove the child from its parent if ($child->_parent) { $child->_parent->remove_child($child, false); } $child->_parent = $this; $decorator = $child->get_decorator(); // force an update to the cached parent if ($decorator !== null) { $decorator->get_parent(false); } $child->_next_sibling = null; // Handle the first child if (!$this->_last_child) { $this->_first_child = $child; $this->_last_child = $child; $child->_prev_sibling = null; } else { $this->_last_child->_next_sibling = $child; $child->_prev_sibling = $this->_last_child; $this->_last_child = $child; } } /** * Inserts a new child immediately before the specified frame * * @param Frame $new_child The new Frame to insert * @param Frame $ref The Frame after the new Frame * @param bool $update_node Whether or not to update the DOM * * @throws Exception */ public function insert_child_before(Frame $new_child, Frame $ref, $update_node = true) { if ($ref === $this->_first_child) { $this->prepend_child($new_child, $update_node); return; } if (is_null($ref)) { $this->append_child($new_child, $update_node); return; } if ($ref->_parent !== $this) { throw new Exception("Reference child is not a child of this node."); } // Update the node if ($update_node) { $this->_node->insertBefore($new_child->_node, $ref->_node); } // Remove the child from its parent if ($new_child->_parent) { $new_child->_parent->remove_child($new_child, false); } $new_child->_parent = $this; $new_child->_next_sibling = $ref; $new_child->_prev_sibling = $ref->_prev_sibling; if ($ref->_prev_sibling) { $ref->_prev_sibling->_next_sibling = $new_child; } $ref->_prev_sibling = $new_child; } /** * Inserts a new child immediately after the specified frame * * @param Frame $new_child The new Frame to insert * @param Frame $ref The Frame before the new Frame * @param bool $update_node Whether or not to update the DOM * * @throws Exception */ public function insert_child_after(Frame $new_child, Frame $ref, $update_node = true) { if ($ref === $this->_last_child) { $this->append_child($new_child, $update_node); return; } if (is_null($ref)) { $this->prepend_child($new_child, $update_node); return; } if ($ref->_parent !== $this) { throw new Exception("Reference child is not a child of this node."); } // Update the node if ($update_node) { if ($ref->_next_sibling) { $next_node = $ref->_next_sibling->_node; $this->_node->insertBefore($new_child->_node, $next_node); } else { $new_child->_node = $this->_node->appendChild($new_child->_node); } } // Remove the child from its parent if ($new_child->_parent) { $new_child->_parent->remove_child($new_child, false); } $new_child->_parent = $this; $new_child->_prev_sibling = $ref; $new_child->_next_sibling = $ref->_next_sibling; if ($ref->_next_sibling) { $ref->_next_sibling->_prev_sibling = $new_child; } $ref->_next_sibling = $new_child; } /** * Remove a child frame * * @param Frame $child * @param bool $update_node Whether or not to remove the DOM node * * @throws Exception * @return Frame The removed child frame */ public function remove_child(Frame $child, $update_node = true) { if ($child->_parent !== $this) { throw new Exception("Child not found in this frame"); } if ($update_node) { $this->_node->removeChild($child->_node); } if ($child === $this->_first_child) { $this->_first_child = $child->_next_sibling; } if ($child === $this->_last_child) { $this->_last_child = $child->_prev_sibling; } if ($child->_prev_sibling) { $child->_prev_sibling->_next_sibling = $child->_next_sibling; } if ($child->_next_sibling) { $child->_next_sibling->_prev_sibling = $child->_prev_sibling; } $child->_next_sibling = null; $child->_prev_sibling = null; $child->_parent = null; return $child; } //........................................................................ // Debugging function: /** * @return string */ public function __toString() { // Skip empty text frames // if ( $this->is_text_node() && // preg_replace("/\s/", "", $this->_node->data) === "" ) // return ""; $str = "<b>" . $this->_node->nodeName . ":</b><br/>"; //$str .= spl_object_hash($this->_node) . "<br/>"; $str .= "Id: " . $this->get_id() . "<br/>"; $str .= "Class: " . get_class($this) . "<br/>"; if ($this->is_text_node()) { $tmp = htmlspecialchars($this->_node->nodeValue); $str .= "<pre>'" . mb_substr($tmp, 0, 70) . (mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>"; } elseif ($css_class = $this->_node->getAttribute("class")) { $str .= "CSS class: '$css_class'<br/>"; } if ($this->_parent) { $str .= "\nParent:" . $this->_parent->_node->nodeName . " (" . spl_object_hash($this->_parent->_node) . ") " . "<br/>"; } if ($this->_prev_sibling) { $str .= "Prev: " . $this->_prev_sibling->_node->nodeName . " (" . spl_object_hash($this->_prev_sibling->_node) . ") " . "<br/>"; } if ($this->_next_sibling) { $str .= "Next: " . $this->_next_sibling->_node->nodeName . " (" . spl_object_hash($this->_next_sibling->_node) . ") " . "<br/>"; } $d = $this->get_decorator(); while ($d && $d != $d->get_decorator()) { $str .= "Decorator: " . get_class($d) . "<br/>"; $d = $d->get_decorator(); } $str .= "Position: " . Helpers::pre_r($this->_position, true); $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true); $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true); $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true); $str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>"; if ($this->_decorator instanceof FrameDecorator\Block) { $str .= "Lines:<pre>"; foreach ($this->_decorator->get_line_boxes() as $line) { foreach ($line->get_frames() as $frame) { if ($frame instanceof FrameDecorator\Text) { $str .= "\ntext: "; $str .= "'" . htmlspecialchars($frame->get_text()) . "'"; } else { $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")"; } } $str .= "\ny => " . $line->y . "\n" . "w => " . $line->w . "\n" . "h => " . $line->h . "\n" . "left => " . $line->left . "\n" . "right => " . $line->right . "\n"; } $str .= "</pre>"; } $str .= "\n"; if (php_sapi_name() === "cli") { $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"], ["\n", "", ""], $str)); } return $str; } } dompdf/src/FontMetrics.php 0000644 00000043212 15024772104 0011556 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; use FontLib\Font; /** * The font metrics class * * This class provides information about fonts and text. It can resolve * font names into actual installed font files, as well as determine the * size of text in a particular font and size. * * @static * @package dompdf */ class FontMetrics { /** * Name of the user font families file * * This file must be writable by the webserver process only to update it * with save_font_families() after adding the .afm file references of a new font family * with FontMetrics::saveFontFamilies(). * This is typically done only from command line with load_font.php on converting * ttf fonts to ufm with php-font-lib. */ const USER_FONTS_FILE = "installed-fonts.json"; /** * Underlying {@link Canvas} object to perform text size calculations * * @var Canvas */ protected $canvas; /** * Array of bundled font family names to variants * * @var array */ protected $bundledFonts = []; /** * Array of user defined font family names to variants * * @var array */ protected $userFonts = []; /** * combined list of all font families with absolute paths * * @var array */ protected $fontFamilies; /** * @var Options */ private $options; /** * Class initialization */ public function __construct(Canvas $canvas, Options $options) { $this->setCanvas($canvas); $this->setOptions($options); $this->loadFontFamilies(); } /** * @deprecated */ public function save_font_families() { $this->saveFontFamilies(); } /** * Saves the stored font family cache * * The name and location of the cache file are determined by {@link * FontMetrics::USER_FONTS_FILE}. This file should be writable by the * webserver process. * * @see FontMetrics::loadFontFamilies() */ public function saveFontFamilies() { file_put_contents($this->getUserFontsFilePath(), json_encode($this->userFonts, JSON_PRETTY_PRINT)); } /** * @deprecated */ public function load_font_families() { $this->loadFontFamilies(); } /** * Loads the stored font family cache * * @see FontMetrics::saveFontFamilies() */ public function loadFontFamilies() { $file = $this->options->getRootDir() . "/lib/fonts/installed-fonts.dist.json"; $this->bundledFonts = json_decode(file_get_contents($file), true); if (is_readable($this->getUserFontsFilePath())) { $this->userFonts = json_decode(file_get_contents($this->getUserFontsFilePath()), true); } else { $this->loadFontFamiliesLegacy(); } } private function loadFontFamiliesLegacy() { $legacyCacheFile = $this->options->getFontDir() . '/dompdf_font_family_cache.php'; if (is_readable($legacyCacheFile)) { $fontDir = $this->options->getFontDir(); $rootDir = $this->options->getRootDir(); if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); } if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); } $cacheDataClosure = require $legacyCacheFile; $cacheData = is_array($cacheDataClosure) ? $cacheDataClosure : $cacheDataClosure($fontDir, $rootDir); if (is_array($cacheData)) { foreach ($cacheData as $family => $variants) { if (!isset($this->bundledFonts[$family]) && is_array($variants)) { foreach ($variants as $variant => $variantPath) { $variantName = basename($variantPath); $variantDir = dirname($variantPath); if ($variantDir == $fontDir) { $this->userFonts[$family][$variant] = $variantName; } else { $this->userFonts[$family][$variant] = $variantPath; } } } } $this->saveFontFamilies(); } } } /** * @param array $style * @param string $remote_file * @param resource $context * @return bool * @deprecated */ public function register_font($style, $remote_file, $context = null) { return $this->registerFont($style, $remote_file); } /** * @param array $style * @param string $remoteFile * @param resource $context * @return bool */ public function registerFont($style, $remoteFile, $context = null) { $fontname = mb_strtolower($style["family"]); $families = $this->getFontFamilies(); $entry = []; if (isset($families[$fontname])) { $entry = $families[$fontname]; } $styleString = $this->getType("{$style['weight']} {$style['style']}"); $remoteHash = md5($remoteFile); $prefix = $fontname . "_" . $styleString; $prefix = trim($prefix, "-"); if (function_exists('iconv')) { $prefix = @iconv('utf-8', 'us-ascii//TRANSLIT', $prefix); } $prefix_encoding = mb_detect_encoding($prefix, mb_detect_order(), true); $substchar = mb_substitute_character(); mb_substitute_character(0x005F); $prefix = mb_convert_encoding($prefix, "ISO-8859-1", $prefix_encoding); mb_substitute_character($substchar); $prefix = preg_replace("[\W]", "_", $prefix); $prefix = preg_replace("/[^-_\w]+/", "", $prefix); $localFile = $prefix . "_" . $remoteHash; $localFilePath = $this->getOptions()->getFontDir() . "/" . $localFile; if (isset($entry[$styleString]) && $localFilePath == $entry[$styleString]) { return true; } $entry[$styleString] = $localFile; // Download the remote file [$protocol] = Helpers::explode_url($remoteFile); $allowed_protocols = $this->options->getAllowedProtocols(); if (!array_key_exists($protocol, $allowed_protocols)) { Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The communication protocol is not supported.", __FILE__, __LINE__); return false; } foreach ($allowed_protocols[$protocol]["rules"] as $rule) { [$result, $message] = $rule($remoteFile); if ($result !== true) { Helpers::record_warnings(E_USER_WARNING, "Error loading $remoteFile: $message", __FILE__, __LINE__); return false; } } list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context); if ($remoteFileContent === null) { return false; } $localTempFile = @tempnam($this->options->get("tempDir"), "dompdf-font-"); file_put_contents($localTempFile, $remoteFileContent); $font = Font::load($localTempFile); if (!$font) { unlink($localTempFile); return false; } $font->parse(); $font->saveAdobeFontMetrics("$localFilePath.ufm"); $font->close(); unlink($localTempFile); if ( !file_exists("$localFilePath.ufm") ) { return false; } $fontExtension = ".ttf"; switch ($font->getFontType()) { case "TrueType": default: $fontExtension = ".ttf"; break; } // Save the changes file_put_contents($localFilePath.$fontExtension, $remoteFileContent); if ( !file_exists($localFilePath.$fontExtension) ) { unlink("$localFilePath.ufm"); return false; } $this->setFontFamily($fontname, $entry); return true; } /** * @param $text * @param $font * @param $size * @param float $word_spacing * @param float $char_spacing * @return float * @deprecated */ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0) { //return self::$_pdf->get_text_width($text, $font, $size, $word_spacing, $char_spacing); return $this->getTextWidth($text, $font, $size, $word_spacing, $char_spacing); } /** * Calculates text size, in points * * @param string $text The text to be sized * @param string $font The font file to use * @param float $size The font size, in points * @param float $wordSpacing Word spacing, if any * @param float $charSpacing Char spacing, if any * * @return float */ public function getTextWidth(string $text, $font, float $size, float $wordSpacing = 0.0, float $charSpacing = 0.0): float { // @todo Make sure this cache is efficient before enabling it static $cache = []; if ($text === "") { return 0; } // Don't cache long strings $useCache = !isset($text[50]); // Faster than strlen // Text-size calculations depend on the canvas used. Make sure to not // return wrong values when switching canvas backends $canvasClass = get_class($this->canvas); $key = "$canvasClass/$font/$size/$wordSpacing/$charSpacing"; if ($useCache && isset($cache[$key][$text])) { return $cache[$key][$text]; } $width = $this->canvas->get_text_width($text, $font, $size, $wordSpacing, $charSpacing); if ($useCache) { $cache[$key][$text] = $width; } return $width; } /** * @param $font * @param $size * @return float * @deprecated */ public function get_font_height($font, $size) { return $this->getFontHeight($font, $size); } /** * Calculates font height, in points * * @param string $font The font file to use * @param float $size The font size, in points * * @return float */ public function getFontHeight($font, float $size): float { return $this->canvas->get_font_height($font, $size); } /** * Calculates font baseline, in points * * @param string $font The font file to use * @param float $size The font size, in points * * @return float */ public function getFontBaseline($font, float $size): float { return $this->canvas->get_font_baseline($font, $size); } /** * @param $family_raw * @param string $subtype_raw * @return string * @deprecated */ public function get_font($family_raw, $subtype_raw = "normal") { return $this->getFont($family_raw, $subtype_raw); } /** * Resolves a font family & subtype into an actual font file * Subtype can be one of 'normal', 'bold', 'italic' or 'bold_italic'. If * the particular font family has no suitable font file, the default font * ({@link Options::defaultFont}) is used. The font file returned * is the absolute pathname to the font file on the system. * * @param string|null $familyRaw * @param string $subtypeRaw * * @return string|null */ public function getFont($familyRaw, $subtypeRaw = "normal") { static $cache = []; if (isset($cache[$familyRaw][$subtypeRaw])) { return $cache[$familyRaw][$subtypeRaw]; } /* Allow calling for various fonts in search path. Therefore not immediately * return replacement on non match. * Only when called with NULL try replacement. * When this is also missing there is really trouble. * If only the subtype fails, nevertheless return failure. * Only on checking the fallback font, check various subtypes on same font. */ $subtype = strtolower($subtypeRaw); $families = $this->getFontFamilies(); if ($familyRaw) { $family = str_replace(["'", '"'], "", strtolower($familyRaw)); if (isset($families[$family][$subtype])) { return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype]; } return null; } $fallback_families = [strtolower($this->options->getDefaultFont()), "serif"]; foreach ($fallback_families as $family) { if (isset($families[$family][$subtype])) { return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype]; } if (!isset($families[$family])) { continue; } $family = $families[$family]; foreach ($family as $sub => $font) { if (strpos($subtype, $sub) !== false) { return $cache[$familyRaw][$subtypeRaw] = $font; } } if ($subtype !== "normal") { foreach ($family as $sub => $font) { if ($sub !== "normal") { return $cache[$familyRaw][$subtypeRaw] = $font; } } } $subtype = "normal"; if (isset($family[$subtype])) { return $cache[$familyRaw][$subtypeRaw] = $family[$subtype]; } } return null; } /** * @param $family * @return null|string * @deprecated */ public function get_family($family) { return $this->getFamily($family); } /** * @param string $family * @return null|string */ public function getFamily($family) { $family = str_replace(["'", '"'], "", mb_strtolower($family)); $families = $this->getFontFamilies(); if (isset($families[$family])) { return $families[$family]; } return null; } /** * @param $type * @return string * @deprecated */ public function get_type($type) { return $this->getType($type); } /** * @param string $type * @return string */ public function getType($type) { if (preg_match('/bold/i', $type)) { $weight = 700; } elseif (preg_match('/([1-9]00)/', $type, $match)) { $weight = (int)$match[0]; } else { $weight = 400; } $weight = $weight === 400 ? 'normal' : $weight; $weight = $weight === 700 ? 'bold' : $weight; $style = preg_match('/italic|oblique/i', $type) ? 'italic' : null; if ($weight === 'normal' && $style !== null) { return $style; } return $style === null ? $weight : $weight.'_'.$style; } /** * @return array * @deprecated */ public function get_font_families() { return $this->getFontFamilies(); } /** * Returns the current font lookup table * * @return array */ public function getFontFamilies() { if (!isset($this->fontFamilies)) { $this->setFontFamilies(); } return $this->fontFamilies; } /** * Convert loaded fonts to font lookup table * * @return array */ public function setFontFamilies() { $fontFamilies = []; if (isset($this->bundledFonts) && is_array($this->bundledFonts)) { foreach ($this->bundledFonts as $family => $variants) { if (!isset($fontFamilies[$family])) { $fontFamilies[$family] = array_map(function ($variant) { return $this->getOptions()->getRootDir() . '/lib/fonts/' . $variant; }, $variants); } } } if (isset($this->userFonts) && is_array($this->userFonts)) { foreach ($this->userFonts as $family => $variants) { $fontFamilies[$family] = array_map(function ($variant) { $variantName = basename($variant); if ($variantName === $variant) { return $this->getOptions()->getFontDir() . '/' . $variant; } return $variant; }, $variants); } } $this->fontFamilies = $fontFamilies; } /** * @param string $fontname * @param mixed $entry * @deprecated */ public function set_font_family($fontname, $entry) { $this->setFontFamily($fontname, $entry); } /** * @param string $fontname * @param mixed $entry */ public function setFontFamily($fontname, $entry) { $this->userFonts[mb_strtolower($fontname)] = $entry; $this->saveFontFamilies(); unset($this->fontFamilies); } /** * @return string */ public function getUserFontsFilePath() { return $this->options->getFontDir() . '/' . self::USER_FONTS_FILE; } /** * @param Options $options * @return $this */ public function setOptions(Options $options) { $this->options = $options; unset($this->fontFamilies); return $this; } /** * @return Options */ public function getOptions() { return $this->options; } /** * @param Canvas $canvas * @return $this */ public function setCanvas(Canvas $canvas) { $this->canvas = $canvas; return $this; } /** * @return Canvas */ public function getCanvas() { return $this->canvas; } } dompdf/src/Adapter/PDFLib.php 0000644 00000123712 15024772104 0011745 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Adapter; use Dompdf\Canvas; use Dompdf\Dompdf; use Dompdf\Exception; use Dompdf\FontMetrics; use Dompdf\Helpers; use Dompdf\Image\Cache; /** * PDF rendering interface * * Dompdf\Adapter\PDFLib provides a simple, stateless interface to the one * provided by PDFLib. * * Unless otherwise mentioned, all dimensions are in points (1/72 in). * The coordinate origin is in the top left corner and y values * increase downwards. * * See {@link http://www.pdflib.com/} for more complete documentation * on the underlying PDFlib functions. * * @package dompdf */ class PDFLib implements Canvas { /** * Dimensions of paper sizes in points * * @var array */ public static $PAPER_SIZES = []; // Set to Dompdf\Adapter\CPDF::$PAPER_SIZES below. /** * Whether to create PDFs in memory or on disk * * @var bool */ static $IN_MEMORY = true; /** * Saves the major version of PDFLib for compatibility requests * * @var null|int */ protected static $MAJOR_VERSION = null; /** * Transforms the list of native fonts into PDFLib compatible names (casesensitive) * * @var array */ public static $nativeFontsTpPDFLib = [ "courier" => "Courier", "courier-bold" => "Courier-Bold", "courier-oblique" => "Courier-Oblique", "courier-boldoblique" => "Courier-BoldOblique", "helvetica" => "Helvetica", "helvetica-bold" => "Helvetica-Bold", "helvetica-oblique" => "Helvetica-Oblique", "helvetica-boldoblique" => "Helvetica-BoldOblique", "times" => "Times-Roman", "times-roman" => "Times-Roman", "times-bold" => "Times-Bold", "times-italic" => "Times-Italic", "times-bolditalic" => "Times-BoldItalic", "symbol" => "Symbol", "zapfdinbats" => "ZapfDingbats", "zapfdingbats" => "ZapfDingbats", ]; /** * @var \Dompdf\Dompdf */ protected $_dompdf; /** * Instance of PDFLib class * * @var \PDFLib */ protected $_pdf; /** * Name of temporary file used for PDFs created on disk * * @var string */ protected $_file; /** * PDF width, in points * * @var float */ protected $_width; /** * PDF height, in points * * @var float */ protected $_height; /** * Last fill color used * * @var array */ protected $_last_fill_color; /** * Last stroke color used * * @var array */ protected $_last_stroke_color; /** * The current opacity level * * @var float|null */ protected $_current_opacity; /** * Cache of image handles * * @var array */ protected $_imgs; /** * Cache of font handles * * @var array */ protected $_fonts; /** * Cache of fontFile checks * * @var array */ protected $_fontsFiles; /** * List of objects (templates) to add to multiple pages * * @var array */ protected $_objs; /** * List of gstate objects created for this PDF (for reuse) * * @var array */ protected $_gstates = []; /** * Current page number * * @var int */ protected $_page_number; /** * Total number of pages * * @var int */ protected $_page_count; /** * Array of pages for accessing after rendering is initially complete * * @var array */ protected $_pages; public function __construct($paper = "letter", string $orientation = "portrait", ?Dompdf $dompdf = null) { if (is_array($paper)) { $size = array_map("floatval", $paper); } else { $paper = strtolower($paper); $size = self::$PAPER_SIZES[$paper] ?? self::$PAPER_SIZES["letter"]; } if (strtolower($orientation) === "landscape") { [$size[2], $size[3]] = [$size[3], $size[2]]; } $this->_width = $size[2] - $size[0]; $this->_height = $size[3] - $size[1]; if ($dompdf === null) { $this->_dompdf = new Dompdf(); } else { $this->_dompdf = $dompdf; } $this->_pdf = new \PDFLib(); $license = $dompdf->getOptions()->getPdflibLicense(); if (strlen($license) > 0) { $this->setPDFLibParameter("license", $license); } if ($this->getPDFLibMajorVersion() < 10) { $this->setPDFLibParameter("textformat", "utf8"); } if ($this->getPDFLibMajorVersion() >= 7) { $this->setPDFLibParameter("errorpolicy", "return"); // $this->_pdf->set_option('logging={filename=' . \APP_PATH . '/logs/pdflib.log classes={api=1 warning=2}}'); // $this->_pdf->set_option('errorpolicy=exception'); } else { $this->setPDFLibParameter("fontwarning", "false"); } $searchPath = $this->_dompdf->getOptions()->getFontDir(); if (empty($searchPath) === false) { $this->_pdf->set_option('searchpath={' . $searchPath . '}'); } // fetch PDFLib version information for the producer field $this->_pdf->set_info("Producer Addendum", sprintf("%s + PDFLib %s", $dompdf->version, $this->getPDFLibMajorVersion())); // Silence pedantic warnings about missing TZ settings $tz = @date_default_timezone_get(); date_default_timezone_set("UTC"); $this->_pdf->set_info("Date", date("Y-m-d")); date_default_timezone_set($tz); if (self::$IN_MEMORY) { $this->_pdf->begin_document("", ""); } else { $tmp_dir = $this->_dompdf->getOptions()->getTempDir(); $tmp_name = @tempnam($tmp_dir, "libdompdf_pdf_"); @unlink($tmp_name); $this->_file = "$tmp_name.pdf"; $this->_pdf->begin_document($this->_file, ""); } $this->_pdf->begin_page_ext($this->_width, $this->_height, ""); $this->_page_number = $this->_page_count = 1; $this->_imgs = []; $this->_fonts = []; $this->_objs = []; } function get_dompdf() { return $this->_dompdf; } /** * Close the pdf */ protected function _close() { $this->_place_objects(); // Close all pages $this->_pdf->suspend_page(""); for ($p = 1; $p <= $this->_page_count; $p++) { $this->_pdf->resume_page("pagenumber=$p"); $this->_pdf->end_page_ext(""); } $this->_pdf->end_document(""); } /** * Returns the PDFLib instance * * @return PDFLib */ public function get_pdflib() { return $this->_pdf; } public function add_info(string $label, string $value): void { $this->_pdf->set_info($label, $value); } /** * Opens a new 'object' (template in PDFLib-speak) * * While an object is open, all drawing actions are recorded to the * object instead of being drawn on the current page. Objects can * be added later to a specific page or to several pages. * * The return value is an integer ID for the new object. * * @see PDFLib::close_object() * @see PDFLib::add_object() * * @return int */ public function open_object() { $this->_pdf->suspend_page(""); if ($this->getPDFLibMajorVersion() >= 7) { $ret = $this->_pdf->begin_template_ext($this->_width, $this->_height, null); } else { $ret = $this->_pdf->begin_template($this->_width, $this->_height); } $this->_pdf->save(); $this->_objs[$ret] = ["start_page" => $this->_page_number]; return $ret; } /** * Reopen an existing object (NOT IMPLEMENTED) * PDFLib does not seem to support reopening templates. * * @param int $object the ID of a previously opened object * * @throws Exception * @return void */ public function reopen_object($object) { throw new Exception("PDFLib does not support reopening objects."); } /** * Close the current template * * @see PDFLib::open_object() */ public function close_object() { $this->_pdf->restore(); if ($this->getPDFLibMajorVersion() >= 7) { $this->_pdf->end_template_ext($this->_width, $this->_height); } else { $this->_pdf->end_template(); } $this->_pdf->resume_page("pagenumber=" . $this->_page_number); } /** * Adds the specified object to the document * * $where can be one of: * - 'add' add to current page only * - 'all' add to every page from the current one onwards * - 'odd' add to all odd numbered pages from now on * - 'even' add to all even numbered pages from now on * - 'next' add the object to the next page only * - 'nextodd' add to all odd numbered pages from the next one * - 'nexteven' add to all even numbered pages from the next one * * @param int $object the object handle returned by open_object() * @param string $where */ public function add_object($object, $where = 'all') { if (mb_strpos($where, "next") !== false) { $this->_objs[$object]["start_page"]++; $where = str_replace("next", "", $where); if ($where == "") { $where = "add"; } } $this->_objs[$object]["where"] = $where; } /** * Stops the specified template from appearing in the document. * * The object will stop being displayed on the page following the * current one. * * @param int $object */ public function stop_object($object) { if (!isset($this->_objs[$object])) { return; } $start = $this->_objs[$object]["start_page"]; $where = $this->_objs[$object]["where"]; // Place the object on this page if required if ($this->_page_number >= $start && (($this->_page_number % 2 == 0 && $where === "even") || ($this->_page_number % 2 == 1 && $where === "odd") || ($where === "all")) ) { $this->_pdf->fit_image($object, 0, 0, ""); } $this->_objs[$object] = null; unset($this->_objs[$object]); } /** * Add all active objects to the current page */ protected function _place_objects() { foreach ($this->_objs as $obj => $props) { $start = $props["start_page"]; $where = $props["where"]; // Place the object on this page if required if ($this->_page_number >= $start && (($this->_page_number % 2 == 0 && $where === "even") || ($this->_page_number % 2 == 1 && $where === "odd") || ($where === "all")) ) { $this->_pdf->fit_image($obj, 0, 0, ""); } } } public function get_width() { return $this->_width; } public function get_height() { return $this->_height; } public function get_page_number() { return $this->_page_number; } public function get_page_count() { return $this->_page_count; } /** * @param $num */ public function set_page_number($num) { $this->_page_number = (int)$num; } public function set_page_count($count) { $this->_page_count = (int)$count; } /** * Sets the line style * * @param float $width * @param string $cap * @param string $join * @param array $dash */ protected function _set_line_style($width, $cap, $join, $dash) { if (!is_array($dash)) { $dash = []; } // Work around PDFLib limitation with 0 dash length: // Value 0 for option 'dasharray' is too small (minimum 1.5e-05) foreach ($dash as &$d) { if ($d == 0) { $d = 1.5e-5; } } if (count($dash) === 1) { $dash[] = $dash[0]; } if ($this->getPDFLibMajorVersion() >= 9) { if (count($dash) > 1) { $this->_pdf->set_graphics_option("dasharray={" . implode(" ", $dash) . "}"); } else { $this->_pdf->set_graphics_option("dasharray=none"); } } else { if (count($dash) > 1) { $this->_pdf->setdashpattern("dasharray={" . implode(" ", $dash) . "}"); } else { $this->_pdf->setdash(0, 0); } } switch ($join) { case "miter": if ($this->getPDFLibMajorVersion() >= 9) { $this->_pdf->set_graphics_option('linejoin=0'); } else { $this->_pdf->setlinejoin(0); } break; case "round": if ($this->getPDFLibMajorVersion() >= 9) { $this->_pdf->set_graphics_option('linejoin=1'); } else { $this->_pdf->setlinejoin(1); } break; case "bevel": if ($this->getPDFLibMajorVersion() >= 9) { $this->_pdf->set_graphics_option('linejoin=2'); } else { $this->_pdf->setlinejoin(2); } break; default: break; } switch ($cap) { case "butt": if ($this->getPDFLibMajorVersion() >= 9) { $this->_pdf->set_graphics_option('linecap=0'); } else { $this->_pdf->setlinecap(0); } break; case "round": if ($this->getPDFLibMajorVersion() >= 9) { $this->_pdf->set_graphics_option('linecap=1'); } else { $this->_pdf->setlinecap(1); } break; case "square": if ($this->getPDFLibMajorVersion() >= 9) { $this->_pdf->set_graphics_option('linecap=2'); } else { $this->_pdf->setlinecap(2); } break; default: break; } $this->_pdf->setlinewidth($width); } /** * Sets the line color * * @param array $color array(r,g,b) */ protected function _set_stroke_color($color) { // TODO: we should check the current PDF stroke color // instead of the cached value if ($this->_last_stroke_color == $color) { // FIXME: do nothing, this optimization is broken by the // stroke being set as a side effect of other operations //return; } $alpha = isset($color["alpha"]) ? $color["alpha"] : 1; if (isset($this->_current_opacity)) { $alpha *= $this->_current_opacity; } $this->_last_stroke_color = $color; if (isset($color[3])) { $type = "cmyk"; list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], $color[3]]; } elseif (isset($color[2])) { $type = "rgb"; list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], null]; } else { $type = "gray"; list($c1, $c2, $c3, $c4) = [$color[0], $color[1], null, null]; } $this->_set_stroke_opacity($alpha, "Normal"); $this->_pdf->setcolor("stroke", $type, $c1, $c2, $c3, $c4); } /** * Sets the fill color * * @param array $color array(r,g,b) */ protected function _set_fill_color($color) { // TODO: we should check the current PDF fill color // instead of the cached value if ($this->_last_fill_color == $color) { // FIXME: do nothing, this optimization is broken by the // fill being set as a side effect of other operations //return; } $alpha = isset($color["alpha"]) ? $color["alpha"] : 1; if (isset($this->_current_opacity)) { $alpha *= $this->_current_opacity; } $this->_last_fill_color = $color; if (isset($color[3])) { $type = "cmyk"; list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], $color[3]]; } elseif (isset($color[2])) { $type = "rgb"; list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], null]; } else { $type = "gray"; list($c1, $c2, $c3, $c4) = [$color[0], $color[1], null, null]; } $this->_set_fill_opacity($alpha, "Normal"); $this->_pdf->setcolor("fill", $type, $c1, $c2, $c3, $c4); } /** * Sets the fill opacity * * @param float $opacity * @param string $mode */ public function _set_fill_opacity($opacity, $mode = "Normal") { if ($mode === "Normal" && isset($opacity)) { $this->_set_gstate("opacityfill=$opacity"); } } /** * Sets the stroke opacity * * @param float $opacity * @param string $mode */ public function _set_stroke_opacity($opacity, $mode = "Normal") { if ($mode === "Normal" && isset($opacity)) { $this->_set_gstate("opacitystroke=$opacity"); } } public function set_opacity(float $opacity, string $mode = "Normal"): void { if ($mode === "Normal") { $this->_set_gstate("opacityfill=$opacity opacitystroke=$opacity"); $this->_current_opacity = $opacity; } } /** * Sets the gstate * * @param $gstate_options * @return int */ public function _set_gstate($gstate_options) { if (($gstate = array_search($gstate_options, $this->_gstates)) === false) { $gstate = $this->_pdf->create_gstate($gstate_options); $this->_gstates[$gstate] = $gstate_options; } return $this->_pdf->set_gstate($gstate); } public function set_default_view($view, $options = []) { // TODO // http://www.pdflib.com/fileadmin/pdflib/pdf/manuals/PDFlib-8.0.2-API-reference.pdf /** * fitheight Fit the page height to the window, with the x coordinate left at the left edge of the window. * fitrect Fit the rectangle specified by left, bottom, right, and top to the window. * fitvisible Fit the visible contents of the page (the ArtBox) to the window. * fitvisibleheight Fit the visible contents of the page to the window with the x coordinate left at the left edge of the window. * fitvisiblewidth Fit the visible contents of the page to the window with the y coordinate top at the top edge of the window. * fitwidth Fit the page width to the window, with the y coordinate top at the top edge of the window. * fitwindow Fit the complete page to the window. * fixed */ //$this->setPDFLibParameter("openaction", $view); } /** * Loads a specific font and stores the corresponding descriptor. * * @param string $font * @param string $encoding * @param string $options * * @return int the font descriptor for the font */ protected function _load_font($font, $encoding = null, $options = "") { // Fix for PDFLibs case-sensitive font names $baseFont = basename($font); $isNativeFont = false; if (isset(self::$nativeFontsTpPDFLib[$baseFont])) { $font = self::$nativeFontsTpPDFLib[$baseFont]; $isNativeFont = true; } // Check if the font is a native PDF font // Embed non-native fonts $test = strtolower($baseFont); if (in_array($test, DOMPDF::$nativeFonts)) { $font = basename($font); } else { // Embed non-native fonts $options .= " embedding=true"; } $options .= " autosubsetting=" . ($this->_dompdf->getOptions()->getIsFontSubsettingEnabled() === false ? "false" : "true"); if (is_null($encoding)) { // Unicode encoding is only available for the commerical // version of PDFlib and not PDFlib-Lite if (strlen($this->_dompdf->getOptions()->getPdflibLicense()) > 0) { $encoding = "unicode"; } else { $encoding = "auto"; } } $key = "$font:$encoding:$options"; if (isset($this->_fonts[$key])) { return $this->_fonts[$key]; } // Native fonts are build in, just load it if ($isNativeFont) { $this->_fonts[$key] = $this->_pdf->load_font($font, $encoding, $options); return $this->_fonts[$key]; } $fontOutline = $this->getPDFLibParameter("FontOutline", 1); if ($fontOutline === "" || $fontOutline <= 0) { $families = $this->_dompdf->getFontMetrics()->getFontFamilies(); foreach ($families as $files) { foreach ($files as $file) { $face = basename($file); $afm = null; if (isset($this->_fontsFiles[$face])) { continue; } // Prefer ttfs to afms if (file_exists("$file.ttf")) { $outline = "$file.ttf"; } elseif (file_exists("$file.TTF")) { $outline = "$file.TTF"; } elseif (file_exists("$file.pfb")) { $outline = "$file.pfb"; if (file_exists("$file.afm")) { $afm = "$file.afm"; } } elseif (file_exists("$file.PFB")) { $outline = "$file.PFB"; if (file_exists("$file.AFM")) { $afm = "$file.AFM"; } } else { continue; } $this->_fontsFiles[$face] = true; if ($this->getPDFLibMajorVersion() >= 9) { $this->setPDFLibParameter("FontOutline", '{' . "$face=$outline" . '}'); } else { $this->setPDFLibParameter("FontOutline", "\{$face\}=\{$outline\}"); } if (is_null($afm)) { continue; } if ($this->getPDFLibMajorVersion() >= 9) { $this->setPDFLibParameter("FontAFM", '{' . "$face=$afm" . '}'); } else { $this->setPDFLibParameter("FontAFM", "\{$face\}=\{$afm\}"); } } } } $this->_fonts[$key] = $this->_pdf->load_font($font, $encoding, $options); return $this->_fonts[$key]; } /** * Remaps y coords from 4th to 1st quadrant * * @param float $y * @return float */ protected function y($y) { return $this->_height - $y; } public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt") { $this->_set_line_style($width, $cap, "", $style); $this->_set_stroke_color($color); $y1 = $this->y($y1); $y2 = $this->y($y2); $this->_pdf->moveto($x1, $y1); $this->_pdf->lineto($x2, $y2); $this->_pdf->stroke(); $this->_set_stroke_opacity($this->_current_opacity, "Normal"); } public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt") { $this->_set_line_style($width, $cap, "", $style); $this->_set_stroke_color($color); $y = $this->y($y); $this->_pdf->arc($x, $y, $r1, $astart, $aend); $this->_pdf->stroke(); $this->_set_stroke_opacity($this->_current_opacity, "Normal"); } public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt") { $this->_set_stroke_color($color); $this->_set_line_style($width, $cap, "", $style); $y1 = $this->y($y1) - $h; $this->_pdf->rect($x1, $y1, $w, $h); $this->_pdf->stroke(); $this->_set_stroke_opacity($this->_current_opacity, "Normal"); } public function filled_rectangle($x1, $y1, $w, $h, $color) { $this->_set_fill_color($color); $y1 = $this->y($y1) - $h; $this->_pdf->rect(floatval($x1), floatval($y1), floatval($w), floatval($h)); $this->_pdf->fill(); $this->_set_fill_opacity($this->_current_opacity, "Normal"); } public function clipping_rectangle($x1, $y1, $w, $h) { $this->_pdf->save(); $y1 = $this->y($y1) - $h; $this->_pdf->rect(floatval($x1), floatval($y1), floatval($w), floatval($h)); $this->_pdf->clip(); } public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) { if ($this->getPDFLibMajorVersion() < 9) { //TODO: add PDFLib7 support $this->clipping_rectangle($x1, $y1, $w, $h); return; } $this->_pdf->save(); // we use 0,0 for the base coordinates for the path points // since we're drawing the path at the $x1,$y1 coordinates $path = 0; //start: left edge, top end $path = $this->_pdf->add_path_point($path, 0, 0 - $rTL + $h, "move", ""); // line: left edge, bottom end $path = $this->_pdf->add_path_point($path, 0, 0 + $rBL, "line", ""); // curve: bottom-left corner if ($rBL > 0) { $path = $this->_pdf->add_path_point($path, 0 + $rBL, 0, "elliptical", "radius=$rBL clockwise=false"); } // line: bottom edge, left end $path = $this->_pdf->add_path_point($path, 0 - $rBR + $w, 0, "line", ""); // curve: bottom-right corner if ($rBR > 0) { $path = $this->_pdf->add_path_point($path, 0 + $w, 0 + $rBR, "elliptical", "radius=$rBR clockwise=false"); } // line: right edge, top end $path = $this->_pdf->add_path_point($path, 0 + $w, 0 - $rTR + $h, "line", ""); // curve: top-right corner if ($rTR > 0) { $path = $this->_pdf->add_path_point($path, 0 - $rTR + $w, 0 + $h, "elliptical", "radius=$rTR clockwise=false"); } // line: top edge, left end $path = $this->_pdf->add_path_point($path, 0 + $rTL, 0 + $h, "line", ""); // curve: top-left corner if ($rTL > 0) { $path = $this->_pdf->add_path_point($path, 0, 0 - $rTL + $h, "elliptical", "radius=$rTL clockwise=false"); } $this->_pdf->draw_path($path, $x1, $this->_height-$y1-$h, "clip=true"); } public function clipping_polygon(array $points): void { $this->_pdf->save(); $y = $this->y(array_pop($points)); $x = array_pop($points); $this->_pdf->moveto($x, $y); while (count($points) > 1) { $y = $this->y(array_pop($points)); $x = array_pop($points); $this->_pdf->lineto($x, $y); } $this->_pdf->closepath(); $this->_pdf->clip(); } public function clipping_end() { $this->_pdf->restore(); } public function save() { $this->_pdf->save(); } function restore() { $this->_pdf->restore(); } public function rotate($angle, $x, $y) { $pdf = $this->_pdf; $pdf->translate($x, $this->_height - $y); $pdf->rotate(-$angle); $pdf->translate(-$x, -$this->_height + $y); } public function skew($angle_x, $angle_y, $x, $y) { $pdf = $this->_pdf; $pdf->translate($x, $this->_height - $y); $pdf->skew($angle_y, $angle_x); // Needs to be inverted $pdf->translate(-$x, -$this->_height + $y); } public function scale($s_x, $s_y, $x, $y) { $pdf = $this->_pdf; $pdf->translate($x, $this->_height - $y); $pdf->scale($s_x, $s_y); $pdf->translate(-$x, -$this->_height + $y); } public function translate($t_x, $t_y) { $this->_pdf->translate($t_x, -$t_y); } public function transform($a, $b, $c, $d, $e, $f) { $this->_pdf->concat($a, $b, $c, $d, $e, $f); } public function polygon($points, $color, $width = null, $style = [], $fill = false) { $this->_set_fill_color($color); $this->_set_stroke_color($color); if (!$fill && isset($width)) { $this->_set_line_style($width, "square", "miter", $style); } $y = $this->y(array_pop($points)); $x = array_pop($points); $this->_pdf->moveto($x, $y); while (count($points) > 1) { $y = $this->y(array_pop($points)); $x = array_pop($points); $this->_pdf->lineto($x, $y); } if ($fill) { $this->_pdf->fill(); } else { $this->_pdf->closepath_stroke(); } $this->_set_fill_opacity($this->_current_opacity, "Normal"); $this->_set_stroke_opacity($this->_current_opacity, "Normal"); } public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false) { $this->_set_fill_color($color); $this->_set_stroke_color($color); if (!$fill && isset($width)) { $this->_set_line_style($width, "round", "round", $style); } $y = $this->y($y); $this->_pdf->circle($x, $y, $r); if ($fill) { $this->_pdf->fill(); } else { $this->_pdf->stroke(); } $this->_set_fill_opacity($this->_current_opacity, "Normal"); $this->_set_stroke_opacity($this->_current_opacity, "Normal"); } public function image($img, $x, $y, $w, $h, $resolution = "normal") { $w = (int)$w; $h = (int)$h; $img_type = Cache::detect_type($img, $this->get_dompdf()->getHttpContext()); // Strip file:// prefix if (substr($img, 0, 7) === "file://") { $img = substr($img, 7); } if (!isset($this->_imgs[$img])) { if (strtolower($img_type) === "svg") { //FIXME: PDFLib loads SVG but returns error message "Function must not be called in 'page' scope" $image_load_response = $this->_pdf->load_graphics($img_type, $img, ""); } else { $image_load_response = $this->_pdf->load_image($img_type, $img, ""); } if ($image_load_response === 0) { //TODO: should do something with the error message $error = $this->_pdf->get_errmsg(); return; } $this->_imgs[$img] = $image_load_response; } $img = $this->_imgs[$img]; $y = $this->y($y) - $h; if (strtolower($img_type) === "svg") { $this->_pdf->fit_graphics($img, $x, $y, 'boxsize={' . "$w $h" . '} fitmethod=entire'); } else { $this->_pdf->fit_image($img, $x, $y, 'boxsize={' . "$w $h" . '} fitmethod=entire'); } } public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0, $char_spacing = 0, $angle = 0) { if ($size == 0) { return; } $fh = $this->_load_font($font); $this->_pdf->setfont($fh, $size); $this->_set_fill_color($color); $y = $this->y($y) - $this->get_font_height($font, $size); $word_spacing = (float)$word_spacing; $char_spacing = (float)$char_spacing; $angle = -(float)$angle; $this->_pdf->fit_textline($text, $x, $y, "rotate=$angle wordspacing=$word_spacing charspacing=$char_spacing "); $this->_set_fill_opacity($this->_current_opacity, "Normal"); } public function javascript($code) { if (strlen($this->_dompdf->getOptions()->getPdflibLicense()) > 0) { $this->_pdf->create_action("JavaScript", $code); } } public function add_named_dest($anchorname) { $this->_pdf->add_nameddest($anchorname, ""); } public function add_link($url, $x, $y, $width, $height) { $y = $this->y($y) - $height; if (strpos($url, '#') === 0) { // Local link $name = substr($url, 1); if ($name) { $this->_pdf->create_annotation($x, $y, $x + $width, $y + $height, 'Link', "contents={$url} destname=" . substr($url, 1) . " linewidth=0"); } } else { //TODO: PDFLib::create_action does not permit non-HTTP links for URI actions $action = $this->_pdf->create_action("URI", "url={{$url}}"); // add the annotation only if the action was created if ($action !== 0) { $this->_pdf->create_annotation($x, $y, $x + $width, $y + $height, 'Link', "contents={{$url}} action={activate=$action} linewidth=0"); } } } public function get_text_width($text, $font, $size, $word_spacing = 0.0, $letter_spacing = 0.0) { if ($size == 0) { return 0.0; } $fh = $this->_load_font($font); // Determine the additional width due to extra spacing $num_spaces = mb_substr_count($text, " "); $delta = $word_spacing * $num_spaces; if ($letter_spacing) { $num_chars = mb_strlen($text); $delta += $num_chars * $letter_spacing; } return $this->_pdf->stringwidth($text, $fh, $size) + $delta; } public function get_font_height($font, $size) { if ($size == 0) { return 0.0; } $fh = $this->_load_font($font); $this->_pdf->setfont($fh, $size); $asc = $this->_pdf->info_font($fh, "ascender", "fontsize=$size"); $desc = $this->_pdf->info_font($fh, "descender", "fontsize=$size"); // $desc is usually < 0, $ratio = $this->_dompdf->getOptions()->getFontHeightRatio(); return (abs($asc) + abs($desc)) * $ratio; } public function get_font_baseline($font, $size) { $ratio = $this->_dompdf->getOptions()->getFontHeightRatio(); return $this->get_font_height($font, $size) / $ratio * 1.1; } /** * Processes a callback or script on every page. * * The callback function receives the four parameters `int $pageNumber`, * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in * that order. If a script is passed as string, the variables `$PAGE_NUM`, * `$PAGE_COUNT`, `$pdf`, and `$fontMetrics` are available instead. Passing * a script as string is deprecated and will be removed in a future version. * * This function can be used to add page numbers to all pages after the * first one, for example. * * @param callable|string $callback The callback function or PHP script to process on every page */ public function page_script($callback): void { if (is_string($callback)) { $this->processPageScript(function ( int $PAGE_NUM, int $PAGE_COUNT, self $pdf, FontMetrics $fontMetrics ) use ($callback) { eval($callback); }); return; } $this->processPageScript($callback); } public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { $this->processPageScript(function (int $pageNumber, int $pageCount) use ($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle) { $text = str_replace( ["{PAGE_NUM}", "{PAGE_COUNT}"], [$pageNumber, $pageCount], $text ); $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle); }); } public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = []) { $this->processPageScript(function () use ($x1, $y1, $x2, $y2, $color, $width, $style) { $this->line($x1, $y1, $x2, $y2, $color, $width, $style); }); } public function new_page() { // Add objects to the current page $this->_place_objects(); $this->_pdf->suspend_page(""); $this->_pdf->begin_page_ext($this->_width, $this->_height, ""); $this->_page_number = ++$this->_page_count; } protected function processPageScript(callable $callback): void { $this->_pdf->suspend_page(""); for ($p = 1; $p <= $this->_page_count; $p++) { $this->_pdf->resume_page("pagenumber=$p"); $fontMetrics = $this->_dompdf->getFontMetrics(); $callback($p, $this->_page_count, $this, $fontMetrics); $this->_pdf->suspend_page(""); } $this->_pdf->resume_page("pagenumber=" . $this->_page_number); } /** * @throws Exception */ public function stream($filename = "document.pdf", $options = []) { if (headers_sent()) { die("Unable to stream pdf: headers already sent"); } if (!isset($options["compress"])) { $options["compress"] = true; } if (!isset($options["Attachment"])) { $options["Attachment"] = true; } if ($options["compress"]) { $this->setPDFLibValue("compress", 6); } else { $this->setPDFLibValue("compress", 0); } $this->_close(); $data = ""; if (self::$IN_MEMORY) { $data = $this->_pdf->get_buffer(); $size = mb_strlen($data, "8bit"); } else { $size = filesize($this->_file); } header("Cache-Control: private"); header("Content-Type: application/pdf"); header("Content-Length: " . $size); $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf"; $attachment = $options["Attachment"] ? "attachment" : "inline"; header(Helpers::buildContentDispositionHeader($attachment, $filename)); if (self::$IN_MEMORY) { echo $data; } else { // Chunked readfile() $chunk = (1 << 21); // 2 MB $fh = fopen($this->_file, "rb"); if (!$fh) { throw new Exception("Unable to load temporary PDF file: " . $this->_file); } while (!feof($fh)) { echo fread($fh, $chunk); } fclose($fh); //debugpng if ($this->_dompdf->getOptions()->getDebugPng()) { print '[pdflib stream unlink ' . $this->_file . ']'; } if (!$this->_dompdf->getOptions()->getDebugKeepTemp()) { unlink($this->_file); } $this->_file = null; unset($this->_file); } flush(); } public function output($options = []) { if (!isset($options["compress"])) { $options["compress"] = true; } if ($options["compress"]) { $this->setPDFLibValue("compress", 6); } else { $this->setPDFLibValue("compress", 0); } $this->_close(); if (self::$IN_MEMORY) { $data = $this->_pdf->get_buffer(); } else { $data = file_get_contents($this->_file); //debugpng if ($this->_dompdf->getOptions()->getDebugPng()) { print '[pdflib output unlink ' . $this->_file . ']'; } if (!$this->_dompdf->getOptions()->getDebugKeepTemp()) { unlink($this->_file); } $this->_file = null; unset($this->_file); } return $data; } /** * @param string $keyword * @param string $optlist * @return mixed */ protected function getPDFLibParameter($keyword, $optlist = "") { if ($this->getPDFLibMajorVersion() >= 9) { return $this->_pdf->get_option($keyword, ""); } return $this->_pdf->get_parameter($keyword, $optlist); } /** * @param string $keyword * @param string $value * @return mixed */ protected function setPDFLibParameter($keyword, $value) { if ($this->getPDFLibMajorVersion() >= 9) { return $this->_pdf->set_option($keyword . "=" . $value); } return $this->_pdf->set_parameter($keyword, $value); } /** * @param string $keyword * @param string $optlist * @return mixed */ protected function getPDFLibValue($keyword, $optlist = "") { if ($this->getPDFLibMajorVersion() >= 9) { return $this->getPDFLibParameter($keyword, $optlist); } return $this->_pdf->get_value($keyword); } /** * @param string $keyword * @param string $value * @return mixed */ protected function setPDFLibValue($keyword, $value) { if ($this->getPDFLibMajorVersion() >= 9) { return $this->setPDFLibParameter($keyword, $value); } return $this->_pdf->set_value($keyword, $value); } /** * @return int */ protected function getPDFLibMajorVersion() { if (is_null(self::$MAJOR_VERSION)) { if (method_exists($this->_pdf, "get_option")) { self::$MAJOR_VERSION = abs(intval($this->_pdf->get_option("major", ""))); } else { self::$MAJOR_VERSION = abs(intval($this->_pdf->get_value("major", ""))); } } return self::$MAJOR_VERSION; } } // Workaround for idiotic limitation on statics... PDFLib::$PAPER_SIZES = CPDF::$PAPER_SIZES; dompdf/src/Adapter/GD.php 0000644 00000061132 15024772104 0011174 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Adapter; use Dompdf\Canvas; use Dompdf\Dompdf; use Dompdf\Helpers; use Dompdf\Image\Cache; /** * Image rendering interface * * Renders to an image format supported by GD (jpeg, gif, png, xpm). * Not super-useful day-to-day but handy nonetheless * * @package dompdf */ class GD implements Canvas { /** * @var Dompdf */ protected $_dompdf; /** * Resource handle for the image * * @var \GdImage|resource */ protected $_img; /** * Resource handle for the image * * @var \GdImage[]|resource[] */ protected $_imgs; /** * Apparent canvas width in pixels * * @var int */ protected $_width; /** * Apparent canvas height in pixels * * @var int */ protected $_height; /** * Actual image width in pixels * * @var int */ protected $_actual_width; /** * Actual image height in pixels * * @var int */ protected $_actual_height; /** * Current page number * * @var int */ protected $_page_number; /** * Total number of pages * * @var int */ protected $_page_count; /** * Image antialias factor * * @var float */ protected $_aa_factor; /** * Allocated colors * * @var array */ protected $_colors; /** * Background color * * @var int */ protected $_bg_color; /** * Background color array * * @var array */ protected $_bg_color_array; /** * Actual DPI * * @var int */ protected $dpi; /** * Amount to scale font sizes * * Font sizes are 72 DPI, GD internally uses 96. Scale them proportionally. * 72 / 96 = 0.75. * * @var float */ const FONT_SCALE = 0.75; /** * @param string|float[] $paper The paper size to use as either a standard paper size (see {@link CPDF::$PAPER_SIZES}) or * an array of the form `[x1, y1, x2, y2]` (typically `[0, 0, width, height]`). * @param string $orientation The paper orientation, either `portrait` or `landscape`. * @param Dompdf|null $dompdf The Dompdf instance. * @param float $aa_factor Anti-aliasing factor, 1 for no AA * @param array $bg_color Image background color: array(r,g,b,a), 0 <= r,g,b,a <= 1 */ public function __construct($paper = "letter", string $orientation = "portrait", ?Dompdf $dompdf = null, float $aa_factor = 1.0, array $bg_color = [1, 1, 1, 0]) { if (is_array($paper)) { $size = array_map("floatval", $paper); } else { $paper = strtolower($paper); $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"]; } if (strtolower($orientation) === "landscape") { [$size[2], $size[3]] = [$size[3], $size[2]]; } if ($dompdf === null) { $this->_dompdf = new Dompdf(); } else { $this->_dompdf = $dompdf; } $this->dpi = $this->get_dompdf()->getOptions()->getDpi(); if ($aa_factor < 1) { $aa_factor = 1; } $this->_aa_factor = $aa_factor; $size[2] *= $aa_factor; $size[3] *= $aa_factor; $this->_width = $size[2] - $size[0]; $this->_height = $size[3] - $size[1]; $this->_actual_width = $this->_upscale($this->_width); $this->_actual_height = $this->_upscale($this->_height); $this->_page_number = $this->_page_count = 0; if (is_null($bg_color) || !is_array($bg_color)) { // Pure white bg $bg_color = [1, 1, 1, 0]; } $this->_bg_color_array = $bg_color; $this->new_page(); } public function get_dompdf() { return $this->_dompdf; } /** * Return the GD image resource * * @return \GdImage|resource */ public function get_image() { return $this->_img; } /** * Return the image's width in pixels * * @return int */ public function get_width() { return round($this->_width / $this->_aa_factor); } /** * Return the image's height in pixels * * @return int */ public function get_height() { return round($this->_height / $this->_aa_factor); } public function get_page_number() { return $this->_page_number; } public function get_page_count() { return $this->_page_count; } /** * Sets the current page number * * @param int $num */ public function set_page_number($num) { $this->_page_number = $num; } public function set_page_count($count) { $this->_page_count = $count; } public function set_opacity(float $opacity, string $mode = "Normal"): void { // FIXME } /** * Allocate a new color. Allocate with GD as needed and store * previously allocated colors in $this->_colors. * * @param array $color The new current color * @return int The allocated color */ protected function _allocate_color($color) { $a = isset($color["alpha"]) ? $color["alpha"] : 1; if (isset($color["c"])) { $color = Helpers::cmyk_to_rgb($color); } list($r, $g, $b) = $color; $r = round($r * 255); $g = round($g * 255); $b = round($b * 255); $a = round(127 - ($a * 127)); // Clip values $r = $r > 255 ? 255 : $r; $g = $g > 255 ? 255 : $g; $b = $b > 255 ? 255 : $b; $a = $a > 127 ? 127 : $a; $r = $r < 0 ? 0 : $r; $g = $g < 0 ? 0 : $g; $b = $b < 0 ? 0 : $b; $a = $a < 0 ? 0 : $a; $key = sprintf("#%02X%02X%02X%02X", $r, $g, $b, $a); if (isset($this->_colors[$key])) { return $this->_colors[$key]; } if ($a != 0) { $this->_colors[$key] = imagecolorallocatealpha($this->get_image(), $r, $g, $b, $a); } else { $this->_colors[$key] = imagecolorallocate($this->get_image(), $r, $g, $b); } return $this->_colors[$key]; } /** * Scales value up to the current canvas DPI from 72 DPI * * @param float $length * @return int */ protected function _upscale($length) { return round(($length * $this->dpi) / 72 * $this->_aa_factor); } /** * Scales value down from the current canvas DPI to 72 DPI * * @param float $length * @return float */ protected function _downscale($length) { return round(($length / $this->dpi * 72) / $this->_aa_factor); } protected function convertStyle(array $style, int $color, int $width): array { $gdStyle = []; if (count($style) === 1) { $style[] = $style[0]; } foreach ($style as $index => $s) { $d = $this->_upscale($s); for ($i = 0; $i < $d; $i++) { for ($j = 0; $j < $width; $j++) { $gdStyle[] = $index % 2 === 0 ? $color : IMG_COLOR_TRANSPARENT; } } } return $gdStyle; } public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt") { // Account for the fact that round and square caps are expected to // extend outwards if ($cap === "round" || $cap === "square") { // Shift line by half width $w = $width / 2; $a = $x2 - $x1; $b = $y2 - $y1; $c = sqrt($a ** 2 + $b ** 2); $dx = $a * $w / $c; $dy = $b * $w / $c; $x1 -= $dx; $x2 -= $dx; $y1 -= $dy; $y2 -= $dy; // Adapt dash pattern if (is_array($style)) { foreach ($style as $index => &$s) { $s = $index % 2 === 0 ? $s + $width : $s - $width; } } } // Scale by the AA factor and DPI $x1 = $this->_upscale($x1); $y1 = $this->_upscale($y1); $x2 = $this->_upscale($x2); $y2 = $this->_upscale($y2); $width = $this->_upscale($width); $c = $this->_allocate_color($color); // Convert the style array if required if (is_array($style) && count($style) > 0) { $gd_style = $this->convertStyle($style, $c, $width); if (!empty($gd_style)) { imagesetstyle($this->get_image(), $gd_style); $c = IMG_COLOR_STYLED; } } imagesetthickness($this->get_image(), $width); imageline($this->get_image(), $x1, $y1, $x2, $y2, $c); } public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt") { // Account for the fact that round and square caps are expected to // extend outwards if ($cap === "round" || $cap === "square") { // Adapt dash pattern if (is_array($style)) { foreach ($style as $index => &$s) { $s = $index % 2 === 0 ? $s + $width : $s - $width; } } } // Scale by the AA factor and DPI $x = $this->_upscale($x); $y = $this->_upscale($y); $w = $this->_upscale($r1 * 2); $h = $this->_upscale($r2 * 2); $width = $this->_upscale($width); // Adapt angles as imagearc counts clockwise $start = 360 - $aend; $end = 360 - $astart; $c = $this->_allocate_color($color); // Convert the style array if required if (is_array($style) && count($style) > 0) { $gd_style = $this->convertStyle($style, $c, $width); if (!empty($gd_style)) { imagesetstyle($this->get_image(), $gd_style); $c = IMG_COLOR_STYLED; } } imagesetthickness($this->get_image(), $width); imagearc($this->get_image(), $x, $y, $w, $h, $start, $end, $c); } public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt") { // Account for the fact that round and square caps are expected to // extend outwards if ($cap === "round" || $cap === "square") { // Adapt dash pattern if (is_array($style)) { foreach ($style as $index => &$s) { $s = $index % 2 === 0 ? $s + $width : $s - $width; } } } // Scale by the AA factor and DPI $x1 = $this->_upscale($x1); $y1 = $this->_upscale($y1); $w = $this->_upscale($w); $h = $this->_upscale($h); $width = $this->_upscale($width); $c = $this->_allocate_color($color); // Convert the style array if required if (is_array($style) && count($style) > 0) { $gd_style = $this->convertStyle($style, $c, $width); if (!empty($gd_style)) { imagesetstyle($this->get_image(), $gd_style); $c = IMG_COLOR_STYLED; } } imagesetthickness($this->get_image(), $width); if ($c === IMG_COLOR_STYLED) { imagepolygon($this->get_image(), [ $x1, $y1, $x1 + $w, $y1, $x1 + $w, $y1 + $h, $x1, $y1 + $h ], $c); } else { imagerectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c); } } public function filled_rectangle($x1, $y1, $w, $h, $color) { // Scale by the AA factor and DPI $x1 = $this->_upscale($x1); $y1 = $this->_upscale($y1); $w = $this->_upscale($w); $h = $this->_upscale($h); $c = $this->_allocate_color($color); imagefilledrectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c); } public function clipping_rectangle($x1, $y1, $w, $h) { // @todo } public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) { // @todo } public function clipping_polygon(array $points): void { // @todo } public function clipping_end() { // @todo } public function save() { $this->get_dompdf()->getOptions()->setDpi(72); } public function restore() { $this->get_dompdf()->getOptions()->setDpi($this->dpi); } public function rotate($angle, $x, $y) { // @todo } public function skew($angle_x, $angle_y, $x, $y) { // @todo } public function scale($s_x, $s_y, $x, $y) { // @todo } public function translate($t_x, $t_y) { // @todo } public function transform($a, $b, $c, $d, $e, $f) { // @todo } public function polygon($points, $color, $width = null, $style = [], $fill = false) { // Scale each point by the AA factor and DPI foreach (array_keys($points) as $i) { $points[$i] = $this->_upscale($points[$i]); } $width = isset($width) ? $this->_upscale($width) : null; $c = $this->_allocate_color($color); // Convert the style array if required if (is_array($style) && count($style) > 0 && isset($width) && !$fill) { $gd_style = $this->convertStyle($style, $c, $width); if (!empty($gd_style)) { imagesetstyle($this->get_image(), $gd_style); $c = IMG_COLOR_STYLED; } } imagesetthickness($this->get_image(), isset($width) ? $width : 0); if ($fill) { imagefilledpolygon($this->get_image(), $points, $c); } else { imagepolygon($this->get_image(), $points, $c); } } public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false) { // Scale by the AA factor and DPI $x = $this->_upscale($x); $y = $this->_upscale($y); $d = $this->_upscale(2 * $r); $width = isset($width) ? $this->_upscale($width) : null; $c = $this->_allocate_color($color); // Convert the style array if required if (is_array($style) && count($style) > 0 && isset($width) && !$fill) { $gd_style = $this->convertStyle($style, $c, $width); if (!empty($gd_style)) { imagesetstyle($this->get_image(), $gd_style); $c = IMG_COLOR_STYLED; } } imagesetthickness($this->get_image(), isset($width) ? $width : 0); if ($fill) { imagefilledellipse($this->get_image(), $x, $y, $d, $d, $c); } else { imageellipse($this->get_image(), $x, $y, $d, $d, $c); } } /** * @throws \Exception */ public function image($img, $x, $y, $w, $h, $resolution = "normal") { $img_type = Cache::detect_type($img, $this->get_dompdf()->getHttpContext()); if (!$img_type) { return; } $func_name = "imagecreatefrom$img_type"; if (!function_exists($func_name)) { if (!method_exists(Helpers::class, $func_name)) { throw new \Exception("Function $func_name() not found. Cannot convert $img_type image: $img. Please install the image PHP extension."); } $func_name = [Helpers::class, $func_name]; } $src = @call_user_func($func_name, $img); if (!$src) { return; // Probably should add to $_dompdf_errors or whatever here } // Scale by the AA factor and DPI $x = $this->_upscale($x); $y = $this->_upscale($y); $w = $this->_upscale($w); $h = $this->_upscale($h); $img_w = imagesx($src); $img_h = imagesy($src); imagecopyresampled($this->get_image(), $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h); } public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0) { // Scale by the AA factor and DPI $x = $this->_upscale($x); $y = $this->_upscale($y); $size = $this->_upscale($size) * self::FONT_SCALE; $h = round($this->get_font_height_actual($font, $size)); $c = $this->_allocate_color($color); // imagettftext() converts numeric entities to their respective // character. Preserve any originally double encoded entities to be // represented as is. // eg: &#160; will render   rather than its character. $text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&\1', $text); $text = mb_encode_numericentity($text, [0x0080, 0xff, 0, 0xff], 'UTF-8'); $font = $this->get_ttf_file($font); // FIXME: word spacing imagettftext($this->get_image(), $size, $angle, $x, $y + $h, $c, $font, $text); } public function javascript($code) { // Not implemented } public function add_named_dest($anchorname) { // Not implemented } public function add_link($url, $x, $y, $width, $height) { // Not implemented } public function add_info(string $label, string $value): void { // N/A } public function set_default_view($view, $options = []) { // N/A } public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0) { $font = $this->get_ttf_file($font); $size = $this->_upscale($size) * self::FONT_SCALE; // imagettfbbox() converts numeric entities to their respective // character. Preserve any originally double encoded entities to be // represented as is. // eg: &#160; will render   rather than its character. $text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&\1', $text); $text = mb_encode_numericentity($text, [0x0080, 0xffff, 0, 0xffff], 'UTF-8'); // FIXME: word spacing list($x1, , $x2) = imagettfbbox($size, 0, $font, $text); // Add additional 1pt to prevent text overflow issues return $this->_downscale($x2 - $x1) + 1; } /** * @param string|null $font * @return string */ public function get_ttf_file($font) { if ($font === null) { $font = ""; } if ( stripos($font, ".ttf") === false ) { $font .= ".ttf"; } if (!file_exists($font)) { $font_metrics = $this->_dompdf->getFontMetrics(); $font = $font_metrics->getFont($this->_dompdf->getOptions()->getDefaultFont()) . ".ttf"; if (!file_exists($font)) { if (strpos($font, "mono")) { $font = $font_metrics->getFont("DejaVu Mono") . ".ttf"; } elseif (strpos($font, "sans") !== false) { $font = $font_metrics->getFont("DejaVu Sans") . ".ttf"; } elseif (strpos($font, "serif")) { $font = $font_metrics->getFont("DejaVu Serif") . ".ttf"; } else { $font = $font_metrics->getFont("DejaVu Sans") . ".ttf"; } } } return $font; } public function get_font_height($font, $size) { $size = $this->_upscale($size) * self::FONT_SCALE; $height = $this->get_font_height_actual($font, $size); return $this->_downscale($height); } /** * @param string $font * @param float $size * * @return float */ protected function get_font_height_actual($font, $size) { $font = $this->get_ttf_file($font); $ratio = $this->_dompdf->getOptions()->getFontHeightRatio(); // FIXME: word spacing list(, $y2, , , , $y1) = imagettfbbox($size, 0, $font, "MXjpqytfhl"); // Test string with ascenders, descenders and caps return ($y2 - $y1) * $ratio; } public function get_font_baseline($font, $size) { $ratio = $this->_dompdf->getOptions()->getFontHeightRatio(); return $this->get_font_height($font, $size) / $ratio; } public function new_page() { $this->_page_number++; $this->_page_count++; $this->_img = imagecreatetruecolor($this->_actual_width, $this->_actual_height); $this->_bg_color = $this->_allocate_color($this->_bg_color_array); imagealphablending($this->_img, true); imagesavealpha($this->_img, true); imagefill($this->_img, 0, 0, $this->_bg_color); $this->_imgs[] = $this->_img; } public function open_object() { // N/A } public function close_object() { // N/A } public function add_object() { // N/A } public function page_script($callback): void { // N/A } public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { // N/A } public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = []) { // N/A } /** * Streams the image to the client. * * @param string $filename The filename to present to the client. * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only); * 'page' => Number of the page to output (defaults to the first); 'Attachment': 1 or 0 (default 1). */ public function stream($filename, $options = []) { if (headers_sent()) { die("Unable to stream image: headers already sent"); } if (!isset($options["type"])) $options["type"] = "png"; if (!isset($options["Attachment"])) $options["Attachment"] = true; $type = strtolower($options["type"]); switch ($type) { case "jpg": case "jpeg": $contentType = "image/jpeg"; $extension = ".jpg"; break; case "png": default: $contentType = "image/png"; $extension = ".png"; break; } header("Cache-Control: private"); header("Content-Type: $contentType"); $filename = str_replace(["\n", "'"], "", basename($filename, ".$type")) . $extension; $attachment = $options["Attachment"] ? "attachment" : "inline"; header(Helpers::buildContentDispositionHeader($attachment, $filename)); $this->_output($options); flush(); } /** * Returns the image as a string. * * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only); * 'page' => Number of the page to output (defaults to the first). * @return string */ public function output($options = []) { ob_start(); $this->_output($options); return ob_get_clean(); } /** * Outputs the image stream directly. * * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only); * 'page' => Number of the page to output (defaults to the first). */ protected function _output($options = []) { if (!isset($options["type"])) $options["type"] = "png"; if (!isset($options["page"])) $options["page"] = 1; $type = strtolower($options["type"]); if (isset($this->_imgs[$options["page"] - 1])) { $img = $this->_imgs[$options["page"] - 1]; } else { $img = $this->_imgs[0]; } // Perform any antialiasing if ($this->_aa_factor != 1) { $dst_w = round($this->_actual_width / $this->_aa_factor); $dst_h = round($this->_actual_height / $this->_aa_factor); $dst = imagecreatetruecolor($dst_w, $dst_h); imagecopyresampled($dst, $img, 0, 0, 0, 0, $dst_w, $dst_h, $this->_actual_width, $this->_actual_height); } else { $dst = $img; } switch ($type) { case "jpg": case "jpeg": if (!isset($options["quality"])) { $options["quality"] = 75; } imagejpeg($dst, null, $options["quality"]); break; case "png": default: imagepng($dst); break; } if ($this->_aa_factor != 1) { imagedestroy($dst); } } } dompdf/src/Adapter/CPDF.php 0000644 00000065673 15024772104 0011434 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ // FIXME: Need to sanity check inputs to this class namespace Dompdf\Adapter; use Dompdf\Canvas; use Dompdf\Dompdf; use Dompdf\Exception; use Dompdf\FontMetrics; use Dompdf\Helpers; use Dompdf\Image\Cache; use FontLib\Exception\FontNotFoundException; /** * PDF rendering interface * * Dompdf\Adapter\CPDF provides a simple stateless interface to the stateful one * provided by the Cpdf class. * * Unless otherwise mentioned, all dimensions are in points (1/72 in). The * coordinate origin is in the top left corner, and y values increase * downwards. * * See {@link http://www.ros.co.nz/pdf/} for more complete documentation * on the underlying {@link Cpdf} class. * * @package dompdf */ class CPDF implements Canvas { /** * Dimensions of paper sizes in points * * @var array */ static $PAPER_SIZES = [ "4a0" => [0.0, 0.0, 4767.87, 6740.79], "2a0" => [0.0, 0.0, 3370.39, 4767.87], "a0" => [0.0, 0.0, 2383.94, 3370.39], "a1" => [0.0, 0.0, 1683.78, 2383.94], "a2" => [0.0, 0.0, 1190.55, 1683.78], "a3" => [0.0, 0.0, 841.89, 1190.55], "a4" => [0.0, 0.0, 595.28, 841.89], "a5" => [0.0, 0.0, 419.53, 595.28], "a6" => [0.0, 0.0, 297.64, 419.53], "a7" => [0.0, 0.0, 209.76, 297.64], "a8" => [0.0, 0.0, 147.40, 209.76], "a9" => [0.0, 0.0, 104.88, 147.40], "a10" => [0.0, 0.0, 73.70, 104.88], "b0" => [0.0, 0.0, 2834.65, 4008.19], "b1" => [0.0, 0.0, 2004.09, 2834.65], "b2" => [0.0, 0.0, 1417.32, 2004.09], "b3" => [0.0, 0.0, 1000.63, 1417.32], "b4" => [0.0, 0.0, 708.66, 1000.63], "b5" => [0.0, 0.0, 498.90, 708.66], "b6" => [0.0, 0.0, 354.33, 498.90], "b7" => [0.0, 0.0, 249.45, 354.33], "b8" => [0.0, 0.0, 175.75, 249.45], "b9" => [0.0, 0.0, 124.72, 175.75], "b10" => [0.0, 0.0, 87.87, 124.72], "c0" => [0.0, 0.0, 2599.37, 3676.54], "c1" => [0.0, 0.0, 1836.85, 2599.37], "c2" => [0.0, 0.0, 1298.27, 1836.85], "c3" => [0.0, 0.0, 918.43, 1298.27], "c4" => [0.0, 0.0, 649.13, 918.43], "c5" => [0.0, 0.0, 459.21, 649.13], "c6" => [0.0, 0.0, 323.15, 459.21], "c7" => [0.0, 0.0, 229.61, 323.15], "c8" => [0.0, 0.0, 161.57, 229.61], "c9" => [0.0, 0.0, 113.39, 161.57], "c10" => [0.0, 0.0, 79.37, 113.39], "ra0" => [0.0, 0.0, 2437.80, 3458.27], "ra1" => [0.0, 0.0, 1729.13, 2437.80], "ra2" => [0.0, 0.0, 1218.90, 1729.13], "ra3" => [0.0, 0.0, 864.57, 1218.90], "ra4" => [0.0, 0.0, 609.45, 864.57], "sra0" => [0.0, 0.0, 2551.18, 3628.35], "sra1" => [0.0, 0.0, 1814.17, 2551.18], "sra2" => [0.0, 0.0, 1275.59, 1814.17], "sra3" => [0.0, 0.0, 907.09, 1275.59], "sra4" => [0.0, 0.0, 637.80, 907.09], "letter" => [0.0, 0.0, 612.00, 792.00], "half-letter" => [0.0, 0.0, 396.00, 612.00], "legal" => [0.0, 0.0, 612.00, 1008.00], "ledger" => [0.0, 0.0, 1224.00, 792.00], "tabloid" => [0.0, 0.0, 792.00, 1224.00], "executive" => [0.0, 0.0, 521.86, 756.00], "folio" => [0.0, 0.0, 612.00, 936.00], "commercial #10 envelope" => [0.0, 0.0, 684.00, 297.00], "catalog #10 1/2 envelope" => [0.0, 0.0, 648.00, 864.00], "8.5x11" => [0.0, 0.0, 612.00, 792.00], "8.5x14" => [0.0, 0.0, 612.00, 1008.00], "11x17" => [0.0, 0.0, 792.00, 1224.00], ]; /** * The Dompdf object * * @var Dompdf */ protected $_dompdf; /** * Instance of Cpdf class * * @var \Dompdf\Cpdf */ protected $_pdf; /** * PDF width, in points * * @var float */ protected $_width; /** * PDF height, in points * * @var float */ protected $_height; /** * Current page number * * @var int */ protected $_page_number; /** * Total number of pages * * @var int */ protected $_page_count; /** * Array of pages for accessing after rendering is initially complete * * @var array */ protected $_pages; /** * Currently-applied opacity level (0 - 1) * * @var float */ protected $_current_opacity = 1; public function __construct($paper = "letter", string $orientation = "portrait", ?Dompdf $dompdf = null) { if (is_array($paper)) { $size = array_map("floatval", $paper); } else { $paper = strtolower($paper); $size = self::$PAPER_SIZES[$paper] ?? self::$PAPER_SIZES["letter"]; } if (strtolower($orientation) === "landscape") { [$size[2], $size[3]] = [$size[3], $size[2]]; } if ($dompdf === null) { $this->_dompdf = new Dompdf(); } else { $this->_dompdf = $dompdf; } $this->_pdf = new \Dompdf\Cpdf( $size, true, $this->_dompdf->getOptions()->getFontCache(), $this->_dompdf->getOptions()->getTempDir() ); $this->_pdf->addInfo("Producer", sprintf("%s + CPDF", $this->_dompdf->version)); $time = substr_replace(date('YmdHisO'), '\'', -2, 0) . '\''; $this->_pdf->addInfo("CreationDate", "D:$time"); $this->_pdf->addInfo("ModDate", "D:$time"); $this->_width = $size[2] - $size[0]; $this->_height = $size[3] - $size[1]; $this->_page_number = $this->_page_count = 1; $this->_pages = [$this->_pdf->getFirstPageId()]; } public function get_dompdf() { return $this->_dompdf; } /** * Returns the Cpdf instance * * @return \Dompdf\Cpdf */ public function get_cpdf() { return $this->_pdf; } public function add_info(string $label, string $value): void { $this->_pdf->addInfo($label, $value); } /** * Opens a new 'object' * * While an object is open, all drawing actions are recorded in the object, * as opposed to being drawn on the current page. Objects can be added * later to a specific page or to several pages. * * The return value is an integer ID for the new object. * * @see CPDF::close_object() * @see CPDF::add_object() * * @return int */ public function open_object() { $ret = $this->_pdf->openObject(); $this->_pdf->saveState(); return $ret; } /** * Reopens an existing 'object' * * @see CPDF::open_object() * @param int $object the ID of a previously opened object */ public function reopen_object($object) { $this->_pdf->reopenObject($object); $this->_pdf->saveState(); } /** * Closes the current 'object' * * @see CPDF::open_object() */ public function close_object() { $this->_pdf->restoreState(); $this->_pdf->closeObject(); } /** * Adds a specified 'object' to the document * * $object int specifying an object created with {@link * CPDF::open_object()}. $where can be one of: * - 'add' add to current page only * - 'all' add to every page from the current one onwards * - 'odd' add to all odd numbered pages from now on * - 'even' add to all even numbered pages from now on * - 'next' add the object to the next page only * - 'nextodd' add to all odd numbered pages from the next one * - 'nexteven' add to all even numbered pages from the next one * * @see Cpdf::addObject() * * @param int $object * @param string $where */ public function add_object($object, $where = 'all') { $this->_pdf->addObject($object, $where); } /** * Stops the specified 'object' from appearing in the document. * * The object will stop being displayed on the page following the current * one. * * @param int $object */ public function stop_object($object) { $this->_pdf->stopObject($object); } /** * Serialize the pdf object's current state for retrieval later */ public function serialize_object($id) { return $this->_pdf->serializeObject($id); } public function reopen_serialized_object($obj) { return $this->_pdf->restoreSerializedObject($obj); } //........................................................................ public function get_width() { return $this->_width; } public function get_height() { return $this->_height; } public function get_page_number() { return $this->_page_number; } public function get_page_count() { return $this->_page_count; } /** * Sets the current page number * * @param int $num */ public function set_page_number($num) { $this->_page_number = $num; } public function set_page_count($count) { $this->_page_count = $count; } /** * Sets the stroke color * * See {@link Style::set_color()} for the format of the color array. * * @param array $color */ protected function _set_stroke_color($color) { $this->_pdf->setStrokeColor($color); $alpha = isset($color["alpha"]) ? $color["alpha"] : 1; $alpha *= $this->_current_opacity; $this->_set_line_transparency("Normal", $alpha); } /** * Sets the fill colour * * See {@link Style::set_color()} for the format of the colour array. * * @param array $color */ protected function _set_fill_color($color) { $this->_pdf->setColor($color); $alpha = isset($color["alpha"]) ? $color["alpha"] : 1; $alpha *= $this->_current_opacity; $this->_set_fill_transparency("Normal", $alpha); } /** * Sets line transparency * @see Cpdf::setLineTransparency() * * Valid blend modes are (case-sensitive): * * Normal, Multiply, Screen, Overlay, Darken, Lighten, * ColorDodge, ColorBurn, HardLight, SoftLight, Difference, * Exclusion * * @param string $mode the blending mode to use * @param float $opacity 0.0 fully transparent, 1.0 fully opaque */ protected function _set_line_transparency($mode, $opacity) { $this->_pdf->setLineTransparency($mode, $opacity); } /** * Sets fill transparency * @see Cpdf::setFillTransparency() * * Valid blend modes are (case-sensitive): * * Normal, Multiply, Screen, Overlay, Darken, Lighten, * ColorDogde, ColorBurn, HardLight, SoftLight, Difference, * Exclusion * * @param string $mode the blending mode to use * @param float $opacity 0.0 fully transparent, 1.0 fully opaque */ protected function _set_fill_transparency($mode, $opacity) { $this->_pdf->setFillTransparency($mode, $opacity); } /** * Sets the line style * * @see Cpdf::setLineStyle() * * @param float $width * @param string $cap * @param string $join * @param array $dash */ protected function _set_line_style($width, $cap, $join, $dash) { $this->_pdf->setLineStyle($width, $cap, $join, $dash); } public function set_opacity(float $opacity, string $mode = "Normal"): void { $this->_set_line_transparency($mode, $opacity); $this->_set_fill_transparency($mode, $opacity); $this->_current_opacity = $opacity; } public function set_default_view($view, $options = []) { array_unshift($options, $view); call_user_func_array([$this->_pdf, "openHere"], $options); } /** * Remaps y coords from 4th to 1st quadrant * * @param float $y * @return float */ protected function y($y) { return $this->_height - $y; } public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt") { $this->_set_stroke_color($color); $this->_set_line_style($width, $cap, "", $style); $this->_pdf->line($x1, $this->y($y1), $x2, $this->y($y2)); $this->_set_line_transparency("Normal", $this->_current_opacity); } public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt") { $this->_set_stroke_color($color); $this->_set_line_style($width, $cap, "", $style); $this->_pdf->ellipse($x, $this->y($y), $r1, $r2, 0, 8, $astart, $aend, false, false, true, false); $this->_set_line_transparency("Normal", $this->_current_opacity); } public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt") { $this->_set_stroke_color($color); $this->_set_line_style($width, $cap, "", $style); $this->_pdf->rectangle($x1, $this->y($y1) - $h, $w, $h); $this->_set_line_transparency("Normal", $this->_current_opacity); } public function filled_rectangle($x1, $y1, $w, $h, $color) { $this->_set_fill_color($color); $this->_pdf->filledRectangle($x1, $this->y($y1) - $h, $w, $h); $this->_set_fill_transparency("Normal", $this->_current_opacity); } public function clipping_rectangle($x1, $y1, $w, $h) { $this->_pdf->clippingRectangle($x1, $this->y($y1) - $h, $w, $h); } public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) { $this->_pdf->clippingRectangleRounded($x1, $this->y($y1) - $h, $w, $h, $rTL, $rTR, $rBR, $rBL); } public function clipping_polygon(array $points): void { // Adjust y values for ($i = 1; $i < count($points); $i += 2) { $points[$i] = $this->y($points[$i]); } $this->_pdf->clippingPolygon($points); } public function clipping_end() { $this->_pdf->clippingEnd(); } public function save() { $this->_pdf->saveState(); } public function restore() { $this->_pdf->restoreState(); } public function rotate($angle, $x, $y) { $this->_pdf->rotate($angle, $x, $y); } public function skew($angle_x, $angle_y, $x, $y) { $this->_pdf->skew($angle_x, $angle_y, $x, $y); } public function scale($s_x, $s_y, $x, $y) { $this->_pdf->scale($s_x, $s_y, $x, $y); } public function translate($t_x, $t_y) { $this->_pdf->translate($t_x, $t_y); } public function transform($a, $b, $c, $d, $e, $f) { $this->_pdf->transform([$a, $b, $c, $d, $e, $f]); } public function polygon($points, $color, $width = null, $style = [], $fill = false) { $this->_set_fill_color($color); $this->_set_stroke_color($color); if (!$fill && isset($width)) { $this->_set_line_style($width, "square", "miter", $style); } // Adjust y values for ($i = 1; $i < count($points); $i += 2) { $points[$i] = $this->y($points[$i]); } $this->_pdf->polygon($points, $fill); $this->_set_fill_transparency("Normal", $this->_current_opacity); $this->_set_line_transparency("Normal", $this->_current_opacity); } public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false) { $this->_set_fill_color($color); $this->_set_stroke_color($color); if (!$fill && isset($width)) { $this->_set_line_style($width, "round", "round", $style); } $this->_pdf->ellipse($x, $this->y($y), $r, 0, 0, 8, 0, 360, 1, $fill); $this->_set_fill_transparency("Normal", $this->_current_opacity); $this->_set_line_transparency("Normal", $this->_current_opacity); } /** * Convert image to a PNG image * * @param string $image_url * @param string $type * * @return string|null The url of the newly converted image */ protected function _convert_to_png($image_url, $type) { $filename = Cache::getTempImage($image_url); if ($filename !== null && file_exists($filename)) { return $filename; } $func_name = "imagecreatefrom$type"; set_error_handler([Helpers::class, "record_warnings"]); if (!function_exists($func_name)) { if (!method_exists(Helpers::class, $func_name)) { throw new Exception("Function $func_name() not found. Cannot convert $type image: $image_url. Please install the image PHP extension."); } $func_name = [Helpers::class, $func_name]; } try { $im = call_user_func($func_name, $image_url); if ($im) { imageinterlace($im, false); $tmp_dir = $this->_dompdf->getOptions()->getTempDir(); $tmp_name = @tempnam($tmp_dir, "{$type}_dompdf_img_"); @unlink($tmp_name); $filename = "$tmp_name.png"; imagepng($im, $filename); imagedestroy($im); } else { $filename = null; } } finally { restore_error_handler(); } if ($filename !== null) { Cache::addTempImage($image_url, $filename); } return $filename; } public function image($img, $x, $y, $w, $h, $resolution = "normal") { [$width, $height, $type] = Helpers::dompdf_getimagesize($img, $this->get_dompdf()->getHttpContext()); $debug_png = $this->_dompdf->getOptions()->getDebugPng(); if ($debug_png) { print "[image:$img|$width|$height|$type]"; } switch ($type) { case "jpeg": if ($debug_png) { print '!!!jpg!!!'; } $this->_pdf->addJpegFromFile($img, $x, $this->y($y) - $h, $w, $h); break; case "webp": /** @noinspection PhpMissingBreakStatementInspection */ case "gif": /** @noinspection PhpMissingBreakStatementInspection */ case "bmp": if ($debug_png) print "!!!{$type}!!!"; $img = $this->_convert_to_png($img, $type); if ($img === null) { if ($debug_png) print '!!!conversion to PDF failed!!!'; $this->image(Cache::$broken_image, $x, $y, $w, $h, $resolution); break; } case "png": if ($debug_png) print '!!!png!!!'; $this->_pdf->addPngFromFile($img, $x, $this->y($y) - $h, $w, $h); break; case "svg": if ($debug_png) print '!!!SVG!!!'; $this->_pdf->addSvgFromFile($img, $x, $this->y($y) - $h, $w, $h); break; default: if ($debug_png) print '!!!unknown!!!'; } } public function select($x, $y, $w, $h, $font, $size, $color = [0, 0, 0], $opts = []) { $pdf = $this->_pdf; $font .= ".afm"; $pdf->selectFont($font); if (!isset($pdf->acroFormId)) { $pdf->addForm(); } $ft = \Dompdf\Cpdf::ACROFORM_FIELD_CHOICE; $ff = \Dompdf\Cpdf::ACROFORM_FIELD_CHOICE_COMBO; $id = $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color); $pdf->setFormFieldOpt($id, $opts); } public function textarea($x, $y, $w, $h, $font, $size, $color = [0, 0, 0]) { $pdf = $this->_pdf; $font .= ".afm"; $pdf->selectFont($font); if (!isset($pdf->acroFormId)) { $pdf->addForm(); } $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT; $ff = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT_MULTILINE; $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color); } public function input($x, $y, $w, $h, $type, $font, $size, $color = [0, 0, 0]) { $pdf = $this->_pdf; $font .= ".afm"; $pdf->selectFont($font); if (!isset($pdf->acroFormId)) { $pdf->addForm(); } $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT; $ff = 0; switch ($type) { case 'text': $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT; break; case 'password': $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT; $ff = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT_PASSWORD; break; case 'submit': $ft = \Dompdf\Cpdf::ACROFORM_FIELD_BUTTON; break; } $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color); } public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { $pdf = $this->_pdf; $this->_set_fill_color($color); $is_font_subsetting = $this->_dompdf->getOptions()->getIsFontSubsettingEnabled(); $pdf->selectFont($font . '.afm', '', true, $is_font_subsetting); $pdf->addText($x, $this->y($y) - $pdf->getFontHeight($size), $size, $text, $angle, $word_space, $char_space); $this->_set_fill_transparency("Normal", $this->_current_opacity); } public function javascript($code) { $this->_pdf->addJavascript($code); } //........................................................................ public function add_named_dest($anchorname) { $this->_pdf->addDestination($anchorname, "Fit"); } public function add_link($url, $x, $y, $width, $height) { $y = $this->y($y) - $height; if (strpos($url, '#') === 0) { // Local link $name = substr($url, 1); if ($name) { $this->_pdf->addInternalLink($name, $x, $y, $x + $width, $y + $height); } } else { $this->_pdf->addLink($url, $x, $y, $x + $width, $y + $height); } } /** * @throws FontNotFoundException */ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0) { $this->_pdf->selectFont($font, '', true, $this->_dompdf->getOptions()->getIsFontSubsettingEnabled()); return $this->_pdf->getTextWidth($size, $text, $word_spacing, $char_spacing); } /** * @throws FontNotFoundException */ public function get_font_height($font, $size) { $options = $this->_dompdf->getOptions(); $this->_pdf->selectFont($font, '', true, $options->getIsFontSubsettingEnabled()); return $this->_pdf->getFontHeight($size) * $options->getFontHeightRatio(); } /*function get_font_x_height($font, $size) { $this->_pdf->selectFont($font); $ratio = $this->_dompdf->getOptions()->getFontHeightRatio(); return $this->_pdf->getFontXHeight($size) * $ratio; }*/ /** * @throws FontNotFoundException */ public function get_font_baseline($font, $size) { $ratio = $this->_dompdf->getOptions()->getFontHeightRatio(); return $this->get_font_height($font, $size) / $ratio; } /** * Processes a callback or script on every page. * * The callback function receives the four parameters `int $pageNumber`, * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in * that order. If a script is passed as string, the variables `$PAGE_NUM`, * `$PAGE_COUNT`, `$pdf`, and `$fontMetrics` are available instead. Passing * a script as string is deprecated and will be removed in a future version. * * This function can be used to add page numbers to all pages after the * first one, for example. * * @param callable|string $callback The callback function or PHP script to process on every page */ public function page_script($callback): void { if (is_string($callback)) { $this->processPageScript(function ( int $PAGE_NUM, int $PAGE_COUNT, self $pdf, FontMetrics $fontMetrics ) use ($callback) { eval($callback); }); return; } $this->processPageScript($callback); } public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0) { $this->processPageScript(function (int $pageNumber, int $pageCount) use ($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle) { $text = str_replace( ["{PAGE_NUM}", "{PAGE_COUNT}"], [$pageNumber, $pageCount], $text ); $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle); }); } public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = []) { $this->processPageScript(function () use ($x1, $y1, $x2, $y2, $color, $width, $style) { $this->line($x1, $y1, $x2, $y2, $color, $width, $style); }); } /** * @return int */ public function new_page() { $this->_page_number++; $this->_page_count++; $ret = $this->_pdf->newPage(); $this->_pages[] = $ret; return $ret; } protected function processPageScript(callable $callback): void { $pageNumber = 1; foreach ($this->_pages as $pid) { $this->reopen_object($pid); $fontMetrics = $this->_dompdf->getFontMetrics(); $callback($pageNumber, $this->_page_count, $this, $fontMetrics); $this->close_object(); $pageNumber++; } } public function stream($filename = "document.pdf", $options = []) { if (headers_sent()) { die("Unable to stream pdf: headers already sent"); } if (!isset($options["compress"])) $options["compress"] = true; if (!isset($options["Attachment"])) $options["Attachment"] = true; $debug = !$options['compress']; $tmp = ltrim($this->_pdf->output($debug)); header("Cache-Control: private"); header("Content-Type: application/pdf"); header("Content-Length: " . mb_strlen($tmp, "8bit")); $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf"; $attachment = $options["Attachment"] ? "attachment" : "inline"; header(Helpers::buildContentDispositionHeader($attachment, $filename)); echo $tmp; flush(); } public function output($options = []) { if (!isset($options["compress"])) $options["compress"] = true; $debug = !$options['compress']; return $this->_pdf->output($debug); } /** * Returns logging messages generated by the Cpdf class * * @return string */ public function get_messages() { return $this->_pdf->messages; } } dompdf/src/Cellmap.php 0000644 00000067211 15024772104 0010703 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\Table as TableFrameDecorator; use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator; /** * Maps table cells to the table grid. * * This class resolves borders in tables with collapsed borders and helps * place row & column spanned table cells. * * @package dompdf */ class Cellmap { /** * Border style weight lookup for collapsed border resolution. */ protected const BORDER_STYLE_SCORE = [ "double" => 8, "solid" => 7, "dashed" => 6, "dotted" => 5, "ridge" => 4, "outset" => 3, "groove" => 2, "inset" => 1, "none" => 0 ]; /** * The table object this cellmap is attached to. * * @var TableFrameDecorator */ protected $_table; /** * The total number of rows in the table * * @var int */ protected $_num_rows; /** * The total number of columns in the table * * @var int */ protected $_num_cols; /** * 2D array mapping <row,column> to frames * * @var Frame[][] */ protected $_cells; /** * 1D array of column dimensions * * @var array */ protected $_columns; /** * 1D array of row dimensions * * @var array */ protected $_rows; /** * 2D array of border specs * * @var array */ protected $_borders; /** * 1D Array mapping frames to (multiple) <row, col> pairs, keyed on frame_id. * * @var array[] */ protected $_frames; /** * Current column when adding cells, 0-based * * @var int */ private $__col; /** * Current row when adding cells, 0-based * * @var int */ private $__row; /** * Tells whether the columns' width can be modified * * @var bool */ private $_columns_locked = false; /** * Tells whether the table has table-layout:fixed * * @var bool */ private $_fixed_layout = false; /** * @param TableFrameDecorator $table */ public function __construct(TableFrameDecorator $table) { $this->_table = $table; $this->reset(); } public function reset(): void { $this->_num_rows = 0; $this->_num_cols = 0; $this->_cells = []; $this->_frames = []; if (!$this->_columns_locked) { $this->_columns = []; } $this->_rows = []; $this->_borders = []; $this->__col = $this->__row = 0; } public function lock_columns(): void { $this->_columns_locked = true; } /** * @return bool */ public function is_columns_locked() { return $this->_columns_locked; } /** * @param bool $fixed */ public function set_layout_fixed(bool $fixed) { $this->_fixed_layout = $fixed; } /** * @return bool */ public function is_layout_fixed() { return $this->_fixed_layout; } /** * @return int */ public function get_num_rows() { return $this->_num_rows; } /** * @return int */ public function get_num_cols() { return $this->_num_cols; } /** * @return array */ public function &get_columns() { return $this->_columns; } /** * @param $columns */ public function set_columns($columns) { $this->_columns = $columns; } /** * @param int $i * * @return mixed */ public function &get_column($i) { if (!isset($this->_columns[$i])) { $this->_columns[$i] = [ "x" => 0, "min-width" => 0, "max-width" => 0, "used-width" => null, "absolute" => 0, "percent" => 0, "auto" => true, ]; } return $this->_columns[$i]; } /** * @return array */ public function &get_rows() { return $this->_rows; } /** * @param int $j * * @return mixed */ public function &get_row($j) { if (!isset($this->_rows[$j])) { $this->_rows[$j] = [ "y" => 0, "first-column" => 0, "height" => null, ]; } return $this->_rows[$j]; } /** * @param int $i * @param int $j * @param mixed $h_v * @param null|mixed $prop * * @return mixed */ public function get_border($i, $j, $h_v, $prop = null) { if (!isset($this->_borders[$i][$j][$h_v])) { $this->_borders[$i][$j][$h_v] = [ "width" => 0, "style" => "solid", "color" => "black", ]; } if (isset($prop)) { return $this->_borders[$i][$j][$h_v][$prop]; } return $this->_borders[$i][$j][$h_v]; } /** * @param int $i * @param int $j * * @return array */ public function get_border_properties($i, $j) { return [ "top" => $this->get_border($i, $j, "horizontal"), "right" => $this->get_border($i, $j + 1, "vertical"), "bottom" => $this->get_border($i + 1, $j, "horizontal"), "left" => $this->get_border($i, $j, "vertical"), ]; } /** * @param Frame $frame * * @return array|null */ public function get_spanned_cells(Frame $frame) { $key = $frame->get_id(); if (isset($this->_frames[$key])) { return $this->_frames[$key]; } return null; } /** * @param Frame $frame * * @return bool */ public function frame_exists_in_cellmap(Frame $frame) { $key = $frame->get_id(); return isset($this->_frames[$key]); } /** * @param Frame $frame * * @return array * @throws Exception */ public function get_frame_position(Frame $frame) { global $_dompdf_warnings; $key = $frame->get_id(); if (!isset($this->_frames[$key])) { throw new Exception("Frame not found in cellmap"); } // Positions are stored relative to the table position [$table_x, $table_y] = $this->_table->get_position(); $col = $this->_frames[$key]["columns"][0]; $row = $this->_frames[$key]["rows"][0]; if (!isset($this->_columns[$col])) { $_dompdf_warnings[] = "Frame not found in columns array. Check your table layout for missing or extra TDs."; $x = $table_x; } else { $x = $table_x + $this->_columns[$col]["x"]; } if (!isset($this->_rows[$row])) { $_dompdf_warnings[] = "Frame not found in row array. Check your table layout for missing or extra TDs."; $y = $table_y; } else { $y = $table_y + $this->_rows[$row]["y"]; } return [$x, $y, "x" => $x, "y" => $y]; } /** * @param Frame $frame * * @return int * @throws Exception */ public function get_frame_width(Frame $frame) { $key = $frame->get_id(); if (!isset($this->_frames[$key])) { throw new Exception("Frame not found in cellmap"); } $cols = $this->_frames[$key]["columns"]; $w = 0; foreach ($cols as $i) { $w += $this->_columns[$i]["used-width"]; } return $w; } /** * @param Frame $frame * * @return int * @throws Exception * @throws Exception */ public function get_frame_height(Frame $frame) { $key = $frame->get_id(); if (!isset($this->_frames[$key])) { throw new Exception("Frame not found in cellmap"); } $rows = $this->_frames[$key]["rows"]; $h = 0; foreach ($rows as $i) { if (!isset($this->_rows[$i])) { throw new Exception("The row #$i could not be found, please file an issue in the tracker with the HTML code"); } $h += $this->_rows[$i]["height"]; } return $h; } /** * @param int $j * @param mixed $width */ public function set_column_width($j, $width) { if ($this->_columns_locked) { return; } $col =& $this->get_column($j); $col["used-width"] = $width; $next_col =& $this->get_column($j + 1); $next_col["x"] = $col["x"] + $width; } /** * @param int $i * @param long $height */ public function set_row_height($i, $height) { $row =& $this->get_row($i); if ($height > $row["height"]) { $row["height"] = $height; } $next_row =& $this->get_row($i + 1); $next_row["y"] = $row["y"] + $row["height"]; } /** * https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution * * @param int $i * @param int $j * @param string $h_v `horizontal` or `vertical` * @param array $border_spec */ protected function resolve_border(int $i, int $j, string $h_v, array $border_spec): void { if (!isset($this->_borders[$i][$j][$h_v])) { $this->_borders[$i][$j][$h_v] = $border_spec; return; } $border = $this->_borders[$i][$j][$h_v]; $n_width = $border_spec["width"]; $n_style = $border_spec["style"]; $o_width = $border["width"]; $o_style = $border["style"]; if ($o_style === "hidden") { return; } // A style of `none` has lowest priority independent of its specified // width here, as its resolved width is always 0 if ($n_style === "hidden" || $n_width > $o_width || ($o_width == $n_width && isset(self::BORDER_STYLE_SCORE[$n_style]) && isset(self::BORDER_STYLE_SCORE[$o_style]) && self::BORDER_STYLE_SCORE[$n_style] > self::BORDER_STYLE_SCORE[$o_style]) ) { $this->_borders[$i][$j][$h_v] = $border_spec; } } /** * Get the resolved border properties for the given frame. * * @param AbstractFrameDecorator $frame * * @return array[] */ protected function get_resolved_border(AbstractFrameDecorator $frame): array { $key = $frame->get_id(); $columns = $this->_frames[$key]["columns"]; $rows = $this->_frames[$key]["rows"]; $first_col = $columns[0]; $last_col = $columns[count($columns) - 1]; $first_row = $rows[0]; $last_row = $rows[count($rows) - 1]; $max_top = null; $max_bottom = null; $max_left = null; $max_right = null; foreach ($columns as $col) { $top = $this->_borders[$first_row][$col]["horizontal"]; $bottom = $this->_borders[$last_row + 1][$col]["horizontal"]; if ($max_top === null || $top["width"] > $max_top["width"]) { $max_top = $top; } if ($max_bottom === null || $bottom["width"] > $max_bottom["width"]) { $max_bottom = $bottom; } } foreach ($rows as $row) { $left = $this->_borders[$row][$first_col]["vertical"]; $right = $this->_borders[$row][$last_col + 1]["vertical"]; if ($max_left === null || $left["width"] > $max_left["width"]) { $max_left = $left; } if ($max_right === null || $right["width"] > $max_right["width"]) { $max_right = $right; } } return [$max_top, $max_right, $max_bottom, $max_left]; } /** * @param AbstractFrameDecorator $frame */ public function add_frame(Frame $frame): void { $style = $frame->get_style(); $display = $style->display; $collapse = $this->_table->get_style()->border_collapse === "collapse"; // Recursively add the frames within the table, its row groups and rows if ($frame === $this->_table || $display === "table-row" || in_array($display, TableFrameDecorator::ROW_GROUPS, true) ) { $start_row = $this->__row; foreach ($frame->get_children() as $child) { $this->add_frame($child); } if ($display === "table-row") { $this->add_row(); } $num_rows = $this->__row - $start_row - 1; $key = $frame->get_id(); // Row groups always span across the entire table $this->_frames[$key]["columns"] = range(0, max(0, $this->_num_cols - 1)); $this->_frames[$key]["rows"] = range($start_row, max(0, $this->__row - 1)); $this->_frames[$key]["frame"] = $frame; if ($collapse) { $bp = $style->get_border_properties(); // Resolve vertical borders for ($i = 0; $i < $num_rows + 1; $i++) { $this->resolve_border($start_row + $i, 0, "vertical", $bp["left"]); $this->resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]); } // Resolve horizontal borders for ($j = 0; $j < $this->_num_cols; $j++) { $this->resolve_border($start_row, $j, "horizontal", $bp["top"]); $this->resolve_border($this->__row, $j, "horizontal", $bp["bottom"]); } if ($frame === $this->_table) { // Clear borders because the cells are now using them. The // border width still needs to be set to half the resolved // width so that the table is positioned properly [$top, $right, $bottom, $left] = $this->get_resolved_border($frame); $style->set_used("border_top_width", $top["width"] / 2); $style->set_used("border_right_width", $right["width"] / 2); $style->set_used("border_bottom_width", $bottom["width"] / 2); $style->set_used("border_left_width", $left["width"] / 2); $style->set_used("border_style", "none"); } else { // Clear borders for rows and row groups $style->set_used("border_width", 0); $style->set_used("border_style", "none"); } } if ($frame === $this->_table) { // Apply resolved borders to table cells and calculate column // widths after all frames have been added $this->calculate_column_widths(); } return; } // Add the frame to the cellmap $key = $frame->get_id(); $node = $frame->get_node(); $bp = $style->get_border_properties(); // Determine where this cell is going $colspan = max((int) $node->getAttribute("colspan"), 1); $rowspan = max((int) $node->getAttribute("rowspan"), 1); // Find the next available column (fix by Ciro Mondueri) $ac = $this->__col; while (isset($this->_cells[$this->__row][$ac])) { $ac++; } $this->__col = $ac; // Rows: for ($i = 0; $i < $rowspan; $i++) { $row = $this->__row + $i; $this->_frames[$key]["rows"][] = $row; for ($j = 0; $j < $colspan; $j++) { $this->_cells[$row][$this->__col + $j] = $frame; } if ($collapse) { // Resolve vertical borders $this->resolve_border($row, $this->__col, "vertical", $bp["left"]); $this->resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"]); } } // Columns: for ($j = 0; $j < $colspan; $j++) { $col = $this->__col + $j; $this->_frames[$key]["columns"][] = $col; if ($collapse) { // Resolve horizontal borders $this->resolve_border($this->__row, $col, "horizontal", $bp["top"]); $this->resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"]); } } $this->_frames[$key]["frame"] = $frame; $this->__col += $colspan; if ($this->__col > $this->_num_cols) { $this->_num_cols = $this->__col; } } /** * Apply resolved borders to table cells and calculate column widths. */ protected function calculate_column_widths(): void { $table = $this->_table; $table_style = $table->get_style(); $collapse = $table_style->border_collapse === "collapse"; if ($collapse) { $v_spacing = 0; $h_spacing = 0; } else { // The additional 1/2 width gets added to the table proper [$h, $v] = $table_style->border_spacing; $v_spacing = $v / 2; $h_spacing = $h / 2; } foreach ($this->_frames as $frame_info) { /** @var TableCellFrameDecorator */ $frame = $frame_info["frame"]; $style = $frame->get_style(); $display = $style->display; if ($display !== "table-cell") { continue; } if ($collapse) { // Set the resolved border at half width [$top, $right, $bottom, $left] = $this->get_resolved_border($frame); $style->set_used("border_top_width", $top["width"] / 2); $style->set_used("border_top_style", $top["style"]); $style->set_used("border_top_color", $top["color"]); $style->set_used("border_right_width", $right["width"] / 2); $style->set_used("border_right_style", $right["style"]); $style->set_used("border_right_color", $right["color"]); $style->set_used("border_bottom_width", $bottom["width"] / 2); $style->set_used("border_bottom_style", $bottom["style"]); $style->set_used("border_bottom_color", $bottom["color"]); $style->set_used("border_left_width", $left["width"] / 2); $style->set_used("border_left_style", $left["style"]); $style->set_used("border_left_color", $left["color"]); $style->set_used("margin", 0); } else { // Border spacing is effectively a margin between cells $style->set_used("margin_top", $v_spacing); $style->set_used("margin_bottom", $v_spacing); $style->set_used("margin_left", $h_spacing); $style->set_used("margin_right", $h_spacing); } if ($this->_columns_locked) { continue; } $node = $frame->get_node(); $colspan = max((int) $node->getAttribute("colspan"), 1); $first_col = $frame_info["columns"][0]; // Resolve the frame's width if ($this->_fixed_layout) { list($frame_min, $frame_max) = [0, 10e-10]; } else { list($frame_min, $frame_max) = $frame->get_min_max_width(); } $width = $style->width; $val = null; if (Helpers::is_percent($width) && $colspan === 1) { $var = "percent"; $val = (float)rtrim($width, "% "); } elseif ($width !== "auto" && $colspan === 1) { $var = "absolute"; $val = $frame_min; } $min = 0; $max = 0; for ($cs = 0; $cs < $colspan; $cs++) { // Resolve the frame's width(s) with other cells $col =& $this->get_column($first_col + $cs); // Note: $var is either 'percent' or 'absolute'. We compare the // requested percentage or absolute values with the existing widths // and adjust accordingly. if (isset($var) && $val > $col[$var]) { $col[$var] = $val; $col["auto"] = false; } $min += $col["min-width"]; $max += $col["max-width"]; } if ($frame_min > $min && $colspan === 1) { // The frame needs more space. Expand each sub-column // FIXME try to avoid putting this dummy value when table-layout:fixed $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_min - $min)); for ($c = 0; $c < $colspan; $c++) { $col =& $this->get_column($first_col + $c); $col["min-width"] += $inc; } } if ($frame_max > $max) { // FIXME try to avoid putting this dummy value when table-layout:fixed $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_max - $max) / $colspan); for ($c = 0; $c < $colspan; $c++) { $col =& $this->get_column($first_col + $c); $col["max-width"] += $inc; } } } // Adjust absolute columns so that the absolute (and max) width is the // largest minimum width of all cells. This accounts for cells without // absolute width within an absolute column foreach ($this->_columns as &$col) { if ($col["absolute"] > 0) { $col["absolute"] = $col["min-width"]; $col["max-width"] = $col["min-width"]; } } } protected function add_row(): void { $this->__row++; $this->_num_rows++; // Find the next available column $i = 0; while (isset($this->_cells[$this->__row][$i])) { $i++; } $this->__col = $i; } /** * Remove a row from the cellmap. * * @param Frame */ public function remove_row(Frame $row) { $key = $row->get_id(); if (!isset($this->_frames[$key])) { return; // Presumably this row has already been removed } $this->__row = $this->_num_rows--; $rows = $this->_frames[$key]["rows"]; $columns = $this->_frames[$key]["columns"]; // Remove all frames from this row foreach ($rows as $r) { foreach ($columns as $c) { if (isset($this->_cells[$r][$c])) { $id = $this->_cells[$r][$c]->get_id(); $this->_cells[$r][$c] = null; unset($this->_cells[$r][$c]); // has multiple rows? if (isset($this->_frames[$id]) && count($this->_frames[$id]["rows"]) > 1) { // remove just the desired row, but leave the frame if (($row_key = array_search($r, $this->_frames[$id]["rows"])) !== false) { unset($this->_frames[$id]["rows"][$row_key]); } continue; } $this->_frames[$id] = null; unset($this->_frames[$id]); } } $this->_rows[$r] = null; unset($this->_rows[$r]); } $this->_frames[$key] = null; unset($this->_frames[$key]); } /** * Remove a row group from the cellmap. * * @param Frame $group The group to remove */ public function remove_row_group(Frame $group) { $key = $group->get_id(); if (!isset($this->_frames[$key])) { return; // Presumably this row has already been removed } $iter = $group->get_first_child(); while ($iter) { $this->remove_row($iter); $iter = $iter->get_next_sibling(); } $this->_frames[$key] = null; unset($this->_frames[$key]); } /** * Update a row group after rows have been removed * * @param Frame $group The group to update * @param Frame $last_row The last row in the row group */ public function update_row_group(Frame $group, Frame $last_row) { $g_key = $group->get_id(); $first_index = $this->_frames[$g_key]["rows"][0]; $last_index = $first_index; $row = $last_row; while ($row = $row->get_prev_sibling()) { $last_index++; } $this->_frames[$g_key]["rows"] = range($first_index, $last_index); } public function assign_x_positions(): void { // Pre-condition: widths must be resolved and assigned to columns and // column[0]["x"] must be set. if ($this->_columns_locked) { return; } $x = $this->_columns[0]["x"]; foreach (array_keys($this->_columns) as $j) { $this->_columns[$j]["x"] = $x; $x += $this->_columns[$j]["used-width"]; } } public function assign_frame_heights(): void { // Pre-condition: widths and heights of each column & row must be // calcluated foreach ($this->_frames as $arr) { $frame = $arr["frame"]; $h = 0.0; foreach ($arr["rows"] as $row) { if (!isset($this->_rows[$row])) { // The row has been removed because of a page split, so skip it. continue; } $h += $this->_rows[$row]["height"]; } if ($frame instanceof TableCellFrameDecorator) { $frame->set_cell_height($h); } else { $frame->get_style()->set_used("height", $h); } } } /** * Re-adjust frame height if the table height is larger than its content */ public function set_frame_heights(float $table_height, float $content_height): void { // Distribute the increased height proportionally amongst each row foreach ($this->_frames as $arr) { $frame = $arr["frame"]; $h = 0.0; foreach ($arr["rows"] as $row) { if (!isset($this->_rows[$row])) { continue; } $h += $this->_rows[$row]["height"]; } if ($content_height > 0) { $new_height = ($h / $content_height) * $table_height; } else { $new_height = 0.0; } if ($frame instanceof TableCellFrameDecorator) { $frame->set_cell_height($new_height); } else { $frame->get_style()->set_used("height", $new_height); } } } /** * Used for debugging: * * @return string */ public function __toString(): string { $str = ""; $str .= "Columns:<br/>"; $str .= Helpers::pre_r($this->_columns, true); $str .= "Rows:<br/>"; $str .= Helpers::pre_r($this->_rows, true); $str .= "Frames:<br/>"; $arr = []; foreach ($this->_frames as $key => $val) { $arr[$key] = ["columns" => $val["columns"], "rows" => $val["rows"]]; } $str .= Helpers::pre_r($arr, true); if (php_sapi_name() == "cli") { $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"], ["\n", chr(27) . "[01;33m", chr(27) . "[0m"], $str)); } return $str; } } dompdf/src/PhpEvaluator.php 0000644 00000002411 15024772104 0011727 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; /** * Executes inline PHP code during the rendering process * * @package dompdf */ class PhpEvaluator { /** * @var Canvas */ protected $_canvas; /** * PhpEvaluator constructor. * @param Canvas $canvas */ public function __construct(Canvas $canvas) { $this->_canvas = $canvas; } /** * @param $code * @param array $vars */ public function evaluate($code, $vars = []) { if (!$this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) { return; } // Set up some variables for the inline code $pdf = $this->_canvas; $fontMetrics = $pdf->get_dompdf()->getFontMetrics(); $PAGE_NUM = $pdf->get_page_number(); $PAGE_COUNT = $pdf->get_page_count(); // Override those variables if passed in foreach ($vars as $k => $v) { $$k = $v; } eval($code); } /** * @param Frame $frame */ public function render(Frame $frame) { $this->evaluate($frame->get_node()->nodeValue); } } dompdf/src/LineBox.php 0000644 00000023755 15024772104 0010673 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\Block; use Dompdf\FrameDecorator\ListBullet; use Dompdf\FrameDecorator\Page; use Dompdf\FrameReflower\Text as TextFrameReflower; use Dompdf\Positioner\Inline as InlinePositioner; /** * The line box class * * This class represents a line box * http://www.w3.org/TR/CSS2/visuren.html#line-box * * @package dompdf */ class LineBox { /** * @var Block */ protected $_block_frame; /** * @var AbstractFrameDecorator[] */ protected $_frames = []; /** * @var ListBullet[] */ protected $list_markers = []; /** * @var int */ public $wc = 0; /** * @var float */ public $y = null; /** * @var float */ public $w = 0.0; /** * @var float */ public $h = 0.0; /** * @var float */ public $left = 0.0; /** * @var float */ public $right = 0.0; /** * @var AbstractFrameDecorator */ public $tallest_frame = null; /** * @var bool[] */ public $floating_blocks = []; /** * @var bool */ public $br = false; /** * Whether the line box contains any inline-positioned frames. * * @var bool */ public $inline = false; /** * Class constructor * * @param Block $frame the Block containing this line * @param int $y */ public function __construct(Block $frame, $y = 0) { $this->_block_frame = $frame; $this->_frames = []; $this->y = $y; $this->get_float_offsets(); } /** * Returns the floating elements inside the first floating parent * * @param Page $root * * @return Frame[] */ public function get_floats_inside(Page $root) { $floating_frames = $root->get_floating_frames(); if (count($floating_frames) == 0) { return $floating_frames; } // Find nearest floating element $p = $this->_block_frame; while ($p->get_style()->float === "none") { $parent = $p->get_parent(); if (!$parent) { break; } $p = $parent; } if ($p == $root) { return $floating_frames; } $parent = $p; $childs = []; foreach ($floating_frames as $_floating) { $p = $_floating->get_parent(); while (($p = $p->get_parent()) && $p !== $parent); if ($p) { $childs[] = $p; } } return $childs; } public function get_float_offsets() { static $anti_infinite_loop = 10000; // FIXME smelly hack $reflower = $this->_block_frame->get_reflower(); if (!$reflower) { return; } $cb_w = null; $block = $this->_block_frame; $root = $block->get_root(); if (!$root) { return; } $style = $this->_block_frame->get_style(); $floating_frames = $this->get_floats_inside($root); $inside_left_floating_width = 0; $inside_right_floating_width = 0; $outside_left_floating_width = 0; $outside_right_floating_width = 0; foreach ($floating_frames as $child_key => $floating_frame) { $floating_frame_parent = $floating_frame->get_parent(); $id = $floating_frame->get_id(); if (isset($this->floating_blocks[$id])) { continue; } $float = $floating_frame->get_style()->float; $floating_width = $floating_frame->get_margin_width(); if (!$cb_w) { $cb_w = $floating_frame->get_containing_block("w"); } $line_w = $this->get_width(); if (!$floating_frame->_float_next_line && ($cb_w <= $line_w + $floating_width) && ($cb_w > $line_w)) { $floating_frame->_float_next_line = true; continue; } // If the child is still shifted by the floating element if ($anti_infinite_loop-- > 0 && $floating_frame->get_position("y") + $floating_frame->get_margin_height() >= $this->y && $block->get_position("x") + $block->get_margin_width() >= $floating_frame->get_position("x") ) { if ($float === "left") { if ($floating_frame_parent === $this->_block_frame) { $inside_left_floating_width += $floating_width; } else { $outside_left_floating_width += $floating_width; } } elseif ($float === "right") { if ($floating_frame_parent === $this->_block_frame) { $inside_right_floating_width += $floating_width; } else { $outside_right_floating_width += $floating_width; } } $this->floating_blocks[$id] = true; } // else, the floating element won't shift anymore else { $root->remove_floating_frame($child_key); } } $this->left += $inside_left_floating_width; if ($outside_left_floating_width > 0 && $outside_left_floating_width > ((float)$style->length_in_pt($style->margin_left) + (float)$style->length_in_pt($style->padding_left))) { $this->left += $outside_left_floating_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->padding_left); } $this->right += $inside_right_floating_width; if ($outside_right_floating_width > 0 && $outside_right_floating_width > ((float)$style->length_in_pt($style->margin_left) + (float)$style->length_in_pt($style->padding_right))) { $this->right += $outside_right_floating_width - (float)$style->length_in_pt($style->margin_right) - (float)$style->length_in_pt($style->padding_right); } } /** * @return float */ public function get_width() { return $this->left + $this->w + $this->right; } /** * @return Block */ public function get_block_frame() { return $this->_block_frame; } /** * @return AbstractFrameDecorator[] */ function &get_frames() { return $this->_frames; } /** * @param AbstractFrameDecorator $frame */ public function add_frame(Frame $frame): void { $this->_frames[] = $frame; if ($frame->get_positioner() instanceof InlinePositioner) { $this->inline = true; } } /** * Remove the frame at the given index and all following frames from the * line. * * @param int $index */ public function remove_frames(int $index): void { $lastIndex = count($this->_frames) - 1; if ($index < 0 || $index > $lastIndex) { return; } for ($i = $lastIndex; $i >= $index; $i--) { $f = $this->_frames[$i]; unset($this->_frames[$i]); $this->w -= $f->get_margin_width(); } // Reset array indices $this->_frames = array_values($this->_frames); // Recalculate the height of the line $h = 0.0; $this->inline = false; foreach ($this->_frames as $f) { $h = max($h, $f->get_margin_height()); if ($f->get_positioner() instanceof InlinePositioner) { $this->inline = true; } } $this->h = $h; } /** * Get the `outside` positioned list markers to be vertically aligned with * the line box. * * @return ListBullet[] */ public function get_list_markers(): array { return $this->list_markers; } /** * Add a list marker to the line box. * * The list marker is only added for the purpose of vertical alignment, it * is not actually added to the list of frames of the line box. */ public function add_list_marker(ListBullet $marker): void { $this->list_markers[] = $marker; } /** * An iterator of all list markers and inline positioned frames of the line * box. * * @return \Iterator<AbstractFrameDecorator> */ public function frames_to_align(): \Iterator { yield from $this->list_markers; foreach ($this->_frames as $frame) { if ($frame->get_positioner() instanceof InlinePositioner) { yield $frame; } } } /** * Trim trailing whitespace from the line. */ public function trim_trailing_ws(): void { $lastIndex = count($this->_frames) - 1; if ($lastIndex < 0) { return; } $lastFrame = $this->_frames[$lastIndex]; $reflower = $lastFrame->get_reflower(); if ($reflower instanceof TextFrameReflower && !$lastFrame->is_pre()) { $reflower->trim_trailing_ws(); $this->recalculate_width(); } } /** * Recalculate LineBox width based on the contained frames total width. * * @return float */ public function recalculate_width(): float { $width = 0.0; foreach ($this->_frames as $frame) { $width += $frame->get_margin_width(); } return $this->w = $width; } /** * @return string */ public function __toString(): string { $props = ["wc", "y", "w", "h", "left", "right", "br"]; $s = ""; foreach ($props as $prop) { $s .= "$prop: " . $this->$prop . "\n"; } $s .= count($this->_frames) . " frames\n"; return $s; } } /* class LineBoxList implements Iterator { private $_p = 0; private $_lines = array(); } */ dompdf/src/Exception.php 0000644 00000001047 15024772104 0011257 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; /** * Standard exception thrown by DOMPDF classes * * @package dompdf */ class Exception extends \Exception { /** * Class constructor * * @param string $message Error message * @param int $code Error code */ public function __construct($message = null, $code = 0) { parent::__construct($message, $code); } } dompdf/src/CanvasFactory.php 0000644 00000002744 15024772104 0012071 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; /** * Create canvas instances * * The canvas factory creates canvas instances based on the * availability of rendering backends and config options. * * @package dompdf */ class CanvasFactory { /** * Constructor is private: this is a static class */ private function __construct() { } /** * @param Dompdf $dompdf * @param string|float[] $paper * @param string $orientation * @param string|null $class * * @return Canvas */ static function get_instance(Dompdf $dompdf, $paper, string $orientation, ?string $class = null) { $backend = strtolower($dompdf->getOptions()->getPdfBackend()); if (isset($class) && class_exists($class, false)) { $class .= "_Adapter"; } else { if (($backend === "auto" || $backend === "pdflib") && class_exists("PDFLib", false) ) { $class = "Dompdf\\Adapter\\PDFLib"; } else { if ($backend === "gd" && extension_loaded('gd')) { $class = "Dompdf\\Adapter\\GD"; } else { $class = "Dompdf\\Adapter\\CPDF"; } } } return new $class($paper, $orientation, $dompdf); } } dompdf/src/Renderer.php 0000644 00000020502 15024772104 0011064 0 ustar 00 <?php /** * @package dompdf * @link https://github.com/dompdf/dompdf * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf; use Dompdf\Renderer\AbstractRenderer; use Dompdf\Renderer\Block; use Dompdf\Renderer\Image; use Dompdf\Renderer\ListBullet; use Dompdf\Renderer\TableCell; use Dompdf\Renderer\TableRowGroup; use Dompdf\Renderer\Text; /** * Concrete renderer * * Instantiates several specific renderers in order to render any given frame. * * @package dompdf */ class Renderer extends AbstractRenderer { /** * Array of renderers for specific frame types * * @var AbstractRenderer[] */ protected $_renderers; /** * Cache of the callbacks array * * @var array */ private $_callbacks; /** * Advance the canvas to the next page */ function new_page() { $this->_canvas->new_page(); } /** * Render frames recursively * * @param Frame $frame the frame to render */ public function render(Frame $frame) { global $_dompdf_debug; $this->_check_callbacks("begin_frame", $frame); if ($_dompdf_debug) { echo $frame; flush(); } $style = $frame->get_style(); if (in_array($style->visibility, ["hidden", "collapse"], true)) { return; } $display = $style->display; $transformList = $style->transform; $hasTransform = $transformList !== []; // Starts the CSS transformation if ($hasTransform) { $this->_canvas->save(); list($x, $y) = $frame->get_padding_box(); $origin = $style->transform_origin; foreach ($transformList as $transform) { list($function, $values) = $transform; if ($function === "matrix") { $function = "transform"; } $values = array_map("floatval", $values); $values[] = $x + (float)$style->length_in_pt($origin[0], (float)$style->length_in_pt($style->width)); $values[] = $y + (float)$style->length_in_pt($origin[1], (float)$style->length_in_pt($style->height)); call_user_func_array([$this->_canvas, $function], $values); } } switch ($display) { case "block": case "list-item": case "inline-block": case "table": case "inline-table": $this->_render_frame("block", $frame); break; case "inline": if ($frame->is_text_node()) { $this->_render_frame("text", $frame); } else { $this->_render_frame("inline", $frame); } break; case "table-cell": $this->_render_frame("table-cell", $frame); break; case "table-row-group": case "table-header-group": case "table-footer-group": $this->_render_frame("table-row-group", $frame); break; case "-dompdf-list-bullet": $this->_render_frame("list-bullet", $frame); break; case "-dompdf-image": $this->_render_frame("image", $frame); break; case "none": $node = $frame->get_node(); if ($node->nodeName === "script") { if ($node->getAttribute("type") === "text/php" || $node->getAttribute("language") === "php" ) { // Evaluate embedded php scripts $this->_render_frame("php", $frame); } elseif ($node->getAttribute("type") === "text/javascript" || $node->getAttribute("language") === "javascript" ) { // Insert JavaScript $this->_render_frame("javascript", $frame); } } // Don't render children, so skip to next iter return; default: break; } // Starts the overflow: hidden box if ($style->overflow === "hidden") { $padding_box = $frame->get_padding_box(); [$x, $y, $w, $h] = $padding_box; $style = $frame->get_style(); if ($style->has_border_radius()) { $border_box = $frame->get_border_box(); [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $padding_box); $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl); } else { $this->_canvas->clipping_rectangle($x, $y, $w, $h); } } $stack = []; foreach ($frame->get_children() as $child) { // < 0 : negative z-index // = 0 : no z-index, no stacking context // = 1 : stacking context without z-index // > 1 : z-index $child_style = $child->get_style(); $child_z_index = $child_style->z_index; $z_index = 0; if ($child_z_index !== "auto") { $z_index = $child_z_index + 1; } elseif ($child_style->float !== "none" || $child->is_positioned()) { $z_index = 1; } $stack[$z_index][] = $child; } ksort($stack); foreach ($stack as $by_index) { foreach ($by_index as $child) { $this->render($child); } } // Ends the overflow: hidden box if ($style->overflow === "hidden") { $this->_canvas->clipping_end(); } if ($hasTransform) { $this->_canvas->restore(); } // Check for end frame callback $this->_check_callbacks("end_frame", $frame); } /** * Check for callbacks that need to be performed when a given event * gets triggered on a frame * * @param string $event The type of event * @param Frame $frame The frame that event is triggered on */ protected function _check_callbacks(string $event, Frame $frame): void { if (!isset($this->_callbacks)) { $this->_callbacks = $this->_dompdf->getCallbacks(); } if (isset($this->_callbacks[$event])) { $fs = $this->_callbacks[$event]; $canvas = $this->_canvas; $fontMetrics = $this->_dompdf->getFontMetrics(); foreach ($fs as $f) { $f($frame, $canvas, $fontMetrics); } } } /** * Render a single frame * * Creates Renderer objects on demand * * @param string $type type of renderer to use * @param Frame $frame the frame to render */ protected function _render_frame($type, $frame) { if (!isset($this->_renderers[$type])) { switch ($type) { case "block": $this->_renderers[$type] = new Block($this->_dompdf); break; case "inline": $this->_renderers[$type] = new Renderer\Inline($this->_dompdf); break; case "text": $this->_renderers[$type] = new Text($this->_dompdf); break; case "image": $this->_renderers[$type] = new Image($this->_dompdf); break; case "table-cell": $this->_renderers[$type] = new TableCell($this->_dompdf); break; case "table-row-group": $this->_renderers[$type] = new TableRowGroup($this->_dompdf); break; case "list-bullet": $this->_renderers[$type] = new ListBullet($this->_dompdf); break; case "php": $this->_renderers[$type] = new PhpEvaluator($this->_canvas); break; case "javascript": $this->_renderers[$type] = new JavascriptEmbedder($this->_dompdf); break; } } $this->_renderers[$type]->render($frame); } } dompdf/lib/fonts/Helvetica-BoldOblique.afm 0000644 00000207607 15024772104 0014522 0 ustar 00 StartFontMetrics 4.1 Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved. Comment Creation Date: Thu May 1 12:45:12 1997 Comment UniqueID 43053 Comment VMusage 14482 68586 FontName Helvetica-BoldOblique FullName Helvetica Bold Oblique FamilyName Helvetica Weight Bold ItalicAngle -12 IsFixedPitch false CharacterSet ExtendedRoman FontBBox -174 -228 1114 962 UnderlinePosition -100 UnderlineThickness 50 Version 002.000 Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries. EncodingScheme WinAnsiEncoding CapHeight 718 XHeight 532 Ascender 718 Descender -207 StdHW 118 StdVW 140 StartCharMetrics 317 C 32 ; WX 278 ; N space ; B 0 0 0 0 ; C 160 ; WX 278 ; N space ; B 0 0 0 0 ; C 33 ; WX 333 ; N exclam ; B 94 0 397 718 ; C 34 ; WX 474 ; N quotedbl ; B 193 447 529 718 ; C 35 ; WX 556 ; N numbersign ; B 60 0 644 698 ; C 36 ; WX 556 ; N dollar ; B 67 -115 622 775 ; C 37 ; WX 889 ; N percent ; B 136 -19 901 710 ; C 38 ; WX 722 ; N ampersand ; B 89 -19 732 718 ; C 146 ; WX 278 ; N quoteright ; B 167 445 362 718 ; C 40 ; WX 333 ; N parenleft ; B 76 -208 470 734 ; C 41 ; WX 333 ; N parenright ; B -25 -208 369 734 ; C 42 ; WX 389 ; N asterisk ; B 146 387 481 718 ; C 43 ; WX 584 ; N plus ; B 82 0 610 506 ; C 44 ; WX 278 ; N comma ; B 28 -168 245 146 ; C 45 ; WX 333 ; N hyphen ; B 73 215 379 345 ; C 173 ; WX 333 ; N hyphen ; B 44 232 289 322 ; C 46 ; WX 278 ; N period ; B 64 0 245 146 ; C 47 ; WX 278 ; N slash ; B -37 -19 468 737 ; C 48 ; WX 556 ; N zero ; B 86 -19 617 710 ; C 49 ; WX 556 ; N one ; B 173 0 529 710 ; C 50 ; WX 556 ; N two ; B 26 0 619 710 ; C 51 ; WX 556 ; N three ; B 65 -19 608 710 ; C 52 ; WX 556 ; N four ; B 60 0 598 710 ; C 53 ; WX 556 ; N five ; B 64 -19 636 698 ; C 54 ; WX 556 ; N six ; B 85 -19 619 710 ; C 55 ; WX 556 ; N seven ; B 125 0 676 698 ; C 56 ; WX 556 ; N eight ; B 69 -19 616 710 ; C 57 ; WX 556 ; N nine ; B 78 -19 615 710 ; C 58 ; WX 333 ; N colon ; B 92 0 351 512 ; C 59 ; WX 333 ; N semicolon ; B 56 -168 351 512 ; C 60 ; WX 584 ; N less ; B 82 -8 655 514 ; C 61 ; WX 584 ; N equal ; B 58 87 633 419 ; C 62 ; WX 584 ; N greater ; B 36 -8 609 514 ; C 63 ; WX 611 ; N question ; B 165 0 671 727 ; C 64 ; WX 975 ; N at ; B 186 -19 954 737 ; C 65 ; WX 722 ; N A ; B 20 0 702 718 ; C 66 ; WX 722 ; N B ; B 76 0 764 718 ; C 67 ; WX 722 ; N C ; B 107 -19 789 737 ; C 68 ; WX 722 ; N D ; B 76 0 777 718 ; C 69 ; WX 667 ; N E ; B 76 0 757 718 ; C 70 ; WX 611 ; N F ; B 76 0 740 718 ; C 71 ; WX 778 ; N G ; B 108 -19 817 737 ; C 72 ; WX 722 ; N H ; B 71 0 804 718 ; C 73 ; WX 278 ; N I ; B 64 0 367 718 ; C 74 ; WX 556 ; N J ; B 60 -18 637 718 ; C 75 ; WX 722 ; N K ; B 87 0 858 718 ; C 76 ; WX 611 ; N L ; B 76 0 611 718 ; C 77 ; WX 833 ; N M ; B 69 0 918 718 ; C 78 ; WX 722 ; N N ; B 69 0 807 718 ; C 79 ; WX 778 ; N O ; B 107 -19 823 737 ; C 80 ; WX 667 ; N P ; B 76 0 738 718 ; C 81 ; WX 778 ; N Q ; B 107 -52 823 737 ; C 82 ; WX 722 ; N R ; B 76 0 778 718 ; C 83 ; WX 667 ; N S ; B 81 -19 718 737 ; C 84 ; WX 611 ; N T ; B 140 0 751 718 ; C 85 ; WX 722 ; N U ; B 116 -19 804 718 ; C 86 ; WX 667 ; N V ; B 172 0 801 718 ; C 87 ; WX 944 ; N W ; B 169 0 1082 718 ; C 88 ; WX 667 ; N X ; B 14 0 791 718 ; C 89 ; WX 667 ; N Y ; B 168 0 806 718 ; C 90 ; WX 611 ; N Z ; B 25 0 737 718 ; C 91 ; WX 333 ; N bracketleft ; B 21 -196 462 722 ; C 92 ; WX 278 ; N backslash ; B 124 -19 307 737 ; C 93 ; WX 333 ; N bracketright ; B -18 -196 423 722 ; C 94 ; WX 584 ; N asciicircum ; B 131 323 591 698 ; C 95 ; WX 556 ; N underscore ; B -27 -125 540 -75 ; C 145 ; WX 278 ; N quoteleft ; B 165 454 361 727 ; C 97 ; WX 556 ; N a ; B 55 -14 583 546 ; C 98 ; WX 611 ; N b ; B 61 -14 645 718 ; C 99 ; WX 556 ; N c ; B 79 -14 599 546 ; C 100 ; WX 611 ; N d ; B 82 -14 704 718 ; C 101 ; WX 556 ; N e ; B 70 -14 593 546 ; C 102 ; WX 333 ; N f ; B 87 0 469 727 ; L i fi ; L l fl ; C 103 ; WX 611 ; N g ; B 38 -217 666 546 ; C 104 ; WX 611 ; N h ; B 65 0 629 718 ; C 105 ; WX 278 ; N i ; B 69 0 363 725 ; C 106 ; WX 278 ; N j ; B -42 -214 363 725 ; C 107 ; WX 556 ; N k ; B 69 0 670 718 ; C 108 ; WX 278 ; N l ; B 69 0 362 718 ; C 109 ; WX 889 ; N m ; B 64 0 909 546 ; C 110 ; WX 611 ; N n ; B 65 0 629 546 ; C 111 ; WX 611 ; N o ; B 82 -14 643 546 ; C 112 ; WX 611 ; N p ; B 18 -207 645 546 ; C 113 ; WX 611 ; N q ; B 80 -207 665 546 ; C 114 ; WX 389 ; N r ; B 64 0 489 546 ; C 115 ; WX 556 ; N s ; B 63 -14 584 546 ; C 116 ; WX 333 ; N t ; B 100 -6 422 676 ; C 117 ; WX 611 ; N u ; B 98 -14 658 532 ; C 118 ; WX 556 ; N v ; B 126 0 656 532 ; C 119 ; WX 778 ; N w ; B 123 0 882 532 ; C 120 ; WX 556 ; N x ; B 15 0 648 532 ; C 121 ; WX 556 ; N y ; B 42 -214 652 532 ; C 122 ; WX 500 ; N z ; B 20 0 583 532 ; C 123 ; WX 389 ; N braceleft ; B 94 -196 518 722 ; C 124 ; WX 280 ; N bar ; B 36 -225 361 775 ; C 125 ; WX 389 ; N braceright ; B -18 -196 407 722 ; C 126 ; WX 584 ; N asciitilde ; B 115 163 577 343 ; C 161 ; WX 333 ; N exclamdown ; B 50 -186 353 532 ; C 162 ; WX 556 ; N cent ; B 79 -118 599 628 ; C 163 ; WX 556 ; N sterling ; B 50 -16 635 718 ; C -1 ; WX 167 ; N fraction ; B -174 -19 487 710 ; C 165 ; WX 556 ; N yen ; B 60 0 713 698 ; C 131 ; WX 556 ; N florin ; B -50 -210 669 737 ; C 167 ; WX 556 ; N section ; B 61 -184 598 727 ; C 164 ; WX 556 ; N currency ; B 27 76 680 636 ; C 39 ; WX 238 ; N quotesingle ; B 165 447 321 718 ; C 147 ; WX 500 ; N quotedblleft ; B 160 454 588 727 ; C 171 ; WX 556 ; N guillemotleft ; B 135 76 571 484 ; C 139 ; WX 333 ; N guilsinglleft ; B 130 76 353 484 ; C 155 ; WX 333 ; N guilsinglright ; B 99 76 322 484 ; C -1 ; WX 611 ; N fi ; B 87 0 696 727 ; C -1 ; WX 611 ; N fl ; B 87 0 695 727 ; C 150 ; WX 556 ; N endash ; B 48 227 627 333 ; C 134 ; WX 556 ; N dagger ; B 118 -171 626 718 ; C 135 ; WX 556 ; N daggerdbl ; B 46 -171 628 718 ; C 183 ; WX 278 ; N periodcentered ; B 110 172 276 334 ; C 182 ; WX 556 ; N paragraph ; B 98 -191 688 700 ; C 149 ; WX 350 ; N bullet ; B 83 194 420 524 ; C 130 ; WX 278 ; N quotesinglbase ; B 41 -146 236 127 ; C 132 ; WX 500 ; N quotedblbase ; B 36 -146 463 127 ; C 148 ; WX 500 ; N quotedblright ; B 162 445 589 718 ; C 187 ; WX 556 ; N guillemotright ; B 104 76 540 484 ; C 133 ; WX 1000 ; N ellipsis ; B 92 0 939 146 ; C 137 ; WX 1000 ; N perthousand ; B 76 -19 1038 710 ; C 191 ; WX 611 ; N questiondown ; B 53 -195 559 532 ; C 96 ; WX 333 ; N grave ; B 136 604 353 750 ; C 180 ; WX 333 ; N acute ; B 236 604 515 750 ; C 136 ; WX 333 ; N circumflex ; B 118 604 471 750 ; C 152 ; WX 333 ; N tilde ; B 113 610 507 737 ; C 175 ; WX 333 ; N macron ; B 122 604 483 678 ; C -1 ; WX 333 ; N breve ; B 156 604 494 750 ; C -1 ; WX 333 ; N dotaccent ; B 235 614 385 729 ; C 168 ; WX 333 ; N dieresis ; B 137 614 482 729 ; C -1 ; WX 333 ; N ring ; B 200 568 420 776 ; C 184 ; WX 333 ; N cedilla ; B -37 -228 220 0 ; C -1 ; WX 333 ; N hungarumlaut ; B 137 604 645 750 ; C -1 ; WX 333 ; N ogonek ; B 41 -228 264 0 ; C -1 ; WX 333 ; N caron ; B 149 604 502 750 ; C 151 ; WX 1000 ; N emdash ; B 48 227 1071 333 ; C 198 ; WX 1000 ; N AE ; B 5 0 1100 718 ; C 170 ; WX 370 ; N ordfeminine ; B 125 401 465 737 ; C -1 ; WX 611 ; N Lslash ; B 34 0 611 718 ; C 216 ; WX 778 ; N Oslash ; B 35 -27 894 745 ; C 140 ; WX 1000 ; N OE ; B 99 -19 1114 737 ; C 186 ; WX 365 ; N ordmasculine ; B 123 401 485 737 ; C 230 ; WX 889 ; N ae ; B 56 -14 923 546 ; C -1 ; WX 278 ; N dotlessi ; B 69 0 322 532 ; C -1 ; WX 278 ; N lslash ; B 40 0 407 718 ; C 248 ; WX 611 ; N oslash ; B 22 -29 701 560 ; C 156 ; WX 944 ; N oe ; B 82 -14 977 546 ; C 223 ; WX 611 ; N germandbls ; B 69 -14 657 731 ; C 207 ; WX 278 ; N Idieresis ; B 64 0 494 915 ; C 233 ; WX 556 ; N eacute ; B 70 -14 627 750 ; C -1 ; WX 556 ; N abreve ; B 55 -14 606 750 ; C -1 ; WX 611 ; N uhungarumlaut ; B 98 -14 784 750 ; C -1 ; WX 556 ; N ecaron ; B 70 -14 614 750 ; C 159 ; WX 667 ; N Ydieresis ; B 168 0 806 915 ; C 247 ; WX 584 ; N divide ; B 82 -42 610 548 ; C 221 ; WX 667 ; N Yacute ; B 168 0 806 936 ; C 194 ; WX 722 ; N Acircumflex ; B 20 0 706 936 ; C 225 ; WX 556 ; N aacute ; B 55 -14 627 750 ; C 219 ; WX 722 ; N Ucircumflex ; B 116 -19 804 936 ; C 253 ; WX 556 ; N yacute ; B 42 -214 652 750 ; C -1 ; WX 556 ; N scommaaccent ; B 63 -228 584 546 ; C 234 ; WX 556 ; N ecircumflex ; B 70 -14 593 750 ; C -1 ; WX 722 ; N Uring ; B 116 -19 804 962 ; C 220 ; WX 722 ; N Udieresis ; B 116 -19 804 915 ; C -1 ; WX 556 ; N aogonek ; B 55 -224 583 546 ; C 218 ; WX 722 ; N Uacute ; B 116 -19 804 936 ; C -1 ; WX 611 ; N uogonek ; B 98 -228 658 532 ; C 203 ; WX 667 ; N Edieresis ; B 76 0 757 915 ; C -1 ; WX 722 ; N Dcroat ; B 62 0 777 718 ; C -1 ; WX 250 ; N commaaccent ; B 16 -228 188 -50 ; C 169 ; WX 737 ; N copyright ; B 56 -19 835 737 ; C -1 ; WX 667 ; N Emacron ; B 76 0 757 864 ; C -1 ; WX 556 ; N ccaron ; B 79 -14 614 750 ; C 229 ; WX 556 ; N aring ; B 55 -14 583 776 ; C -1 ; WX 722 ; N Ncommaaccent ; B 69 -228 807 718 ; C -1 ; WX 278 ; N lacute ; B 69 0 528 936 ; C 224 ; WX 556 ; N agrave ; B 55 -14 583 750 ; C -1 ; WX 611 ; N Tcommaaccent ; B 140 -228 751 718 ; C -1 ; WX 722 ; N Cacute ; B 107 -19 789 936 ; C 227 ; WX 556 ; N atilde ; B 55 -14 619 737 ; C -1 ; WX 667 ; N Edotaccent ; B 76 0 757 915 ; C 154 ; WX 556 ; N scaron ; B 63 -14 614 750 ; C -1 ; WX 556 ; N scedilla ; B 63 -228 584 546 ; C 237 ; WX 278 ; N iacute ; B 69 0 488 750 ; C -1 ; WX 494 ; N lozenge ; B 90 0 564 745 ; C -1 ; WX 722 ; N Rcaron ; B 76 0 778 936 ; C -1 ; WX 778 ; N Gcommaaccent ; B 108 -228 817 737 ; C 251 ; WX 611 ; N ucircumflex ; B 98 -14 658 750 ; C 226 ; WX 556 ; N acircumflex ; B 55 -14 583 750 ; C -1 ; WX 722 ; N Amacron ; B 20 0 718 864 ; C -1 ; WX 389 ; N rcaron ; B 64 0 530 750 ; C 231 ; WX 556 ; N ccedilla ; B 79 -228 599 546 ; C -1 ; WX 611 ; N Zdotaccent ; B 25 0 737 915 ; C 222 ; WX 667 ; N Thorn ; B 76 0 716 718 ; C -1 ; WX 778 ; N Omacron ; B 107 -19 823 864 ; C -1 ; WX 722 ; N Racute ; B 76 0 778 936 ; C -1 ; WX 667 ; N Sacute ; B 81 -19 722 936 ; C -1 ; WX 743 ; N dcaron ; B 82 -14 903 718 ; C -1 ; WX 722 ; N Umacron ; B 116 -19 804 864 ; C -1 ; WX 611 ; N uring ; B 98 -14 658 776 ; C 179 ; WX 333 ; N threesuperior ; B 91 271 441 710 ; C 210 ; WX 778 ; N Ograve ; B 107 -19 823 936 ; C 192 ; WX 722 ; N Agrave ; B 20 0 702 936 ; C -1 ; WX 722 ; N Abreve ; B 20 0 729 936 ; C 215 ; WX 584 ; N multiply ; B 57 1 635 505 ; C 250 ; WX 611 ; N uacute ; B 98 -14 658 750 ; C -1 ; WX 611 ; N Tcaron ; B 140 0 751 936 ; C -1 ; WX 494 ; N partialdiff ; B 43 -21 585 750 ; C 255 ; WX 556 ; N ydieresis ; B 42 -214 652 729 ; C -1 ; WX 722 ; N Nacute ; B 69 0 807 936 ; C 238 ; WX 278 ; N icircumflex ; B 69 0 444 750 ; C 202 ; WX 667 ; N Ecircumflex ; B 76 0 757 936 ; C 228 ; WX 556 ; N adieresis ; B 55 -14 594 729 ; C 235 ; WX 556 ; N edieresis ; B 70 -14 594 729 ; C -1 ; WX 556 ; N cacute ; B 79 -14 627 750 ; C -1 ; WX 611 ; N nacute ; B 65 0 654 750 ; C -1 ; WX 611 ; N umacron ; B 98 -14 658 678 ; C -1 ; WX 722 ; N Ncaron ; B 69 0 807 936 ; C 205 ; WX 278 ; N Iacute ; B 64 0 528 936 ; C 177 ; WX 584 ; N plusminus ; B 40 0 625 506 ; C 166 ; WX 280 ; N brokenbar ; B 52 -150 345 700 ; C 174 ; WX 737 ; N registered ; B 55 -19 834 737 ; C -1 ; WX 778 ; N Gbreve ; B 108 -19 817 936 ; C -1 ; WX 278 ; N Idotaccent ; B 64 0 397 915 ; C -1 ; WX 600 ; N summation ; B 14 -10 670 706 ; C 200 ; WX 667 ; N Egrave ; B 76 0 757 936 ; C -1 ; WX 389 ; N racute ; B 64 0 543 750 ; C -1 ; WX 611 ; N omacron ; B 82 -14 643 678 ; C -1 ; WX 611 ; N Zacute ; B 25 0 737 936 ; C 142 ; WX 611 ; N Zcaron ; B 25 0 737 936 ; C -1 ; WX 549 ; N greaterequal ; B 26 0 629 704 ; C 208 ; WX 722 ; N Eth ; B 62 0 777 718 ; C 199 ; WX 722 ; N Ccedilla ; B 107 -228 789 737 ; C -1 ; WX 278 ; N lcommaaccent ; B 30 -228 362 718 ; C -1 ; WX 389 ; N tcaron ; B 100 -6 608 878 ; C -1 ; WX 556 ; N eogonek ; B 70 -228 593 546 ; C -1 ; WX 722 ; N Uogonek ; B 116 -228 804 718 ; C 193 ; WX 722 ; N Aacute ; B 20 0 750 936 ; C 196 ; WX 722 ; N Adieresis ; B 20 0 716 915 ; C 232 ; WX 556 ; N egrave ; B 70 -14 593 750 ; C -1 ; WX 500 ; N zacute ; B 20 0 599 750 ; C -1 ; WX 278 ; N iogonek ; B -14 -224 363 725 ; C 211 ; WX 778 ; N Oacute ; B 107 -19 823 936 ; C 243 ; WX 611 ; N oacute ; B 82 -14 654 750 ; C -1 ; WX 556 ; N amacron ; B 55 -14 595 678 ; C -1 ; WX 556 ; N sacute ; B 63 -14 627 750 ; C 239 ; WX 278 ; N idieresis ; B 69 0 455 729 ; C 212 ; WX 778 ; N Ocircumflex ; B 107 -19 823 936 ; C 217 ; WX 722 ; N Ugrave ; B 116 -19 804 936 ; C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ; C 254 ; WX 611 ; N thorn ; B 18 -208 645 718 ; C 178 ; WX 333 ; N twosuperior ; B 69 283 449 710 ; C 214 ; WX 778 ; N Odieresis ; B 107 -19 823 915 ; C 181 ; WX 611 ; N mu ; B 22 -207 658 532 ; C 236 ; WX 278 ; N igrave ; B 69 0 326 750 ; C -1 ; WX 611 ; N ohungarumlaut ; B 82 -14 784 750 ; C -1 ; WX 667 ; N Eogonek ; B 76 -224 757 718 ; C -1 ; WX 611 ; N dcroat ; B 82 -14 789 718 ; C 190 ; WX 834 ; N threequarters ; B 99 -19 839 710 ; C -1 ; WX 667 ; N Scedilla ; B 81 -228 718 737 ; C -1 ; WX 400 ; N lcaron ; B 69 0 561 718 ; C -1 ; WX 722 ; N Kcommaaccent ; B 87 -228 858 718 ; C -1 ; WX 611 ; N Lacute ; B 76 0 611 936 ; C 153 ; WX 1000 ; N trademark ; B 179 306 1109 718 ; C -1 ; WX 556 ; N edotaccent ; B 70 -14 593 729 ; C 204 ; WX 278 ; N Igrave ; B 64 0 367 936 ; C -1 ; WX 278 ; N Imacron ; B 64 0 496 864 ; C -1 ; WX 611 ; N Lcaron ; B 76 0 643 718 ; C 189 ; WX 834 ; N onehalf ; B 132 -19 858 710 ; C -1 ; WX 549 ; N lessequal ; B 29 0 676 704 ; C 244 ; WX 611 ; N ocircumflex ; B 82 -14 643 750 ; C 241 ; WX 611 ; N ntilde ; B 65 0 646 737 ; C -1 ; WX 722 ; N Uhungarumlaut ; B 116 -19 880 936 ; C 201 ; WX 667 ; N Eacute ; B 76 0 757 936 ; C -1 ; WX 556 ; N emacron ; B 70 -14 595 678 ; C -1 ; WX 611 ; N gbreve ; B 38 -217 666 750 ; C 188 ; WX 834 ; N onequarter ; B 132 -19 806 710 ; C 138 ; WX 667 ; N Scaron ; B 81 -19 718 936 ; C -1 ; WX 667 ; N Scommaaccent ; B 81 -228 718 737 ; C -1 ; WX 778 ; N Ohungarumlaut ; B 107 -19 908 936 ; C 176 ; WX 400 ; N degree ; B 175 426 467 712 ; C 242 ; WX 611 ; N ograve ; B 82 -14 643 750 ; C -1 ; WX 722 ; N Ccaron ; B 107 -19 789 936 ; C 249 ; WX 611 ; N ugrave ; B 98 -14 658 750 ; C -1 ; WX 549 ; N radical ; B 112 -46 689 850 ; C -1 ; WX 722 ; N Dcaron ; B 76 0 777 936 ; C -1 ; WX 389 ; N rcommaaccent ; B 26 -228 489 546 ; C 209 ; WX 722 ; N Ntilde ; B 69 0 807 923 ; C 245 ; WX 611 ; N otilde ; B 82 -14 646 737 ; C -1 ; WX 722 ; N Rcommaaccent ; B 76 -228 778 718 ; C -1 ; WX 611 ; N Lcommaaccent ; B 76 -228 611 718 ; C 195 ; WX 722 ; N Atilde ; B 20 0 741 923 ; C -1 ; WX 722 ; N Aogonek ; B 20 -224 702 718 ; C 197 ; WX 722 ; N Aring ; B 20 0 702 962 ; C 213 ; WX 778 ; N Otilde ; B 107 -19 823 923 ; C -1 ; WX 500 ; N zdotaccent ; B 20 0 583 729 ; C -1 ; WX 667 ; N Ecaron ; B 76 0 757 936 ; C -1 ; WX 278 ; N Iogonek ; B -41 -228 367 718 ; C -1 ; WX 556 ; N kcommaaccent ; B 69 -228 670 718 ; C -1 ; WX 584 ; N minus ; B 82 197 610 309 ; C 206 ; WX 278 ; N Icircumflex ; B 64 0 484 936 ; C -1 ; WX 611 ; N ncaron ; B 65 0 641 750 ; C -1 ; WX 333 ; N tcommaaccent ; B 58 -228 422 676 ; C 172 ; WX 584 ; N logicalnot ; B 105 108 633 419 ; C 246 ; WX 611 ; N odieresis ; B 82 -14 643 729 ; C 252 ; WX 611 ; N udieresis ; B 98 -14 658 729 ; C -1 ; WX 549 ; N notequal ; B 32 -49 630 570 ; C -1 ; WX 611 ; N gcommaaccent ; B 38 -217 666 850 ; C 240 ; WX 611 ; N eth ; B 82 -14 670 737 ; C 158 ; WX 500 ; N zcaron ; B 20 0 586 750 ; C -1 ; WX 611 ; N ncommaaccent ; B 65 -228 629 546 ; C 185 ; WX 333 ; N onesuperior ; B 148 283 388 710 ; C -1 ; WX 278 ; N imacron ; B 69 0 429 678 ; C 128 ; WX 556 ; N Euro ; B 0 0 0 0 ; EndCharMetrics StartKernData StartKernPairs 2481 KPX A C -40 KPX A Cacute -40 KPX A Ccaron -40 KPX A Ccedilla -40 KPX A G -50 KPX A Gbreve -50 KPX A Gcommaaccent -50 KPX A O -40 KPX A Oacute -40 KPX A Ocircumflex -40 KPX A Odieresis -40 KPX A Ograve -40 KPX A Ohungarumlaut -40 KPX A Omacron -40 KPX A Oslash -40 KPX A Otilde -40 KPX A Q -40 KPX A T -90 KPX A Tcaron -90 KPX A Tcommaaccent -90 KPX A U -50 KPX A Uacute -50 KPX A Ucircumflex -50 KPX A Udieresis -50 KPX A Ugrave -50 KPX A Uhungarumlaut -50 KPX A Umacron -50 KPX A Uogonek -50 KPX A Uring -50 KPX A V -80 KPX A W -60 KPX A Y -110 KPX A Yacute -110 KPX A Ydieresis -110 KPX A u -30 KPX A uacute -30 KPX A ucircumflex -30 KPX A udieresis -30 KPX A ugrave -30 KPX A uhungarumlaut -30 KPX A umacron -30 KPX A uogonek -30 KPX A uring -30 KPX A v -40 KPX A w -30 KPX A y -30 KPX A yacute -30 KPX A ydieresis -30 KPX Aacute C -40 KPX Aacute Cacute -40 KPX Aacute Ccaron -40 KPX Aacute Ccedilla -40 KPX Aacute G -50 KPX Aacute Gbreve -50 KPX Aacute Gcommaaccent -50 KPX Aacute O -40 KPX Aacute Oacute -40 KPX Aacute Ocircumflex -40 KPX Aacute Odieresis -40 KPX Aacute Ograve -40 KPX Aacute Ohungarumlaut -40 KPX Aacute Omacron -40 KPX Aacute Oslash -40 KPX Aacute Otilde -40 KPX Aacute Q -40 KPX Aacute T -90 KPX Aacute Tcaron -90 KPX Aacute Tcommaaccent -90 KPX Aacute U -50 KPX Aacute Uacute -50 KPX Aacute Ucircumflex -50 KPX Aacute Udieresis -50 KPX Aacute Ugrave -50 KPX Aacute Uhungarumlaut -50 KPX Aacute Umacron -50 KPX Aacute Uogonek -50 KPX Aacute Uring -50 KPX Aacute V -80 KPX Aacute W -60 KPX Aacute Y -110 KPX Aacute Yacute -110 KPX Aacute Ydieresis -110 KPX Aacute u -30 KPX Aacute uacute -30 KPX Aacute ucircumflex -30 KPX Aacute udieresis -30 KPX Aacute ugrave -30 KPX Aacute uhungarumlaut -30 KPX Aacute umacron -30 KPX Aacute uogonek -30 KPX Aacute uring -30 KPX Aacute v -40 KPX Aacute w -30 KPX Aacute y -30 KPX Aacute yacute -30 KPX Aacute ydieresis -30 KPX Abreve C -40 KPX Abreve Cacute -40 KPX Abreve Ccaron -40 KPX Abreve Ccedilla -40 KPX Abreve G -50 KPX Abreve Gbreve -50 KPX Abreve Gcommaaccent -50 KPX Abreve O -40 KPX Abreve Oacute -40 KPX Abreve Ocircumflex -40 KPX Abreve Odieresis -40 KPX Abreve Ograve -40 KPX Abreve Ohungarumlaut -40 KPX Abreve Omacron -40 KPX Abreve Oslash -40 KPX Abreve Otilde -40 KPX Abreve Q -40 KPX Abreve T -90 KPX Abreve Tcaron -90 KPX Abreve Tcommaaccent -90 KPX Abreve U -50 KPX Abreve Uacute -50 KPX Abreve Ucircumflex -50 KPX Abreve Udieresis -50 KPX Abreve Ugrave -50 KPX Abreve Uhungarumlaut -50 KPX Abreve Umacron -50 KPX Abreve Uogonek -50 KPX Abreve Uring -50 KPX Abreve V -80 KPX Abreve W -60 KPX Abreve Y -110 KPX Abreve Yacute -110 KPX Abreve Ydieresis -110 KPX Abreve u -30 KPX Abreve uacute -30 KPX Abreve ucircumflex -30 KPX Abreve udieresis -30 KPX Abreve ugrave -30 KPX Abreve uhungarumlaut -30 KPX Abreve umacron -30 KPX Abreve uogonek -30 KPX Abreve uring -30 KPX Abreve v -40 KPX Abreve w -30 KPX Abreve y -30 KPX Abreve yacute -30 KPX Abreve ydieresis -30 KPX Acircumflex C -40 KPX Acircumflex Cacute -40 KPX Acircumflex Ccaron -40 KPX Acircumflex Ccedilla -40 KPX Acircumflex G -50 KPX Acircumflex Gbreve -50 KPX Acircumflex Gcommaaccent -50 KPX Acircumflex O -40 KPX Acircumflex Oacute -40 KPX Acircumflex Ocircumflex -40 KPX Acircumflex Odieresis -40 KPX Acircumflex Ograve -40 KPX Acircumflex Ohungarumlaut -40 KPX Acircumflex Omacron -40 KPX Acircumflex Oslash -40 KPX Acircumflex Otilde -40 KPX Acircumflex Q -40 KPX Acircumflex T -90 KPX Acircumflex Tcaron -90 KPX Acircumflex Tcommaaccent -90 KPX Acircumflex U -50 KPX Acircumflex Uacute -50 KPX Acircumflex Ucircumflex -50 KPX Acircumflex Udieresis -50 KPX Acircumflex Ugrave -50 KPX Acircumflex Uhungarumlaut -50 KPX Acircumflex Umacron -50 KPX Acircumflex Uogonek -50 KPX Acircumflex Uring -50 KPX Acircumflex V -80 KPX Acircumflex W -60 KPX Acircumflex Y -110 KPX Acircumflex Yacute -110 KPX Acircumflex Ydieresis -110 KPX Acircumflex u -30 KPX Acircumflex uacute -30 KPX Acircumflex ucircumflex -30 KPX Acircumflex udieresis -30 KPX Acircumflex ugrave -30 KPX Acircumflex uhungarumlaut -30 KPX Acircumflex umacron -30 KPX Acircumflex uogonek -30 KPX Acircumflex uring -30 KPX Acircumflex v -40 KPX Acircumflex w -30 KPX Acircumflex y -30 KPX Acircumflex yacute -30 KPX Acircumflex ydieresis -30 KPX Adieresis C -40 KPX Adieresis Cacute -40 KPX Adieresis Ccaron -40 KPX Adieresis Ccedilla -40 KPX Adieresis G -50 KPX Adieresis Gbreve -50 KPX Adieresis Gcommaaccent -50 KPX Adieresis O -40 KPX Adieresis Oacute -40 KPX Adieresis Ocircumflex -40 KPX Adieresis Odieresis -40 KPX Adieresis Ograve -40 KPX Adieresis Ohungarumlaut -40 KPX Adieresis Omacron -40 KPX Adieresis Oslash -40 KPX Adieresis Otilde -40 KPX Adieresis Q -40 KPX Adieresis T -90 KPX Adieresis Tcaron -90 KPX Adieresis Tcommaaccent -90 KPX Adieresis U -50 KPX Adieresis Uacute -50 KPX Adieresis Ucircumflex -50 KPX Adieresis Udieresis -50 KPX Adieresis Ugrave -50 KPX Adieresis Uhungarumlaut -50 KPX Adieresis Umacron -50 KPX Adieresis Uogonek -50 KPX Adieresis Uring -50 KPX Adieresis V -80 KPX Adieresis W -60 KPX Adieresis Y -110 KPX Adieresis Yacute -110 KPX Adieresis Ydieresis -110 KPX Adieresis u -30 KPX Adieresis uacute -30 KPX Adieresis ucircumflex -30 KPX Adieresis udieresis -30 KPX Adieresis ugrave -30 KPX Adieresis uhungarumlaut -30 KPX Adieresis umacron -30 KPX Adieresis uogonek -30 KPX Adieresis uring -30 KPX Adieresis v -40 KPX Adieresis w -30 KPX Adieresis y -30 KPX Adieresis yacute -30 KPX Adieresis ydieresis -30 KPX Agrave C -40 KPX Agrave Cacute -40 KPX Agrave Ccaron -40 KPX Agrave Ccedilla -40 KPX Agrave G -50 KPX Agrave Gbreve -50 KPX Agrave Gcommaaccent -50 KPX Agrave O -40 KPX Agrave Oacute -40 KPX Agrave Ocircumflex -40 KPX Agrave Odieresis -40 KPX Agrave Ograve -40 KPX Agrave Ohungarumlaut -40 KPX Agrave Omacron -40 KPX Agrave Oslash -40 KPX Agrave Otilde -40 KPX Agrave Q -40 KPX Agrave T -90 KPX Agrave Tcaron -90 KPX Agrave Tcommaaccent -90 KPX Agrave U -50 KPX Agrave Uacute -50 KPX Agrave Ucircumflex -50 KPX Agrave Udieresis -50 KPX Agrave Ugrave -50 KPX Agrave Uhungarumlaut -50 KPX Agrave Umacron -50 KPX Agrave Uogonek -50 KPX Agrave Uring -50 KPX Agrave V -80 KPX Agrave W -60 KPX Agrave Y -110 KPX Agrave Yacute -110 KPX Agrave Ydieresis -110 KPX Agrave u -30 KPX Agrave uacute -30 KPX Agrave ucircumflex -30 KPX Agrave udieresis -30 KPX Agrave ugrave -30 KPX Agrave uhungarumlaut -30 KPX Agrave umacron -30 KPX Agrave uogonek -30 KPX Agrave uring -30 KPX Agrave v -40 KPX Agrave w -30 KPX Agrave y -30 KPX Agrave yacute -30 KPX Agrave ydieresis -30 KPX Amacron C -40 KPX Amacron Cacute -40 KPX Amacron Ccaron -40 KPX Amacron Ccedilla -40 KPX Amacron G -50 KPX Amacron Gbreve -50 KPX Amacron Gcommaaccent -50 KPX Amacron O -40 KPX Amacron Oacute -40 KPX Amacron Ocircumflex -40 KPX Amacron Odieresis -40 KPX Amacron Ograve -40 KPX Amacron Ohungarumlaut -40 KPX Amacron Omacron -40 KPX Amacron Oslash -40 KPX Amacron Otilde -40 KPX Amacron Q -40 KPX Amacron T -90 KPX Amacron Tcaron -90 KPX Amacron Tcommaaccent -90 KPX Amacron U -50 KPX Amacron Uacute -50 KPX Amacron Ucircumflex -50 KPX Amacron Udieresis -50 KPX Amacron Ugrave -50 KPX Amacron Uhungarumlaut -50 KPX Amacron Umacron -50 KPX Amacron Uogonek -50 KPX Amacron Uring -50 KPX Amacron V -80 KPX Amacron W -60 KPX Amacron Y -110 KPX Amacron Yacute -110 KPX Amacron Ydieresis -110 KPX Amacron u -30 KPX Amacron uacute -30 KPX Amacron ucircumflex -30 KPX Amacron udieresis -30 KPX Amacron ugrave -30 KPX Amacron uhungarumlaut -30 KPX Amacron umacron -30 KPX Amacron uogonek -30 KPX Amacron uring -30 KPX Amacron v -40 KPX Amacron w -30 KPX Amacron y -30 KPX Amacron yacute -30 KPX Amacron ydieresis -30 KPX Aogonek C -40 KPX Aogonek Cacute -40 KPX Aogonek Ccaron -40 KPX Aogonek Ccedilla -40 KPX Aogonek G -50 KPX Aogonek Gbreve -50 KPX Aogonek Gcommaaccent -50 KPX Aogonek O -40 KPX Aogonek Oacute -40 KPX Aogonek Ocircumflex -40 KPX Aogonek Odieresis -40 KPX Aogonek Ograve -40 KPX Aogonek Ohungarumlaut -40 KPX Aogonek Omacron -40 KPX Aogonek Oslash -40 KPX Aogonek Otilde -40 KPX Aogonek Q -40 KPX Aogonek T -90 KPX Aogonek Tcaron -90 KPX Aogonek Tcommaaccent -90 KPX Aogonek U -50 KPX Aogonek Uacute -50 KPX Aogonek Ucircumflex -50 KPX Aogonek Udieresis -50 KPX Aogonek Ugrave -50 KPX Aogonek Uhungarumlaut -50 KPX Aogonek Umacron -50 KPX Aogonek Uogonek -50 KPX Aogonek Uring -50 KPX Aogonek V -80 KPX Aogonek W -60 KPX Aogonek Y -110 KPX Aogonek Yacute -110 KPX Aogonek Ydieresis -110 KPX Aogonek u -30 KPX Aogonek uacute -30 KPX Aogonek ucircumflex -30 KPX Aogonek udieresis -30 KPX Aogonek ugrave -30 KPX Aogonek uhungarumlaut -30 KPX Aogonek umacron -30 KPX Aogonek uogonek -30 KPX Aogonek uring -30 KPX Aogonek v -40 KPX Aogonek w -30 KPX Aogonek y -30 KPX Aogonek yacute -30 KPX Aogonek ydieresis -30 KPX Aring C -40 KPX Aring Cacute -40 KPX Aring Ccaron -40 KPX Aring Ccedilla -40 KPX Aring G -50 KPX Aring Gbreve -50 KPX Aring Gcommaaccent -50 KPX Aring O -40 KPX Aring Oacute -40 KPX Aring Ocircumflex -40 KPX Aring Odieresis -40 KPX Aring Ograve -40 KPX Aring Ohungarumlaut -40 KPX Aring Omacron -40 KPX Aring Oslash -40 KPX Aring Otilde -40 KPX Aring Q -40 KPX Aring T -90 KPX Aring Tcaron -90 KPX Aring Tcommaaccent -90 KPX Aring U -50 KPX Aring Uacute -50 KPX Aring Ucircumflex -50 KPX Aring Udieresis -50 KPX Aring Ugrave -50 KPX Aring Uhungarumlaut -50 KPX Aring Umacron -50 KPX Aring Uogonek -50 KPX Aring Uring -50 KPX Aring V -80 KPX Aring W -60 KPX Aring Y -110 KPX Aring Yacute -110 KPX Aring Ydieresis -110 KPX Aring u -30 KPX Aring uacute -30 KPX Aring ucircumflex -30 KPX Aring udieresis -30 KPX Aring ugrave -30 KPX Aring uhungarumlaut -30 KPX Aring umacron -30 KPX Aring uogonek -30 KPX Aring uring -30 KPX Aring v -40 KPX Aring w -30 KPX Aring y -30 KPX Aring yacute -30 KPX Aring ydieresis -30 KPX Atilde C -40 KPX Atilde Cacute -40 KPX Atilde Ccaron -40 KPX Atilde Ccedilla -40 KPX Atilde G -50 KPX Atilde Gbreve -50 KPX Atilde Gcommaaccent -50 KPX Atilde O -40 KPX Atilde Oacute -40 KPX Atilde Ocircumflex -40 KPX Atilde Odieresis -40 KPX Atilde Ograve -40 KPX Atilde Ohungarumlaut -40 KPX Atilde Omacron -40 KPX Atilde Oslash -40 KPX Atilde Otilde -40 KPX Atilde Q -40 KPX Atilde T -90 KPX Atilde Tcaron -90 KPX Atilde Tcommaaccent -90 KPX Atilde U -50 KPX Atilde Uacute -50 KPX Atilde Ucircumflex -50 KPX Atilde Udieresis -50 KPX Atilde Ugrave -50 KPX Atilde Uhungarumlaut -50 KPX Atilde Umacron -50 KPX Atilde Uogonek -50 KPX Atilde Uring -50 KPX Atilde V -80 KPX Atilde W -60 KPX Atilde Y -110 KPX Atilde Yacute -110 KPX Atilde Ydieresis -110 KPX Atilde u -30 KPX Atilde uacute -30 KPX Atilde ucircumflex -30 KPX Atilde udieresis -30 KPX Atilde ugrave -30 KPX Atilde uhungarumlaut -30 KPX Atilde umacron -30 KPX Atilde uogonek -30 KPX Atilde uring -30 KPX Atilde v -40 KPX Atilde w -30 KPX Atilde y -30 KPX Atilde yacute -30 KPX Atilde ydieresis -30 KPX B A -30 KPX B Aacute -30 KPX B Abreve -30 KPX B Acircumflex -30 KPX B Adieresis -30 KPX B Agrave -30 KPX B Amacron -30 KPX B Aogonek -30 KPX B Aring -30 KPX B Atilde -30 KPX B U -10 KPX B Uacute -10 KPX B Ucircumflex -10 KPX B Udieresis -10 KPX B Ugrave -10 KPX B Uhungarumlaut -10 KPX B Umacron -10 KPX B Uogonek -10 KPX B Uring -10 KPX D A -40 KPX D Aacute -40 KPX D Abreve -40 KPX D Acircumflex -40 KPX D Adieresis -40 KPX D Agrave -40 KPX D Amacron -40 KPX D Aogonek -40 KPX D Aring -40 KPX D Atilde -40 KPX D V -40 KPX D W -40 KPX D Y -70 KPX D Yacute -70 KPX D Ydieresis -70 KPX D comma -30 KPX D period -30 KPX Dcaron A -40 KPX Dcaron Aacute -40 KPX Dcaron Abreve -40 KPX Dcaron Acircumflex -40 KPX Dcaron Adieresis -40 KPX Dcaron Agrave -40 KPX Dcaron Amacron -40 KPX Dcaron Aogonek -40 KPX Dcaron Aring -40 KPX Dcaron Atilde -40 KPX Dcaron V -40 KPX Dcaron W -40 KPX Dcaron Y -70 KPX Dcaron Yacute -70 KPX Dcaron Ydieresis -70 KPX Dcaron comma -30 KPX Dcaron period -30 KPX Dcroat A -40 KPX Dcroat Aacute -40 KPX Dcroat Abreve -40 KPX Dcroat Acircumflex -40 KPX Dcroat Adieresis -40 KPX Dcroat Agrave -40 KPX Dcroat Amacron -40 KPX Dcroat Aogonek -40 KPX Dcroat Aring -40 KPX Dcroat Atilde -40 KPX Dcroat V -40 KPX Dcroat W -40 KPX Dcroat Y -70 KPX Dcroat Yacute -70 KPX Dcroat Ydieresis -70 KPX Dcroat comma -30 KPX Dcroat period -30 KPX F A -80 KPX F Aacute -80 KPX F Abreve -80 KPX F Acircumflex -80 KPX F Adieresis -80 KPX F Agrave -80 KPX F Amacron -80 KPX F Aogonek -80 KPX F Aring -80 KPX F Atilde -80 KPX F a -20 KPX F aacute -20 KPX F abreve -20 KPX F acircumflex -20 KPX F adieresis -20 KPX F agrave -20 KPX F amacron -20 KPX F aogonek -20 KPX F aring -20 KPX F atilde -20 KPX F comma -100 KPX F period -100 KPX J A -20 KPX J Aacute -20 KPX J Abreve -20 KPX J Acircumflex -20 KPX J Adieresis -20 KPX J Agrave -20 KPX J Amacron -20 KPX J Aogonek -20 KPX J Aring -20 KPX J Atilde -20 KPX J comma -20 KPX J period -20 KPX J u -20 KPX J uacute -20 KPX J ucircumflex -20 KPX J udieresis -20 KPX J ugrave -20 KPX J uhungarumlaut -20 KPX J umacron -20 KPX J uogonek -20 KPX J uring -20 KPX K O -30 KPX K Oacute -30 KPX K Ocircumflex -30 KPX K Odieresis -30 KPX K Ograve -30 KPX K Ohungarumlaut -30 KPX K Omacron -30 KPX K Oslash -30 KPX K Otilde -30 KPX K e -15 KPX K eacute -15 KPX K ecaron -15 KPX K ecircumflex -15 KPX K edieresis -15 KPX K edotaccent -15 KPX K egrave -15 KPX K emacron -15 KPX K eogonek -15 KPX K o -35 KPX K oacute -35 KPX K ocircumflex -35 KPX K odieresis -35 KPX K ograve -35 KPX K ohungarumlaut -35 KPX K omacron -35 KPX K oslash -35 KPX K otilde -35 KPX K u -30 KPX K uacute -30 KPX K ucircumflex -30 KPX K udieresis -30 KPX K ugrave -30 KPX K uhungarumlaut -30 KPX K umacron -30 KPX K uogonek -30 KPX K uring -30 KPX K y -40 KPX K yacute -40 KPX K ydieresis -40 KPX Kcommaaccent O -30 KPX Kcommaaccent Oacute -30 KPX Kcommaaccent Ocircumflex -30 KPX Kcommaaccent Odieresis -30 KPX Kcommaaccent Ograve -30 KPX Kcommaaccent Ohungarumlaut -30 KPX Kcommaaccent Omacron -30 KPX Kcommaaccent Oslash -30 KPX Kcommaaccent Otilde -30 KPX Kcommaaccent e -15 KPX Kcommaaccent eacute -15 KPX Kcommaaccent ecaron -15 KPX Kcommaaccent ecircumflex -15 KPX Kcommaaccent edieresis -15 KPX Kcommaaccent edotaccent -15 KPX Kcommaaccent egrave -15 KPX Kcommaaccent emacron -15 KPX Kcommaaccent eogonek -15 KPX Kcommaaccent o -35 KPX Kcommaaccent oacute -35 KPX Kcommaaccent ocircumflex -35 KPX Kcommaaccent odieresis -35 KPX Kcommaaccent ograve -35 KPX Kcommaaccent ohungarumlaut -35 KPX Kcommaaccent omacron -35 KPX Kcommaaccent oslash -35 KPX Kcommaaccent otilde -35 KPX Kcommaaccent u -30 KPX Kcommaaccent uacute -30 KPX Kcommaaccent ucircumflex -30 KPX Kcommaaccent udieresis -30 KPX Kcommaaccent ugrave -30 KPX Kcommaaccent uhungarumlaut -30 KPX Kcommaaccent umacron -30 KPX Kcommaaccent uogonek -30 KPX Kcommaaccent uring -30 KPX Kcommaaccent y -40 KPX Kcommaaccent yacute -40 KPX Kcommaaccent ydieresis -40 KPX L T -90 KPX L Tcaron -90 KPX L Tcommaaccent -90 KPX L V -110 KPX L W -80 KPX L Y -120 KPX L Yacute -120 KPX L Ydieresis -120 KPX L quotedblright -140 KPX L quoteright -140 KPX L y -30 KPX L yacute -30 KPX L ydieresis -30 KPX Lacute T -90 KPX Lacute Tcaron -90 KPX Lacute Tcommaaccent -90 KPX Lacute V -110 KPX Lacute W -80 KPX Lacute Y -120 KPX Lacute Yacute -120 KPX Lacute Ydieresis -120 KPX Lacute quotedblright -140 KPX Lacute quoteright -140 KPX Lacute y -30 KPX Lacute yacute -30 KPX Lacute ydieresis -30 KPX Lcommaaccent T -90 KPX Lcommaaccent Tcaron -90 KPX Lcommaaccent Tcommaaccent -90 KPX Lcommaaccent V -110 KPX Lcommaaccent W -80 KPX Lcommaaccent Y -120 KPX Lcommaaccent Yacute -120 KPX Lcommaaccent Ydieresis -120 KPX Lcommaaccent quotedblright -140 KPX Lcommaaccent quoteright -140 KPX Lcommaaccent y -30 KPX Lcommaaccent yacute -30 KPX Lcommaaccent ydieresis -30 KPX Lslash T -90 KPX Lslash Tcaron -90 KPX Lslash Tcommaaccent -90 KPX Lslash V -110 KPX Lslash W -80 KPX Lslash Y -120 KPX Lslash Yacute -120 KPX Lslash Ydieresis -120 KPX Lslash quotedblright -140 KPX Lslash quoteright -140 KPX Lslash y -30 KPX Lslash yacute -30 KPX Lslash ydieresis -30 KPX O A -50 KPX O Aacute -50 KPX O Abreve -50 KPX O Acircumflex -50 KPX O Adieresis -50 KPX O Agrave -50 KPX O Amacron -50 KPX O Aogonek -50 KPX O Aring -50 KPX O Atilde -50 KPX O T -40 KPX O Tcaron -40 KPX O Tcommaaccent -40 KPX O V -50 KPX O W -50 KPX O X -50 KPX O Y -70 KPX O Yacute -70 KPX O Ydieresis -70 KPX O comma -40 KPX O period -40 KPX Oacute A -50 KPX Oacute Aacute -50 KPX Oacute Abreve -50 KPX Oacute Acircumflex -50 KPX Oacute Adieresis -50 KPX Oacute Agrave -50 KPX Oacute Amacron -50 KPX Oacute Aogonek -50 KPX Oacute Aring -50 KPX Oacute Atilde -50 KPX Oacute T -40 KPX Oacute Tcaron -40 KPX Oacute Tcommaaccent -40 KPX Oacute V -50 KPX Oacute W -50 KPX Oacute X -50 KPX Oacute Y -70 KPX Oacute Yacute -70 KPX Oacute Ydieresis -70 KPX Oacute comma -40 KPX Oacute period -40 KPX Ocircumflex A -50 KPX Ocircumflex Aacute -50 KPX Ocircumflex Abreve -50 KPX Ocircumflex Acircumflex -50 KPX Ocircumflex Adieresis -50 KPX Ocircumflex Agrave -50 KPX Ocircumflex Amacron -50 KPX Ocircumflex Aogonek -50 KPX Ocircumflex Aring -50 KPX Ocircumflex Atilde -50 KPX Ocircumflex T -40 KPX Ocircumflex Tcaron -40 KPX Ocircumflex Tcommaaccent -40 KPX Ocircumflex V -50 KPX Ocircumflex W -50 KPX Ocircumflex X -50 KPX Ocircumflex Y -70 KPX Ocircumflex Yacute -70 KPX Ocircumflex Ydieresis -70 KPX Ocircumflex comma -40 KPX Ocircumflex period -40 KPX Odieresis A -50 KPX Odieresis Aacute -50 KPX Odieresis Abreve -50 KPX Odieresis Acircumflex -50 KPX Odieresis Adieresis -50 KPX Odieresis Agrave -50 KPX Odieresis Amacron -50 KPX Odieresis Aogonek -50 KPX Odieresis Aring -50 KPX Odieresis Atilde -50 KPX Odieresis T -40 KPX Odieresis Tcaron -40 KPX Odieresis Tcommaaccent -40 KPX Odieresis V -50 KPX Odieresis W -50 KPX Odieresis X -50 KPX Odieresis Y -70 KPX Odieresis Yacute -70 KPX Odieresis Ydieresis -70 KPX Odieresis comma -40 KPX Odieresis period -40 KPX Ograve A -50 KPX Ograve Aacute -50 KPX Ograve Abreve -50 KPX Ograve Acircumflex -50 KPX Ograve Adieresis -50 KPX Ograve Agrave -50 KPX Ograve Amacron -50 KPX Ograve Aogonek -50 KPX Ograve Aring -50 KPX Ograve Atilde -50 KPX Ograve T -40 KPX Ograve Tcaron -40 KPX Ograve Tcommaaccent -40 KPX Ograve V -50 KPX Ograve W -50 KPX Ograve X -50 KPX Ograve Y -70 KPX Ograve Yacute -70 KPX Ograve Ydieresis -70 KPX Ograve comma -40 KPX Ograve period -40 KPX Ohungarumlaut A -50 KPX Ohungarumlaut Aacute -50 KPX Ohungarumlaut Abreve -50 KPX Ohungarumlaut Acircumflex -50 KPX Ohungarumlaut Adieresis -50 KPX Ohungarumlaut Agrave -50 KPX Ohungarumlaut Amacron -50 KPX Ohungarumlaut Aogonek -50 KPX Ohungarumlaut Aring -50 KPX Ohungarumlaut Atilde -50 KPX Ohungarumlaut T -40 KPX Ohungarumlaut Tcaron -40 KPX Ohungarumlaut Tcommaaccent -40 KPX Ohungarumlaut V -50 KPX Ohungarumlaut W -50 KPX Ohungarumlaut X -50 KPX Ohungarumlaut Y -70 KPX Ohungarumlaut Yacute -70 KPX Ohungarumlaut Ydieresis -70 KPX Ohungarumlaut comma -40 KPX Ohungarumlaut period -40 KPX Omacron A -50 KPX Omacron Aacute -50 KPX Omacron Abreve -50 KPX Omacron Acircumflex -50 KPX Omacron Adieresis -50 KPX Omacron Agrave -50 KPX Omacron Amacron -50 KPX Omacron Aogonek -50 KPX Omacron Aring -50 KPX Omacron Atilde -50 KPX Omacron T -40 KPX Omacron Tcaron -40 KPX Omacron Tcommaaccent -40 KPX Omacron V -50 KPX Omacron W -50 KPX Omacron X -50 KPX Omacron Y -70 KPX Omacron Yacute -70 KPX Omacron Ydieresis -70 KPX Omacron comma -40 KPX Omacron period -40 KPX Oslash A -50 KPX Oslash Aacute -50 KPX Oslash Abreve -50 KPX Oslash Acircumflex -50 KPX Oslash Adieresis -50 KPX Oslash Agrave -50 KPX Oslash Amacron -50 KPX Oslash Aogonek -50 KPX Oslash Aring -50 KPX Oslash Atilde -50 KPX Oslash T -40 KPX Oslash Tcaron -40 KPX Oslash Tcommaaccent -40 KPX Oslash V -50 KPX Oslash W -50 KPX Oslash X -50 KPX Oslash Y -70 KPX Oslash Yacute -70 KPX Oslash Ydieresis -70 KPX Oslash comma -40 KPX Oslash period -40 KPX Otilde A -50 KPX Otilde Aacute -50 KPX Otilde Abreve -50 KPX Otilde Acircumflex -50 KPX Otilde Adieresis -50 KPX Otilde Agrave -50 KPX Otilde Amacron -50 KPX Otilde Aogonek -50 KPX Otilde Aring -50 KPX Otilde Atilde -50 KPX Otilde T -40 KPX Otilde Tcaron -40 KPX Otilde Tcommaaccent -40 KPX Otilde V -50 KPX Otilde W -50 KPX Otilde X -50 KPX Otilde Y -70 KPX Otilde Yacute -70 KPX Otilde Ydieresis -70 KPX Otilde comma -40 KPX Otilde period -40 KPX P A -100 KPX P Aacute -100 KPX P Abreve -100 KPX P Acircumflex -100 KPX P Adieresis -100 KPX P Agrave -100 KPX P Amacron -100 KPX P Aogonek -100 KPX P Aring -100 KPX P Atilde -100 KPX P a -30 KPX P aacute -30 KPX P abreve -30 KPX P acircumflex -30 KPX P adieresis -30 KPX P agrave -30 KPX P amacron -30 KPX P aogonek -30 KPX P aring -30 KPX P atilde -30 KPX P comma -120 KPX P e -30 KPX P eacute -30 KPX P ecaron -30 KPX P ecircumflex -30 KPX P edieresis -30 KPX P edotaccent -30 KPX P egrave -30 KPX P emacron -30 KPX P eogonek -30 KPX P o -40 KPX P oacute -40 KPX P ocircumflex -40 KPX P odieresis -40 KPX P ograve -40 KPX P ohungarumlaut -40 KPX P omacron -40 KPX P oslash -40 KPX P otilde -40 KPX P period -120 KPX Q U -10 KPX Q Uacute -10 KPX Q Ucircumflex -10 KPX Q Udieresis -10 KPX Q Ugrave -10 KPX Q Uhungarumlaut -10 KPX Q Umacron -10 KPX Q Uogonek -10 KPX Q Uring -10 KPX Q comma 20 KPX Q period 20 KPX R O -20 KPX R Oacute -20 KPX R Ocircumflex -20 KPX R Odieresis -20 KPX R Ograve -20 KPX R Ohungarumlaut -20 KPX R Omacron -20 KPX R Oslash -20 KPX R Otilde -20 KPX R T -20 KPX R Tcaron -20 KPX R Tcommaaccent -20 KPX R U -20 KPX R Uacute -20 KPX R Ucircumflex -20 KPX R Udieresis -20 KPX R Ugrave -20 KPX R Uhungarumlaut -20 KPX R Umacron -20 KPX R Uogonek -20 KPX R Uring -20 KPX R V -50 KPX R W -40 KPX R Y -50 KPX R Yacute -50 KPX R Ydieresis -50 KPX Racute O -20 KPX Racute Oacute -20 KPX Racute Ocircumflex -20 KPX Racute Odieresis -20 KPX Racute Ograve -20 KPX Racute Ohungarumlaut -20 KPX Racute Omacron -20 KPX Racute Oslash -20 KPX Racute Otilde -20 KPX Racute T -20 KPX Racute Tcaron -20 KPX Racute Tcommaaccent -20 KPX Racute U -20 KPX Racute Uacute -20 KPX Racute Ucircumflex -20 KPX Racute Udieresis -20 KPX Racute Ugrave -20 KPX Racute Uhungarumlaut -20 KPX Racute Umacron -20 KPX Racute Uogonek -20 KPX Racute Uring -20 KPX Racute V -50 KPX Racute W -40 KPX Racute Y -50 KPX Racute Yacute -50 KPX Racute Ydieresis -50 KPX Rcaron O -20 KPX Rcaron Oacute -20 KPX Rcaron Ocircumflex -20 KPX Rcaron Odieresis -20 KPX Rcaron Ograve -20 KPX Rcaron Ohungarumlaut -20 KPX Rcaron Omacron -20 KPX Rcaron Oslash -20 KPX Rcaron Otilde -20 KPX Rcaron T -20 KPX Rcaron Tcaron -20 KPX Rcaron Tcommaaccent -20 KPX Rcaron U -20 KPX Rcaron Uacute -20 KPX Rcaron Ucircumflex -20 KPX Rcaron Udieresis -20 KPX Rcaron Ugrave -20 KPX Rcaron Uhungarumlaut -20 KPX Rcaron Umacron -20 KPX Rcaron Uogonek -20 KPX Rcaron Uring -20 KPX Rcaron V -50 KPX Rcaron W -40 KPX Rcaron Y -50 KPX Rcaron Yacute -50 KPX Rcaron Ydieresis -50 KPX Rcommaaccent O -20 KPX Rcommaaccent Oacute -20 KPX Rcommaaccent Ocircumflex -20 KPX Rcommaaccent Odieresis -20 KPX Rcommaaccent Ograve -20 KPX Rcommaaccent Ohungarumlaut -20 KPX Rcommaaccent Omacron -20 KPX Rcommaaccent Oslash -20 KPX Rcommaaccent Otilde -20 KPX Rcommaaccent T -20 KPX Rcommaaccent Tcaron -20 KPX Rcommaaccent Tcommaaccent -20 KPX Rcommaaccent U -20 KPX Rcommaaccent Uacute -20 KPX Rcommaaccent Ucircumflex -20 KPX Rcommaaccent Udieresis -20 KPX Rcommaaccent Ugrave -20 KPX Rcommaaccent Uhungarumlaut -20 KPX Rcommaaccent Umacron -20 KPX Rcommaaccent Uogonek -20 KPX Rcommaaccent Uring -20 KPX Rcommaaccent V -50 KPX Rcommaaccent W -40 KPX Rcommaaccent Y -50 KPX Rcommaaccent Yacute -50 KPX Rcommaaccent Ydieresis -50 KPX T A -90 KPX T Aacute -90 KPX T Abreve -90 KPX T Acircumflex -90 KPX T Adieresis -90 KPX T Agrave -90 KPX T Amacron -90 KPX T Aogonek -90 KPX T Aring -90 KPX T Atilde -90 KPX T O -40 KPX T Oacute -40 KPX T Ocircumflex -40 KPX T Odieresis -40 KPX T Ograve -40 KPX T Ohungarumlaut -40 KPX T Omacron -40 KPX T Oslash -40 KPX T Otilde -40 KPX T a -80 KPX T aacute -80 KPX T abreve -80 KPX T acircumflex -80 KPX T adieresis -80 KPX T agrave -80 KPX T amacron -80 KPX T aogonek -80 KPX T aring -80 KPX T atilde -80 KPX T colon -40 KPX T comma -80 KPX T e -60 KPX T eacute -60 KPX T ecaron -60 KPX T ecircumflex -60 KPX T edieresis -60 KPX T edotaccent -60 KPX T egrave -60 KPX T emacron -60 KPX T eogonek -60 KPX T hyphen -120 KPX T o -80 KPX T oacute -80 KPX T ocircumflex -80 KPX T odieresis -80 KPX T ograve -80 KPX T ohungarumlaut -80 KPX T omacron -80 KPX T oslash -80 KPX T otilde -80 KPX T period -80 KPX T r -80 KPX T racute -80 KPX T rcommaaccent -80 KPX T semicolon -40 KPX T u -90 KPX T uacute -90 KPX T ucircumflex -90 KPX T udieresis -90 KPX T ugrave -90 KPX T uhungarumlaut -90 KPX T umacron -90 KPX T uogonek -90 KPX T uring -90 KPX T w -60 KPX T y -60 KPX T yacute -60 KPX T ydieresis -60 KPX Tcaron A -90 KPX Tcaron Aacute -90 KPX Tcaron Abreve -90 KPX Tcaron Acircumflex -90 KPX Tcaron Adieresis -90 KPX Tcaron Agrave -90 KPX Tcaron Amacron -90 KPX Tcaron Aogonek -90 KPX Tcaron Aring -90 KPX Tcaron Atilde -90 KPX Tcaron O -40 KPX Tcaron Oacute -40 KPX Tcaron Ocircumflex -40 KPX Tcaron Odieresis -40 KPX Tcaron Ograve -40 KPX Tcaron Ohungarumlaut -40 KPX Tcaron Omacron -40 KPX Tcaron Oslash -40 KPX Tcaron Otilde -40 KPX Tcaron a -80 KPX Tcaron aacute -80 KPX Tcaron abreve -80 KPX Tcaron acircumflex -80 KPX Tcaron adieresis -80 KPX Tcaron agrave -80 KPX Tcaron amacron -80 KPX Tcaron aogonek -80 KPX Tcaron aring -80 KPX Tcaron atilde -80 KPX Tcaron colon -40 KPX Tcaron comma -80 KPX Tcaron e -60 KPX Tcaron eacute -60 KPX Tcaron ecaron -60 KPX Tcaron ecircumflex -60 KPX Tcaron edieresis -60 KPX Tcaron edotaccent -60 KPX Tcaron egrave -60 KPX Tcaron emacron -60 KPX Tcaron eogonek -60 KPX Tcaron hyphen -120 KPX Tcaron o -80 KPX Tcaron oacute -80 KPX Tcaron ocircumflex -80 KPX Tcaron odieresis -80 KPX Tcaron ograve -80 KPX Tcaron ohungarumlaut -80 KPX Tcaron omacron -80 KPX Tcaron oslash -80 KPX Tcaron otilde -80 KPX Tcaron period -80 KPX Tcaron r -80 KPX Tcaron racute -80 KPX Tcaron rcommaaccent -80 KPX Tcaron semicolon -40 KPX Tcaron u -90 KPX Tcaron uacute -90 KPX Tcaron ucircumflex -90 KPX Tcaron udieresis -90 KPX Tcaron ugrave -90 KPX Tcaron uhungarumlaut -90 KPX Tcaron umacron -90 KPX Tcaron uogonek -90 KPX Tcaron uring -90 KPX Tcaron w -60 KPX Tcaron y -60 KPX Tcaron yacute -60 KPX Tcaron ydieresis -60 KPX Tcommaaccent A -90 KPX Tcommaaccent Aacute -90 KPX Tcommaaccent Abreve -90 KPX Tcommaaccent Acircumflex -90 KPX Tcommaaccent Adieresis -90 KPX Tcommaaccent Agrave -90 KPX Tcommaaccent Amacron -90 KPX Tcommaaccent Aogonek -90 KPX Tcommaaccent Aring -90 KPX Tcommaaccent Atilde -90 KPX Tcommaaccent O -40 KPX Tcommaaccent Oacute -40 KPX Tcommaaccent Ocircumflex -40 KPX Tcommaaccent Odieresis -40 KPX Tcommaaccent Ograve -40 KPX Tcommaaccent Ohungarumlaut -40 KPX Tcommaaccent Omacron -40 KPX Tcommaaccent Oslash -40 KPX Tcommaaccent Otilde -40 KPX Tcommaaccent a -80 KPX Tcommaaccent aacute -80 KPX Tcommaaccent abreve -80 KPX Tcommaaccent acircumflex -80 KPX Tcommaaccent adieresis -80 KPX Tcommaaccent agrave -80 KPX Tcommaaccent amacron -80 KPX Tcommaaccent aogonek -80 KPX Tcommaaccent aring -80 KPX Tcommaaccent atilde -80 KPX Tcommaaccent colon -40 KPX Tcommaaccent comma -80 KPX Tcommaaccent e -60 KPX Tcommaaccent eacute -60 KPX Tcommaaccent ecaron -60 KPX Tcommaaccent ecircumflex -60 KPX Tcommaaccent edieresis -60 KPX Tcommaaccent edotaccent -60 KPX Tcommaaccent egrave -60 KPX Tcommaaccent emacron -60 KPX Tcommaaccent eogonek -60 KPX Tcommaaccent hyphen -120 KPX Tcommaaccent o -80 KPX Tcommaaccent oacute -80 KPX Tcommaaccent ocircumflex -80 KPX Tcommaaccent odieresis -80 KPX Tcommaaccent ograve -80 KPX Tcommaaccent ohungarumlaut -80 KPX Tcommaaccent omacron -80 KPX Tcommaaccent oslash -80 KPX Tcommaaccent otilde -80 KPX Tcommaaccent period -80 KPX Tcommaaccent r -80 KPX Tcommaaccent racute -80 KPX Tcommaaccent rcommaaccent -80 KPX Tcommaaccent semicolon -40 KPX Tcommaaccent u -90 KPX Tcommaaccent uacute -90 KPX Tcommaaccent ucircumflex -90 KPX Tcommaaccent udieresis -90 KPX Tcommaaccent ugrave -90 KPX Tcommaaccent uhungarumlaut -90 KPX Tcommaaccent umacron -90 KPX Tcommaaccent uogonek -90 KPX Tcommaaccent uring -90 KPX Tcommaaccent w -60 KPX Tcommaaccent y -60 KPX Tcommaaccent yacute -60 KPX Tcommaaccent ydieresis -60 KPX U A -50 KPX U Aacute -50 KPX U Abreve -50 KPX U Acircumflex -50 KPX U Adieresis -50 KPX U Agrave -50 KPX U Amacron -50 KPX U Aogonek -50 KPX U Aring -50 KPX U Atilde -50 KPX U comma -30 KPX U period -30 KPX Uacute A -50 KPX Uacute Aacute -50 KPX Uacute Abreve -50 KPX Uacute Acircumflex -50 KPX Uacute Adieresis -50 KPX Uacute Agrave -50 KPX Uacute Amacron -50 KPX Uacute Aogonek -50 KPX Uacute Aring -50 KPX Uacute Atilde -50 KPX Uacute comma -30 KPX Uacute period -30 KPX Ucircumflex A -50 KPX Ucircumflex Aacute -50 KPX Ucircumflex Abreve -50 KPX Ucircumflex Acircumflex -50 KPX Ucircumflex Adieresis -50 KPX Ucircumflex Agrave -50 KPX Ucircumflex Amacron -50 KPX Ucircumflex Aogonek -50 KPX Ucircumflex Aring -50 KPX Ucircumflex Atilde -50 KPX Ucircumflex comma -30 KPX Ucircumflex period -30 KPX Udieresis A -50 KPX Udieresis Aacute -50 KPX Udieresis Abreve -50 KPX Udieresis Acircumflex -50 KPX Udieresis Adieresis -50 KPX Udieresis Agrave -50 KPX Udieresis Amacron -50 KPX Udieresis Aogonek -50 KPX Udieresis Aring -50 KPX Udieresis Atilde -50 KPX Udieresis comma -30 KPX Udieresis period -30 KPX Ugrave A -50 KPX Ugrave Aacute -50 KPX Ugrave Abreve -50 KPX Ugrave Acircumflex -50 KPX Ugrave Adieresis -50 KPX Ugrave Agrave -50 KPX Ugrave Amacron -50 KPX Ugrave Aogonek -50 KPX Ugrave Aring -50 KPX Ugrave Atilde -50 KPX Ugrave comma -30 KPX Ugrave period -30 KPX Uhungarumlaut A -50 KPX Uhungarumlaut Aacute -50 KPX Uhungarumlaut Abreve -50 KPX Uhungarumlaut Acircumflex -50 KPX Uhungarumlaut Adieresis -50 KPX Uhungarumlaut Agrave -50 KPX Uhungarumlaut Amacron -50 KPX Uhungarumlaut Aogonek -50 KPX Uhungarumlaut Aring -50 KPX Uhungarumlaut Atilde -50 KPX Uhungarumlaut comma -30 KPX Uhungarumlaut period -30 KPX Umacron A -50 KPX Umacron Aacute -50 KPX Umacron Abreve -50 KPX Umacron Acircumflex -50 KPX Umacron Adieresis -50 KPX Umacron Agrave -50 KPX Umacron Amacron -50 KPX Umacron Aogonek -50 KPX Umacron Aring -50 KPX Umacron Atilde -50 KPX Umacron comma -30 KPX Umacron period -30 KPX Uogonek A -50 KPX Uogonek Aacute -50 KPX Uogonek Abreve -50 KPX Uogonek Acircumflex -50 KPX Uogonek Adieresis -50 KPX Uogonek Agrave -50 KPX Uogonek Amacron -50 KPX Uogonek Aogonek -50 KPX Uogonek Aring -50 KPX Uogonek Atilde -50 KPX Uogonek comma -30 KPX Uogonek period -30 KPX Uring A -50 KPX Uring Aacute -50 KPX Uring Abreve -50 KPX Uring Acircumflex -50 KPX Uring Adieresis -50 KPX Uring Agrave -50 KPX Uring Amacron -50 KPX Uring Aogonek -50 KPX Uring Aring -50 KPX Uring Atilde -50 KPX Uring comma -30 KPX Uring period -30 KPX V A -80 KPX V Aacute -80 KPX V Abreve -80 KPX V Acircumflex -80 KPX V Adieresis -80 KPX V Agrave -80 KPX V Amacron -80 KPX V Aogonek -80 KPX V Aring -80 KPX V Atilde -80 KPX V G -50 KPX V Gbreve -50 KPX V Gcommaaccent -50 KPX V O -50 KPX V Oacute -50 KPX V Ocircumflex -50 KPX V Odieresis -50 KPX V Ograve -50 KPX V Ohungarumlaut -50 KPX V Omacron -50 KPX V Oslash -50 KPX V Otilde -50 KPX V a -60 KPX V aacute -60 KPX V abreve -60 KPX V acircumflex -60 KPX V adieresis -60 KPX V agrave -60 KPX V amacron -60 KPX V aogonek -60 KPX V aring -60 KPX V atilde -60 KPX V colon -40 KPX V comma -120 KPX V e -50 KPX V eacute -50 KPX V ecaron -50 KPX V ecircumflex -50 KPX V edieresis -50 KPX V edotaccent -50 KPX V egrave -50 KPX V emacron -50 KPX V eogonek -50 KPX V hyphen -80 KPX V o -90 KPX V oacute -90 KPX V ocircumflex -90 KPX V odieresis -90 KPX V ograve -90 KPX V ohungarumlaut -90 KPX V omacron -90 KPX V oslash -90 KPX V otilde -90 KPX V period -120 KPX V semicolon -40 KPX V u -60 KPX V uacute -60 KPX V ucircumflex -60 KPX V udieresis -60 KPX V ugrave -60 KPX V uhungarumlaut -60 KPX V umacron -60 KPX V uogonek -60 KPX V uring -60 KPX W A -60 KPX W Aacute -60 KPX W Abreve -60 KPX W Acircumflex -60 KPX W Adieresis -60 KPX W Agrave -60 KPX W Amacron -60 KPX W Aogonek -60 KPX W Aring -60 KPX W Atilde -60 KPX W O -20 KPX W Oacute -20 KPX W Ocircumflex -20 KPX W Odieresis -20 KPX W Ograve -20 KPX W Ohungarumlaut -20 KPX W Omacron -20 KPX W Oslash -20 KPX W Otilde -20 KPX W a -40 KPX W aacute -40 KPX W abreve -40 KPX W acircumflex -40 KPX W adieresis -40 KPX W agrave -40 KPX W amacron -40 KPX W aogonek -40 KPX W aring -40 KPX W atilde -40 KPX W colon -10 KPX W comma -80 KPX W e -35 KPX W eacute -35 KPX W ecaron -35 KPX W ecircumflex -35 KPX W edieresis -35 KPX W edotaccent -35 KPX W egrave -35 KPX W emacron -35 KPX W eogonek -35 KPX W hyphen -40 KPX W o -60 KPX W oacute -60 KPX W ocircumflex -60 KPX W odieresis -60 KPX W ograve -60 KPX W ohungarumlaut -60 KPX W omacron -60 KPX W oslash -60 KPX W otilde -60 KPX W period -80 KPX W semicolon -10 KPX W u -45 KPX W uacute -45 KPX W ucircumflex -45 KPX W udieresis -45 KPX W ugrave -45 KPX W uhungarumlaut -45 KPX W umacron -45 KPX W uogonek -45 KPX W uring -45 KPX W y -20 KPX W yacute -20 KPX W ydieresis -20 KPX Y A -110 KPX Y Aacute -110 KPX Y Abreve -110 KPX Y Acircumflex -110 KPX Y Adieresis -110 KPX Y Agrave -110 KPX Y Amacron -110 KPX Y Aogonek -110 KPX Y Aring -110 KPX Y Atilde -110 KPX Y O -70 KPX Y Oacute -70 KPX Y Ocircumflex -70 KPX Y Odieresis -70 KPX Y Ograve -70 KPX Y Ohungarumlaut -70 KPX Y Omacron -70 KPX Y Oslash -70 KPX Y Otilde -70 KPX Y a -90 KPX Y aacute -90 KPX Y abreve -90 KPX Y acircumflex -90 KPX Y adieresis -90 KPX Y agrave -90 KPX Y amacron -90 KPX Y aogonek -90 KPX Y aring -90 KPX Y atilde -90 KPX Y colon -50 KPX Y comma -100 KPX Y e -80 KPX Y eacute -80 KPX Y ecaron -80 KPX Y ecircumflex -80 KPX Y edieresis -80 KPX Y edotaccent -80 KPX Y egrave -80 KPX Y emacron -80 KPX Y eogonek -80 KPX Y o -100 KPX Y oacute -100 KPX Y ocircumflex -100 KPX Y odieresis -100 KPX Y ograve -100 KPX Y ohungarumlaut -100 KPX Y omacron -100 KPX Y oslash -100 KPX Y otilde -100 KPX Y period -100 KPX Y semicolon -50 KPX Y u -100 KPX Y uacute -100 KPX Y ucircumflex -100 KPX Y udieresis -100 KPX Y ugrave -100 KPX Y uhungarumlaut -100 KPX Y umacron -100 KPX Y uogonek -100 KPX Y uring -100 KPX Yacute A -110 KPX Yacute Aacute -110 KPX Yacute Abreve -110 KPX Yacute Acircumflex -110 KPX Yacute Adieresis -110 KPX Yacute Agrave -110 KPX Yacute Amacron -110 KPX Yacute Aogonek -110 KPX Yacute Aring -110 KPX Yacute Atilde -110 KPX Yacute O -70 KPX Yacute Oacute -70 KPX Yacute Ocircumflex -70 KPX Yacute Odieresis -70 KPX Yacute Ograve -70 KPX Yacute Ohungarumlaut -70 KPX Yacute Omacron -70 KPX Yacute Oslash -70 KPX Yacute Otilde -70 KPX Yacute a -90 KPX Yacute aacute -90 KPX Yacute abreve -90 KPX Yacute acircumflex -90 KPX Yacute adieresis -90 KPX Yacute agrave -90 KPX Yacute amacron -90 KPX Yacute aogonek -90 KPX Yacute aring -90 KPX Yacute atilde -90 KPX Yacute colon -50 KPX Yacute comma -100 KPX Yacute e -80 KPX Yacute eacute -80 KPX Yacute ecaron -80 KPX Yacute ecircumflex -80 KPX Yacute edieresis -80 KPX Yacute edotaccent -80 KPX Yacute egrave -80 KPX Yacute emacron -80 KPX Yacute eogonek -80 KPX Yacute o -100 KPX Yacute oacute -100 KPX Yacute ocircumflex -100 KPX Yacute odieresis -100 KPX Yacute ograve -100 KPX Yacute ohungarumlaut -100 KPX Yacute omacron -100 KPX Yacute oslash -100 KPX Yacute otilde -100 KPX Yacute period -100 KPX Yacute semicolon -50 KPX Yacute u -100 KPX Yacute uacute -100 KPX Yacute ucircumflex -100 KPX Yacute udieresis -100 KPX Yacute ugrave -100 KPX Yacute uhungarumlaut -100 KPX Yacute umacron -100 KPX Yacute uogonek -100 KPX Yacute uring -100 KPX Ydieresis A -110 KPX Ydieresis Aacute -110 KPX Ydieresis Abreve -110 KPX Ydieresis Acircumflex -110 KPX Ydieresis Adieresis -110 KPX Ydieresis Agrave -110 KPX Ydieresis Amacron -110 KPX Ydieresis Aogonek -110 KPX Ydieresis Aring -110 KPX Ydieresis Atilde -110 KPX Ydieresis O -70 KPX Ydieresis Oacute -70 KPX Ydieresis Ocircumflex -70 KPX Ydieresis Odieresis -70 KPX Ydieresis Ograve -70 KPX Ydieresis Ohungarumlaut -70 KPX Ydieresis Omacron -70 KPX Ydieresis Oslash -70 KPX Ydieresis Otilde -70 KPX Ydieresis a -90 KPX Ydieresis aacute -90 KPX Ydieresis abreve -90 KPX Ydieresis acircumflex -90 KPX Ydieresis adieresis -90 KPX Ydieresis agrave -90 KPX Ydieresis amacron -90 KPX Ydieresis aogonek -90 KPX Ydieresis aring -90 KPX Ydieresis atilde -90 KPX Ydieresis colon -50 KPX Ydieresis comma -100 KPX Ydieresis e -80 KPX Ydieresis eacute -80 KPX Ydieresis ecaron -80 KPX Ydieresis ecircumflex -80 KPX Ydieresis edieresis -80 KPX Ydieresis edotaccent -80 KPX Ydieresis egrave -80 KPX Ydieresis emacron -80 KPX Ydieresis eogonek -80 KPX Ydieresis o -100 KPX Ydieresis oacute -100 KPX Ydieresis ocircumflex -100 KPX Ydieresis odieresis -100 KPX Ydieresis ograve -100 KPX Ydieresis ohungarumlaut -100 KPX Ydieresis omacron -100 KPX Ydieresis oslash -100 KPX Ydieresis otilde -100 KPX Ydieresis period -100 KPX Ydieresis semicolon -50 KPX Ydieresis u -100 KPX Ydieresis uacute -100 KPX Ydieresis ucircumflex -100 KPX Ydieresis udieresis -100 KPX Ydieresis ugrave -100 KPX Ydieresis uhungarumlaut -100 KPX Ydieresis umacron -100 KPX Ydieresis uogonek -100 KPX Ydieresis uring -100 KPX a g -10 KPX a gbreve -10 KPX a gcommaaccent -10 KPX a v -15 KPX a w -15 KPX a y -20 KPX a yacute -20 KPX a ydieresis -20 KPX aacute g -10 KPX aacute gbreve -10 KPX aacute gcommaaccent -10 KPX aacute v -15 KPX aacute w -15 KPX aacute y -20 KPX aacute yacute -20 KPX aacute ydieresis -20 KPX abreve g -10 KPX abreve gbreve -10 KPX abreve gcommaaccent -10 KPX abreve v -15 KPX abreve w -15 KPX abreve y -20 KPX abreve yacute -20 KPX abreve ydieresis -20 KPX acircumflex g -10 KPX acircumflex gbreve -10 KPX acircumflex gcommaaccent -10 KPX acircumflex v -15 KPX acircumflex w -15 KPX acircumflex y -20 KPX acircumflex yacute -20 KPX acircumflex ydieresis -20 KPX adieresis g -10 KPX adieresis gbreve -10 KPX adieresis gcommaaccent -10 KPX adieresis v -15 KPX adieresis w -15 KPX adieresis y -20 KPX adieresis yacute -20 KPX adieresis ydieresis -20 KPX agrave g -10 KPX agrave gbreve -10 KPX agrave gcommaaccent -10 KPX agrave v -15 KPX agrave w -15 KPX agrave y -20 KPX agrave yacute -20 KPX agrave ydieresis -20 KPX amacron g -10 KPX amacron gbreve -10 KPX amacron gcommaaccent -10 KPX amacron v -15 KPX amacron w -15 KPX amacron y -20 KPX amacron yacute -20 KPX amacron ydieresis -20 KPX aogonek g -10 KPX aogonek gbreve -10 KPX aogonek gcommaaccent -10 KPX aogonek v -15 KPX aogonek w -15 KPX aogonek y -20 KPX aogonek yacute -20 KPX aogonek ydieresis -20 KPX aring g -10 KPX aring gbreve -10 KPX aring gcommaaccent -10 KPX aring v -15 KPX aring w -15 KPX aring y -20 KPX aring yacute -20 KPX aring ydieresis -20 KPX atilde g -10 KPX atilde gbreve -10 KPX atilde gcommaaccent -10 KPX atilde v -15 KPX atilde w -15 KPX atilde y -20 KPX atilde yacute -20 KPX atilde ydieresis -20 KPX b l -10 KPX b lacute -10 KPX b lcommaaccent -10 KPX b lslash -10 KPX b u -20 KPX b uacute -20 KPX b ucircumflex -20 KPX b udieresis -20 KPX b ugrave -20 KPX b uhungarumlaut -20 KPX b umacron -20 KPX b uogonek -20 KPX b uring -20 KPX b v -20 KPX b y -20 KPX b yacute -20 KPX b ydieresis -20 KPX c h -10 KPX c k -20 KPX c kcommaaccent -20 KPX c l -20 KPX c lacute -20 KPX c lcommaaccent -20 KPX c lslash -20 KPX c y -10 KPX c yacute -10 KPX c ydieresis -10 KPX cacute h -10 KPX cacute k -20 KPX cacute kcommaaccent -20 KPX cacute l -20 KPX cacute lacute -20 KPX cacute lcommaaccent -20 KPX cacute lslash -20 KPX cacute y -10 KPX cacute yacute -10 KPX cacute ydieresis -10 KPX ccaron h -10 KPX ccaron k -20 KPX ccaron kcommaaccent -20 KPX ccaron l -20 KPX ccaron lacute -20 KPX ccaron lcommaaccent -20 KPX ccaron lslash -20 KPX ccaron y -10 KPX ccaron yacute -10 KPX ccaron ydieresis -10 KPX ccedilla h -10 KPX ccedilla k -20 KPX ccedilla kcommaaccent -20 KPX ccedilla l -20 KPX ccedilla lacute -20 KPX ccedilla lcommaaccent -20 KPX ccedilla lslash -20 KPX ccedilla y -10 KPX ccedilla yacute -10 KPX ccedilla ydieresis -10 KPX colon space -40 KPX comma quotedblright -120 KPX comma quoteright -120 KPX comma space -40 KPX d d -10 KPX d dcroat -10 KPX d v -15 KPX d w -15 KPX d y -15 KPX d yacute -15 KPX d ydieresis -15 KPX dcroat d -10 KPX dcroat dcroat -10 KPX dcroat v -15 KPX dcroat w -15 KPX dcroat y -15 KPX dcroat yacute -15 KPX dcroat ydieresis -15 KPX e comma 10 KPX e period 20 KPX e v -15 KPX e w -15 KPX e x -15 KPX e y -15 KPX e yacute -15 KPX e ydieresis -15 KPX eacute comma 10 KPX eacute period 20 KPX eacute v -15 KPX eacute w -15 KPX eacute x -15 KPX eacute y -15 KPX eacute yacute -15 KPX eacute ydieresis -15 KPX ecaron comma 10 KPX ecaron period 20 KPX ecaron v -15 KPX ecaron w -15 KPX ecaron x -15 KPX ecaron y -15 KPX ecaron yacute -15 KPX ecaron ydieresis -15 KPX ecircumflex comma 10 KPX ecircumflex period 20 KPX ecircumflex v -15 KPX ecircumflex w -15 KPX ecircumflex x -15 KPX ecircumflex y -15 KPX ecircumflex yacute -15 KPX ecircumflex ydieresis -15 KPX edieresis comma 10 KPX edieresis period 20 KPX edieresis v -15 KPX edieresis w -15 KPX edieresis x -15 KPX edieresis y -15 KPX edieresis yacute -15 KPX edieresis ydieresis -15 KPX edotaccent comma 10 KPX edotaccent period 20 KPX edotaccent v -15 KPX edotaccent w -15 KPX edotaccent x -15 KPX edotaccent y -15 KPX edotaccent yacute -15 KPX edotaccent ydieresis -15 KPX egrave comma 10 KPX egrave period 20 KPX egrave v -15 KPX egrave w -15 KPX egrave x -15 KPX egrave y -15 KPX egrave yacute -15 KPX egrave ydieresis -15 KPX emacron comma 10 KPX emacron period 20 KPX emacron v -15 KPX emacron w -15 KPX emacron x -15 KPX emacron y -15 KPX emacron yacute -15 KPX emacron ydieresis -15 KPX eogonek comma 10 KPX eogonek period 20 KPX eogonek v -15 KPX eogonek w -15 KPX eogonek x -15 KPX eogonek y -15 KPX eogonek yacute -15 KPX eogonek ydieresis -15 KPX f comma -10 KPX f e -10 KPX f eacute -10 KPX f ecaron -10 KPX f ecircumflex -10 KPX f edieresis -10 KPX f edotaccent -10 KPX f egrave -10 KPX f emacron -10 KPX f eogonek -10 KPX f o -20 KPX f oacute -20 KPX f ocircumflex -20 KPX f odieresis -20 KPX f ograve -20 KPX f ohungarumlaut -20 KPX f omacron -20 KPX f oslash -20 KPX f otilde -20 KPX f period -10 KPX f quotedblright 30 KPX f quoteright 30 KPX g e 10 KPX g eacute 10 KPX g ecaron 10 KPX g ecircumflex 10 KPX g edieresis 10 KPX g edotaccent 10 KPX g egrave 10 KPX g emacron 10 KPX g eogonek 10 KPX g g -10 KPX g gbreve -10 KPX g gcommaaccent -10 KPX gbreve e 10 KPX gbreve eacute 10 KPX gbreve ecaron 10 KPX gbreve ecircumflex 10 KPX gbreve edieresis 10 KPX gbreve edotaccent 10 KPX gbreve egrave 10 KPX gbreve emacron 10 KPX gbreve eogonek 10 KPX gbreve g -10 KPX gbreve gbreve -10 KPX gbreve gcommaaccent -10 KPX gcommaaccent e 10 KPX gcommaaccent eacute 10 KPX gcommaaccent ecaron 10 KPX gcommaaccent ecircumflex 10 KPX gcommaaccent edieresis 10 KPX gcommaaccent edotaccent 10 KPX gcommaaccent egrave 10 KPX gcommaaccent emacron 10 KPX gcommaaccent eogonek 10 KPX gcommaaccent g -10 KPX gcommaaccent gbreve -10 KPX gcommaaccent gcommaaccent -10 KPX h y -20 KPX h yacute -20 KPX h ydieresis -20 KPX k o -15 KPX k oacute -15 KPX k ocircumflex -15 KPX k odieresis -15 KPX k ograve -15 KPX k ohungarumlaut -15 KPX k omacron -15 KPX k oslash -15 KPX k otilde -15 KPX kcommaaccent o -15 KPX kcommaaccent oacute -15 KPX kcommaaccent ocircumflex -15 KPX kcommaaccent odieresis -15 KPX kcommaaccent ograve -15 KPX kcommaaccent ohungarumlaut -15 KPX kcommaaccent omacron -15 KPX kcommaaccent oslash -15 KPX kcommaaccent otilde -15 KPX l w -15 KPX l y -15 KPX l yacute -15 KPX l ydieresis -15 KPX lacute w -15 KPX lacute y -15 KPX lacute yacute -15 KPX lacute ydieresis -15 KPX lcommaaccent w -15 KPX lcommaaccent y -15 KPX lcommaaccent yacute -15 KPX lcommaaccent ydieresis -15 KPX lslash w -15 KPX lslash y -15 KPX lslash yacute -15 KPX lslash ydieresis -15 KPX m u -20 KPX m uacute -20 KPX m ucircumflex -20 KPX m udieresis -20 KPX m ugrave -20 KPX m uhungarumlaut -20 KPX m umacron -20 KPX m uogonek -20 KPX m uring -20 KPX m y -30 KPX m yacute -30 KPX m ydieresis -30 KPX n u -10 KPX n uacute -10 KPX n ucircumflex -10 KPX n udieresis -10 KPX n ugrave -10 KPX n uhungarumlaut -10 KPX n umacron -10 KPX n uogonek -10 KPX n uring -10 KPX n v -40 KPX n y -20 KPX n yacute -20 KPX n ydieresis -20 KPX nacute u -10 KPX nacute uacute -10 KPX nacute ucircumflex -10 KPX nacute udieresis -10 KPX nacute ugrave -10 KPX nacute uhungarumlaut -10 KPX nacute umacron -10 KPX nacute uogonek -10 KPX nacute uring -10 KPX nacute v -40 KPX nacute y -20 KPX nacute yacute -20 KPX nacute ydieresis -20 KPX ncaron u -10 KPX ncaron uacute -10 KPX ncaron ucircumflex -10 KPX ncaron udieresis -10 KPX ncaron ugrave -10 KPX ncaron uhungarumlaut -10 KPX ncaron umacron -10 KPX ncaron uogonek -10 KPX ncaron uring -10 KPX ncaron v -40 KPX ncaron y -20 KPX ncaron yacute -20 KPX ncaron ydieresis -20 KPX ncommaaccent u -10 KPX ncommaaccent uacute -10 KPX ncommaaccent ucircumflex -10 KPX ncommaaccent udieresis -10 KPX ncommaaccent ugrave -10 KPX ncommaaccent uhungarumlaut -10 KPX ncommaaccent umacron -10 KPX ncommaaccent uogonek -10 KPX ncommaaccent uring -10 KPX ncommaaccent v -40 KPX ncommaaccent y -20 KPX ncommaaccent yacute -20 KPX ncommaaccent ydieresis -20 KPX ntilde u -10 KPX ntilde uacute -10 KPX ntilde ucircumflex -10 KPX ntilde udieresis -10 KPX ntilde ugrave -10 KPX ntilde uhungarumlaut -10 KPX ntilde umacron -10 KPX ntilde uogonek -10 KPX ntilde uring -10 KPX ntilde v -40 KPX ntilde y -20 KPX ntilde yacute -20 KPX ntilde ydieresis -20 KPX o v -20 KPX o w -15 KPX o x -30 KPX o y -20 KPX o yacute -20 KPX o ydieresis -20 KPX oacute v -20 KPX oacute w -15 KPX oacute x -30 KPX oacute y -20 KPX oacute yacute -20 KPX oacute ydieresis -20 KPX ocircumflex v -20 KPX ocircumflex w -15 KPX ocircumflex x -30 KPX ocircumflex y -20 KPX ocircumflex yacute -20 KPX ocircumflex ydieresis -20 KPX odieresis v -20 KPX odieresis w -15 KPX odieresis x -30 KPX odieresis y -20 KPX odieresis yacute -20 KPX odieresis ydieresis -20 KPX ograve v -20 KPX ograve w -15 KPX ograve x -30 KPX ograve y -20 KPX ograve yacute -20 KPX ograve ydieresis -20 KPX ohungarumlaut v -20 KPX ohungarumlaut w -15 KPX ohungarumlaut x -30 KPX ohungarumlaut y -20 KPX ohungarumlaut yacute -20 KPX ohungarumlaut ydieresis -20 KPX omacron v -20 KPX omacron w -15 KPX omacron x -30 KPX omacron y -20 KPX omacron yacute -20 KPX omacron ydieresis -20 KPX oslash v -20 KPX oslash w -15 KPX oslash x -30 KPX oslash y -20 KPX oslash yacute -20 KPX oslash ydieresis -20 KPX otilde v -20 KPX otilde w -15 KPX otilde x -30 KPX otilde y -20 KPX otilde yacute -20 KPX otilde ydieresis -20 KPX p y -15 KPX p yacute -15 KPX p ydieresis -15 KPX period quotedblright -120 KPX period quoteright -120 KPX period space -40 KPX quotedblright space -80 KPX quoteleft quoteleft -46 KPX quoteright d -80 KPX quoteright dcroat -80 KPX quoteright l -20 KPX quoteright lacute -20 KPX quoteright lcommaaccent -20 KPX quoteright lslash -20 KPX quoteright quoteright -46 KPX quoteright r -40 KPX quoteright racute -40 KPX quoteright rcaron -40 KPX quoteright rcommaaccent -40 KPX quoteright s -60 KPX quoteright sacute -60 KPX quoteright scaron -60 KPX quoteright scedilla -60 KPX quoteright scommaaccent -60 KPX quoteright space -80 KPX quoteright v -20 KPX r c -20 KPX r cacute -20 KPX r ccaron -20 KPX r ccedilla -20 KPX r comma -60 KPX r d -20 KPX r dcroat -20 KPX r g -15 KPX r gbreve -15 KPX r gcommaaccent -15 KPX r hyphen -20 KPX r o -20 KPX r oacute -20 KPX r ocircumflex -20 KPX r odieresis -20 KPX r ograve -20 KPX r ohungarumlaut -20 KPX r omacron -20 KPX r oslash -20 KPX r otilde -20 KPX r period -60 KPX r q -20 KPX r s -15 KPX r sacute -15 KPX r scaron -15 KPX r scedilla -15 KPX r scommaaccent -15 KPX r t 20 KPX r tcommaaccent 20 KPX r v 10 KPX r y 10 KPX r yacute 10 KPX r ydieresis 10 KPX racute c -20 KPX racute cacute -20 KPX racute ccaron -20 KPX racute ccedilla -20 KPX racute comma -60 KPX racute d -20 KPX racute dcroat -20 KPX racute g -15 KPX racute gbreve -15 KPX racute gcommaaccent -15 KPX racute hyphen -20 KPX racute o -20 KPX racute oacute -20 KPX racute ocircumflex -20 KPX racute odieresis -20 KPX racute ograve -20 KPX racute ohungarumlaut -20 KPX racute omacron -20 KPX racute oslash -20 KPX racute otilde -20 KPX racute period -60 KPX racute q -20 KPX racute s -15 KPX racute sacute -15 KPX racute scaron -15 KPX racute scedilla -15 KPX racute scommaaccent -15 KPX racute t 20 KPX racute tcommaaccent 20 KPX racute v 10 KPX racute y 10 KPX racute yacute 10 KPX racute ydieresis 10 KPX rcaron c -20 KPX rcaron cacute -20 KPX rcaron ccaron -20 KPX rcaron ccedilla -20 KPX rcaron comma -60 KPX rcaron d -20 KPX rcaron dcroat -20 KPX rcaron g -15 KPX rcaron gbreve -15 KPX rcaron gcommaaccent -15 KPX rcaron hyphen -20 KPX rcaron o -20 KPX rcaron oacute -20 KPX rcaron ocircumflex -20 KPX rcaron odieresis -20 KPX rcaron ograve -20 KPX rcaron ohungarumlaut -20 KPX rcaron omacron -20 KPX rcaron oslash -20 KPX rcaron otilde -20 KPX rcaron period -60 KPX rcaron q -20 KPX rcaron s -15 KPX rcaron sacute -15 KPX rcaron scaron -15 KPX rcaron scedilla -15 KPX rcaron scommaaccent -15 KPX rcaron t 20 KPX rcaron tcommaaccent 20 KPX rcaron v 10 KPX rcaron y 10 KPX rcaron yacute 10 KPX rcaron ydieresis 10 KPX rcommaaccent c -20 KPX rcommaaccent cacute -20 KPX rcommaaccent ccaron -20 KPX rcommaaccent ccedilla -20 KPX rcommaaccent comma -60 KPX rcommaaccent d -20 KPX rcommaaccent dcroat -20 KPX rcommaaccent g -15 KPX rcommaaccent gbreve -15 KPX rcommaaccent gcommaaccent -15 KPX rcommaaccent hyphen -20 KPX rcommaaccent o -20 KPX rcommaaccent oacute -20 KPX rcommaaccent ocircumflex -20 KPX rcommaaccent odieresis -20 KPX rcommaaccent ograve -20 KPX rcommaaccent ohungarumlaut -20 KPX rcommaaccent omacron -20 KPX rcommaaccent oslash -20 KPX rcommaaccent otilde -20 KPX rcommaaccent period -60 KPX rcommaaccent q -20 KPX rcommaaccent s -15 KPX rcommaaccent sacute -15 KPX rcommaaccent scaron -15 KPX rcommaaccent scedilla -15 KPX rcommaaccent scommaaccent -15 KPX rcommaaccent t 20 KPX rcommaaccent tcommaaccent 20 KPX rcommaaccent v 10 KPX rcommaaccent y 10 KPX rcommaaccent yacute 10 KPX rcommaaccent ydieresis 10 KPX s w -15 KPX sacute w -15 KPX scaron w -15 KPX scedilla w -15 KPX scommaaccent w -15 KPX semicolon space -40 KPX space T -100 KPX space Tcaron -100 KPX space Tcommaaccent -100 KPX space V -80 KPX space W -80 KPX space Y -120 KPX space Yacute -120 KPX space Ydieresis -120 KPX space quotedblleft -80 KPX space quoteleft -60 KPX v a -20 KPX v aacute -20 KPX v abreve -20 KPX v acircumflex -20 KPX v adieresis -20 KPX v agrave -20 KPX v amacron -20 KPX v aogonek -20 KPX v aring -20 KPX v atilde -20 KPX v comma -80 KPX v o -30 KPX v oacute -30 KPX v ocircumflex -30 KPX v odieresis -30 KPX v ograve -30 KPX v ohungarumlaut -30 KPX v omacron -30 KPX v oslash -30 KPX v otilde -30 KPX v period -80 KPX w comma -40 KPX w o -20 KPX w oacute -20 KPX w ocircumflex -20 KPX w odieresis -20 KPX w ograve -20 KPX w ohungarumlaut -20 KPX w omacron -20 KPX w oslash -20 KPX w otilde -20 KPX w period -40 KPX x e -10 KPX x eacute -10 KPX x ecaron -10 KPX x ecircumflex -10 KPX x edieresis -10 KPX x edotaccent -10 KPX x egrave -10 KPX x emacron -10 KPX x eogonek -10 KPX y a -30 KPX y aacute -30 KPX y abreve -30 KPX y acircumflex -30 KPX y adieresis -30 KPX y agrave -30 KPX y amacron -30 KPX y aogonek -30 KPX y aring -30 KPX y atilde -30 KPX y comma -80 KPX y e -10 KPX y eacute -10 KPX y ecaron -10 KPX y ecircumflex -10 KPX y edieresis -10 KPX y edotaccent -10 KPX y egrave -10 KPX y emacron -10 KPX y eogonek -10 KPX y o -25 KPX y oacute -25 KPX y ocircumflex -25 KPX y odieresis -25 KPX y ograve -25 KPX y ohungarumlaut -25 KPX y omacron -25 KPX y oslash -25 KPX y otilde -25 KPX y period -80 KPX yacute a -30 KPX yacute aacute -30 KPX yacute abreve -30 KPX yacute acircumflex -30 KPX yacute adieresis -30 KPX yacute agrave -30 KPX yacute amacron -30 KPX yacute aogonek -30 KPX yacute aring -30 KPX yacute atilde -30 KPX yacute comma -80 KPX yacute e -10 KPX yacute eacute -10 KPX yacute ecaron -10 KPX yacute ecircumflex -10 KPX yacute edieresis -10 KPX yacute edotaccent -10 KPX yacute egrave -10 KPX yacute emacron -10 KPX yacute eogonek -10 KPX yacute o -25 KPX yacute oacute -25 KPX yacute ocircumflex -25 KPX yacute odieresis -25 KPX yacute ograve -25 KPX yacute ohungarumlaut -25 KPX yacute omacron -25 KPX yacute oslash -25 KPX yacute otilde -25 KPX yacute period -80 KPX ydieresis a -30 KPX ydieresis aacute -30 KPX ydieresis abreve -30 KPX ydieresis acircumflex -30 KPX ydieresis adieresis -30 KPX ydieresis agrave -30 KPX ydieresis amacron -30 KPX ydieresis aogonek -30 KPX ydieresis aring -30 KPX ydieresis atilde -30 KPX ydieresis comma -80 KPX ydieresis e -10 KPX ydieresis eacute -10 KPX ydieresis ecaron -10 KPX ydieresis ecircumflex -10 KPX ydieresis edieresis -10 KPX ydieresis edotaccent -10 KPX ydieresis egrave -10 KPX ydieresis emacron -10 KPX ydieresis eogonek -10 KPX ydieresis o -25 KPX ydieresis oacute -25 KPX ydieresis ocircumflex -25 KPX ydieresis odieresis -25 KPX ydieresis ograve -25 KPX ydieresis ohungarumlaut -25 KPX ydieresis omacron -25 KPX ydieresis oslash -25 KPX ydieresis otilde -25 KPX ydieresis period -80 KPX z e 10 KPX z eacute 10 KPX z ecaron 10 KPX z ecircumflex 10 KPX z edieresis 10 KPX z edotaccent 10 KPX z egrave 10 KPX z emacron 10 KPX z eogonek 10 KPX zacute e 10 KPX zacute eacute 10 KPX zacute ecaron 10 KPX zacute ecircumflex 10 KPX zacute edieresis 10 KPX zacute edotaccent 10 KPX zacute egrave 10 KPX zacute emacron 10 KPX zacute eogonek 10 KPX zcaron e 10 KPX zcaron eacute 10 KPX zcaron ecaron 10 KPX zcaron ecircumflex 10 KPX zcaron edieresis 10 KPX zcaron edotaccent 10 KPX zcaron egrave 10 KPX zcaron emacron 10 KPX zcaron eogonek 10 KPX zdotaccent e 10 KPX zdotaccent eacute 10 KPX zdotaccent ecaron 10 KPX zdotaccent ecircumflex 10 KPX zdotaccent edieresis 10 KPX zdotaccent edotaccent 10 KPX zdotaccent egrave 10 KPX zdotaccent emacron 10 KPX zdotaccent eogonek 10 EndKernPairs EndKernData EndFontMetrics dompdf/lib/fonts/DejaVuSerif-Bold.ufm 0000644 00000412213 15024772104 0013457 0 ustar 00 StartFontMetrics 4.1 Notice Converted by PHP-font-lib Comment https://github.com/PhenX/php-font-lib EncodingScheme FontSpecific FontName DejaVu Serif FontSubfamily Bold UniqueID DejaVu Serif Bold FullName DejaVu Serif Bold Version Version 2.37 PostScriptName DejaVuSerif-Bold Manufacturer DejaVu fonts team FontVendorURL http://dejavu.sourceforge.net LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License PreferredFamily DejaVu Serif PreferredSubfamily Bold Weight Bold ItalicAngle 0 IsFixedPitch false UnderlineThickness 44 UnderlinePosition -63 FontHeightOffset 0 Ascender 939 Descender -236 FontBBox -836 -389 1854 1145 StartCharMetrics 3506 U 32 ; WX 348 ; N space ; G 3 U 33 ; WX 439 ; N exclam ; G 4 U 34 ; WX 521 ; N quotedbl ; G 5 U 35 ; WX 838 ; N numbersign ; G 6 U 36 ; WX 696 ; N dollar ; G 7 U 37 ; WX 950 ; N percent ; G 8 U 38 ; WX 903 ; N ampersand ; G 9 U 39 ; WX 306 ; N quotesingle ; G 10 U 40 ; WX 473 ; N parenleft ; G 11 U 41 ; WX 473 ; N parenright ; G 12 U 42 ; WX 523 ; N asterisk ; G 13 U 43 ; WX 838 ; N plus ; G 14 U 44 ; WX 348 ; N comma ; G 15 U 45 ; WX 415 ; N hyphen ; G 16 U 46 ; WX 348 ; N period ; G 17 U 47 ; WX 365 ; N slash ; G 18 U 48 ; WX 696 ; N zero ; G 19 U 49 ; WX 696 ; N one ; G 20 U 50 ; WX 696 ; N two ; G 21 U 51 ; WX 696 ; N three ; G 22 U 52 ; WX 696 ; N four ; G 23 U 53 ; WX 696 ; N five ; G 24 U 54 ; WX 696 ; N six ; G 25 U 55 ; WX 696 ; N seven ; G 26 U 56 ; WX 696 ; N eight ; G 27 U 57 ; WX 696 ; N nine ; G 28 U 58 ; WX 369 ; N colon ; G 29 U 59 ; WX 369 ; N semicolon ; G 30 U 60 ; WX 838 ; N less ; G 31 U 61 ; WX 838 ; N equal ; G 32 U 62 ; WX 838 ; N greater ; G 33 U 63 ; WX 586 ; N question ; G 34 U 64 ; WX 1000 ; N at ; G 35 U 65 ; WX 776 ; N A ; G 36 U 66 ; WX 845 ; N B ; G 37 U 67 ; WX 796 ; N C ; G 38 U 68 ; WX 867 ; N D ; G 39 U 69 ; WX 762 ; N E ; G 40 U 70 ; WX 710 ; N F ; G 41 U 71 ; WX 854 ; N G ; G 42 U 72 ; WX 945 ; N H ; G 43 U 73 ; WX 468 ; N I ; G 44 U 74 ; WX 473 ; N J ; G 45 U 75 ; WX 869 ; N K ; G 46 U 76 ; WX 703 ; N L ; G 47 U 77 ; WX 1107 ; N M ; G 48 U 78 ; WX 914 ; N N ; G 49 U 79 ; WX 871 ; N O ; G 50 U 80 ; WX 752 ; N P ; G 51 U 81 ; WX 871 ; N Q ; G 52 U 82 ; WX 831 ; N R ; G 53 U 83 ; WX 722 ; N S ; G 54 U 84 ; WX 744 ; N T ; G 55 U 85 ; WX 872 ; N U ; G 56 U 86 ; WX 776 ; N V ; G 57 U 87 ; WX 1123 ; N W ; G 58 U 88 ; WX 776 ; N X ; G 59 U 89 ; WX 714 ; N Y ; G 60 U 90 ; WX 730 ; N Z ; G 61 U 91 ; WX 473 ; N bracketleft ; G 62 U 92 ; WX 365 ; N backslash ; G 63 U 93 ; WX 473 ; N bracketright ; G 64 U 94 ; WX 838 ; N asciicircum ; G 65 U 95 ; WX 500 ; N underscore ; G 66 U 96 ; WX 500 ; N grave ; G 67 U 97 ; WX 648 ; N a ; G 68 U 98 ; WX 699 ; N b ; G 69 U 99 ; WX 609 ; N c ; G 70 U 100 ; WX 699 ; N d ; G 71 U 101 ; WX 636 ; N e ; G 72 U 102 ; WX 430 ; N f ; G 73 U 103 ; WX 699 ; N g ; G 74 U 104 ; WX 727 ; N h ; G 75 U 105 ; WX 380 ; N i ; G 76 U 106 ; WX 362 ; N j ; G 77 U 107 ; WX 693 ; N k ; G 78 U 108 ; WX 380 ; N l ; G 79 U 109 ; WX 1058 ; N m ; G 80 U 110 ; WX 727 ; N n ; G 81 U 111 ; WX 667 ; N o ; G 82 U 112 ; WX 699 ; N p ; G 83 U 113 ; WX 699 ; N q ; G 84 U 114 ; WX 527 ; N r ; G 85 U 115 ; WX 563 ; N s ; G 86 U 116 ; WX 462 ; N t ; G 87 U 117 ; WX 727 ; N u ; G 88 U 118 ; WX 581 ; N v ; G 89 U 119 ; WX 861 ; N w ; G 90 U 120 ; WX 596 ; N x ; G 91 U 121 ; WX 581 ; N y ; G 92 U 122 ; WX 568 ; N z ; G 93 U 123 ; WX 643 ; N braceleft ; G 94 U 124 ; WX 364 ; N bar ; G 95 U 125 ; WX 643 ; N braceright ; G 96 U 126 ; WX 838 ; N asciitilde ; G 97 U 160 ; WX 348 ; N nbspace ; G 98 U 161 ; WX 439 ; N exclamdown ; G 99 U 162 ; WX 696 ; N cent ; G 100 U 163 ; WX 696 ; N sterling ; G 101 U 164 ; WX 636 ; N currency ; G 102 U 165 ; WX 696 ; N yen ; G 103 U 166 ; WX 364 ; N brokenbar ; G 104 U 167 ; WX 523 ; N section ; G 105 U 168 ; WX 500 ; N dieresis ; G 106 U 169 ; WX 1000 ; N copyright ; G 107 U 170 ; WX 487 ; N ordfeminine ; G 108 U 171 ; WX 625 ; N guillemotleft ; G 109 U 172 ; WX 838 ; N logicalnot ; G 110 U 173 ; WX 415 ; N sfthyphen ; G 111 U 174 ; WX 1000 ; N registered ; G 112 U 175 ; WX 500 ; N macron ; G 113 U 176 ; WX 500 ; N degree ; G 114 U 177 ; WX 838 ; N plusminus ; G 115 U 178 ; WX 438 ; N twosuperior ; G 116 U 179 ; WX 438 ; N threesuperior ; G 117 U 180 ; WX 500 ; N acute ; G 118 U 181 ; WX 732 ; N mu ; G 119 U 182 ; WX 636 ; N paragraph ; G 120 U 183 ; WX 348 ; N periodcentered ; G 121 U 184 ; WX 500 ; N cedilla ; G 122 U 185 ; WX 438 ; N onesuperior ; G 123 U 186 ; WX 500 ; N ordmasculine ; G 124 U 187 ; WX 625 ; N guillemotright ; G 125 U 188 ; WX 1043 ; N onequarter ; G 126 U 189 ; WX 1043 ; N onehalf ; G 127 U 190 ; WX 1043 ; N threequarters ; G 128 U 191 ; WX 586 ; N questiondown ; G 129 U 192 ; WX 776 ; N Agrave ; G 130 U 193 ; WX 776 ; N Aacute ; G 131 U 194 ; WX 776 ; N Acircumflex ; G 132 U 195 ; WX 776 ; N Atilde ; G 133 U 196 ; WX 776 ; N Adieresis ; G 134 U 197 ; WX 776 ; N Aring ; G 135 U 198 ; WX 1034 ; N AE ; G 136 U 199 ; WX 796 ; N Ccedilla ; G 137 U 200 ; WX 762 ; N Egrave ; G 138 U 201 ; WX 762 ; N Eacute ; G 139 U 202 ; WX 762 ; N Ecircumflex ; G 140 U 203 ; WX 762 ; N Edieresis ; G 141 U 204 ; WX 468 ; N Igrave ; G 142 U 205 ; WX 468 ; N Iacute ; G 143 U 206 ; WX 468 ; N Icircumflex ; G 144 U 207 ; WX 468 ; N Idieresis ; G 145 U 208 ; WX 874 ; N Eth ; G 146 U 209 ; WX 914 ; N Ntilde ; G 147 U 210 ; WX 871 ; N Ograve ; G 148 U 211 ; WX 871 ; N Oacute ; G 149 U 212 ; WX 871 ; N Ocircumflex ; G 150 U 213 ; WX 871 ; N Otilde ; G 151 U 214 ; WX 871 ; N Odieresis ; G 152 U 215 ; WX 838 ; N multiply ; G 153 U 216 ; WX 871 ; N Oslash ; G 154 U 217 ; WX 872 ; N Ugrave ; G 155 U 218 ; WX 872 ; N Uacute ; G 156 U 219 ; WX 872 ; N Ucircumflex ; G 157 U 220 ; WX 872 ; N Udieresis ; G 158 U 221 ; WX 714 ; N Yacute ; G 159 U 222 ; WX 757 ; N Thorn ; G 160 U 223 ; WX 760 ; N germandbls ; G 161 U 224 ; WX 648 ; N agrave ; G 162 U 225 ; WX 648 ; N aacute ; G 163 U 226 ; WX 648 ; N acircumflex ; G 164 U 227 ; WX 648 ; N atilde ; G 165 U 228 ; WX 648 ; N adieresis ; G 166 U 229 ; WX 648 ; N aring ; G 167 U 230 ; WX 975 ; N ae ; G 168 U 231 ; WX 609 ; N ccedilla ; G 169 U 232 ; WX 636 ; N egrave ; G 170 U 233 ; WX 636 ; N eacute ; G 171 U 234 ; WX 636 ; N ecircumflex ; G 172 U 235 ; WX 636 ; N edieresis ; G 173 U 236 ; WX 380 ; N igrave ; G 174 U 237 ; WX 380 ; N iacute ; G 175 U 238 ; WX 380 ; N icircumflex ; G 176 U 239 ; WX 380 ; N idieresis ; G 177 U 240 ; WX 667 ; N eth ; G 178 U 241 ; WX 727 ; N ntilde ; G 179 U 242 ; WX 667 ; N ograve ; G 180 U 243 ; WX 667 ; N oacute ; G 181 U 244 ; WX 667 ; N ocircumflex ; G 182 U 245 ; WX 667 ; N otilde ; G 183 U 246 ; WX 667 ; N odieresis ; G 184 U 247 ; WX 838 ; N divide ; G 185 U 248 ; WX 667 ; N oslash ; G 186 U 249 ; WX 727 ; N ugrave ; G 187 U 250 ; WX 727 ; N uacute ; G 188 U 251 ; WX 727 ; N ucircumflex ; G 189 U 252 ; WX 727 ; N udieresis ; G 190 U 253 ; WX 581 ; N yacute ; G 191 U 254 ; WX 699 ; N thorn ; G 192 U 255 ; WX 581 ; N ydieresis ; G 193 U 256 ; WX 776 ; N Amacron ; G 194 U 257 ; WX 648 ; N amacron ; G 195 U 258 ; WX 776 ; N Abreve ; G 196 U 259 ; WX 648 ; N abreve ; G 197 U 260 ; WX 776 ; N Aogonek ; G 198 U 261 ; WX 648 ; N aogonek ; G 199 U 262 ; WX 796 ; N Cacute ; G 200 U 263 ; WX 609 ; N cacute ; G 201 U 264 ; WX 796 ; N Ccircumflex ; G 202 U 265 ; WX 609 ; N ccircumflex ; G 203 U 266 ; WX 796 ; N Cdotaccent ; G 204 U 267 ; WX 609 ; N cdotaccent ; G 205 U 268 ; WX 796 ; N Ccaron ; G 206 U 269 ; WX 609 ; N ccaron ; G 207 U 270 ; WX 867 ; N Dcaron ; G 208 U 271 ; WX 699 ; N dcaron ; G 209 U 272 ; WX 874 ; N Dcroat ; G 210 U 273 ; WX 699 ; N dmacron ; G 211 U 274 ; WX 762 ; N Emacron ; G 212 U 275 ; WX 636 ; N emacron ; G 213 U 276 ; WX 762 ; N Ebreve ; G 214 U 277 ; WX 636 ; N ebreve ; G 215 U 278 ; WX 762 ; N Edotaccent ; G 216 U 279 ; WX 636 ; N edotaccent ; G 217 U 280 ; WX 762 ; N Eogonek ; G 218 U 281 ; WX 636 ; N eogonek ; G 219 U 282 ; WX 762 ; N Ecaron ; G 220 U 283 ; WX 636 ; N ecaron ; G 221 U 284 ; WX 854 ; N Gcircumflex ; G 222 U 285 ; WX 699 ; N gcircumflex ; G 223 U 286 ; WX 854 ; N Gbreve ; G 224 U 287 ; WX 699 ; N gbreve ; G 225 U 288 ; WX 854 ; N Gdotaccent ; G 226 U 289 ; WX 699 ; N gdotaccent ; G 227 U 290 ; WX 854 ; N Gcommaaccent ; G 228 U 291 ; WX 699 ; N gcommaaccent ; G 229 U 292 ; WX 945 ; N Hcircumflex ; G 230 U 293 ; WX 727 ; N hcircumflex ; G 231 U 294 ; WX 945 ; N Hbar ; G 232 U 295 ; WX 727 ; N hbar ; G 233 U 296 ; WX 468 ; N Itilde ; G 234 U 297 ; WX 380 ; N itilde ; G 235 U 298 ; WX 468 ; N Imacron ; G 236 U 299 ; WX 380 ; N imacron ; G 237 U 300 ; WX 468 ; N Ibreve ; G 238 U 301 ; WX 380 ; N ibreve ; G 239 U 302 ; WX 468 ; N Iogonek ; G 240 U 303 ; WX 380 ; N iogonek ; G 241 U 304 ; WX 468 ; N Idot ; G 242 U 305 ; WX 380 ; N dotlessi ; G 243 U 306 ; WX 942 ; N IJ ; G 244 U 307 ; WX 751 ; N ij ; G 245 U 308 ; WX 473 ; N Jcircumflex ; G 246 U 309 ; WX 362 ; N jcircumflex ; G 247 U 310 ; WX 869 ; N Kcommaaccent ; G 248 U 311 ; WX 693 ; N kcommaaccent ; G 249 U 312 ; WX 693 ; N kgreenlandic ; G 250 U 313 ; WX 703 ; N Lacute ; G 251 U 314 ; WX 380 ; N lacute ; G 252 U 315 ; WX 703 ; N Lcommaaccent ; G 253 U 316 ; WX 380 ; N lcommaaccent ; G 254 U 317 ; WX 703 ; N Lcaron ; G 255 U 318 ; WX 380 ; N lcaron ; G 256 U 319 ; WX 703 ; N Ldot ; G 257 U 320 ; WX 380 ; N ldot ; G 258 U 321 ; WX 710 ; N Lslash ; G 259 U 322 ; WX 385 ; N lslash ; G 260 U 323 ; WX 914 ; N Nacute ; G 261 U 324 ; WX 727 ; N nacute ; G 262 U 325 ; WX 914 ; N Ncommaaccent ; G 263 U 326 ; WX 727 ; N ncommaaccent ; G 264 U 327 ; WX 914 ; N Ncaron ; G 265 U 328 ; WX 727 ; N ncaron ; G 266 U 329 ; WX 1008 ; N napostrophe ; G 267 U 330 ; WX 872 ; N Eng ; G 268 U 331 ; WX 727 ; N eng ; G 269 U 332 ; WX 871 ; N Omacron ; G 270 U 333 ; WX 667 ; N omacron ; G 271 U 334 ; WX 871 ; N Obreve ; G 272 U 335 ; WX 667 ; N obreve ; G 273 U 336 ; WX 871 ; N Ohungarumlaut ; G 274 U 337 ; WX 667 ; N ohungarumlaut ; G 275 U 338 ; WX 1180 ; N OE ; G 276 U 339 ; WX 1028 ; N oe ; G 277 U 340 ; WX 831 ; N Racute ; G 278 U 341 ; WX 527 ; N racute ; G 279 U 342 ; WX 831 ; N Rcommaaccent ; G 280 U 343 ; WX 527 ; N rcommaaccent ; G 281 U 344 ; WX 831 ; N Rcaron ; G 282 U 345 ; WX 527 ; N rcaron ; G 283 U 346 ; WX 722 ; N Sacute ; G 284 U 347 ; WX 563 ; N sacute ; G 285 U 348 ; WX 722 ; N Scircumflex ; G 286 U 349 ; WX 563 ; N scircumflex ; G 287 U 350 ; WX 722 ; N Scedilla ; G 288 U 351 ; WX 563 ; N scedilla ; G 289 U 352 ; WX 722 ; N Scaron ; G 290 U 353 ; WX 563 ; N scaron ; G 291 U 354 ; WX 744 ; N Tcommaaccent ; G 292 U 355 ; WX 462 ; N tcommaaccent ; G 293 U 356 ; WX 744 ; N Tcaron ; G 294 U 357 ; WX 462 ; N tcaron ; G 295 U 358 ; WX 744 ; N Tbar ; G 296 U 359 ; WX 462 ; N tbar ; G 297 U 360 ; WX 872 ; N Utilde ; G 298 U 361 ; WX 727 ; N utilde ; G 299 U 362 ; WX 872 ; N Umacron ; G 300 U 363 ; WX 727 ; N umacron ; G 301 U 364 ; WX 872 ; N Ubreve ; G 302 U 365 ; WX 727 ; N ubreve ; G 303 U 366 ; WX 872 ; N Uring ; G 304 U 367 ; WX 727 ; N uring ; G 305 U 368 ; WX 872 ; N Uhungarumlaut ; G 306 U 369 ; WX 727 ; N uhungarumlaut ; G 307 U 370 ; WX 872 ; N Uogonek ; G 308 U 371 ; WX 727 ; N uogonek ; G 309 U 372 ; WX 1123 ; N Wcircumflex ; G 310 U 373 ; WX 861 ; N wcircumflex ; G 311 U 374 ; WX 714 ; N Ycircumflex ; G 312 U 375 ; WX 581 ; N ycircumflex ; G 313 U 376 ; WX 714 ; N Ydieresis ; G 314 U 377 ; WX 730 ; N Zacute ; G 315 U 378 ; WX 568 ; N zacute ; G 316 U 379 ; WX 730 ; N Zdotaccent ; G 317 U 380 ; WX 568 ; N zdotaccent ; G 318 U 381 ; WX 730 ; N Zcaron ; G 319 U 382 ; WX 568 ; N zcaron ; G 320 U 383 ; WX 430 ; N longs ; G 321 U 384 ; WX 699 ; N uni0180 ; G 322 U 385 ; WX 845 ; N uni0181 ; G 323 U 386 ; WX 854 ; N uni0182 ; G 324 U 387 ; WX 699 ; N uni0183 ; G 325 U 388 ; WX 854 ; N uni0184 ; G 326 U 389 ; WX 699 ; N uni0185 ; G 327 U 390 ; WX 796 ; N uni0186 ; G 328 U 391 ; WX 796 ; N uni0187 ; G 329 U 392 ; WX 609 ; N uni0188 ; G 330 U 393 ; WX 874 ; N uni0189 ; G 331 U 394 ; WX 867 ; N uni018A ; G 332 U 395 ; WX 854 ; N uni018B ; G 333 U 396 ; WX 699 ; N uni018C ; G 334 U 397 ; WX 667 ; N uni018D ; G 335 U 398 ; WX 762 ; N uni018E ; G 336 U 399 ; WX 871 ; N uni018F ; G 337 U 400 ; WX 721 ; N uni0190 ; G 338 U 401 ; WX 710 ; N uni0191 ; G 339 U 402 ; WX 430 ; N florin ; G 340 U 403 ; WX 854 ; N uni0193 ; G 341 U 404 ; WX 771 ; N uni0194 ; G 342 U 405 ; WX 1043 ; N uni0195 ; G 343 U 406 ; WX 468 ; N uni0196 ; G 344 U 407 ; WX 468 ; N uni0197 ; G 345 U 408 ; WX 869 ; N uni0198 ; G 346 U 409 ; WX 693 ; N uni0199 ; G 347 U 410 ; WX 380 ; N uni019A ; G 348 U 411 ; WX 701 ; N uni019B ; G 349 U 412 ; WX 1058 ; N uni019C ; G 350 U 413 ; WX 914 ; N uni019D ; G 351 U 414 ; WX 727 ; N uni019E ; G 352 U 415 ; WX 871 ; N uni019F ; G 353 U 416 ; WX 871 ; N Ohorn ; G 354 U 417 ; WX 667 ; N ohorn ; G 355 U 418 ; WX 1200 ; N uni01A2 ; G 356 U 419 ; WX 943 ; N uni01A3 ; G 357 U 420 ; WX 752 ; N uni01A4 ; G 358 U 421 ; WX 699 ; N uni01A5 ; G 359 U 422 ; WX 831 ; N uni01A6 ; G 360 U 423 ; WX 722 ; N uni01A7 ; G 361 U 424 ; WX 563 ; N uni01A8 ; G 362 U 425 ; WX 707 ; N uni01A9 ; G 363 U 426 ; WX 331 ; N uni01AA ; G 364 U 427 ; WX 462 ; N uni01AB ; G 365 U 428 ; WX 744 ; N uni01AC ; G 366 U 429 ; WX 462 ; N uni01AD ; G 367 U 430 ; WX 744 ; N uni01AE ; G 368 U 431 ; WX 872 ; N Uhorn ; G 369 U 432 ; WX 727 ; N uhorn ; G 370 U 433 ; WX 890 ; N uni01B1 ; G 371 U 434 ; WX 890 ; N uni01B2 ; G 372 U 435 ; WX 714 ; N uni01B3 ; G 373 U 436 ; WX 708 ; N uni01B4 ; G 374 U 437 ; WX 730 ; N uni01B5 ; G 375 U 438 ; WX 568 ; N uni01B6 ; G 376 U 439 ; WX 657 ; N uni01B7 ; G 377 U 440 ; WX 657 ; N uni01B8 ; G 378 U 441 ; WX 657 ; N uni01B9 ; G 379 U 442 ; WX 657 ; N uni01BA ; G 380 U 443 ; WX 696 ; N uni01BB ; G 381 U 444 ; WX 754 ; N uni01BC ; G 382 U 445 ; WX 568 ; N uni01BD ; G 383 U 446 ; WX 536 ; N uni01BE ; G 384 U 447 ; WX 716 ; N uni01BF ; G 385 U 448 ; WX 295 ; N uni01C0 ; G 386 U 449 ; WX 492 ; N uni01C1 ; G 387 U 450 ; WX 459 ; N uni01C2 ; G 388 U 451 ; WX 295 ; N uni01C3 ; G 389 U 452 ; WX 1597 ; N uni01C4 ; G 390 U 453 ; WX 1435 ; N uni01C5 ; G 391 U 454 ; WX 1267 ; N uni01C6 ; G 392 U 455 ; WX 1176 ; N uni01C7 ; G 393 U 456 ; WX 1065 ; N uni01C8 ; G 394 U 457 ; WX 742 ; N uni01C9 ; G 395 U 458 ; WX 1387 ; N uni01CA ; G 396 U 459 ; WX 1276 ; N uni01CB ; G 397 U 460 ; WX 1089 ; N uni01CC ; G 398 U 461 ; WX 776 ; N uni01CD ; G 399 U 462 ; WX 648 ; N uni01CE ; G 400 U 463 ; WX 468 ; N uni01CF ; G 401 U 464 ; WX 380 ; N uni01D0 ; G 402 U 465 ; WX 871 ; N uni01D1 ; G 403 U 466 ; WX 667 ; N uni01D2 ; G 404 U 467 ; WX 872 ; N uni01D3 ; G 405 U 468 ; WX 727 ; N uni01D4 ; G 406 U 469 ; WX 872 ; N uni01D5 ; G 407 U 470 ; WX 727 ; N uni01D6 ; G 408 U 471 ; WX 872 ; N uni01D7 ; G 409 U 472 ; WX 727 ; N uni01D8 ; G 410 U 473 ; WX 872 ; N uni01D9 ; G 411 U 474 ; WX 727 ; N uni01DA ; G 412 U 475 ; WX 872 ; N uni01DB ; G 413 U 476 ; WX 727 ; N uni01DC ; G 414 U 477 ; WX 636 ; N uni01DD ; G 415 U 478 ; WX 776 ; N uni01DE ; G 416 U 479 ; WX 648 ; N uni01DF ; G 417 U 480 ; WX 776 ; N uni01E0 ; G 418 U 481 ; WX 648 ; N uni01E1 ; G 419 U 482 ; WX 1034 ; N uni01E2 ; G 420 U 483 ; WX 975 ; N uni01E3 ; G 421 U 484 ; WX 896 ; N uni01E4 ; G 422 U 485 ; WX 699 ; N uni01E5 ; G 423 U 486 ; WX 854 ; N Gcaron ; G 424 U 487 ; WX 699 ; N gcaron ; G 425 U 488 ; WX 869 ; N uni01E8 ; G 426 U 489 ; WX 693 ; N uni01E9 ; G 427 U 490 ; WX 871 ; N uni01EA ; G 428 U 491 ; WX 667 ; N uni01EB ; G 429 U 492 ; WX 871 ; N uni01EC ; G 430 U 493 ; WX 667 ; N uni01ED ; G 431 U 494 ; WX 657 ; N uni01EE ; G 432 U 495 ; WX 568 ; N uni01EF ; G 433 U 496 ; WX 380 ; N uni01F0 ; G 434 U 497 ; WX 1597 ; N uni01F1 ; G 435 U 498 ; WX 1435 ; N uni01F2 ; G 436 U 499 ; WX 1267 ; N uni01F3 ; G 437 U 500 ; WX 854 ; N uni01F4 ; G 438 U 501 ; WX 699 ; N uni01F5 ; G 439 U 502 ; WX 1221 ; N uni01F6 ; G 440 U 503 ; WX 787 ; N uni01F7 ; G 441 U 504 ; WX 914 ; N uni01F8 ; G 442 U 505 ; WX 727 ; N uni01F9 ; G 443 U 506 ; WX 776 ; N Aringacute ; G 444 U 507 ; WX 648 ; N aringacute ; G 445 U 508 ; WX 1034 ; N AEacute ; G 446 U 509 ; WX 975 ; N aeacute ; G 447 U 510 ; WX 871 ; N Oslashacute ; G 448 U 511 ; WX 667 ; N oslashacute ; G 449 U 512 ; WX 776 ; N uni0200 ; G 450 U 513 ; WX 648 ; N uni0201 ; G 451 U 514 ; WX 776 ; N uni0202 ; G 452 U 515 ; WX 648 ; N uni0203 ; G 453 U 516 ; WX 762 ; N uni0204 ; G 454 U 517 ; WX 636 ; N uni0205 ; G 455 U 518 ; WX 762 ; N uni0206 ; G 456 U 519 ; WX 636 ; N uni0207 ; G 457 U 520 ; WX 468 ; N uni0208 ; G 458 U 521 ; WX 380 ; N uni0209 ; G 459 U 522 ; WX 468 ; N uni020A ; G 460 U 523 ; WX 380 ; N uni020B ; G 461 U 524 ; WX 871 ; N uni020C ; G 462 U 525 ; WX 667 ; N uni020D ; G 463 U 526 ; WX 871 ; N uni020E ; G 464 U 527 ; WX 667 ; N uni020F ; G 465 U 528 ; WX 831 ; N uni0210 ; G 466 U 529 ; WX 527 ; N uni0211 ; G 467 U 530 ; WX 831 ; N uni0212 ; G 468 U 531 ; WX 527 ; N uni0213 ; G 469 U 532 ; WX 872 ; N uni0214 ; G 470 U 533 ; WX 727 ; N uni0215 ; G 471 U 534 ; WX 872 ; N uni0216 ; G 472 U 535 ; WX 727 ; N uni0217 ; G 473 U 536 ; WX 722 ; N Scommaaccent ; G 474 U 537 ; WX 563 ; N scommaaccent ; G 475 U 538 ; WX 744 ; N uni021A ; G 476 U 539 ; WX 462 ; N uni021B ; G 477 U 540 ; WX 690 ; N uni021C ; G 478 U 541 ; WX 607 ; N uni021D ; G 479 U 542 ; WX 945 ; N uni021E ; G 480 U 543 ; WX 727 ; N uni021F ; G 481 U 544 ; WX 872 ; N uni0220 ; G 482 U 545 ; WX 791 ; N uni0221 ; G 483 U 546 ; WX 703 ; N uni0222 ; G 484 U 547 ; WX 616 ; N uni0223 ; G 485 U 548 ; WX 730 ; N uni0224 ; G 486 U 549 ; WX 568 ; N uni0225 ; G 487 U 550 ; WX 776 ; N uni0226 ; G 488 U 551 ; WX 648 ; N uni0227 ; G 489 U 552 ; WX 762 ; N uni0228 ; G 490 U 553 ; WX 636 ; N uni0229 ; G 491 U 554 ; WX 871 ; N uni022A ; G 492 U 555 ; WX 667 ; N uni022B ; G 493 U 556 ; WX 871 ; N uni022C ; G 494 U 557 ; WX 667 ; N uni022D ; G 495 U 558 ; WX 871 ; N uni022E ; G 496 U 559 ; WX 667 ; N uni022F ; G 497 U 560 ; WX 871 ; N uni0230 ; G 498 U 561 ; WX 667 ; N uni0231 ; G 499 U 562 ; WX 714 ; N uni0232 ; G 500 U 563 ; WX 581 ; N uni0233 ; G 501 U 564 ; WX 573 ; N uni0234 ; G 502 U 565 ; WX 922 ; N uni0235 ; G 503 U 566 ; WX 564 ; N uni0236 ; G 504 U 567 ; WX 362 ; N dotlessj ; G 505 U 568 ; WX 1031 ; N uni0238 ; G 506 U 569 ; WX 1031 ; N uni0239 ; G 507 U 570 ; WX 776 ; N uni023A ; G 508 U 571 ; WX 796 ; N uni023B ; G 509 U 572 ; WX 609 ; N uni023C ; G 510 U 573 ; WX 703 ; N uni023D ; G 511 U 574 ; WX 744 ; N uni023E ; G 512 U 575 ; WX 563 ; N uni023F ; G 513 U 576 ; WX 568 ; N uni0240 ; G 514 U 577 ; WX 660 ; N uni0241 ; G 515 U 578 ; WX 547 ; N uni0242 ; G 516 U 579 ; WX 845 ; N uni0243 ; G 517 U 580 ; WX 872 ; N uni0244 ; G 518 U 581 ; WX 776 ; N uni0245 ; G 519 U 582 ; WX 762 ; N uni0246 ; G 520 U 583 ; WX 636 ; N uni0247 ; G 521 U 584 ; WX 473 ; N uni0248 ; G 522 U 585 ; WX 387 ; N uni0249 ; G 523 U 586 ; WX 848 ; N uni024A ; G 524 U 587 ; WX 699 ; N uni024B ; G 525 U 588 ; WX 831 ; N uni024C ; G 526 U 589 ; WX 527 ; N uni024D ; G 527 U 590 ; WX 714 ; N uni024E ; G 528 U 591 ; WX 581 ; N uni024F ; G 529 U 592 ; WX 648 ; N uni0250 ; G 530 U 593 ; WX 699 ; N uni0251 ; G 531 U 594 ; WX 699 ; N uni0252 ; G 532 U 595 ; WX 699 ; N uni0253 ; G 533 U 596 ; WX 609 ; N uni0254 ; G 534 U 597 ; WX 609 ; N uni0255 ; G 535 U 598 ; WX 699 ; N uni0256 ; G 536 U 599 ; WX 730 ; N uni0257 ; G 537 U 600 ; WX 636 ; N uni0258 ; G 538 U 601 ; WX 636 ; N uni0259 ; G 539 U 602 ; WX 907 ; N uni025A ; G 540 U 603 ; WX 608 ; N uni025B ; G 541 U 604 ; WX 562 ; N uni025C ; G 542 U 605 ; WX 907 ; N uni025D ; G 543 U 606 ; WX 714 ; N uni025E ; G 544 U 607 ; WX 387 ; N uni025F ; G 545 U 608 ; WX 699 ; N uni0260 ; G 546 U 609 ; WX 699 ; N uni0261 ; G 547 U 610 ; WX 638 ; N uni0262 ; G 548 U 611 ; WX 601 ; N uni0263 ; G 549 U 612 ; WX 627 ; N uni0264 ; G 550 U 613 ; WX 727 ; N uni0265 ; G 551 U 614 ; WX 727 ; N uni0266 ; G 552 U 615 ; WX 727 ; N uni0267 ; G 553 U 616 ; WX 380 ; N uni0268 ; G 554 U 617 ; WX 380 ; N uni0269 ; G 555 U 618 ; WX 380 ; N uni026A ; G 556 U 619 ; WX 409 ; N uni026B ; G 557 U 620 ; WX 514 ; N uni026C ; G 558 U 621 ; WX 380 ; N uni026D ; G 559 U 622 ; WX 795 ; N uni026E ; G 560 U 623 ; WX 1058 ; N uni026F ; G 561 U 624 ; WX 1058 ; N uni0270 ; G 562 U 625 ; WX 1058 ; N uni0271 ; G 563 U 626 ; WX 727 ; N uni0272 ; G 564 U 627 ; WX 727 ; N uni0273 ; G 565 U 628 ; WX 712 ; N uni0274 ; G 566 U 629 ; WX 667 ; N uni0275 ; G 567 U 630 ; WX 1061 ; N uni0276 ; G 568 U 631 ; WX 944 ; N uni0277 ; G 569 U 632 ; WX 797 ; N uni0278 ; G 570 U 633 ; WX 571 ; N uni0279 ; G 571 U 634 ; WX 571 ; N uni027A ; G 572 U 635 ; WX 571 ; N uni027B ; G 573 U 636 ; WX 527 ; N uni027C ; G 574 U 637 ; WX 527 ; N uni027D ; G 575 U 638 ; WX 452 ; N uni027E ; G 576 U 639 ; WX 487 ; N uni027F ; G 577 U 640 ; WX 694 ; N uni0280 ; G 578 U 641 ; WX 694 ; N uni0281 ; G 579 U 642 ; WX 563 ; N uni0282 ; G 580 U 643 ; WX 331 ; N uni0283 ; G 581 U 644 ; WX 430 ; N uni0284 ; G 582 U 645 ; WX 540 ; N uni0285 ; G 583 U 646 ; WX 331 ; N uni0286 ; G 584 U 647 ; WX 492 ; N uni0287 ; G 585 U 648 ; WX 462 ; N uni0288 ; G 586 U 649 ; WX 727 ; N uni0289 ; G 587 U 650 ; WX 679 ; N uni028A ; G 588 U 651 ; WX 694 ; N uni028B ; G 589 U 652 ; WX 641 ; N uni028C ; G 590 U 653 ; WX 907 ; N uni028D ; G 591 U 654 ; WX 635 ; N uni028E ; G 592 U 655 ; WX 727 ; N uni028F ; G 593 U 656 ; WX 568 ; N uni0290 ; G 594 U 657 ; WX 568 ; N uni0291 ; G 595 U 658 ; WX 568 ; N uni0292 ; G 596 U 659 ; WX 568 ; N uni0293 ; G 597 U 660 ; WX 551 ; N uni0294 ; G 598 U 661 ; WX 551 ; N uni0295 ; G 599 U 662 ; WX 551 ; N uni0296 ; G 600 U 663 ; WX 545 ; N uni0297 ; G 601 U 664 ; WX 871 ; N uni0298 ; G 602 U 665 ; WX 695 ; N uni0299 ; G 603 U 666 ; WX 714 ; N uni029A ; G 604 U 667 ; WX 689 ; N uni029B ; G 605 U 668 ; WX 732 ; N uni029C ; G 606 U 669 ; WX 384 ; N uni029D ; G 607 U 670 ; WX 740 ; N uni029E ; G 608 U 671 ; WX 617 ; N uni029F ; G 609 U 672 ; WX 699 ; N uni02A0 ; G 610 U 673 ; WX 551 ; N uni02A1 ; G 611 U 674 ; WX 551 ; N uni02A2 ; G 612 U 675 ; WX 1117 ; N uni02A3 ; G 613 U 676 ; WX 1179 ; N uni02A4 ; G 614 U 677 ; WX 1117 ; N uni02A5 ; G 615 U 678 ; WX 938 ; N uni02A6 ; G 616 U 679 ; WX 715 ; N uni02A7 ; G 617 U 680 ; WX 946 ; N uni02A8 ; G 618 U 681 ; WX 1039 ; N uni02A9 ; G 619 U 682 ; WX 870 ; N uni02AA ; G 620 U 683 ; WX 795 ; N uni02AB ; G 621 U 684 ; WX 662 ; N uni02AC ; G 622 U 685 ; WX 443 ; N uni02AD ; G 623 U 686 ; WX 613 ; N uni02AE ; G 624 U 687 ; WX 717 ; N uni02AF ; G 625 U 688 ; WX 521 ; N uni02B0 ; G 626 U 689 ; WX 519 ; N uni02B1 ; G 627 U 690 ; WX 313 ; N uni02B2 ; G 628 U 691 ; WX 414 ; N uni02B3 ; G 629 U 692 ; WX 414 ; N uni02B4 ; G 630 U 693 ; WX 480 ; N uni02B5 ; G 631 U 694 ; WX 527 ; N uni02B6 ; G 632 U 695 ; WX 662 ; N uni02B7 ; G 633 U 696 ; WX 485 ; N uni02B8 ; G 634 U 697 ; WX 302 ; N uni02B9 ; G 635 U 698 ; WX 521 ; N uni02BA ; G 636 U 699 ; WX 348 ; N uni02BB ; G 637 U 700 ; WX 348 ; N uni02BC ; G 638 U 701 ; WX 348 ; N uni02BD ; G 639 U 702 ; WX 366 ; N uni02BE ; G 640 U 703 ; WX 366 ; N uni02BF ; G 641 U 704 ; WX 313 ; N uni02C0 ; G 642 U 705 ; WX 313 ; N uni02C1 ; G 643 U 706 ; WX 500 ; N uni02C2 ; G 644 U 707 ; WX 500 ; N uni02C3 ; G 645 U 708 ; WX 500 ; N uni02C4 ; G 646 U 709 ; WX 500 ; N uni02C5 ; G 647 U 710 ; WX 500 ; N circumflex ; G 648 U 711 ; WX 500 ; N caron ; G 649 U 712 ; WX 282 ; N uni02C8 ; G 650 U 713 ; WX 500 ; N uni02C9 ; G 651 U 714 ; WX 500 ; N uni02CA ; G 652 U 715 ; WX 500 ; N uni02CB ; G 653 U 716 ; WX 282 ; N uni02CC ; G 654 U 717 ; WX 500 ; N uni02CD ; G 655 U 720 ; WX 369 ; N uni02D0 ; G 656 U 721 ; WX 369 ; N uni02D1 ; G 657 U 722 ; WX 366 ; N uni02D2 ; G 658 U 723 ; WX 366 ; N uni02D3 ; G 659 U 726 ; WX 392 ; N uni02D6 ; G 660 U 727 ; WX 392 ; N uni02D7 ; G 661 U 728 ; WX 500 ; N breve ; G 662 U 729 ; WX 500 ; N dotaccent ; G 663 U 730 ; WX 500 ; N ring ; G 664 U 731 ; WX 500 ; N ogonek ; G 665 U 732 ; WX 500 ; N tilde ; G 666 U 733 ; WX 500 ; N hungarumlaut ; G 667 U 734 ; WX 417 ; N uni02DE ; G 668 U 736 ; WX 378 ; N uni02E0 ; G 669 U 737 ; WX 292 ; N uni02E1 ; G 670 U 738 ; WX 395 ; N uni02E2 ; G 671 U 739 ; WX 475 ; N uni02E3 ; G 672 U 740 ; WX 313 ; N uni02E4 ; G 673 U 741 ; WX 500 ; N uni02E5 ; G 674 U 742 ; WX 500 ; N uni02E6 ; G 675 U 743 ; WX 500 ; N uni02E7 ; G 676 U 744 ; WX 500 ; N uni02E8 ; G 677 U 745 ; WX 500 ; N uni02E9 ; G 678 U 748 ; WX 500 ; N uni02EC ; G 679 U 750 ; WX 553 ; N uni02EE ; G 680 U 751 ; WX 500 ; N uni02EF ; G 681 U 752 ; WX 500 ; N uni02F0 ; G 682 U 755 ; WX 500 ; N uni02F3 ; G 683 U 759 ; WX 500 ; N uni02F7 ; G 684 U 768 ; WX 0 ; N gravecomb ; G 685 U 769 ; WX 0 ; N acutecomb ; G 686 U 770 ; WX 0 ; N uni0302 ; G 687 U 771 ; WX 0 ; N tildecomb ; G 688 U 772 ; WX 0 ; N uni0304 ; G 689 U 773 ; WX 0 ; N uni0305 ; G 690 U 774 ; WX 0 ; N uni0306 ; G 691 U 775 ; WX 0 ; N uni0307 ; G 692 U 776 ; WX 0 ; N uni0308 ; G 693 U 777 ; WX 0 ; N hookabovecomb ; G 694 U 778 ; WX 0 ; N uni030A ; G 695 U 779 ; WX 0 ; N uni030B ; G 696 U 780 ; WX 0 ; N uni030C ; G 697 U 781 ; WX 0 ; N uni030D ; G 698 U 782 ; WX 0 ; N uni030E ; G 699 U 783 ; WX 0 ; N uni030F ; G 700 U 784 ; WX 0 ; N uni0310 ; G 701 U 785 ; WX 0 ; N uni0311 ; G 702 U 786 ; WX 0 ; N uni0312 ; G 703 U 787 ; WX 0 ; N uni0313 ; G 704 U 788 ; WX 0 ; N uni0314 ; G 705 U 789 ; WX 0 ; N uni0315 ; G 706 U 790 ; WX 0 ; N uni0316 ; G 707 U 791 ; WX 0 ; N uni0317 ; G 708 U 792 ; WX 0 ; N uni0318 ; G 709 U 793 ; WX 0 ; N uni0319 ; G 710 U 794 ; WX 0 ; N uni031A ; G 711 U 795 ; WX 0 ; N uni031B ; G 712 U 796 ; WX 0 ; N uni031C ; G 713 U 797 ; WX 0 ; N uni031D ; G 714 U 798 ; WX 0 ; N uni031E ; G 715 U 799 ; WX 0 ; N uni031F ; G 716 U 800 ; WX 0 ; N uni0320 ; G 717 U 801 ; WX 0 ; N uni0321 ; G 718 U 802 ; WX 0 ; N uni0322 ; G 719 U 803 ; WX 0 ; N dotbelowcomb ; G 720 U 804 ; WX 0 ; N uni0324 ; G 721 U 805 ; WX 0 ; N uni0325 ; G 722 U 806 ; WX 0 ; N uni0326 ; G 723 U 807 ; WX 0 ; N uni0327 ; G 724 U 808 ; WX 0 ; N uni0328 ; G 725 U 809 ; WX 0 ; N uni0329 ; G 726 U 810 ; WX 0 ; N uni032A ; G 727 U 811 ; WX 0 ; N uni032B ; G 728 U 812 ; WX 0 ; N uni032C ; G 729 U 813 ; WX 0 ; N uni032D ; G 730 U 814 ; WX 0 ; N uni032E ; G 731 U 815 ; WX 0 ; N uni032F ; G 732 U 816 ; WX 0 ; N uni0330 ; G 733 U 817 ; WX 0 ; N uni0331 ; G 734 U 818 ; WX 0 ; N uni0332 ; G 735 U 819 ; WX 0 ; N uni0333 ; G 736 U 820 ; WX 0 ; N uni0334 ; G 737 U 821 ; WX 0 ; N uni0335 ; G 738 U 822 ; WX 0 ; N uni0336 ; G 739 U 823 ; WX 0 ; N uni0337 ; G 740 U 824 ; WX 0 ; N uni0338 ; G 741 U 825 ; WX 0 ; N uni0339 ; G 742 U 826 ; WX 0 ; N uni033A ; G 743 U 827 ; WX 0 ; N uni033B ; G 744 U 828 ; WX 0 ; N uni033C ; G 745 U 829 ; WX 0 ; N uni033D ; G 746 U 830 ; WX 0 ; N uni033E ; G 747 U 831 ; WX 0 ; N uni033F ; G 748 U 835 ; WX 0 ; N uni0343 ; G 749 U 847 ; WX 0 ; N uni034F ; G 750 U 856 ; WX 0 ; N uni0358 ; G 751 U 864 ; WX 0 ; N uni0360 ; G 752 U 865 ; WX 0 ; N uni0361 ; G 753 U 880 ; WX 779 ; N uni0370 ; G 754 U 881 ; WX 576 ; N uni0371 ; G 755 U 882 ; WX 803 ; N uni0372 ; G 756 U 883 ; WX 777 ; N uni0373 ; G 757 U 884 ; WX 302 ; N uni0374 ; G 758 U 885 ; WX 302 ; N uni0375 ; G 759 U 886 ; WX 963 ; N uni0376 ; G 760 U 887 ; WX 737 ; N uni0377 ; G 761 U 890 ; WX 500 ; N uni037A ; G 762 U 891 ; WX 609 ; N uni037B ; G 763 U 892 ; WX 609 ; N uni037C ; G 764 U 893 ; WX 609 ; N uni037D ; G 765 U 894 ; WX 369 ; N uni037E ; G 766 U 895 ; WX 473 ; N uni037F ; G 767 U 900 ; WX 500 ; N tonos ; G 768 U 901 ; WX 500 ; N dieresistonos ; G 769 U 902 ; WX 776 ; N Alphatonos ; G 770 U 903 ; WX 348 ; N anoteleia ; G 771 U 904 ; WX 947 ; N Epsilontonos ; G 772 U 905 ; WX 1118 ; N Etatonos ; G 773 U 906 ; WX 662 ; N Iotatonos ; G 774 U 908 ; WX 887 ; N Omicrontonos ; G 775 U 910 ; WX 953 ; N Upsilontonos ; G 776 U 911 ; WX 911 ; N Omegatonos ; G 777 U 912 ; WX 484 ; N iotadieresistonos ; G 778 U 913 ; WX 776 ; N Alpha ; G 779 U 914 ; WX 845 ; N Beta ; G 780 U 915 ; WX 710 ; N Gamma ; G 781 U 916 ; WX 776 ; N uni0394 ; G 782 U 917 ; WX 762 ; N Epsilon ; G 783 U 918 ; WX 730 ; N Zeta ; G 784 U 919 ; WX 945 ; N Eta ; G 785 U 920 ; WX 871 ; N Theta ; G 786 U 921 ; WX 468 ; N Iota ; G 787 U 922 ; WX 869 ; N Kappa ; G 788 U 923 ; WX 776 ; N Lambda ; G 789 U 924 ; WX 1107 ; N Mu ; G 790 U 925 ; WX 914 ; N Nu ; G 791 U 926 ; WX 704 ; N Xi ; G 792 U 927 ; WX 871 ; N Omicron ; G 793 U 928 ; WX 944 ; N Pi ; G 794 U 929 ; WX 752 ; N Rho ; G 795 U 931 ; WX 707 ; N Sigma ; G 796 U 932 ; WX 744 ; N Tau ; G 797 U 933 ; WX 714 ; N Upsilon ; G 798 U 934 ; WX 871 ; N Phi ; G 799 U 935 ; WX 776 ; N Chi ; G 800 U 936 ; WX 913 ; N Psi ; G 801 U 937 ; WX 890 ; N Omega ; G 802 U 938 ; WX 468 ; N Iotadieresis ; G 803 U 939 ; WX 714 ; N Upsilondieresis ; G 804 U 940 ; WX 770 ; N alphatonos ; G 805 U 941 ; WX 608 ; N epsilontonos ; G 806 U 942 ; WX 727 ; N etatonos ; G 807 U 943 ; WX 484 ; N iotatonos ; G 808 U 944 ; WX 694 ; N upsilondieresistonos ; G 809 U 945 ; WX 770 ; N alpha ; G 810 U 946 ; WX 664 ; N beta ; G 811 U 947 ; WX 660 ; N gamma ; G 812 U 948 ; WX 667 ; N delta ; G 813 U 949 ; WX 608 ; N epsilon ; G 814 U 950 ; WX 592 ; N zeta ; G 815 U 951 ; WX 727 ; N eta ; G 816 U 952 ; WX 667 ; N theta ; G 817 U 953 ; WX 484 ; N iota ; G 818 U 954 ; WX 750 ; N kappa ; G 819 U 955 ; WX 701 ; N lambda ; G 820 U 956 ; WX 732 ; N uni03BC ; G 821 U 957 ; WX 694 ; N nu ; G 822 U 958 ; WX 592 ; N xi ; G 823 U 959 ; WX 667 ; N omicron ; G 824 U 960 ; WX 732 ; N pi ; G 825 U 961 ; WX 665 ; N rho ; G 826 U 962 ; WX 609 ; N sigma1 ; G 827 U 963 ; WX 737 ; N sigma ; G 828 U 964 ; WX 673 ; N tau ; G 829 U 965 ; WX 694 ; N upsilon ; G 830 U 966 ; WX 905 ; N phi ; G 831 U 967 ; WX 658 ; N chi ; G 832 U 968 ; WX 941 ; N psi ; G 833 U 969 ; WX 952 ; N omega ; G 834 U 970 ; WX 484 ; N iotadieresis ; G 835 U 971 ; WX 694 ; N upsilondieresis ; G 836 U 972 ; WX 667 ; N omicrontonos ; G 837 U 973 ; WX 694 ; N upsilontonos ; G 838 U 974 ; WX 952 ; N omegatonos ; G 839 U 975 ; WX 869 ; N uni03CF ; G 840 U 976 ; WX 667 ; N uni03D0 ; G 841 U 977 ; WX 849 ; N theta1 ; G 842 U 978 ; WX 764 ; N Upsilon1 ; G 843 U 979 ; WX 969 ; N uni03D3 ; G 844 U 980 ; WX 764 ; N uni03D4 ; G 845 U 981 ; WX 941 ; N phi1 ; G 846 U 982 ; WX 952 ; N omega1 ; G 847 U 983 ; WX 655 ; N uni03D7 ; G 848 U 984 ; WX 871 ; N uni03D8 ; G 849 U 985 ; WX 667 ; N uni03D9 ; G 850 U 986 ; WX 796 ; N uni03DA ; G 851 U 987 ; WX 609 ; N uni03DB ; G 852 U 988 ; WX 710 ; N uni03DC ; G 853 U 989 ; WX 527 ; N uni03DD ; G 854 U 990 ; WX 590 ; N uni03DE ; G 855 U 991 ; WX 660 ; N uni03DF ; G 856 U 992 ; WX 796 ; N uni03E0 ; G 857 U 993 ; WX 667 ; N uni03E1 ; G 858 U 1008 ; WX 655 ; N uni03F0 ; G 859 U 1009 ; WX 665 ; N uni03F1 ; G 860 U 1010 ; WX 609 ; N uni03F2 ; G 861 U 1011 ; WX 362 ; N uni03F3 ; G 862 U 1012 ; WX 871 ; N uni03F4 ; G 863 U 1013 ; WX 609 ; N uni03F5 ; G 864 U 1014 ; WX 609 ; N uni03F6 ; G 865 U 1015 ; WX 757 ; N uni03F7 ; G 866 U 1016 ; WX 699 ; N uni03F8 ; G 867 U 1017 ; WX 796 ; N uni03F9 ; G 868 U 1018 ; WX 1107 ; N uni03FA ; G 869 U 1019 ; WX 860 ; N uni03FB ; G 870 U 1020 ; WX 692 ; N uni03FC ; G 871 U 1021 ; WX 796 ; N uni03FD ; G 872 U 1022 ; WX 796 ; N uni03FE ; G 873 U 1023 ; WX 796 ; N uni03FF ; G 874 U 1024 ; WX 762 ; N uni0400 ; G 875 U 1025 ; WX 762 ; N uni0401 ; G 876 U 1026 ; WX 901 ; N uni0402 ; G 877 U 1027 ; WX 690 ; N uni0403 ; G 878 U 1028 ; WX 795 ; N uni0404 ; G 879 U 1029 ; WX 722 ; N uni0405 ; G 880 U 1030 ; WX 468 ; N uni0406 ; G 881 U 1031 ; WX 468 ; N uni0407 ; G 882 U 1032 ; WX 473 ; N uni0408 ; G 883 U 1033 ; WX 1202 ; N uni0409 ; G 884 U 1034 ; WX 1262 ; N uni040A ; G 885 U 1035 ; WX 963 ; N uni040B ; G 886 U 1036 ; WX 910 ; N uni040C ; G 887 U 1037 ; WX 945 ; N uni040D ; G 888 U 1038 ; WX 812 ; N uni040E ; G 889 U 1039 ; WX 945 ; N uni040F ; G 890 U 1040 ; WX 814 ; N uni0410 ; G 891 U 1041 ; WX 854 ; N uni0411 ; G 892 U 1042 ; WX 845 ; N uni0412 ; G 893 U 1043 ; WX 690 ; N uni0413 ; G 894 U 1044 ; WX 889 ; N uni0414 ; G 895 U 1045 ; WX 762 ; N uni0415 ; G 896 U 1046 ; WX 1312 ; N uni0416 ; G 897 U 1047 ; WX 721 ; N uni0417 ; G 898 U 1048 ; WX 945 ; N uni0418 ; G 899 U 1049 ; WX 945 ; N uni0419 ; G 900 U 1050 ; WX 910 ; N uni041A ; G 901 U 1051 ; WX 884 ; N uni041B ; G 902 U 1052 ; WX 1107 ; N uni041C ; G 903 U 1053 ; WX 945 ; N uni041D ; G 904 U 1054 ; WX 871 ; N uni041E ; G 905 U 1055 ; WX 944 ; N uni041F ; G 906 U 1056 ; WX 752 ; N uni0420 ; G 907 U 1057 ; WX 796 ; N uni0421 ; G 908 U 1058 ; WX 744 ; N uni0422 ; G 909 U 1059 ; WX 812 ; N uni0423 ; G 910 U 1060 ; WX 949 ; N uni0424 ; G 911 U 1061 ; WX 776 ; N uni0425 ; G 912 U 1062 ; WX 966 ; N uni0426 ; G 913 U 1063 ; WX 913 ; N uni0427 ; G 914 U 1064 ; WX 1268 ; N uni0428 ; G 915 U 1065 ; WX 1293 ; N uni0429 ; G 916 U 1066 ; WX 957 ; N uni042A ; G 917 U 1067 ; WX 1202 ; N uni042B ; G 918 U 1068 ; WX 825 ; N uni042C ; G 919 U 1069 ; WX 795 ; N uni042D ; G 920 U 1070 ; WX 1287 ; N uni042E ; G 921 U 1071 ; WX 882 ; N uni042F ; G 922 U 1072 ; WX 648 ; N uni0430 ; G 923 U 1073 ; WX 667 ; N uni0431 ; G 924 U 1074 ; WX 695 ; N uni0432 ; G 925 U 1075 ; WX 613 ; N uni0433 ; G 926 U 1076 ; WX 667 ; N uni0434 ; G 927 U 1077 ; WX 636 ; N uni0435 ; G 928 U 1078 ; WX 1010 ; N uni0436 ; G 929 U 1079 ; WX 638 ; N uni0437 ; G 930 U 1080 ; WX 742 ; N uni0438 ; G 931 U 1081 ; WX 742 ; N uni0439 ; G 932 U 1082 ; WX 722 ; N uni043A ; G 933 U 1083 ; WX 705 ; N uni043B ; G 934 U 1084 ; WX 869 ; N uni043C ; G 935 U 1085 ; WX 732 ; N uni043D ; G 936 U 1086 ; WX 667 ; N uni043E ; G 937 U 1087 ; WX 732 ; N uni043F ; G 938 U 1088 ; WX 699 ; N uni0440 ; G 939 U 1089 ; WX 609 ; N uni0441 ; G 940 U 1090 ; WX 620 ; N uni0442 ; G 941 U 1091 ; WX 640 ; N uni0443 ; G 942 U 1092 ; WX 902 ; N uni0444 ; G 943 U 1093 ; WX 596 ; N uni0445 ; G 944 U 1094 ; WX 739 ; N uni0446 ; G 945 U 1095 ; WX 732 ; N uni0447 ; G 946 U 1096 ; WX 1075 ; N uni0448 ; G 947 U 1097 ; WX 1082 ; N uni0449 ; G 948 U 1098 ; WX 767 ; N uni044A ; G 949 U 1099 ; WX 1002 ; N uni044B ; G 950 U 1100 ; WX 679 ; N uni044C ; G 951 U 1101 ; WX 609 ; N uni044D ; G 952 U 1102 ; WX 1025 ; N uni044E ; G 953 U 1103 ; WX 739 ; N uni044F ; G 954 U 1104 ; WX 636 ; N uni0450 ; G 955 U 1105 ; WX 636 ; N uni0451 ; G 956 U 1106 ; WX 719 ; N uni0452 ; G 957 U 1107 ; WX 613 ; N uni0453 ; G 958 U 1108 ; WX 609 ; N uni0454 ; G 959 U 1109 ; WX 563 ; N uni0455 ; G 960 U 1110 ; WX 380 ; N uni0456 ; G 961 U 1111 ; WX 380 ; N uni0457 ; G 962 U 1112 ; WX 362 ; N uni0458 ; G 963 U 1113 ; WX 988 ; N uni0459 ; G 964 U 1114 ; WX 1015 ; N uni045A ; G 965 U 1115 ; WX 727 ; N uni045B ; G 966 U 1116 ; WX 722 ; N uni045C ; G 967 U 1117 ; WX 742 ; N uni045D ; G 968 U 1118 ; WX 640 ; N uni045E ; G 969 U 1119 ; WX 732 ; N uni045F ; G 970 U 1122 ; WX 880 ; N uni0462 ; G 971 U 1123 ; WX 703 ; N uni0463 ; G 972 U 1124 ; WX 1195 ; N uni0464 ; G 973 U 1125 ; WX 963 ; N uni0465 ; G 974 U 1130 ; WX 1312 ; N uni046A ; G 975 U 1131 ; WX 1010 ; N uni046B ; G 976 U 1132 ; WX 1630 ; N uni046C ; G 977 U 1133 ; WX 1297 ; N uni046D ; G 978 U 1136 ; WX 1096 ; N uni0470 ; G 979 U 1137 ; WX 1105 ; N uni0471 ; G 980 U 1138 ; WX 871 ; N uni0472 ; G 981 U 1139 ; WX 652 ; N uni0473 ; G 982 U 1140 ; WX 916 ; N uni0474 ; G 983 U 1141 ; WX 749 ; N uni0475 ; G 984 U 1142 ; WX 916 ; N uni0476 ; G 985 U 1143 ; WX 749 ; N uni0477 ; G 986 U 1164 ; WX 846 ; N uni048C ; G 987 U 1165 ; WX 673 ; N uni048D ; G 988 U 1168 ; WX 700 ; N uni0490 ; G 989 U 1169 ; WX 618 ; N uni0491 ; G 990 U 1170 ; WX 690 ; N uni0492 ; G 991 U 1171 ; WX 613 ; N uni0493 ; G 992 U 1172 ; WX 868 ; N uni0494 ; G 993 U 1173 ; WX 716 ; N uni0495 ; G 994 U 1174 ; WX 1312 ; N uni0496 ; G 995 U 1175 ; WX 1010 ; N uni0497 ; G 996 U 1176 ; WX 721 ; N uni0498 ; G 997 U 1177 ; WX 638 ; N uni0499 ; G 998 U 1178 ; WX 947 ; N uni049A ; G 999 U 1179 ; WX 744 ; N uni049B ; G 1000 U 1182 ; WX 910 ; N uni049E ; G 1001 U 1183 ; WX 722 ; N uni049F ; G 1002 U 1184 ; WX 1041 ; N uni04A0 ; G 1003 U 1185 ; WX 827 ; N uni04A1 ; G 1004 U 1186 ; WX 966 ; N uni04A2 ; G 1005 U 1187 ; WX 739 ; N uni04A3 ; G 1006 U 1188 ; WX 1167 ; N uni04A4 ; G 1007 U 1189 ; WX 956 ; N uni04A5 ; G 1008 U 1190 ; WX 1345 ; N uni04A6 ; G 1009 U 1191 ; WX 1059 ; N uni04A7 ; G 1010 U 1194 ; WX 796 ; N uni04AA ; G 1011 U 1195 ; WX 609 ; N uni04AB ; G 1012 U 1196 ; WX 744 ; N uni04AC ; G 1013 U 1197 ; WX 620 ; N uni04AD ; G 1014 U 1198 ; WX 714 ; N uni04AE ; G 1015 U 1199 ; WX 581 ; N uni04AF ; G 1016 U 1200 ; WX 714 ; N uni04B0 ; G 1017 U 1201 ; WX 581 ; N uni04B1 ; G 1018 U 1202 ; WX 866 ; N uni04B2 ; G 1019 U 1203 ; WX 649 ; N uni04B3 ; G 1020 U 1204 ; WX 1022 ; N uni04B4 ; G 1021 U 1205 ; WX 807 ; N uni04B5 ; G 1022 U 1206 ; WX 928 ; N uni04B6 ; G 1023 U 1207 ; WX 739 ; N uni04B7 ; G 1024 U 1210 ; WX 910 ; N uni04BA ; G 1025 U 1211 ; WX 727 ; N uni04BB ; G 1026 U 1216 ; WX 468 ; N uni04C0 ; G 1027 U 1217 ; WX 1312 ; N uni04C1 ; G 1028 U 1218 ; WX 1010 ; N uni04C2 ; G 1029 U 1219 ; WX 869 ; N uni04C3 ; G 1030 U 1220 ; WX 693 ; N uni04C4 ; G 1031 U 1223 ; WX 945 ; N uni04C7 ; G 1032 U 1224 ; WX 732 ; N uni04C8 ; G 1033 U 1227 ; WX 913 ; N uni04CB ; G 1034 U 1228 ; WX 732 ; N uni04CC ; G 1035 U 1231 ; WX 380 ; N uni04CF ; G 1036 U 1232 ; WX 814 ; N uni04D0 ; G 1037 U 1233 ; WX 648 ; N uni04D1 ; G 1038 U 1234 ; WX 814 ; N uni04D2 ; G 1039 U 1235 ; WX 648 ; N uni04D3 ; G 1040 U 1236 ; WX 1034 ; N uni04D4 ; G 1041 U 1237 ; WX 975 ; N uni04D5 ; G 1042 U 1238 ; WX 762 ; N uni04D6 ; G 1043 U 1239 ; WX 636 ; N uni04D7 ; G 1044 U 1240 ; WX 871 ; N uni04D8 ; G 1045 U 1241 ; WX 636 ; N uni04D9 ; G 1046 U 1242 ; WX 871 ; N uni04DA ; G 1047 U 1243 ; WX 636 ; N uni04DB ; G 1048 U 1244 ; WX 1312 ; N uni04DC ; G 1049 U 1245 ; WX 1010 ; N uni04DD ; G 1050 U 1246 ; WX 721 ; N uni04DE ; G 1051 U 1247 ; WX 638 ; N uni04DF ; G 1052 U 1248 ; WX 657 ; N uni04E0 ; G 1053 U 1249 ; WX 568 ; N uni04E1 ; G 1054 U 1250 ; WX 945 ; N uni04E2 ; G 1055 U 1251 ; WX 742 ; N uni04E3 ; G 1056 U 1252 ; WX 945 ; N uni04E4 ; G 1057 U 1253 ; WX 742 ; N uni04E5 ; G 1058 U 1254 ; WX 871 ; N uni04E6 ; G 1059 U 1255 ; WX 667 ; N uni04E7 ; G 1060 U 1256 ; WX 871 ; N uni04E8 ; G 1061 U 1257 ; WX 667 ; N uni04E9 ; G 1062 U 1258 ; WX 871 ; N uni04EA ; G 1063 U 1259 ; WX 667 ; N uni04EB ; G 1064 U 1260 ; WX 795 ; N uni04EC ; G 1065 U 1261 ; WX 609 ; N uni04ED ; G 1066 U 1262 ; WX 812 ; N uni04EE ; G 1067 U 1263 ; WX 640 ; N uni04EF ; G 1068 U 1264 ; WX 812 ; N uni04F0 ; G 1069 U 1265 ; WX 640 ; N uni04F1 ; G 1070 U 1266 ; WX 812 ; N uni04F2 ; G 1071 U 1267 ; WX 640 ; N uni04F3 ; G 1072 U 1268 ; WX 913 ; N uni04F4 ; G 1073 U 1269 ; WX 732 ; N uni04F5 ; G 1074 U 1270 ; WX 690 ; N uni04F6 ; G 1075 U 1271 ; WX 613 ; N uni04F7 ; G 1076 U 1272 ; WX 1202 ; N uni04F8 ; G 1077 U 1273 ; WX 1002 ; N uni04F9 ; G 1078 U 1296 ; WX 721 ; N uni0510 ; G 1079 U 1297 ; WX 638 ; N uni0511 ; G 1080 U 1298 ; WX 884 ; N uni0512 ; G 1081 U 1299 ; WX 705 ; N uni0513 ; G 1082 U 1300 ; WX 1248 ; N uni0514 ; G 1083 U 1301 ; WX 945 ; N uni0515 ; G 1084 U 1306 ; WX 820 ; N uni051A ; G 1085 U 1307 ; WX 640 ; N uni051B ; G 1086 U 1308 ; WX 1028 ; N uni051C ; G 1087 U 1309 ; WX 856 ; N uni051D ; G 1088 U 1329 ; WX 942 ; N uni0531 ; G 1089 U 1330 ; WX 832 ; N uni0532 ; G 1090 U 1331 ; WX 894 ; N uni0533 ; G 1091 U 1332 ; WX 909 ; N uni0534 ; G 1092 U 1333 ; WX 822 ; N uni0535 ; G 1093 U 1334 ; WX 821 ; N uni0536 ; G 1094 U 1335 ; WX 747 ; N uni0537 ; G 1095 U 1336 ; WX 832 ; N uni0538 ; G 1096 U 1337 ; WX 1125 ; N uni0539 ; G 1097 U 1338 ; WX 894 ; N uni053A ; G 1098 U 1339 ; WX 803 ; N uni053B ; G 1099 U 1340 ; WX 722 ; N uni053C ; G 1100 U 1341 ; WX 1188 ; N uni053D ; G 1101 U 1342 ; WX 887 ; N uni053E ; G 1102 U 1343 ; WX 842 ; N uni053F ; G 1103 U 1344 ; WX 737 ; N uni0540 ; G 1104 U 1345 ; WX 863 ; N uni0541 ; G 1105 U 1346 ; WX 918 ; N uni0542 ; G 1106 U 1347 ; WX 851 ; N uni0543 ; G 1107 U 1348 ; WX 977 ; N uni0544 ; G 1108 U 1349 ; WX 833 ; N uni0545 ; G 1109 U 1350 ; WX 914 ; N uni0546 ; G 1110 U 1351 ; WX 843 ; N uni0547 ; G 1111 U 1352 ; WX 871 ; N uni0548 ; G 1112 U 1353 ; WX 818 ; N uni0549 ; G 1113 U 1354 ; WX 1034 ; N uni054A ; G 1114 U 1355 ; WX 846 ; N uni054B ; G 1115 U 1356 ; WX 964 ; N uni054C ; G 1116 U 1357 ; WX 871 ; N uni054D ; G 1117 U 1358 ; WX 914 ; N uni054E ; G 1118 U 1359 ; WX 808 ; N uni054F ; G 1119 U 1360 ; WX 808 ; N uni0550 ; G 1120 U 1361 ; WX 836 ; N uni0551 ; G 1121 U 1362 ; WX 710 ; N uni0552 ; G 1122 U 1363 ; WX 955 ; N uni0553 ; G 1123 U 1364 ; WX 891 ; N uni0554 ; G 1124 U 1365 ; WX 871 ; N uni0555 ; G 1125 U 1366 ; WX 963 ; N uni0556 ; G 1126 U 1369 ; WX 307 ; N uni0559 ; G 1127 U 1370 ; WX 264 ; N uni055A ; G 1128 U 1371 ; WX 293 ; N uni055B ; G 1129 U 1372 ; WX 391 ; N uni055C ; G 1130 U 1373 ; WX 323 ; N uni055D ; G 1131 U 1374 ; WX 439 ; N uni055E ; G 1132 U 1375 ; WX 500 ; N uni055F ; G 1133 U 1377 ; WX 1055 ; N uni0561 ; G 1134 U 1378 ; WX 695 ; N uni0562 ; G 1135 U 1379 ; WX 776 ; N uni0563 ; G 1136 U 1380 ; WX 801 ; N uni0564 ; G 1137 U 1381 ; WX 729 ; N uni0565 ; G 1138 U 1382 ; WX 742 ; N uni0566 ; G 1139 U 1383 ; WX 599 ; N uni0567 ; G 1140 U 1384 ; WX 733 ; N uni0568 ; G 1141 U 1385 ; WX 909 ; N uni0569 ; G 1142 U 1386 ; WX 768 ; N uni056A ; G 1143 U 1387 ; WX 724 ; N uni056B ; G 1144 U 1388 ; WX 398 ; N uni056C ; G 1145 U 1389 ; WX 1087 ; N uni056D ; G 1146 U 1390 ; WX 695 ; N uni056E ; G 1147 U 1391 ; WX 719 ; N uni056F ; G 1148 U 1392 ; WX 737 ; N uni0570 ; G 1149 U 1393 ; WX 684 ; N uni0571 ; G 1150 U 1394 ; WX 738 ; N uni0572 ; G 1151 U 1395 ; WX 703 ; N uni0573 ; G 1152 U 1396 ; WX 724 ; N uni0574 ; G 1153 U 1397 ; WX 359 ; N uni0575 ; G 1154 U 1398 ; WX 719 ; N uni0576 ; G 1155 U 1399 ; WX 496 ; N uni0577 ; G 1156 U 1400 ; WX 738 ; N uni0578 ; G 1157 U 1401 ; WX 428 ; N uni0579 ; G 1158 U 1402 ; WX 1059 ; N uni057A ; G 1159 U 1403 ; WX 668 ; N uni057B ; G 1160 U 1404 ; WX 744 ; N uni057C ; G 1161 U 1405 ; WX 724 ; N uni057D ; G 1162 U 1406 ; WX 724 ; N uni057E ; G 1163 U 1407 ; WX 1040 ; N uni057F ; G 1164 U 1408 ; WX 724 ; N uni0580 ; G 1165 U 1409 ; WX 713 ; N uni0581 ; G 1166 U 1410 ; WX 493 ; N uni0582 ; G 1167 U 1411 ; WX 1040 ; N uni0583 ; G 1168 U 1412 ; WX 734 ; N uni0584 ; G 1169 U 1413 ; WX 693 ; N uni0585 ; G 1170 U 1414 ; WX 956 ; N uni0586 ; G 1171 U 1415 ; WX 833 ; N uni0587 ; G 1172 U 1417 ; WX 340 ; N uni0589 ; G 1173 U 1418 ; WX 388 ; N uni058A ; G 1174 U 3647 ; WX 696 ; N uni0E3F ; G 1175 U 4256 ; WX 755 ; N uni10A0 ; G 1176 U 4257 ; WX 936 ; N uni10A1 ; G 1177 U 4258 ; WX 866 ; N uni10A2 ; G 1178 U 4259 ; WX 874 ; N uni10A3 ; G 1179 U 4260 ; WX 781 ; N uni10A4 ; G 1180 U 4261 ; WX 1078 ; N uni10A5 ; G 1181 U 4262 ; WX 1014 ; N uni10A6 ; G 1182 U 4263 ; WX 1213 ; N uni10A7 ; G 1183 U 4264 ; WX 643 ; N uni10A8 ; G 1184 U 4265 ; WX 818 ; N uni10A9 ; G 1185 U 4266 ; WX 1051 ; N uni10AA ; G 1186 U 4267 ; WX 1051 ; N uni10AB ; G 1187 U 4268 ; WX 796 ; N uni10AC ; G 1188 U 4269 ; WX 1135 ; N uni10AD ; G 1189 U 4270 ; WX 969 ; N uni10AE ; G 1190 U 4271 ; WX 902 ; N uni10AF ; G 1191 U 4272 ; WX 1109 ; N uni10B0 ; G 1192 U 4273 ; WX 792 ; N uni10B1 ; G 1193 U 4274 ; WX 756 ; N uni10B2 ; G 1194 U 4275 ; WX 1076 ; N uni10B3 ; G 1195 U 4276 ; WX 976 ; N uni10B4 ; G 1196 U 4277 ; WX 1066 ; N uni10B5 ; G 1197 U 4278 ; WX 811 ; N uni10B6 ; G 1198 U 4279 ; WX 833 ; N uni10B7 ; G 1199 U 4280 ; WX 821 ; N uni10B8 ; G 1200 U 4281 ; WX 833 ; N uni10B9 ; G 1201 U 4282 ; WX 908 ; N uni10BA ; G 1202 U 4283 ; WX 1077 ; N uni10BB ; G 1203 U 4284 ; WX 769 ; N uni10BC ; G 1204 U 4285 ; WX 822 ; N uni10BD ; G 1205 U 4286 ; WX 813 ; N uni10BE ; G 1206 U 4287 ; WX 1111 ; N uni10BF ; G 1207 U 4288 ; WX 1123 ; N uni10C0 ; G 1208 U 4289 ; WX 802 ; N uni10C1 ; G 1209 U 4290 ; WX 892 ; N uni10C2 ; G 1210 U 4291 ; WX 802 ; N uni10C3 ; G 1211 U 4292 ; WX 880 ; N uni10C4 ; G 1212 U 4293 ; WX 1063 ; N uni10C5 ; G 1213 U 4304 ; WX 594 ; N uni10D0 ; G 1214 U 4305 ; WX 625 ; N uni10D1 ; G 1215 U 4306 ; WX 643 ; N uni10D2 ; G 1216 U 4307 ; WX 887 ; N uni10D3 ; G 1217 U 4308 ; WX 615 ; N uni10D4 ; G 1218 U 4309 ; WX 611 ; N uni10D5 ; G 1219 U 4310 ; WX 667 ; N uni10D6 ; G 1220 U 4311 ; WX 915 ; N uni10D7 ; G 1221 U 4312 ; WX 613 ; N uni10D8 ; G 1222 U 4313 ; WX 600 ; N uni10D9 ; G 1223 U 4314 ; WX 1120 ; N uni10DA ; G 1224 U 4315 ; WX 640 ; N uni10DB ; G 1225 U 4316 ; WX 640 ; N uni10DC ; G 1226 U 4317 ; WX 879 ; N uni10DD ; G 1227 U 4318 ; WX 624 ; N uni10DE ; G 1228 U 4319 ; WX 634 ; N uni10DF ; G 1229 U 4320 ; WX 877 ; N uni10E0 ; G 1230 U 4321 ; WX 666 ; N uni10E1 ; G 1231 U 4322 ; WX 780 ; N uni10E2 ; G 1232 U 4323 ; WX 751 ; N uni10E3 ; G 1233 U 4324 ; WX 869 ; N uni10E4 ; G 1234 U 4325 ; WX 639 ; N uni10E5 ; G 1235 U 4326 ; WX 912 ; N uni10E6 ; G 1236 U 4327 ; WX 622 ; N uni10E7 ; G 1237 U 4328 ; WX 647 ; N uni10E8 ; G 1238 U 4329 ; WX 640 ; N uni10E9 ; G 1239 U 4330 ; WX 729 ; N uni10EA ; G 1240 U 4331 ; WX 641 ; N uni10EB ; G 1241 U 4332 ; WX 630 ; N uni10EC ; G 1242 U 4333 ; WX 629 ; N uni10ED ; G 1243 U 4334 ; WX 670 ; N uni10EE ; G 1244 U 4335 ; WX 753 ; N uni10EF ; G 1245 U 4336 ; WX 625 ; N uni10F0 ; G 1246 U 4337 ; WX 657 ; N uni10F1 ; G 1247 U 4338 ; WX 625 ; N uni10F2 ; G 1248 U 4339 ; WX 625 ; N uni10F3 ; G 1249 U 4340 ; WX 624 ; N uni10F4 ; G 1250 U 4341 ; WX 670 ; N uni10F5 ; G 1251 U 4342 ; WX 940 ; N uni10F6 ; G 1252 U 4343 ; WX 680 ; N uni10F7 ; G 1253 U 4344 ; WX 636 ; N uni10F8 ; G 1254 U 4345 ; WX 672 ; N uni10F9 ; G 1255 U 4346 ; WX 625 ; N uni10FA ; G 1256 U 4347 ; WX 588 ; N uni10FB ; G 1257 U 4348 ; WX 354 ; N uni10FC ; G 1258 U 7424 ; WX 641 ; N uni1D00 ; G 1259 U 7425 ; WX 892 ; N uni1D01 ; G 1260 U 7426 ; WX 940 ; N uni1D02 ; G 1261 U 7427 ; WX 695 ; N uni1D03 ; G 1262 U 7428 ; WX 609 ; N uni1D04 ; G 1263 U 7429 ; WX 675 ; N uni1D05 ; G 1264 U 7430 ; WX 675 ; N uni1D06 ; G 1265 U 7431 ; WX 617 ; N uni1D07 ; G 1266 U 7432 ; WX 509 ; N uni1D08 ; G 1267 U 7433 ; WX 320 ; N uni1D09 ; G 1268 U 7434 ; WX 561 ; N uni1D0A ; G 1269 U 7435 ; WX 722 ; N uni1D0B ; G 1270 U 7436 ; WX 617 ; N uni1D0C ; G 1271 U 7437 ; WX 869 ; N uni1D0D ; G 1272 U 7438 ; WX 737 ; N uni1D0E ; G 1273 U 7439 ; WX 667 ; N uni1D0F ; G 1274 U 7440 ; WX 609 ; N uni1D10 ; G 1275 U 7441 ; WX 628 ; N uni1D11 ; G 1276 U 7442 ; WX 628 ; N uni1D12 ; G 1277 U 7443 ; WX 667 ; N uni1D13 ; G 1278 U 7444 ; WX 989 ; N uni1D14 ; G 1279 U 7445 ; WX 598 ; N uni1D15 ; G 1280 U 7446 ; WX 667 ; N uni1D16 ; G 1281 U 7447 ; WX 667 ; N uni1D17 ; G 1282 U 7448 ; WX 586 ; N uni1D18 ; G 1283 U 7449 ; WX 801 ; N uni1D19 ; G 1284 U 7450 ; WX 801 ; N uni1D1A ; G 1285 U 7451 ; WX 620 ; N uni1D1B ; G 1286 U 7452 ; WX 647 ; N uni1D1C ; G 1287 U 7453 ; WX 664 ; N uni1D1D ; G 1288 U 7454 ; WX 923 ; N uni1D1E ; G 1289 U 7455 ; WX 655 ; N uni1D1F ; G 1290 U 7456 ; WX 581 ; N uni1D20 ; G 1291 U 7457 ; WX 861 ; N uni1D21 ; G 1292 U 7458 ; WX 568 ; N uni1D22 ; G 1293 U 7459 ; WX 568 ; N uni1D23 ; G 1294 U 7460 ; WX 588 ; N uni1D24 ; G 1295 U 7461 ; WX 802 ; N uni1D25 ; G 1296 U 7462 ; WX 586 ; N uni1D26 ; G 1297 U 7463 ; WX 641 ; N uni1D27 ; G 1298 U 7464 ; WX 732 ; N uni1D28 ; G 1299 U 7465 ; WX 586 ; N uni1D29 ; G 1300 U 7466 ; WX 854 ; N uni1D2A ; G 1301 U 7467 ; WX 705 ; N uni1D2B ; G 1302 U 7468 ; WX 489 ; N uni1D2C ; G 1303 U 7469 ; WX 651 ; N uni1D2D ; G 1304 U 7470 ; WX 532 ; N uni1D2E ; G 1305 U 7471 ; WX 532 ; N uni1D2F ; G 1306 U 7472 ; WX 546 ; N uni1D30 ; G 1307 U 7473 ; WX 480 ; N uni1D31 ; G 1308 U 7474 ; WX 480 ; N uni1D32 ; G 1309 U 7475 ; WX 538 ; N uni1D33 ; G 1310 U 7476 ; WX 595 ; N uni1D34 ; G 1311 U 7477 ; WX 294 ; N uni1D35 ; G 1312 U 7478 ; WX 298 ; N uni1D36 ; G 1313 U 7479 ; WX 547 ; N uni1D37 ; G 1314 U 7480 ; WX 443 ; N uni1D38 ; G 1315 U 7481 ; WX 697 ; N uni1D39 ; G 1316 U 7482 ; WX 576 ; N uni1D3A ; G 1317 U 7483 ; WX 606 ; N uni1D3B ; G 1318 U 7484 ; WX 548 ; N uni1D3C ; G 1319 U 7485 ; WX 442 ; N uni1D3D ; G 1320 U 7486 ; WX 474 ; N uni1D3E ; G 1321 U 7487 ; WX 523 ; N uni1D3F ; G 1322 U 7488 ; WX 455 ; N uni1D40 ; G 1323 U 7489 ; WX 469 ; N uni1D41 ; G 1324 U 7490 ; WX 549 ; N uni1D42 ; G 1325 U 7491 ; WX 466 ; N uni1D43 ; G 1326 U 7492 ; WX 466 ; N uni1D44 ; G 1327 U 7493 ; WX 498 ; N uni1D45 ; G 1328 U 7494 ; WX 657 ; N uni1D46 ; G 1329 U 7495 ; WX 499 ; N uni1D47 ; G 1330 U 7496 ; WX 498 ; N uni1D48 ; G 1331 U 7497 ; WX 444 ; N uni1D49 ; G 1332 U 7498 ; WX 444 ; N uni1D4A ; G 1333 U 7499 ; WX 412 ; N uni1D4B ; G 1334 U 7500 ; WX 412 ; N uni1D4C ; G 1335 U 7501 ; WX 498 ; N uni1D4D ; G 1336 U 7502 ; WX 300 ; N uni1D4E ; G 1337 U 7503 ; WX 523 ; N uni1D4F ; G 1338 U 7504 ; WX 729 ; N uni1D50 ; G 1339 U 7505 ; WX 473 ; N uni1D51 ; G 1340 U 7506 ; WX 467 ; N uni1D52 ; G 1341 U 7507 ; WX 427 ; N uni1D53 ; G 1342 U 7508 ; WX 467 ; N uni1D54 ; G 1343 U 7509 ; WX 467 ; N uni1D55 ; G 1344 U 7510 ; WX 499 ; N uni1D56 ; G 1345 U 7511 ; WX 371 ; N uni1D57 ; G 1346 U 7512 ; WX 520 ; N uni1D58 ; G 1347 U 7513 ; WX 418 ; N uni1D59 ; G 1348 U 7514 ; WX 729 ; N uni1D5A ; G 1349 U 7515 ; WX 491 ; N uni1D5B ; G 1350 U 7516 ; WX 505 ; N uni1D5C ; G 1351 U 7517 ; WX 418 ; N uni1D5D ; G 1352 U 7518 ; WX 416 ; N uni1D5E ; G 1353 U 7519 ; WX 420 ; N uni1D5F ; G 1354 U 7520 ; WX 570 ; N uni1D60 ; G 1355 U 7521 ; WX 414 ; N uni1D61 ; G 1356 U 7522 ; WX 239 ; N uni1D62 ; G 1357 U 7523 ; WX 414 ; N uni1D63 ; G 1358 U 7524 ; WX 520 ; N uni1D64 ; G 1359 U 7525 ; WX 491 ; N uni1D65 ; G 1360 U 7526 ; WX 418 ; N uni1D66 ; G 1361 U 7527 ; WX 416 ; N uni1D67 ; G 1362 U 7528 ; WX 419 ; N uni1D68 ; G 1363 U 7529 ; WX 570 ; N uni1D69 ; G 1364 U 7530 ; WX 414 ; N uni1D6A ; G 1365 U 7531 ; WX 1041 ; N uni1D6B ; G 1366 U 7543 ; WX 640 ; N uni1D77 ; G 1367 U 7544 ; WX 595 ; N uni1D78 ; G 1368 U 7547 ; WX 380 ; N uni1D7B ; G 1369 U 7548 ; WX 380 ; N uni1D7C ; G 1370 U 7549 ; WX 699 ; N uni1D7D ; G 1371 U 7550 ; WX 647 ; N uni1D7E ; G 1372 U 7551 ; WX 679 ; N uni1D7F ; G 1373 U 7557 ; WX 380 ; N uni1D85 ; G 1374 U 7579 ; WX 498 ; N uni1D9B ; G 1375 U 7580 ; WX 427 ; N uni1D9C ; G 1376 U 7581 ; WX 427 ; N uni1D9D ; G 1377 U 7582 ; WX 467 ; N uni1D9E ; G 1378 U 7583 ; WX 412 ; N uni1D9F ; G 1379 U 7584 ; WX 383 ; N uni1DA0 ; G 1380 U 7585 ; WX 373 ; N uni1DA1 ; G 1381 U 7586 ; WX 498 ; N uni1DA2 ; G 1382 U 7587 ; WX 522 ; N uni1DA3 ; G 1383 U 7588 ; WX 300 ; N uni1DA4 ; G 1384 U 7589 ; WX 307 ; N uni1DA5 ; G 1385 U 7590 ; WX 300 ; N uni1DA6 ; G 1386 U 7591 ; WX 300 ; N uni1DA7 ; G 1387 U 7592 ; WX 370 ; N uni1DA8 ; G 1388 U 7593 ; WX 368 ; N uni1DA9 ; G 1389 U 7594 ; WX 321 ; N uni1DAA ; G 1390 U 7595 ; WX 430 ; N uni1DAB ; G 1391 U 7596 ; WX 682 ; N uni1DAC ; G 1392 U 7597 ; WX 729 ; N uni1DAD ; G 1393 U 7598 ; WX 588 ; N uni1DAE ; G 1394 U 7599 ; WX 587 ; N uni1DAF ; G 1395 U 7600 ; WX 472 ; N uni1DB0 ; G 1396 U 7601 ; WX 467 ; N uni1DB1 ; G 1397 U 7602 ; WX 522 ; N uni1DB2 ; G 1398 U 7603 ; WX 400 ; N uni1DB3 ; G 1399 U 7604 ; WX 387 ; N uni1DB4 ; G 1400 U 7605 ; WX 371 ; N uni1DB5 ; G 1401 U 7606 ; WX 520 ; N uni1DB6 ; G 1402 U 7607 ; WX 475 ; N uni1DB7 ; G 1403 U 7608 ; WX 408 ; N uni1DB8 ; G 1404 U 7609 ; WX 489 ; N uni1DB9 ; G 1405 U 7610 ; WX 491 ; N uni1DBA ; G 1406 U 7611 ; WX 412 ; N uni1DBB ; G 1407 U 7612 ; WX 527 ; N uni1DBC ; G 1408 U 7613 ; WX 412 ; N uni1DBD ; G 1409 U 7614 ; WX 452 ; N uni1DBE ; G 1410 U 7615 ; WX 467 ; N uni1DBF ; G 1411 U 7620 ; WX 0 ; N uni1DC4 ; G 1412 U 7621 ; WX 0 ; N uni1DC5 ; G 1413 U 7622 ; WX 0 ; N uni1DC6 ; G 1414 U 7623 ; WX 0 ; N uni1DC7 ; G 1415 U 7624 ; WX 0 ; N uni1DC8 ; G 1416 U 7625 ; WX 0 ; N uni1DC9 ; G 1417 U 7680 ; WX 776 ; N uni1E00 ; G 1418 U 7681 ; WX 648 ; N uni1E01 ; G 1419 U 7682 ; WX 845 ; N uni1E02 ; G 1420 U 7683 ; WX 699 ; N uni1E03 ; G 1421 U 7684 ; WX 845 ; N uni1E04 ; G 1422 U 7685 ; WX 699 ; N uni1E05 ; G 1423 U 7686 ; WX 845 ; N uni1E06 ; G 1424 U 7687 ; WX 699 ; N uni1E07 ; G 1425 U 7688 ; WX 796 ; N uni1E08 ; G 1426 U 7689 ; WX 609 ; N uni1E09 ; G 1427 U 7690 ; WX 867 ; N uni1E0A ; G 1428 U 7691 ; WX 699 ; N uni1E0B ; G 1429 U 7692 ; WX 867 ; N uni1E0C ; G 1430 U 7693 ; WX 699 ; N uni1E0D ; G 1431 U 7694 ; WX 867 ; N uni1E0E ; G 1432 U 7695 ; WX 699 ; N uni1E0F ; G 1433 U 7696 ; WX 867 ; N uni1E10 ; G 1434 U 7697 ; WX 699 ; N uni1E11 ; G 1435 U 7698 ; WX 867 ; N uni1E12 ; G 1436 U 7699 ; WX 699 ; N uni1E13 ; G 1437 U 7700 ; WX 762 ; N uni1E14 ; G 1438 U 7701 ; WX 636 ; N uni1E15 ; G 1439 U 7702 ; WX 762 ; N uni1E16 ; G 1440 U 7703 ; WX 636 ; N uni1E17 ; G 1441 U 7704 ; WX 762 ; N uni1E18 ; G 1442 U 7705 ; WX 636 ; N uni1E19 ; G 1443 U 7706 ; WX 762 ; N uni1E1A ; G 1444 U 7707 ; WX 636 ; N uni1E1B ; G 1445 U 7708 ; WX 762 ; N uni1E1C ; G 1446 U 7709 ; WX 636 ; N uni1E1D ; G 1447 U 7710 ; WX 710 ; N uni1E1E ; G 1448 U 7711 ; WX 430 ; N uni1E1F ; G 1449 U 7712 ; WX 854 ; N uni1E20 ; G 1450 U 7713 ; WX 699 ; N uni1E21 ; G 1451 U 7714 ; WX 945 ; N uni1E22 ; G 1452 U 7715 ; WX 727 ; N uni1E23 ; G 1453 U 7716 ; WX 945 ; N uni1E24 ; G 1454 U 7717 ; WX 727 ; N uni1E25 ; G 1455 U 7718 ; WX 945 ; N uni1E26 ; G 1456 U 7719 ; WX 727 ; N uni1E27 ; G 1457 U 7720 ; WX 945 ; N uni1E28 ; G 1458 U 7721 ; WX 727 ; N uni1E29 ; G 1459 U 7722 ; WX 945 ; N uni1E2A ; G 1460 U 7723 ; WX 727 ; N uni1E2B ; G 1461 U 7724 ; WX 468 ; N uni1E2C ; G 1462 U 7725 ; WX 380 ; N uni1E2D ; G 1463 U 7726 ; WX 468 ; N uni1E2E ; G 1464 U 7727 ; WX 380 ; N uni1E2F ; G 1465 U 7728 ; WX 869 ; N uni1E30 ; G 1466 U 7729 ; WX 693 ; N uni1E31 ; G 1467 U 7730 ; WX 869 ; N uni1E32 ; G 1468 U 7731 ; WX 693 ; N uni1E33 ; G 1469 U 7732 ; WX 869 ; N uni1E34 ; G 1470 U 7733 ; WX 693 ; N uni1E35 ; G 1471 U 7734 ; WX 703 ; N uni1E36 ; G 1472 U 7735 ; WX 380 ; N uni1E37 ; G 1473 U 7736 ; WX 703 ; N uni1E38 ; G 1474 U 7737 ; WX 380 ; N uni1E39 ; G 1475 U 7738 ; WX 703 ; N uni1E3A ; G 1476 U 7739 ; WX 380 ; N uni1E3B ; G 1477 U 7740 ; WX 703 ; N uni1E3C ; G 1478 U 7741 ; WX 380 ; N uni1E3D ; G 1479 U 7742 ; WX 1107 ; N uni1E3E ; G 1480 U 7743 ; WX 1058 ; N uni1E3F ; G 1481 U 7744 ; WX 1107 ; N uni1E40 ; G 1482 U 7745 ; WX 1058 ; N uni1E41 ; G 1483 U 7746 ; WX 1107 ; N uni1E42 ; G 1484 U 7747 ; WX 1058 ; N uni1E43 ; G 1485 U 7748 ; WX 914 ; N uni1E44 ; G 1486 U 7749 ; WX 727 ; N uni1E45 ; G 1487 U 7750 ; WX 914 ; N uni1E46 ; G 1488 U 7751 ; WX 727 ; N uni1E47 ; G 1489 U 7752 ; WX 914 ; N uni1E48 ; G 1490 U 7753 ; WX 727 ; N uni1E49 ; G 1491 U 7754 ; WX 914 ; N uni1E4A ; G 1492 U 7755 ; WX 727 ; N uni1E4B ; G 1493 U 7756 ; WX 871 ; N uni1E4C ; G 1494 U 7757 ; WX 667 ; N uni1E4D ; G 1495 U 7758 ; WX 871 ; N uni1E4E ; G 1496 U 7759 ; WX 667 ; N uni1E4F ; G 1497 U 7760 ; WX 871 ; N uni1E50 ; G 1498 U 7761 ; WX 667 ; N uni1E51 ; G 1499 U 7762 ; WX 871 ; N uni1E52 ; G 1500 U 7763 ; WX 667 ; N uni1E53 ; G 1501 U 7764 ; WX 752 ; N uni1E54 ; G 1502 U 7765 ; WX 699 ; N uni1E55 ; G 1503 U 7766 ; WX 752 ; N uni1E56 ; G 1504 U 7767 ; WX 699 ; N uni1E57 ; G 1505 U 7768 ; WX 831 ; N uni1E58 ; G 1506 U 7769 ; WX 527 ; N uni1E59 ; G 1507 U 7770 ; WX 831 ; N uni1E5A ; G 1508 U 7771 ; WX 527 ; N uni1E5B ; G 1509 U 7772 ; WX 831 ; N uni1E5C ; G 1510 U 7773 ; WX 527 ; N uni1E5D ; G 1511 U 7774 ; WX 831 ; N uni1E5E ; G 1512 U 7775 ; WX 527 ; N uni1E5F ; G 1513 U 7776 ; WX 722 ; N uni1E60 ; G 1514 U 7777 ; WX 563 ; N uni1E61 ; G 1515 U 7778 ; WX 722 ; N uni1E62 ; G 1516 U 7779 ; WX 563 ; N uni1E63 ; G 1517 U 7780 ; WX 722 ; N uni1E64 ; G 1518 U 7781 ; WX 563 ; N uni1E65 ; G 1519 U 7782 ; WX 722 ; N uni1E66 ; G 1520 U 7783 ; WX 563 ; N uni1E67 ; G 1521 U 7784 ; WX 722 ; N uni1E68 ; G 1522 U 7785 ; WX 563 ; N uni1E69 ; G 1523 U 7786 ; WX 744 ; N uni1E6A ; G 1524 U 7787 ; WX 462 ; N uni1E6B ; G 1525 U 7788 ; WX 744 ; N uni1E6C ; G 1526 U 7789 ; WX 462 ; N uni1E6D ; G 1527 U 7790 ; WX 744 ; N uni1E6E ; G 1528 U 7791 ; WX 462 ; N uni1E6F ; G 1529 U 7792 ; WX 744 ; N uni1E70 ; G 1530 U 7793 ; WX 462 ; N uni1E71 ; G 1531 U 7794 ; WX 872 ; N uni1E72 ; G 1532 U 7795 ; WX 727 ; N uni1E73 ; G 1533 U 7796 ; WX 872 ; N uni1E74 ; G 1534 U 7797 ; WX 727 ; N uni1E75 ; G 1535 U 7798 ; WX 872 ; N uni1E76 ; G 1536 U 7799 ; WX 727 ; N uni1E77 ; G 1537 U 7800 ; WX 872 ; N uni1E78 ; G 1538 U 7801 ; WX 727 ; N uni1E79 ; G 1539 U 7802 ; WX 872 ; N uni1E7A ; G 1540 U 7803 ; WX 727 ; N uni1E7B ; G 1541 U 7804 ; WX 776 ; N uni1E7C ; G 1542 U 7805 ; WX 581 ; N uni1E7D ; G 1543 U 7806 ; WX 776 ; N uni1E7E ; G 1544 U 7807 ; WX 581 ; N uni1E7F ; G 1545 U 7808 ; WX 1123 ; N Wgrave ; G 1546 U 7809 ; WX 861 ; N wgrave ; G 1547 U 7810 ; WX 1123 ; N Wacute ; G 1548 U 7811 ; WX 861 ; N wacute ; G 1549 U 7812 ; WX 1123 ; N Wdieresis ; G 1550 U 7813 ; WX 861 ; N wdieresis ; G 1551 U 7814 ; WX 1123 ; N uni1E86 ; G 1552 U 7815 ; WX 861 ; N uni1E87 ; G 1553 U 7816 ; WX 1123 ; N uni1E88 ; G 1554 U 7817 ; WX 861 ; N uni1E89 ; G 1555 U 7818 ; WX 776 ; N uni1E8A ; G 1556 U 7819 ; WX 596 ; N uni1E8B ; G 1557 U 7820 ; WX 776 ; N uni1E8C ; G 1558 U 7821 ; WX 596 ; N uni1E8D ; G 1559 U 7822 ; WX 714 ; N uni1E8E ; G 1560 U 7823 ; WX 581 ; N uni1E8F ; G 1561 U 7824 ; WX 730 ; N uni1E90 ; G 1562 U 7825 ; WX 568 ; N uni1E91 ; G 1563 U 7826 ; WX 730 ; N uni1E92 ; G 1564 U 7827 ; WX 568 ; N uni1E93 ; G 1565 U 7828 ; WX 730 ; N uni1E94 ; G 1566 U 7829 ; WX 568 ; N uni1E95 ; G 1567 U 7830 ; WX 727 ; N uni1E96 ; G 1568 U 7831 ; WX 462 ; N uni1E97 ; G 1569 U 7832 ; WX 861 ; N uni1E98 ; G 1570 U 7833 ; WX 581 ; N uni1E99 ; G 1571 U 7834 ; WX 1014 ; N uni1E9A ; G 1572 U 7835 ; WX 430 ; N uni1E9B ; G 1573 U 7836 ; WX 430 ; N uni1E9C ; G 1574 U 7837 ; WX 430 ; N uni1E9D ; G 1575 U 7838 ; WX 947 ; N uni1E9E ; G 1576 U 7839 ; WX 667 ; N uni1E9F ; G 1577 U 7840 ; WX 776 ; N uni1EA0 ; G 1578 U 7841 ; WX 648 ; N uni1EA1 ; G 1579 U 7842 ; WX 776 ; N uni1EA2 ; G 1580 U 7843 ; WX 648 ; N uni1EA3 ; G 1581 U 7844 ; WX 776 ; N uni1EA4 ; G 1582 U 7845 ; WX 648 ; N uni1EA5 ; G 1583 U 7846 ; WX 776 ; N uni1EA6 ; G 1584 U 7847 ; WX 648 ; N uni1EA7 ; G 1585 U 7848 ; WX 776 ; N uni1EA8 ; G 1586 U 7849 ; WX 648 ; N uni1EA9 ; G 1587 U 7850 ; WX 776 ; N uni1EAA ; G 1588 U 7851 ; WX 648 ; N uni1EAB ; G 1589 U 7852 ; WX 776 ; N uni1EAC ; G 1590 U 7853 ; WX 648 ; N uni1EAD ; G 1591 U 7854 ; WX 776 ; N uni1EAE ; G 1592 U 7855 ; WX 648 ; N uni1EAF ; G 1593 U 7856 ; WX 776 ; N uni1EB0 ; G 1594 U 7857 ; WX 648 ; N uni1EB1 ; G 1595 U 7858 ; WX 776 ; N uni1EB2 ; G 1596 U 7859 ; WX 648 ; N uni1EB3 ; G 1597 U 7860 ; WX 776 ; N uni1EB4 ; G 1598 U 7861 ; WX 648 ; N uni1EB5 ; G 1599 U 7862 ; WX 776 ; N uni1EB6 ; G 1600 U 7863 ; WX 648 ; N uni1EB7 ; G 1601 U 7864 ; WX 762 ; N uni1EB8 ; G 1602 U 7865 ; WX 636 ; N uni1EB9 ; G 1603 U 7866 ; WX 762 ; N uni1EBA ; G 1604 U 7867 ; WX 636 ; N uni1EBB ; G 1605 U 7868 ; WX 762 ; N uni1EBC ; G 1606 U 7869 ; WX 636 ; N uni1EBD ; G 1607 U 7870 ; WX 762 ; N uni1EBE ; G 1608 U 7871 ; WX 636 ; N uni1EBF ; G 1609 U 7872 ; WX 762 ; N uni1EC0 ; G 1610 U 7873 ; WX 636 ; N uni1EC1 ; G 1611 U 7874 ; WX 762 ; N uni1EC2 ; G 1612 U 7875 ; WX 636 ; N uni1EC3 ; G 1613 U 7876 ; WX 762 ; N uni1EC4 ; G 1614 U 7877 ; WX 636 ; N uni1EC5 ; G 1615 U 7878 ; WX 762 ; N uni1EC6 ; G 1616 U 7879 ; WX 636 ; N uni1EC7 ; G 1617 U 7880 ; WX 468 ; N uni1EC8 ; G 1618 U 7881 ; WX 380 ; N uni1EC9 ; G 1619 U 7882 ; WX 468 ; N uni1ECA ; G 1620 U 7883 ; WX 380 ; N uni1ECB ; G 1621 U 7884 ; WX 871 ; N uni1ECC ; G 1622 U 7885 ; WX 667 ; N uni1ECD ; G 1623 U 7886 ; WX 871 ; N uni1ECE ; G 1624 U 7887 ; WX 667 ; N uni1ECF ; G 1625 U 7888 ; WX 871 ; N uni1ED0 ; G 1626 U 7889 ; WX 667 ; N uni1ED1 ; G 1627 U 7890 ; WX 871 ; N uni1ED2 ; G 1628 U 7891 ; WX 667 ; N uni1ED3 ; G 1629 U 7892 ; WX 871 ; N uni1ED4 ; G 1630 U 7893 ; WX 667 ; N uni1ED5 ; G 1631 U 7894 ; WX 871 ; N uni1ED6 ; G 1632 U 7895 ; WX 667 ; N uni1ED7 ; G 1633 U 7896 ; WX 871 ; N uni1ED8 ; G 1634 U 7897 ; WX 667 ; N uni1ED9 ; G 1635 U 7898 ; WX 871 ; N uni1EDA ; G 1636 U 7899 ; WX 667 ; N uni1EDB ; G 1637 U 7900 ; WX 871 ; N uni1EDC ; G 1638 U 7901 ; WX 667 ; N uni1EDD ; G 1639 U 7902 ; WX 871 ; N uni1EDE ; G 1640 U 7903 ; WX 667 ; N uni1EDF ; G 1641 U 7904 ; WX 871 ; N uni1EE0 ; G 1642 U 7905 ; WX 667 ; N uni1EE1 ; G 1643 U 7906 ; WX 871 ; N uni1EE2 ; G 1644 U 7907 ; WX 667 ; N uni1EE3 ; G 1645 U 7908 ; WX 872 ; N uni1EE4 ; G 1646 U 7909 ; WX 727 ; N uni1EE5 ; G 1647 U 7910 ; WX 872 ; N uni1EE6 ; G 1648 U 7911 ; WX 727 ; N uni1EE7 ; G 1649 U 7912 ; WX 872 ; N uni1EE8 ; G 1650 U 7913 ; WX 727 ; N uni1EE9 ; G 1651 U 7914 ; WX 872 ; N uni1EEA ; G 1652 U 7915 ; WX 727 ; N uni1EEB ; G 1653 U 7916 ; WX 872 ; N uni1EEC ; G 1654 U 7917 ; WX 727 ; N uni1EED ; G 1655 U 7918 ; WX 872 ; N uni1EEE ; G 1656 U 7919 ; WX 727 ; N uni1EEF ; G 1657 U 7920 ; WX 872 ; N uni1EF0 ; G 1658 U 7921 ; WX 727 ; N uni1EF1 ; G 1659 U 7922 ; WX 714 ; N Ygrave ; G 1660 U 7923 ; WX 581 ; N ygrave ; G 1661 U 7924 ; WX 714 ; N uni1EF4 ; G 1662 U 7925 ; WX 581 ; N uni1EF5 ; G 1663 U 7926 ; WX 714 ; N uni1EF6 ; G 1664 U 7927 ; WX 581 ; N uni1EF7 ; G 1665 U 7928 ; WX 714 ; N uni1EF8 ; G 1666 U 7929 ; WX 581 ; N uni1EF9 ; G 1667 U 7930 ; WX 1078 ; N uni1EFA ; G 1668 U 7931 ; WX 701 ; N uni1EFB ; G 1669 U 7936 ; WX 770 ; N uni1F00 ; G 1670 U 7937 ; WX 770 ; N uni1F01 ; G 1671 U 7938 ; WX 770 ; N uni1F02 ; G 1672 U 7939 ; WX 770 ; N uni1F03 ; G 1673 U 7940 ; WX 770 ; N uni1F04 ; G 1674 U 7941 ; WX 770 ; N uni1F05 ; G 1675 U 7942 ; WX 770 ; N uni1F06 ; G 1676 U 7943 ; WX 770 ; N uni1F07 ; G 1677 U 7944 ; WX 776 ; N uni1F08 ; G 1678 U 7945 ; WX 776 ; N uni1F09 ; G 1679 U 7946 ; WX 978 ; N uni1F0A ; G 1680 U 7947 ; WX 978 ; N uni1F0B ; G 1681 U 7948 ; WX 832 ; N uni1F0C ; G 1682 U 7949 ; WX 849 ; N uni1F0D ; G 1683 U 7950 ; WX 776 ; N uni1F0E ; G 1684 U 7951 ; WX 776 ; N uni1F0F ; G 1685 U 7952 ; WX 608 ; N uni1F10 ; G 1686 U 7953 ; WX 608 ; N uni1F11 ; G 1687 U 7954 ; WX 608 ; N uni1F12 ; G 1688 U 7955 ; WX 608 ; N uni1F13 ; G 1689 U 7956 ; WX 608 ; N uni1F14 ; G 1690 U 7957 ; WX 608 ; N uni1F15 ; G 1691 U 7960 ; WX 917 ; N uni1F18 ; G 1692 U 7961 ; WX 909 ; N uni1F19 ; G 1693 U 7962 ; WX 1169 ; N uni1F1A ; G 1694 U 7963 ; WX 1169 ; N uni1F1B ; G 1695 U 7964 ; WX 1093 ; N uni1F1C ; G 1696 U 7965 ; WX 1120 ; N uni1F1D ; G 1697 U 7968 ; WX 727 ; N uni1F20 ; G 1698 U 7969 ; WX 727 ; N uni1F21 ; G 1699 U 7970 ; WX 727 ; N uni1F22 ; G 1700 U 7971 ; WX 727 ; N uni1F23 ; G 1701 U 7972 ; WX 727 ; N uni1F24 ; G 1702 U 7973 ; WX 727 ; N uni1F25 ; G 1703 U 7974 ; WX 727 ; N uni1F26 ; G 1704 U 7975 ; WX 727 ; N uni1F27 ; G 1705 U 7976 ; WX 1100 ; N uni1F28 ; G 1706 U 7977 ; WX 1094 ; N uni1F29 ; G 1707 U 7978 ; WX 1358 ; N uni1F2A ; G 1708 U 7979 ; WX 1361 ; N uni1F2B ; G 1709 U 7980 ; WX 1279 ; N uni1F2C ; G 1710 U 7981 ; WX 1308 ; N uni1F2D ; G 1711 U 7982 ; WX 1197 ; N uni1F2E ; G 1712 U 7983 ; WX 1194 ; N uni1F2F ; G 1713 U 7984 ; WX 484 ; N uni1F30 ; G 1714 U 7985 ; WX 484 ; N uni1F31 ; G 1715 U 7986 ; WX 484 ; N uni1F32 ; G 1716 U 7987 ; WX 484 ; N uni1F33 ; G 1717 U 7988 ; WX 484 ; N uni1F34 ; G 1718 U 7989 ; WX 484 ; N uni1F35 ; G 1719 U 7990 ; WX 484 ; N uni1F36 ; G 1720 U 7991 ; WX 484 ; N uni1F37 ; G 1721 U 7992 ; WX 629 ; N uni1F38 ; G 1722 U 7993 ; WX 617 ; N uni1F39 ; G 1723 U 7994 ; WX 878 ; N uni1F3A ; G 1724 U 7995 ; WX 881 ; N uni1F3B ; G 1725 U 7996 ; WX 799 ; N uni1F3C ; G 1726 U 7997 ; WX 831 ; N uni1F3D ; G 1727 U 7998 ; WX 723 ; N uni1F3E ; G 1728 U 7999 ; WX 714 ; N uni1F3F ; G 1729 U 8000 ; WX 667 ; N uni1F40 ; G 1730 U 8001 ; WX 667 ; N uni1F41 ; G 1731 U 8002 ; WX 667 ; N uni1F42 ; G 1732 U 8003 ; WX 667 ; N uni1F43 ; G 1733 U 8004 ; WX 667 ; N uni1F44 ; G 1734 U 8005 ; WX 667 ; N uni1F45 ; G 1735 U 8008 ; WX 900 ; N uni1F48 ; G 1736 U 8009 ; WX 935 ; N uni1F49 ; G 1737 U 8010 ; WX 1240 ; N uni1F4A ; G 1738 U 8011 ; WX 1237 ; N uni1F4B ; G 1739 U 8012 ; WX 1035 ; N uni1F4C ; G 1740 U 8013 ; WX 1066 ; N uni1F4D ; G 1741 U 8016 ; WX 694 ; N uni1F50 ; G 1742 U 8017 ; WX 694 ; N uni1F51 ; G 1743 U 8018 ; WX 694 ; N uni1F52 ; G 1744 U 8019 ; WX 694 ; N uni1F53 ; G 1745 U 8020 ; WX 694 ; N uni1F54 ; G 1746 U 8021 ; WX 694 ; N uni1F55 ; G 1747 U 8022 ; WX 694 ; N uni1F56 ; G 1748 U 8023 ; WX 694 ; N uni1F57 ; G 1749 U 8025 ; WX 922 ; N uni1F59 ; G 1750 U 8027 ; WX 1186 ; N uni1F5B ; G 1751 U 8029 ; WX 1133 ; N uni1F5D ; G 1752 U 8031 ; WX 1019 ; N uni1F5F ; G 1753 U 8032 ; WX 952 ; N uni1F60 ; G 1754 U 8033 ; WX 952 ; N uni1F61 ; G 1755 U 8034 ; WX 952 ; N uni1F62 ; G 1756 U 8035 ; WX 952 ; N uni1F63 ; G 1757 U 8036 ; WX 952 ; N uni1F64 ; G 1758 U 8037 ; WX 952 ; N uni1F65 ; G 1759 U 8038 ; WX 952 ; N uni1F66 ; G 1760 U 8039 ; WX 952 ; N uni1F67 ; G 1761 U 8040 ; WX 931 ; N uni1F68 ; G 1762 U 8041 ; WX 963 ; N uni1F69 ; G 1763 U 8042 ; WX 1268 ; N uni1F6A ; G 1764 U 8043 ; WX 1274 ; N uni1F6B ; G 1765 U 8044 ; WX 1054 ; N uni1F6C ; G 1766 U 8045 ; WX 1088 ; N uni1F6D ; G 1767 U 8046 ; WX 1023 ; N uni1F6E ; G 1768 U 8047 ; WX 1060 ; N uni1F6F ; G 1769 U 8048 ; WX 770 ; N uni1F70 ; G 1770 U 8049 ; WX 770 ; N uni1F71 ; G 1771 U 8050 ; WX 608 ; N uni1F72 ; G 1772 U 8051 ; WX 608 ; N uni1F73 ; G 1773 U 8052 ; WX 727 ; N uni1F74 ; G 1774 U 8053 ; WX 727 ; N uni1F75 ; G 1775 U 8054 ; WX 484 ; N uni1F76 ; G 1776 U 8055 ; WX 484 ; N uni1F77 ; G 1777 U 8056 ; WX 667 ; N uni1F78 ; G 1778 U 8057 ; WX 667 ; N uni1F79 ; G 1779 U 8058 ; WX 694 ; N uni1F7A ; G 1780 U 8059 ; WX 694 ; N uni1F7B ; G 1781 U 8060 ; WX 952 ; N uni1F7C ; G 1782 U 8061 ; WX 952 ; N uni1F7D ; G 1783 U 8064 ; WX 770 ; N uni1F80 ; G 1784 U 8065 ; WX 770 ; N uni1F81 ; G 1785 U 8066 ; WX 770 ; N uni1F82 ; G 1786 U 8067 ; WX 770 ; N uni1F83 ; G 1787 U 8068 ; WX 770 ; N uni1F84 ; G 1788 U 8069 ; WX 770 ; N uni1F85 ; G 1789 U 8070 ; WX 770 ; N uni1F86 ; G 1790 U 8071 ; WX 770 ; N uni1F87 ; G 1791 U 8072 ; WX 776 ; N uni1F88 ; G 1792 U 8073 ; WX 776 ; N uni1F89 ; G 1793 U 8074 ; WX 978 ; N uni1F8A ; G 1794 U 8075 ; WX 978 ; N uni1F8B ; G 1795 U 8076 ; WX 832 ; N uni1F8C ; G 1796 U 8077 ; WX 849 ; N uni1F8D ; G 1797 U 8078 ; WX 776 ; N uni1F8E ; G 1798 U 8079 ; WX 776 ; N uni1F8F ; G 1799 U 8080 ; WX 727 ; N uni1F90 ; G 1800 U 8081 ; WX 727 ; N uni1F91 ; G 1801 U 8082 ; WX 727 ; N uni1F92 ; G 1802 U 8083 ; WX 727 ; N uni1F93 ; G 1803 U 8084 ; WX 727 ; N uni1F94 ; G 1804 U 8085 ; WX 727 ; N uni1F95 ; G 1805 U 8086 ; WX 727 ; N uni1F96 ; G 1806 U 8087 ; WX 727 ; N uni1F97 ; G 1807 U 8088 ; WX 1100 ; N uni1F98 ; G 1808 U 8089 ; WX 1094 ; N uni1F99 ; G 1809 U 8090 ; WX 1358 ; N uni1F9A ; G 1810 U 8091 ; WX 1361 ; N uni1F9B ; G 1811 U 8092 ; WX 1279 ; N uni1F9C ; G 1812 U 8093 ; WX 1308 ; N uni1F9D ; G 1813 U 8094 ; WX 1197 ; N uni1F9E ; G 1814 U 8095 ; WX 1194 ; N uni1F9F ; G 1815 U 8096 ; WX 952 ; N uni1FA0 ; G 1816 U 8097 ; WX 952 ; N uni1FA1 ; G 1817 U 8098 ; WX 952 ; N uni1FA2 ; G 1818 U 8099 ; WX 952 ; N uni1FA3 ; G 1819 U 8100 ; WX 952 ; N uni1FA4 ; G 1820 U 8101 ; WX 952 ; N uni1FA5 ; G 1821 U 8102 ; WX 952 ; N uni1FA6 ; G 1822 U 8103 ; WX 952 ; N uni1FA7 ; G 1823 U 8104 ; WX 931 ; N uni1FA8 ; G 1824 U 8105 ; WX 963 ; N uni1FA9 ; G 1825 U 8106 ; WX 1268 ; N uni1FAA ; G 1826 U 8107 ; WX 1274 ; N uni1FAB ; G 1827 U 8108 ; WX 1054 ; N uni1FAC ; G 1828 U 8109 ; WX 1088 ; N uni1FAD ; G 1829 U 8110 ; WX 1023 ; N uni1FAE ; G 1830 U 8111 ; WX 1060 ; N uni1FAF ; G 1831 U 8112 ; WX 770 ; N uni1FB0 ; G 1832 U 8113 ; WX 770 ; N uni1FB1 ; G 1833 U 8114 ; WX 770 ; N uni1FB2 ; G 1834 U 8115 ; WX 770 ; N uni1FB3 ; G 1835 U 8116 ; WX 770 ; N uni1FB4 ; G 1836 U 8118 ; WX 770 ; N uni1FB6 ; G 1837 U 8119 ; WX 770 ; N uni1FB7 ; G 1838 U 8120 ; WX 776 ; N uni1FB8 ; G 1839 U 8121 ; WX 776 ; N uni1FB9 ; G 1840 U 8122 ; WX 811 ; N uni1FBA ; G 1841 U 8123 ; WX 776 ; N uni1FBB ; G 1842 U 8124 ; WX 776 ; N uni1FBC ; G 1843 U 8125 ; WX 500 ; N uni1FBD ; G 1844 U 8126 ; WX 500 ; N uni1FBE ; G 1845 U 8127 ; WX 500 ; N uni1FBF ; G 1846 U 8128 ; WX 500 ; N uni1FC0 ; G 1847 U 8129 ; WX 500 ; N uni1FC1 ; G 1848 U 8130 ; WX 727 ; N uni1FC2 ; G 1849 U 8131 ; WX 727 ; N uni1FC3 ; G 1850 U 8132 ; WX 727 ; N uni1FC4 ; G 1851 U 8134 ; WX 727 ; N uni1FC6 ; G 1852 U 8135 ; WX 727 ; N uni1FC7 ; G 1853 U 8136 ; WX 1000 ; N uni1FC8 ; G 1854 U 8137 ; WX 947 ; N uni1FC9 ; G 1855 U 8138 ; WX 1191 ; N uni1FCA ; G 1856 U 8139 ; WX 1118 ; N uni1FCB ; G 1857 U 8140 ; WX 945 ; N uni1FCC ; G 1858 U 8141 ; WX 500 ; N uni1FCD ; G 1859 U 8142 ; WX 500 ; N uni1FCE ; G 1860 U 8143 ; WX 500 ; N uni1FCF ; G 1861 U 8144 ; WX 484 ; N uni1FD0 ; G 1862 U 8145 ; WX 484 ; N uni1FD1 ; G 1863 U 8146 ; WX 484 ; N uni1FD2 ; G 1864 U 8147 ; WX 484 ; N uni1FD3 ; G 1865 U 8150 ; WX 484 ; N uni1FD6 ; G 1866 U 8151 ; WX 484 ; N uni1FD7 ; G 1867 U 8152 ; WX 468 ; N uni1FD8 ; G 1868 U 8153 ; WX 468 ; N uni1FD9 ; G 1869 U 8154 ; WX 714 ; N uni1FDA ; G 1870 U 8155 ; WX 662 ; N uni1FDB ; G 1871 U 8157 ; WX 500 ; N uni1FDD ; G 1872 U 8158 ; WX 500 ; N uni1FDE ; G 1873 U 8159 ; WX 500 ; N uni1FDF ; G 1874 U 8160 ; WX 694 ; N uni1FE0 ; G 1875 U 8161 ; WX 694 ; N uni1FE1 ; G 1876 U 8162 ; WX 694 ; N uni1FE2 ; G 1877 U 8163 ; WX 694 ; N uni1FE3 ; G 1878 U 8164 ; WX 665 ; N uni1FE4 ; G 1879 U 8165 ; WX 665 ; N uni1FE5 ; G 1880 U 8166 ; WX 694 ; N uni1FE6 ; G 1881 U 8167 ; WX 694 ; N uni1FE7 ; G 1882 U 8168 ; WX 714 ; N uni1FE8 ; G 1883 U 8169 ; WX 714 ; N uni1FE9 ; G 1884 U 8170 ; WX 1019 ; N uni1FEA ; G 1885 U 8171 ; WX 953 ; N uni1FEB ; G 1886 U 8172 ; WX 910 ; N uni1FEC ; G 1887 U 8173 ; WX 500 ; N uni1FED ; G 1888 U 8174 ; WX 500 ; N uni1FEE ; G 1889 U 8175 ; WX 500 ; N uni1FEF ; G 1890 U 8178 ; WX 952 ; N uni1FF2 ; G 1891 U 8179 ; WX 952 ; N uni1FF3 ; G 1892 U 8180 ; WX 952 ; N uni1FF4 ; G 1893 U 8182 ; WX 952 ; N uni1FF6 ; G 1894 U 8183 ; WX 952 ; N uni1FF7 ; G 1895 U 8184 ; WX 1069 ; N uni1FF8 ; G 1896 U 8185 ; WX 887 ; N uni1FF9 ; G 1897 U 8186 ; WX 1101 ; N uni1FFA ; G 1898 U 8187 ; WX 911 ; N uni1FFB ; G 1899 U 8188 ; WX 890 ; N uni1FFC ; G 1900 U 8189 ; WX 500 ; N uni1FFD ; G 1901 U 8190 ; WX 500 ; N uni1FFE ; G 1902 U 8192 ; WX 500 ; N uni2000 ; G 1903 U 8193 ; WX 1000 ; N uni2001 ; G 1904 U 8194 ; WX 500 ; N uni2002 ; G 1905 U 8195 ; WX 1000 ; N uni2003 ; G 1906 U 8196 ; WX 330 ; N uni2004 ; G 1907 U 8197 ; WX 250 ; N uni2005 ; G 1908 U 8198 ; WX 167 ; N uni2006 ; G 1909 U 8199 ; WX 696 ; N uni2007 ; G 1910 U 8200 ; WX 348 ; N uni2008 ; G 1911 U 8201 ; WX 200 ; N uni2009 ; G 1912 U 8202 ; WX 100 ; N uni200A ; G 1913 U 8203 ; WX 0 ; N uni200B ; G 1914 U 8204 ; WX 0 ; N uni200C ; G 1915 U 8205 ; WX 0 ; N uni200D ; G 1916 U 8206 ; WX 0 ; N uni200E ; G 1917 U 8207 ; WX 0 ; N uni200F ; G 1918 U 8208 ; WX 415 ; N uni2010 ; G 1919 U 8209 ; WX 415 ; N uni2011 ; G 1920 U 8210 ; WX 696 ; N figuredash ; G 1921 U 8211 ; WX 500 ; N endash ; G 1922 U 8212 ; WX 1000 ; N emdash ; G 1923 U 8213 ; WX 1000 ; N uni2015 ; G 1924 U 8214 ; WX 500 ; N uni2016 ; G 1925 U 8215 ; WX 500 ; N underscoredbl ; G 1926 U 8216 ; WX 348 ; N quoteleft ; G 1927 U 8217 ; WX 348 ; N quoteright ; G 1928 U 8218 ; WX 348 ; N quotesinglbase ; G 1929 U 8219 ; WX 348 ; N quotereversed ; G 1930 U 8220 ; WX 575 ; N quotedblleft ; G 1931 U 8221 ; WX 575 ; N quotedblright ; G 1932 U 8222 ; WX 575 ; N quotedblbase ; G 1933 U 8223 ; WX 575 ; N uni201F ; G 1934 U 8224 ; WX 523 ; N dagger ; G 1935 U 8225 ; WX 523 ; N daggerdbl ; G 1936 U 8226 ; WX 639 ; N bullet ; G 1937 U 8227 ; WX 639 ; N uni2023 ; G 1938 U 8228 ; WX 348 ; N onedotenleader ; G 1939 U 8229 ; WX 674 ; N twodotenleader ; G 1940 U 8230 ; WX 1000 ; N ellipsis ; G 1941 U 8234 ; WX 0 ; N uni202A ; G 1942 U 8235 ; WX 0 ; N uni202B ; G 1943 U 8236 ; WX 0 ; N uni202C ; G 1944 U 8237 ; WX 0 ; N uni202D ; G 1945 U 8238 ; WX 0 ; N uni202E ; G 1946 U 8239 ; WX 200 ; N uni202F ; G 1947 U 8240 ; WX 1385 ; N perthousand ; G 1948 U 8241 ; WX 1820 ; N uni2031 ; G 1949 U 8242 ; WX 264 ; N minute ; G 1950 U 8243 ; WX 447 ; N second ; G 1951 U 8244 ; WX 630 ; N uni2034 ; G 1952 U 8245 ; WX 264 ; N uni2035 ; G 1953 U 8246 ; WX 447 ; N uni2036 ; G 1954 U 8247 ; WX 630 ; N uni2037 ; G 1955 U 8248 ; WX 733 ; N uni2038 ; G 1956 U 8249 ; WX 400 ; N guilsinglleft ; G 1957 U 8250 ; WX 400 ; N guilsinglright ; G 1958 U 8252 ; WX 629 ; N exclamdbl ; G 1959 U 8253 ; WX 586 ; N uni203D ; G 1960 U 8254 ; WX 500 ; N uni203E ; G 1961 U 8258 ; WX 1023 ; N uni2042 ; G 1962 U 8260 ; WX 167 ; N fraction ; G 1963 U 8261 ; WX 473 ; N uni2045 ; G 1964 U 8262 ; WX 473 ; N uni2046 ; G 1965 U 8263 ; WX 1082 ; N uni2047 ; G 1966 U 8264 ; WX 856 ; N uni2048 ; G 1967 U 8265 ; WX 856 ; N uni2049 ; G 1968 U 8267 ; WX 636 ; N uni204B ; G 1969 U 8268 ; WX 500 ; N uni204C ; G 1970 U 8269 ; WX 500 ; N uni204D ; G 1971 U 8270 ; WX 523 ; N uni204E ; G 1972 U 8271 ; WX 369 ; N uni204F ; G 1973 U 8273 ; WX 523 ; N uni2051 ; G 1974 U 8274 ; WX 556 ; N uni2052 ; G 1975 U 8275 ; WX 1000 ; N uni2053 ; G 1976 U 8279 ; WX 813 ; N uni2057 ; G 1977 U 8287 ; WX 222 ; N uni205F ; G 1978 U 8288 ; WX 0 ; N uni2060 ; G 1979 U 8289 ; WX 0 ; N uni2061 ; G 1980 U 8290 ; WX 0 ; N uni2062 ; G 1981 U 8291 ; WX 0 ; N uni2063 ; G 1982 U 8292 ; WX 0 ; N uni2064 ; G 1983 U 8298 ; WX 0 ; N uni206A ; G 1984 U 8299 ; WX 0 ; N uni206B ; G 1985 U 8300 ; WX 0 ; N uni206C ; G 1986 U 8301 ; WX 0 ; N uni206D ; G 1987 U 8302 ; WX 0 ; N uni206E ; G 1988 U 8303 ; WX 0 ; N uni206F ; G 1989 U 8304 ; WX 438 ; N uni2070 ; G 1990 U 8305 ; WX 239 ; N uni2071 ; G 1991 U 8308 ; WX 438 ; N uni2074 ; G 1992 U 8309 ; WX 438 ; N uni2075 ; G 1993 U 8310 ; WX 438 ; N uni2076 ; G 1994 U 8311 ; WX 438 ; N uni2077 ; G 1995 U 8312 ; WX 438 ; N uni2078 ; G 1996 U 8313 ; WX 438 ; N uni2079 ; G 1997 U 8314 ; WX 528 ; N uni207A ; G 1998 U 8315 ; WX 528 ; N uni207B ; G 1999 U 8316 ; WX 528 ; N uni207C ; G 2000 U 8317 ; WX 298 ; N uni207D ; G 2001 U 8318 ; WX 298 ; N uni207E ; G 2002 U 8319 ; WX 519 ; N uni207F ; G 2003 U 8320 ; WX 438 ; N uni2080 ; G 2004 U 8321 ; WX 438 ; N uni2081 ; G 2005 U 8322 ; WX 438 ; N uni2082 ; G 2006 U 8323 ; WX 438 ; N uni2083 ; G 2007 U 8324 ; WX 438 ; N uni2084 ; G 2008 U 8325 ; WX 438 ; N uni2085 ; G 2009 U 8326 ; WX 438 ; N uni2086 ; G 2010 U 8327 ; WX 438 ; N uni2087 ; G 2011 U 8328 ; WX 438 ; N uni2088 ; G 2012 U 8329 ; WX 438 ; N uni2089 ; G 2013 U 8330 ; WX 528 ; N uni208A ; G 2014 U 8331 ; WX 528 ; N uni208B ; G 2015 U 8332 ; WX 528 ; N uni208C ; G 2016 U 8333 ; WX 298 ; N uni208D ; G 2017 U 8334 ; WX 298 ; N uni208E ; G 2018 U 8336 ; WX 466 ; N uni2090 ; G 2019 U 8337 ; WX 444 ; N uni2091 ; G 2020 U 8338 ; WX 467 ; N uni2092 ; G 2021 U 8339 ; WX 475 ; N uni2093 ; G 2022 U 8340 ; WX 444 ; N uni2094 ; G 2023 U 8341 ; WX 521 ; N uni2095 ; G 2024 U 8342 ; WX 523 ; N uni2096 ; G 2025 U 8343 ; WX 292 ; N uni2097 ; G 2026 U 8344 ; WX 729 ; N uni2098 ; G 2027 U 8345 ; WX 519 ; N uni2099 ; G 2028 U 8346 ; WX 499 ; N uni209A ; G 2029 U 8347 ; WX 395 ; N uni209B ; G 2030 U 8348 ; WX 371 ; N uni209C ; G 2031 U 8358 ; WX 696 ; N uni20A6 ; G 2032 U 8364 ; WX 696 ; N Euro ; G 2033 U 8367 ; WX 1155 ; N uni20AF ; G 2034 U 8369 ; WX 790 ; N uni20B1 ; G 2035 U 8372 ; WX 876 ; N uni20B4 ; G 2036 U 8373 ; WX 696 ; N uni20B5 ; G 2037 U 8376 ; WX 696 ; N uni20B8 ; G 2038 U 8377 ; WX 696 ; N uni20B9 ; G 2039 U 8378 ; WX 696 ; N uni20BA ; G 2040 U 8381 ; WX 696 ; N uni20BD ; G 2041 U 8451 ; WX 1198 ; N uni2103 ; G 2042 U 8457 ; WX 1112 ; N uni2109 ; G 2043 U 8462 ; WX 727 ; N uni210E ; G 2044 U 8463 ; WX 727 ; N uni210F ; G 2045 U 8470 ; WX 1087 ; N uni2116 ; G 2046 U 8482 ; WX 1000 ; N trademark ; G 2047 U 8486 ; WX 890 ; N uni2126 ; G 2048 U 8487 ; WX 890 ; N uni2127 ; G 2049 U 8490 ; WX 869 ; N uni212A ; G 2050 U 8491 ; WX 776 ; N uni212B ; G 2051 U 8498 ; WX 710 ; N uni2132 ; G 2052 U 8513 ; WX 775 ; N uni2141 ; G 2053 U 8514 ; WX 557 ; N uni2142 ; G 2054 U 8515 ; WX 637 ; N uni2143 ; G 2055 U 8516 ; WX 760 ; N uni2144 ; G 2056 U 8523 ; WX 903 ; N uni214B ; G 2057 U 8526 ; WX 592 ; N uni214E ; G 2058 U 8528 ; WX 1035 ; N uni2150 ; G 2059 U 8529 ; WX 1035 ; N uni2151 ; G 2060 U 8530 ; WX 1473 ; N uni2152 ; G 2061 U 8531 ; WX 1035 ; N onethird ; G 2062 U 8532 ; WX 1035 ; N twothirds ; G 2063 U 8533 ; WX 1035 ; N uni2155 ; G 2064 U 8534 ; WX 1035 ; N uni2156 ; G 2065 U 8535 ; WX 1035 ; N uni2157 ; G 2066 U 8536 ; WX 1035 ; N uni2158 ; G 2067 U 8537 ; WX 1035 ; N uni2159 ; G 2068 U 8538 ; WX 1035 ; N uni215A ; G 2069 U 8539 ; WX 1035 ; N oneeighth ; G 2070 U 8540 ; WX 1035 ; N threeeighths ; G 2071 U 8541 ; WX 1035 ; N fiveeighths ; G 2072 U 8542 ; WX 1035 ; N seveneighths ; G 2073 U 8543 ; WX 615 ; N uni215F ; G 2074 U 8544 ; WX 468 ; N uni2160 ; G 2075 U 8545 ; WX 843 ; N uni2161 ; G 2076 U 8546 ; WX 1218 ; N uni2162 ; G 2077 U 8547 ; WX 1135 ; N uni2163 ; G 2078 U 8548 ; WX 776 ; N uni2164 ; G 2079 U 8549 ; WX 1150 ; N uni2165 ; G 2080 U 8550 ; WX 1525 ; N uni2166 ; G 2081 U 8551 ; WX 1900 ; N uni2167 ; G 2082 U 8552 ; WX 1126 ; N uni2168 ; G 2083 U 8553 ; WX 776 ; N uni2169 ; G 2084 U 8554 ; WX 1127 ; N uni216A ; G 2085 U 8555 ; WX 1502 ; N uni216B ; G 2086 U 8556 ; WX 703 ; N uni216C ; G 2087 U 8557 ; WX 796 ; N uni216D ; G 2088 U 8558 ; WX 867 ; N uni216E ; G 2089 U 8559 ; WX 1107 ; N uni216F ; G 2090 U 8560 ; WX 380 ; N uni2170 ; G 2091 U 8561 ; WX 760 ; N uni2171 ; G 2092 U 8562 ; WX 1140 ; N uni2172 ; G 2093 U 8563 ; WX 961 ; N uni2173 ; G 2094 U 8564 ; WX 581 ; N uni2174 ; G 2095 U 8565 ; WX 961 ; N uni2175 ; G 2096 U 8566 ; WX 1341 ; N uni2176 ; G 2097 U 8567 ; WX 1721 ; N uni2177 ; G 2098 U 8568 ; WX 976 ; N uni2178 ; G 2099 U 8569 ; WX 596 ; N uni2179 ; G 2100 U 8570 ; WX 976 ; N uni217A ; G 2101 U 8571 ; WX 1356 ; N uni217B ; G 2102 U 8572 ; WX 380 ; N uni217C ; G 2103 U 8573 ; WX 609 ; N uni217D ; G 2104 U 8574 ; WX 699 ; N uni217E ; G 2105 U 8575 ; WX 1058 ; N uni217F ; G 2106 U 8576 ; WX 1255 ; N uni2180 ; G 2107 U 8577 ; WX 867 ; N uni2181 ; G 2108 U 8578 ; WX 1268 ; N uni2182 ; G 2109 U 8579 ; WX 796 ; N uni2183 ; G 2110 U 8580 ; WX 609 ; N uni2184 ; G 2111 U 8581 ; WX 796 ; N uni2185 ; G 2112 U 8585 ; WX 1035 ; N uni2189 ; G 2113 U 8592 ; WX 838 ; N arrowleft ; G 2114 U 8593 ; WX 838 ; N arrowup ; G 2115 U 8594 ; WX 838 ; N arrowright ; G 2116 U 8595 ; WX 838 ; N arrowdown ; G 2117 U 8596 ; WX 838 ; N arrowboth ; G 2118 U 8597 ; WX 838 ; N arrowupdn ; G 2119 U 8598 ; WX 838 ; N uni2196 ; G 2120 U 8599 ; WX 838 ; N uni2197 ; G 2121 U 8600 ; WX 838 ; N uni2198 ; G 2122 U 8601 ; WX 838 ; N uni2199 ; G 2123 U 8602 ; WX 838 ; N uni219A ; G 2124 U 8603 ; WX 838 ; N uni219B ; G 2125 U 8604 ; WX 838 ; N uni219C ; G 2126 U 8605 ; WX 838 ; N uni219D ; G 2127 U 8606 ; WX 838 ; N uni219E ; G 2128 U 8607 ; WX 838 ; N uni219F ; G 2129 U 8608 ; WX 838 ; N uni21A0 ; G 2130 U 8609 ; WX 838 ; N uni21A1 ; G 2131 U 8610 ; WX 838 ; N uni21A2 ; G 2132 U 8611 ; WX 838 ; N uni21A3 ; G 2133 U 8612 ; WX 838 ; N uni21A4 ; G 2134 U 8613 ; WX 838 ; N uni21A5 ; G 2135 U 8614 ; WX 838 ; N uni21A6 ; G 2136 U 8615 ; WX 838 ; N uni21A7 ; G 2137 U 8616 ; WX 838 ; N arrowupdnbse ; G 2138 U 8617 ; WX 838 ; N uni21A9 ; G 2139 U 8618 ; WX 838 ; N uni21AA ; G 2140 U 8619 ; WX 838 ; N uni21AB ; G 2141 U 8620 ; WX 838 ; N uni21AC ; G 2142 U 8621 ; WX 838 ; N uni21AD ; G 2143 U 8622 ; WX 838 ; N uni21AE ; G 2144 U 8623 ; WX 850 ; N uni21AF ; G 2145 U 8624 ; WX 838 ; N uni21B0 ; G 2146 U 8625 ; WX 838 ; N uni21B1 ; G 2147 U 8626 ; WX 838 ; N uni21B2 ; G 2148 U 8627 ; WX 838 ; N uni21B3 ; G 2149 U 8628 ; WX 838 ; N uni21B4 ; G 2150 U 8629 ; WX 838 ; N carriagereturn ; G 2151 U 8630 ; WX 838 ; N uni21B6 ; G 2152 U 8631 ; WX 838 ; N uni21B7 ; G 2153 U 8632 ; WX 838 ; N uni21B8 ; G 2154 U 8633 ; WX 838 ; N uni21B9 ; G 2155 U 8634 ; WX 838 ; N uni21BA ; G 2156 U 8635 ; WX 838 ; N uni21BB ; G 2157 U 8636 ; WX 838 ; N uni21BC ; G 2158 U 8637 ; WX 838 ; N uni21BD ; G 2159 U 8638 ; WX 838 ; N uni21BE ; G 2160 U 8639 ; WX 838 ; N uni21BF ; G 2161 U 8640 ; WX 838 ; N uni21C0 ; G 2162 U 8641 ; WX 838 ; N uni21C1 ; G 2163 U 8642 ; WX 838 ; N uni21C2 ; G 2164 U 8643 ; WX 838 ; N uni21C3 ; G 2165 U 8644 ; WX 838 ; N uni21C4 ; G 2166 U 8645 ; WX 838 ; N uni21C5 ; G 2167 U 8646 ; WX 838 ; N uni21C6 ; G 2168 U 8647 ; WX 838 ; N uni21C7 ; G 2169 U 8648 ; WX 838 ; N uni21C8 ; G 2170 U 8649 ; WX 838 ; N uni21C9 ; G 2171 U 8650 ; WX 838 ; N uni21CA ; G 2172 U 8651 ; WX 838 ; N uni21CB ; G 2173 U 8652 ; WX 838 ; N uni21CC ; G 2174 U 8653 ; WX 838 ; N uni21CD ; G 2175 U 8654 ; WX 838 ; N uni21CE ; G 2176 U 8655 ; WX 838 ; N uni21CF ; G 2177 U 8656 ; WX 838 ; N arrowdblleft ; G 2178 U 8657 ; WX 838 ; N arrowdblup ; G 2179 U 8658 ; WX 838 ; N arrowdblright ; G 2180 U 8659 ; WX 838 ; N arrowdbldown ; G 2181 U 8660 ; WX 838 ; N arrowdblboth ; G 2182 U 8661 ; WX 838 ; N uni21D5 ; G 2183 U 8662 ; WX 838 ; N uni21D6 ; G 2184 U 8663 ; WX 838 ; N uni21D7 ; G 2185 U 8664 ; WX 838 ; N uni21D8 ; G 2186 U 8665 ; WX 838 ; N uni21D9 ; G 2187 U 8666 ; WX 838 ; N uni21DA ; G 2188 U 8667 ; WX 838 ; N uni21DB ; G 2189 U 8668 ; WX 838 ; N uni21DC ; G 2190 U 8669 ; WX 838 ; N uni21DD ; G 2191 U 8670 ; WX 838 ; N uni21DE ; G 2192 U 8671 ; WX 838 ; N uni21DF ; G 2193 U 8672 ; WX 838 ; N uni21E0 ; G 2194 U 8673 ; WX 838 ; N uni21E1 ; G 2195 U 8674 ; WX 838 ; N uni21E2 ; G 2196 U 8675 ; WX 838 ; N uni21E3 ; G 2197 U 8676 ; WX 838 ; N uni21E4 ; G 2198 U 8677 ; WX 838 ; N uni21E5 ; G 2199 U 8678 ; WX 838 ; N uni21E6 ; G 2200 U 8679 ; WX 838 ; N uni21E7 ; G 2201 U 8680 ; WX 838 ; N uni21E8 ; G 2202 U 8681 ; WX 838 ; N uni21E9 ; G 2203 U 8682 ; WX 838 ; N uni21EA ; G 2204 U 8683 ; WX 838 ; N uni21EB ; G 2205 U 8684 ; WX 838 ; N uni21EC ; G 2206 U 8685 ; WX 838 ; N uni21ED ; G 2207 U 8686 ; WX 838 ; N uni21EE ; G 2208 U 8687 ; WX 838 ; N uni21EF ; G 2209 U 8688 ; WX 838 ; N uni21F0 ; G 2210 U 8689 ; WX 838 ; N uni21F1 ; G 2211 U 8690 ; WX 838 ; N uni21F2 ; G 2212 U 8691 ; WX 838 ; N uni21F3 ; G 2213 U 8692 ; WX 838 ; N uni21F4 ; G 2214 U 8693 ; WX 838 ; N uni21F5 ; G 2215 U 8694 ; WX 838 ; N uni21F6 ; G 2216 U 8695 ; WX 838 ; N uni21F7 ; G 2217 U 8696 ; WX 838 ; N uni21F8 ; G 2218 U 8697 ; WX 838 ; N uni21F9 ; G 2219 U 8698 ; WX 838 ; N uni21FA ; G 2220 U 8699 ; WX 838 ; N uni21FB ; G 2221 U 8700 ; WX 838 ; N uni21FC ; G 2222 U 8701 ; WX 838 ; N uni21FD ; G 2223 U 8702 ; WX 838 ; N uni21FE ; G 2224 U 8703 ; WX 838 ; N uni21FF ; G 2225 U 8704 ; WX 641 ; N universal ; G 2226 U 8706 ; WX 534 ; N partialdiff ; G 2227 U 8707 ; WX 620 ; N existential ; G 2228 U 8708 ; WX 620 ; N uni2204 ; G 2229 U 8710 ; WX 753 ; N increment ; G 2230 U 8711 ; WX 753 ; N gradient ; G 2231 U 8712 ; WX 740 ; N element ; G 2232 U 8713 ; WX 740 ; N notelement ; G 2233 U 8715 ; WX 740 ; N suchthat ; G 2234 U 8716 ; WX 740 ; N uni220C ; G 2235 U 8719 ; WX 842 ; N product ; G 2236 U 8720 ; WX 842 ; N uni2210 ; G 2237 U 8721 ; WX 753 ; N summation ; G 2238 U 8722 ; WX 838 ; N minus ; G 2239 U 8723 ; WX 838 ; N uni2213 ; G 2240 U 8724 ; WX 838 ; N uni2214 ; G 2241 U 8725 ; WX 365 ; N uni2215 ; G 2242 U 8727 ; WX 691 ; N asteriskmath ; G 2243 U 8728 ; WX 519 ; N uni2218 ; G 2244 U 8729 ; WX 519 ; N uni2219 ; G 2245 U 8730 ; WX 657 ; N radical ; G 2246 U 8731 ; WX 657 ; N uni221B ; G 2247 U 8732 ; WX 657 ; N uni221C ; G 2248 U 8733 ; WX 672 ; N proportional ; G 2249 U 8734 ; WX 833 ; N infinity ; G 2250 U 8735 ; WX 838 ; N orthogonal ; G 2251 U 8736 ; WX 838 ; N angle ; G 2252 U 8739 ; WX 324 ; N uni2223 ; G 2253 U 8740 ; WX 607 ; N uni2224 ; G 2254 U 8741 ; WX 529 ; N uni2225 ; G 2255 U 8742 ; WX 773 ; N uni2226 ; G 2256 U 8743 ; WX 812 ; N logicaland ; G 2257 U 8744 ; WX 812 ; N logicalor ; G 2258 U 8745 ; WX 838 ; N intersection ; G 2259 U 8746 ; WX 838 ; N union ; G 2260 U 8747 ; WX 579 ; N integral ; G 2261 U 8748 ; WX 1000 ; N uni222C ; G 2262 U 8749 ; WX 1391 ; N uni222D ; G 2263 U 8760 ; WX 838 ; N uni2238 ; G 2264 U 8761 ; WX 838 ; N uni2239 ; G 2265 U 8762 ; WX 838 ; N uni223A ; G 2266 U 8763 ; WX 838 ; N uni223B ; G 2267 U 8764 ; WX 838 ; N similar ; G 2268 U 8765 ; WX 838 ; N uni223D ; G 2269 U 8770 ; WX 838 ; N uni2242 ; G 2270 U 8771 ; WX 838 ; N uni2243 ; G 2271 U 8776 ; WX 838 ; N approxequal ; G 2272 U 8784 ; WX 838 ; N uni2250 ; G 2273 U 8785 ; WX 838 ; N uni2251 ; G 2274 U 8786 ; WX 838 ; N uni2252 ; G 2275 U 8787 ; WX 838 ; N uni2253 ; G 2276 U 8788 ; WX 1082 ; N uni2254 ; G 2277 U 8789 ; WX 1082 ; N uni2255 ; G 2278 U 8800 ; WX 838 ; N notequal ; G 2279 U 8801 ; WX 838 ; N equivalence ; G 2280 U 8804 ; WX 838 ; N lessequal ; G 2281 U 8805 ; WX 838 ; N greaterequal ; G 2282 U 8834 ; WX 838 ; N propersubset ; G 2283 U 8835 ; WX 838 ; N propersuperset ; G 2284 U 8836 ; WX 838 ; N notsubset ; G 2285 U 8837 ; WX 838 ; N uni2285 ; G 2286 U 8838 ; WX 838 ; N reflexsubset ; G 2287 U 8839 ; WX 838 ; N reflexsuperset ; G 2288 U 8844 ; WX 838 ; N uni228C ; G 2289 U 8845 ; WX 838 ; N uni228D ; G 2290 U 8846 ; WX 838 ; N uni228E ; G 2291 U 8847 ; WX 838 ; N uni228F ; G 2292 U 8848 ; WX 838 ; N uni2290 ; G 2293 U 8849 ; WX 838 ; N uni2291 ; G 2294 U 8850 ; WX 838 ; N uni2292 ; G 2295 U 8851 ; WX 838 ; N uni2293 ; G 2296 U 8852 ; WX 838 ; N uni2294 ; G 2297 U 8853 ; WX 838 ; N circleplus ; G 2298 U 8854 ; WX 838 ; N uni2296 ; G 2299 U 8855 ; WX 838 ; N circlemultiply ; G 2300 U 8856 ; WX 838 ; N uni2298 ; G 2301 U 8857 ; WX 838 ; N uni2299 ; G 2302 U 8858 ; WX 838 ; N uni229A ; G 2303 U 8859 ; WX 838 ; N uni229B ; G 2304 U 8860 ; WX 838 ; N uni229C ; G 2305 U 8861 ; WX 838 ; N uni229D ; G 2306 U 8862 ; WX 838 ; N uni229E ; G 2307 U 8863 ; WX 838 ; N uni229F ; G 2308 U 8864 ; WX 838 ; N uni22A0 ; G 2309 U 8865 ; WX 838 ; N uni22A1 ; G 2310 U 8866 ; WX 884 ; N uni22A2 ; G 2311 U 8867 ; WX 884 ; N uni22A3 ; G 2312 U 8868 ; WX 960 ; N uni22A4 ; G 2313 U 8869 ; WX 960 ; N perpendicular ; G 2314 U 8870 ; WX 616 ; N uni22A6 ; G 2315 U 8871 ; WX 616 ; N uni22A7 ; G 2316 U 8872 ; WX 884 ; N uni22A8 ; G 2317 U 8873 ; WX 884 ; N uni22A9 ; G 2318 U 8874 ; WX 884 ; N uni22AA ; G 2319 U 8875 ; WX 1080 ; N uni22AB ; G 2320 U 8876 ; WX 884 ; N uni22AC ; G 2321 U 8877 ; WX 884 ; N uni22AD ; G 2322 U 8878 ; WX 884 ; N uni22AE ; G 2323 U 8879 ; WX 1080 ; N uni22AF ; G 2324 U 8900 ; WX 626 ; N uni22C4 ; G 2325 U 8901 ; WX 398 ; N dotmath ; G 2326 U 8962 ; WX 834 ; N house ; G 2327 U 8968 ; WX 473 ; N uni2308 ; G 2328 U 8969 ; WX 473 ; N uni2309 ; G 2329 U 8970 ; WX 473 ; N uni230A ; G 2330 U 8971 ; WX 473 ; N uni230B ; G 2331 U 8976 ; WX 838 ; N revlogicalnot ; G 2332 U 8977 ; WX 539 ; N uni2311 ; G 2333 U 8984 ; WX 928 ; N uni2318 ; G 2334 U 8985 ; WX 838 ; N uni2319 ; G 2335 U 8992 ; WX 579 ; N integraltp ; G 2336 U 8993 ; WX 579 ; N integralbt ; G 2337 U 8997 ; WX 1000 ; N uni2325 ; G 2338 U 9000 ; WX 1443 ; N uni2328 ; G 2339 U 9085 ; WX 1008 ; N uni237D ; G 2340 U 9115 ; WX 500 ; N uni239B ; G 2341 U 9116 ; WX 500 ; N uni239C ; G 2342 U 9117 ; WX 500 ; N uni239D ; G 2343 U 9118 ; WX 500 ; N uni239E ; G 2344 U 9119 ; WX 500 ; N uni239F ; G 2345 U 9120 ; WX 500 ; N uni23A0 ; G 2346 U 9121 ; WX 500 ; N uni23A1 ; G 2347 U 9122 ; WX 500 ; N uni23A2 ; G 2348 U 9123 ; WX 500 ; N uni23A3 ; G 2349 U 9124 ; WX 500 ; N uni23A4 ; G 2350 U 9125 ; WX 500 ; N uni23A5 ; G 2351 U 9126 ; WX 500 ; N uni23A6 ; G 2352 U 9127 ; WX 750 ; N uni23A7 ; G 2353 U 9128 ; WX 750 ; N uni23A8 ; G 2354 U 9129 ; WX 750 ; N uni23A9 ; G 2355 U 9130 ; WX 750 ; N uni23AA ; G 2356 U 9131 ; WX 750 ; N uni23AB ; G 2357 U 9132 ; WX 750 ; N uni23AC ; G 2358 U 9133 ; WX 750 ; N uni23AD ; G 2359 U 9134 ; WX 579 ; N uni23AE ; G 2360 U 9167 ; WX 945 ; N uni23CF ; G 2361 U 9251 ; WX 834 ; N uni2423 ; G 2362 U 9472 ; WX 602 ; N SF100000 ; G 2363 U 9473 ; WX 602 ; N uni2501 ; G 2364 U 9474 ; WX 602 ; N SF110000 ; G 2365 U 9475 ; WX 602 ; N uni2503 ; G 2366 U 9476 ; WX 602 ; N uni2504 ; G 2367 U 9477 ; WX 602 ; N uni2505 ; G 2368 U 9478 ; WX 602 ; N uni2506 ; G 2369 U 9479 ; WX 602 ; N uni2507 ; G 2370 U 9480 ; WX 602 ; N uni2508 ; G 2371 U 9481 ; WX 602 ; N uni2509 ; G 2372 U 9482 ; WX 602 ; N uni250A ; G 2373 U 9483 ; WX 602 ; N uni250B ; G 2374 U 9484 ; WX 602 ; N SF010000 ; G 2375 U 9485 ; WX 602 ; N uni250D ; G 2376 U 9486 ; WX 602 ; N uni250E ; G 2377 U 9487 ; WX 602 ; N uni250F ; G 2378 U 9488 ; WX 602 ; N SF030000 ; G 2379 U 9489 ; WX 602 ; N uni2511 ; G 2380 U 9490 ; WX 602 ; N uni2512 ; G 2381 U 9491 ; WX 602 ; N uni2513 ; G 2382 U 9492 ; WX 602 ; N SF020000 ; G 2383 U 9493 ; WX 602 ; N uni2515 ; G 2384 U 9494 ; WX 602 ; N uni2516 ; G 2385 U 9495 ; WX 602 ; N uni2517 ; G 2386 U 9496 ; WX 602 ; N SF040000 ; G 2387 U 9497 ; WX 602 ; N uni2519 ; G 2388 U 9498 ; WX 602 ; N uni251A ; G 2389 U 9499 ; WX 602 ; N uni251B ; G 2390 U 9500 ; WX 602 ; N SF080000 ; G 2391 U 9501 ; WX 602 ; N uni251D ; G 2392 U 9502 ; WX 602 ; N uni251E ; G 2393 U 9503 ; WX 602 ; N uni251F ; G 2394 U 9504 ; WX 602 ; N uni2520 ; G 2395 U 9505 ; WX 602 ; N uni2521 ; G 2396 U 9506 ; WX 602 ; N uni2522 ; G 2397 U 9507 ; WX 602 ; N uni2523 ; G 2398 U 9508 ; WX 602 ; N SF090000 ; G 2399 U 9509 ; WX 602 ; N uni2525 ; G 2400 U 9510 ; WX 602 ; N uni2526 ; G 2401 U 9511 ; WX 602 ; N uni2527 ; G 2402 U 9512 ; WX 602 ; N uni2528 ; G 2403 U 9513 ; WX 602 ; N uni2529 ; G 2404 U 9514 ; WX 602 ; N uni252A ; G 2405 U 9515 ; WX 602 ; N uni252B ; G 2406 U 9516 ; WX 602 ; N SF060000 ; G 2407 U 9517 ; WX 602 ; N uni252D ; G 2408 U 9518 ; WX 602 ; N uni252E ; G 2409 U 9519 ; WX 602 ; N uni252F ; G 2410 U 9520 ; WX 602 ; N uni2530 ; G 2411 U 9521 ; WX 602 ; N uni2531 ; G 2412 U 9522 ; WX 602 ; N uni2532 ; G 2413 U 9523 ; WX 602 ; N uni2533 ; G 2414 U 9524 ; WX 602 ; N SF070000 ; G 2415 U 9525 ; WX 602 ; N uni2535 ; G 2416 U 9526 ; WX 602 ; N uni2536 ; G 2417 U 9527 ; WX 602 ; N uni2537 ; G 2418 U 9528 ; WX 602 ; N uni2538 ; G 2419 U 9529 ; WX 602 ; N uni2539 ; G 2420 U 9530 ; WX 602 ; N uni253A ; G 2421 U 9531 ; WX 602 ; N uni253B ; G 2422 U 9532 ; WX 602 ; N SF050000 ; G 2423 U 9533 ; WX 602 ; N uni253D ; G 2424 U 9534 ; WX 602 ; N uni253E ; G 2425 U 9535 ; WX 602 ; N uni253F ; G 2426 U 9536 ; WX 602 ; N uni2540 ; G 2427 U 9537 ; WX 602 ; N uni2541 ; G 2428 U 9538 ; WX 602 ; N uni2542 ; G 2429 U 9539 ; WX 602 ; N uni2543 ; G 2430 U 9540 ; WX 602 ; N uni2544 ; G 2431 U 9541 ; WX 602 ; N uni2545 ; G 2432 U 9542 ; WX 602 ; N uni2546 ; G 2433 U 9543 ; WX 602 ; N uni2547 ; G 2434 U 9544 ; WX 602 ; N uni2548 ; G 2435 U 9545 ; WX 602 ; N uni2549 ; G 2436 U 9546 ; WX 602 ; N uni254A ; G 2437 U 9547 ; WX 602 ; N uni254B ; G 2438 U 9548 ; WX 602 ; N uni254C ; G 2439 U 9549 ; WX 602 ; N uni254D ; G 2440 U 9550 ; WX 602 ; N uni254E ; G 2441 U 9551 ; WX 602 ; N uni254F ; G 2442 U 9552 ; WX 602 ; N SF430000 ; G 2443 U 9553 ; WX 602 ; N SF240000 ; G 2444 U 9554 ; WX 602 ; N SF510000 ; G 2445 U 9555 ; WX 602 ; N SF520000 ; G 2446 U 9556 ; WX 602 ; N SF390000 ; G 2447 U 9557 ; WX 602 ; N SF220000 ; G 2448 U 9558 ; WX 602 ; N SF210000 ; G 2449 U 9559 ; WX 602 ; N SF250000 ; G 2450 U 9560 ; WX 602 ; N SF500000 ; G 2451 U 9561 ; WX 602 ; N SF490000 ; G 2452 U 9562 ; WX 602 ; N SF380000 ; G 2453 U 9563 ; WX 602 ; N SF280000 ; G 2454 U 9564 ; WX 602 ; N SF270000 ; G 2455 U 9565 ; WX 602 ; N SF260000 ; G 2456 U 9566 ; WX 602 ; N SF360000 ; G 2457 U 9567 ; WX 602 ; N SF370000 ; G 2458 U 9568 ; WX 602 ; N SF420000 ; G 2459 U 9569 ; WX 602 ; N SF190000 ; G 2460 U 9570 ; WX 602 ; N SF200000 ; G 2461 U 9571 ; WX 602 ; N SF230000 ; G 2462 U 9572 ; WX 602 ; N SF470000 ; G 2463 U 9573 ; WX 602 ; N SF480000 ; G 2464 U 9574 ; WX 602 ; N SF410000 ; G 2465 U 9575 ; WX 602 ; N SF450000 ; G 2466 U 9576 ; WX 602 ; N SF460000 ; G 2467 U 9577 ; WX 602 ; N SF400000 ; G 2468 U 9578 ; WX 602 ; N SF540000 ; G 2469 U 9579 ; WX 602 ; N SF530000 ; G 2470 U 9580 ; WX 602 ; N SF440000 ; G 2471 U 9581 ; WX 602 ; N uni256D ; G 2472 U 9582 ; WX 602 ; N uni256E ; G 2473 U 9583 ; WX 602 ; N uni256F ; G 2474 U 9584 ; WX 602 ; N uni2570 ; G 2475 U 9585 ; WX 602 ; N uni2571 ; G 2476 U 9586 ; WX 602 ; N uni2572 ; G 2477 U 9587 ; WX 602 ; N uni2573 ; G 2478 U 9588 ; WX 602 ; N uni2574 ; G 2479 U 9589 ; WX 602 ; N uni2575 ; G 2480 U 9590 ; WX 602 ; N uni2576 ; G 2481 U 9591 ; WX 602 ; N uni2577 ; G 2482 U 9592 ; WX 602 ; N uni2578 ; G 2483 U 9593 ; WX 602 ; N uni2579 ; G 2484 U 9594 ; WX 602 ; N uni257A ; G 2485 U 9595 ; WX 602 ; N uni257B ; G 2486 U 9596 ; WX 602 ; N uni257C ; G 2487 U 9597 ; WX 602 ; N uni257D ; G 2488 U 9598 ; WX 602 ; N uni257E ; G 2489 U 9599 ; WX 602 ; N uni257F ; G 2490 U 9600 ; WX 769 ; N upblock ; G 2491 U 9601 ; WX 769 ; N uni2581 ; G 2492 U 9602 ; WX 769 ; N uni2582 ; G 2493 U 9603 ; WX 769 ; N uni2583 ; G 2494 U 9604 ; WX 769 ; N dnblock ; G 2495 U 9605 ; WX 769 ; N uni2585 ; G 2496 U 9606 ; WX 769 ; N uni2586 ; G 2497 U 9607 ; WX 769 ; N uni2587 ; G 2498 U 9608 ; WX 769 ; N block ; G 2499 U 9609 ; WX 769 ; N uni2589 ; G 2500 U 9610 ; WX 769 ; N uni258A ; G 2501 U 9611 ; WX 769 ; N uni258B ; G 2502 U 9612 ; WX 769 ; N lfblock ; G 2503 U 9613 ; WX 769 ; N uni258D ; G 2504 U 9614 ; WX 769 ; N uni258E ; G 2505 U 9615 ; WX 769 ; N uni258F ; G 2506 U 9616 ; WX 769 ; N rtblock ; G 2507 U 9617 ; WX 769 ; N ltshade ; G 2508 U 9618 ; WX 769 ; N shade ; G 2509 U 9619 ; WX 769 ; N dkshade ; G 2510 U 9620 ; WX 769 ; N uni2594 ; G 2511 U 9621 ; WX 769 ; N uni2595 ; G 2512 U 9622 ; WX 769 ; N uni2596 ; G 2513 U 9623 ; WX 769 ; N uni2597 ; G 2514 U 9624 ; WX 769 ; N uni2598 ; G 2515 U 9625 ; WX 769 ; N uni2599 ; G 2516 U 9626 ; WX 769 ; N uni259A ; G 2517 U 9627 ; WX 769 ; N uni259B ; G 2518 U 9628 ; WX 769 ; N uni259C ; G 2519 U 9629 ; WX 769 ; N uni259D ; G 2520 U 9630 ; WX 769 ; N uni259E ; G 2521 U 9631 ; WX 769 ; N uni259F ; G 2522 U 9632 ; WX 945 ; N filledbox ; G 2523 U 9633 ; WX 945 ; N H22073 ; G 2524 U 9634 ; WX 945 ; N uni25A2 ; G 2525 U 9635 ; WX 945 ; N uni25A3 ; G 2526 U 9636 ; WX 945 ; N uni25A4 ; G 2527 U 9637 ; WX 945 ; N uni25A5 ; G 2528 U 9638 ; WX 945 ; N uni25A6 ; G 2529 U 9639 ; WX 945 ; N uni25A7 ; G 2530 U 9640 ; WX 945 ; N uni25A8 ; G 2531 U 9641 ; WX 945 ; N uni25A9 ; G 2532 U 9642 ; WX 678 ; N H18543 ; G 2533 U 9643 ; WX 678 ; N H18551 ; G 2534 U 9644 ; WX 945 ; N filledrect ; G 2535 U 9645 ; WX 945 ; N uni25AD ; G 2536 U 9646 ; WX 550 ; N uni25AE ; G 2537 U 9647 ; WX 550 ; N uni25AF ; G 2538 U 9648 ; WX 769 ; N uni25B0 ; G 2539 U 9649 ; WX 769 ; N uni25B1 ; G 2540 U 9650 ; WX 769 ; N triagup ; G 2541 U 9651 ; WX 769 ; N uni25B3 ; G 2542 U 9652 ; WX 502 ; N uni25B4 ; G 2543 U 9653 ; WX 502 ; N uni25B5 ; G 2544 U 9654 ; WX 769 ; N uni25B6 ; G 2545 U 9655 ; WX 769 ; N uni25B7 ; G 2546 U 9656 ; WX 502 ; N uni25B8 ; G 2547 U 9657 ; WX 502 ; N uni25B9 ; G 2548 U 9658 ; WX 769 ; N triagrt ; G 2549 U 9659 ; WX 769 ; N uni25BB ; G 2550 U 9660 ; WX 769 ; N triagdn ; G 2551 U 9661 ; WX 769 ; N uni25BD ; G 2552 U 9662 ; WX 502 ; N uni25BE ; G 2553 U 9663 ; WX 502 ; N uni25BF ; G 2554 U 9664 ; WX 769 ; N uni25C0 ; G 2555 U 9665 ; WX 769 ; N uni25C1 ; G 2556 U 9666 ; WX 502 ; N uni25C2 ; G 2557 U 9667 ; WX 502 ; N uni25C3 ; G 2558 U 9668 ; WX 769 ; N triaglf ; G 2559 U 9669 ; WX 769 ; N uni25C5 ; G 2560 U 9670 ; WX 769 ; N uni25C6 ; G 2561 U 9671 ; WX 769 ; N uni25C7 ; G 2562 U 9672 ; WX 769 ; N uni25C8 ; G 2563 U 9673 ; WX 873 ; N uni25C9 ; G 2564 U 9674 ; WX 494 ; N lozenge ; G 2565 U 9675 ; WX 873 ; N circle ; G 2566 U 9676 ; WX 873 ; N uni25CC ; G 2567 U 9677 ; WX 873 ; N uni25CD ; G 2568 U 9678 ; WX 873 ; N uni25CE ; G 2569 U 9679 ; WX 873 ; N H18533 ; G 2570 U 9680 ; WX 873 ; N uni25D0 ; G 2571 U 9681 ; WX 873 ; N uni25D1 ; G 2572 U 9682 ; WX 873 ; N uni25D2 ; G 2573 U 9683 ; WX 873 ; N uni25D3 ; G 2574 U 9684 ; WX 873 ; N uni25D4 ; G 2575 U 9685 ; WX 873 ; N uni25D5 ; G 2576 U 9686 ; WX 527 ; N uni25D6 ; G 2577 U 9687 ; WX 527 ; N uni25D7 ; G 2578 U 9688 ; WX 791 ; N invbullet ; G 2579 U 9689 ; WX 970 ; N invcircle ; G 2580 U 9690 ; WX 970 ; N uni25DA ; G 2581 U 9691 ; WX 970 ; N uni25DB ; G 2582 U 9692 ; WX 387 ; N uni25DC ; G 2583 U 9693 ; WX 387 ; N uni25DD ; G 2584 U 9694 ; WX 387 ; N uni25DE ; G 2585 U 9695 ; WX 387 ; N uni25DF ; G 2586 U 9696 ; WX 873 ; N uni25E0 ; G 2587 U 9697 ; WX 873 ; N uni25E1 ; G 2588 U 9698 ; WX 769 ; N uni25E2 ; G 2589 U 9699 ; WX 769 ; N uni25E3 ; G 2590 U 9700 ; WX 769 ; N uni25E4 ; G 2591 U 9701 ; WX 769 ; N uni25E5 ; G 2592 U 9702 ; WX 590 ; N openbullet ; G 2593 U 9703 ; WX 945 ; N uni25E7 ; G 2594 U 9704 ; WX 945 ; N uni25E8 ; G 2595 U 9705 ; WX 945 ; N uni25E9 ; G 2596 U 9706 ; WX 945 ; N uni25EA ; G 2597 U 9707 ; WX 945 ; N uni25EB ; G 2598 U 9708 ; WX 769 ; N uni25EC ; G 2599 U 9709 ; WX 769 ; N uni25ED ; G 2600 U 9710 ; WX 769 ; N uni25EE ; G 2601 U 9711 ; WX 1119 ; N uni25EF ; G 2602 U 9712 ; WX 945 ; N uni25F0 ; G 2603 U 9713 ; WX 945 ; N uni25F1 ; G 2604 U 9714 ; WX 945 ; N uni25F2 ; G 2605 U 9715 ; WX 945 ; N uni25F3 ; G 2606 U 9716 ; WX 873 ; N uni25F4 ; G 2607 U 9717 ; WX 873 ; N uni25F5 ; G 2608 U 9718 ; WX 873 ; N uni25F6 ; G 2609 U 9719 ; WX 873 ; N uni25F7 ; G 2610 U 9720 ; WX 769 ; N uni25F8 ; G 2611 U 9721 ; WX 769 ; N uni25F9 ; G 2612 U 9722 ; WX 769 ; N uni25FA ; G 2613 U 9723 ; WX 830 ; N uni25FB ; G 2614 U 9724 ; WX 830 ; N uni25FC ; G 2615 U 9725 ; WX 732 ; N uni25FD ; G 2616 U 9726 ; WX 732 ; N uni25FE ; G 2617 U 9727 ; WX 769 ; N uni25FF ; G 2618 U 9728 ; WX 896 ; N uni2600 ; G 2619 U 9784 ; WX 896 ; N uni2638 ; G 2620 U 9785 ; WX 896 ; N uni2639 ; G 2621 U 9786 ; WX 896 ; N smileface ; G 2622 U 9787 ; WX 896 ; N invsmileface ; G 2623 U 9788 ; WX 896 ; N sun ; G 2624 U 9791 ; WX 614 ; N uni263F ; G 2625 U 9792 ; WX 731 ; N female ; G 2626 U 9793 ; WX 731 ; N uni2641 ; G 2627 U 9794 ; WX 896 ; N male ; G 2628 U 9795 ; WX 896 ; N uni2643 ; G 2629 U 9796 ; WX 896 ; N uni2644 ; G 2630 U 9797 ; WX 896 ; N uni2645 ; G 2631 U 9798 ; WX 896 ; N uni2646 ; G 2632 U 9799 ; WX 896 ; N uni2647 ; G 2633 U 9824 ; WX 896 ; N spade ; G 2634 U 9825 ; WX 896 ; N uni2661 ; G 2635 U 9826 ; WX 896 ; N uni2662 ; G 2636 U 9827 ; WX 896 ; N club ; G 2637 U 9828 ; WX 896 ; N uni2664 ; G 2638 U 9829 ; WX 896 ; N heart ; G 2639 U 9830 ; WX 896 ; N diamond ; G 2640 U 9831 ; WX 896 ; N uni2667 ; G 2641 U 9833 ; WX 472 ; N uni2669 ; G 2642 U 9834 ; WX 638 ; N musicalnote ; G 2643 U 9835 ; WX 896 ; N musicalnotedbl ; G 2644 U 9836 ; WX 896 ; N uni266C ; G 2645 U 9837 ; WX 472 ; N uni266D ; G 2646 U 9838 ; WX 357 ; N uni266E ; G 2647 U 9839 ; WX 484 ; N uni266F ; G 2648 U 10145 ; WX 838 ; N uni27A1 ; G 2649 U 10181 ; WX 457 ; N uni27C5 ; G 2650 U 10182 ; WX 457 ; N uni27C6 ; G 2651 U 10208 ; WX 494 ; N uni27E0 ; G 2652 U 10216 ; WX 457 ; N uni27E8 ; G 2653 U 10217 ; WX 457 ; N uni27E9 ; G 2654 U 10224 ; WX 838 ; N uni27F0 ; G 2655 U 10225 ; WX 838 ; N uni27F1 ; G 2656 U 10226 ; WX 838 ; N uni27F2 ; G 2657 U 10227 ; WX 838 ; N uni27F3 ; G 2658 U 10228 ; WX 1033 ; N uni27F4 ; G 2659 U 10229 ; WX 1434 ; N uni27F5 ; G 2660 U 10230 ; WX 1434 ; N uni27F6 ; G 2661 U 10231 ; WX 1434 ; N uni27F7 ; G 2662 U 10232 ; WX 1434 ; N uni27F8 ; G 2663 U 10233 ; WX 1434 ; N uni27F9 ; G 2664 U 10234 ; WX 1434 ; N uni27FA ; G 2665 U 10235 ; WX 1434 ; N uni27FB ; G 2666 U 10236 ; WX 1434 ; N uni27FC ; G 2667 U 10237 ; WX 1434 ; N uni27FD ; G 2668 U 10238 ; WX 1434 ; N uni27FE ; G 2669 U 10239 ; WX 1434 ; N uni27FF ; G 2670 U 10240 ; WX 781 ; N uni2800 ; G 2671 U 10241 ; WX 781 ; N uni2801 ; G 2672 U 10242 ; WX 781 ; N uni2802 ; G 2673 U 10243 ; WX 781 ; N uni2803 ; G 2674 U 10244 ; WX 781 ; N uni2804 ; G 2675 U 10245 ; WX 781 ; N uni2805 ; G 2676 U 10246 ; WX 781 ; N uni2806 ; G 2677 U 10247 ; WX 781 ; N uni2807 ; G 2678 U 10248 ; WX 781 ; N uni2808 ; G 2679 U 10249 ; WX 781 ; N uni2809 ; G 2680 U 10250 ; WX 781 ; N uni280A ; G 2681 U 10251 ; WX 781 ; N uni280B ; G 2682 U 10252 ; WX 781 ; N uni280C ; G 2683 U 10253 ; WX 781 ; N uni280D ; G 2684 U 10254 ; WX 781 ; N uni280E ; G 2685 U 10255 ; WX 781 ; N uni280F ; G 2686 U 10256 ; WX 781 ; N uni2810 ; G 2687 U 10257 ; WX 781 ; N uni2811 ; G 2688 U 10258 ; WX 781 ; N uni2812 ; G 2689 U 10259 ; WX 781 ; N uni2813 ; G 2690 U 10260 ; WX 781 ; N uni2814 ; G 2691 U 10261 ; WX 781 ; N uni2815 ; G 2692 U 10262 ; WX 781 ; N uni2816 ; G 2693 U 10263 ; WX 781 ; N uni2817 ; G 2694 U 10264 ; WX 781 ; N uni2818 ; G 2695 U 10265 ; WX 781 ; N uni2819 ; G 2696 U 10266 ; WX 781 ; N uni281A ; G 2697 U 10267 ; WX 781 ; N uni281B ; G 2698 U 10268 ; WX 781 ; N uni281C ; G 2699 U 10269 ; WX 781 ; N uni281D ; G 2700 U 10270 ; WX 781 ; N uni281E ; G 2701 U 10271 ; WX 781 ; N uni281F ; G 2702 U 10272 ; WX 781 ; N uni2820 ; G 2703 U 10273 ; WX 781 ; N uni2821 ; G 2704 U 10274 ; WX 781 ; N uni2822 ; G 2705 U 10275 ; WX 781 ; N uni2823 ; G 2706 U 10276 ; WX 781 ; N uni2824 ; G 2707 U 10277 ; WX 781 ; N uni2825 ; G 2708 U 10278 ; WX 781 ; N uni2826 ; G 2709 U 10279 ; WX 781 ; N uni2827 ; G 2710 U 10280 ; WX 781 ; N uni2828 ; G 2711 U 10281 ; WX 781 ; N uni2829 ; G 2712 U 10282 ; WX 781 ; N uni282A ; G 2713 U 10283 ; WX 781 ; N uni282B ; G 2714 U 10284 ; WX 781 ; N uni282C ; G 2715 U 10285 ; WX 781 ; N uni282D ; G 2716 U 10286 ; WX 781 ; N uni282E ; G 2717 U 10287 ; WX 781 ; N uni282F ; G 2718 U 10288 ; WX 781 ; N uni2830 ; G 2719 U 10289 ; WX 781 ; N uni2831 ; G 2720 U 10290 ; WX 781 ; N uni2832 ; G 2721 U 10291 ; WX 781 ; N uni2833 ; G 2722 U 10292 ; WX 781 ; N uni2834 ; G 2723 U 10293 ; WX 781 ; N uni2835 ; G 2724 U 10294 ; WX 781 ; N uni2836 ; G 2725 U 10295 ; WX 781 ; N uni2837 ; G 2726 U 10296 ; WX 781 ; N uni2838 ; G 2727 U 10297 ; WX 781 ; N uni2839 ; G 2728 U 10298 ; WX 781 ; N uni283A ; G 2729 U 10299 ; WX 781 ; N uni283B ; G 2730 U 10300 ; WX 781 ; N uni283C ; G 2731 U 10301 ; WX 781 ; N uni283D ; G 2732 U 10302 ; WX 781 ; N uni283E ; G 2733 U 10303 ; WX 781 ; N uni283F ; G 2734 U 10304 ; WX 781 ; N uni2840 ; G 2735 U 10305 ; WX 781 ; N uni2841 ; G 2736 U 10306 ; WX 781 ; N uni2842 ; G 2737 U 10307 ; WX 781 ; N uni2843 ; G 2738 U 10308 ; WX 781 ; N uni2844 ; G 2739 U 10309 ; WX 781 ; N uni2845 ; G 2740 U 10310 ; WX 781 ; N uni2846 ; G 2741 U 10311 ; WX 781 ; N uni2847 ; G 2742 U 10312 ; WX 781 ; N uni2848 ; G 2743 U 10313 ; WX 781 ; N uni2849 ; G 2744 U 10314 ; WX 781 ; N uni284A ; G 2745 U 10315 ; WX 781 ; N uni284B ; G 2746 U 10316 ; WX 781 ; N uni284C ; G 2747 U 10317 ; WX 781 ; N uni284D ; G 2748 U 10318 ; WX 781 ; N uni284E ; G 2749 U 10319 ; WX 781 ; N uni284F ; G 2750 U 10320 ; WX 781 ; N uni2850 ; G 2751 U 10321 ; WX 781 ; N uni2851 ; G 2752 U 10322 ; WX 781 ; N uni2852 ; G 2753 U 10323 ; WX 781 ; N uni2853 ; G 2754 U 10324 ; WX 781 ; N uni2854 ; G 2755 U 10325 ; WX 781 ; N uni2855 ; G 2756 U 10326 ; WX 781 ; N uni2856 ; G 2757 U 10327 ; WX 781 ; N uni2857 ; G 2758 U 10328 ; WX 781 ; N uni2858 ; G 2759 U 10329 ; WX 781 ; N uni2859 ; G 2760 U 10330 ; WX 781 ; N uni285A ; G 2761 U 10331 ; WX 781 ; N uni285B ; G 2762 U 10332 ; WX 781 ; N uni285C ; G 2763 U 10333 ; WX 781 ; N uni285D ; G 2764 U 10334 ; WX 781 ; N uni285E ; G 2765 U 10335 ; WX 781 ; N uni285F ; G 2766 U 10336 ; WX 781 ; N uni2860 ; G 2767 U 10337 ; WX 781 ; N uni2861 ; G 2768 U 10338 ; WX 781 ; N uni2862 ; G 2769 U 10339 ; WX 781 ; N uni2863 ; G 2770 U 10340 ; WX 781 ; N uni2864 ; G 2771 U 10341 ; WX 781 ; N uni2865 ; G 2772 U 10342 ; WX 781 ; N uni2866 ; G 2773 U 10343 ; WX 781 ; N uni2867 ; G 2774 U 10344 ; WX 781 ; N uni2868 ; G 2775 U 10345 ; WX 781 ; N uni2869 ; G 2776 U 10346 ; WX 781 ; N uni286A ; G 2777 U 10347 ; WX 781 ; N uni286B ; G 2778 U 10348 ; WX 781 ; N uni286C ; G 2779 U 10349 ; WX 781 ; N uni286D ; G 2780 U 10350 ; WX 781 ; N uni286E ; G 2781 U 10351 ; WX 781 ; N uni286F ; G 2782 U 10352 ; WX 781 ; N uni2870 ; G 2783 U 10353 ; WX 781 ; N uni2871 ; G 2784 U 10354 ; WX 781 ; N uni2872 ; G 2785 U 10355 ; WX 781 ; N uni2873 ; G 2786 U 10356 ; WX 781 ; N uni2874 ; G 2787 U 10357 ; WX 781 ; N uni2875 ; G 2788 U 10358 ; WX 781 ; N uni2876 ; G 2789 U 10359 ; WX 781 ; N uni2877 ; G 2790 U 10360 ; WX 781 ; N uni2878 ; G 2791 U 10361 ; WX 781 ; N uni2879 ; G 2792 U 10362 ; WX 781 ; N uni287A ; G 2793 U 10363 ; WX 781 ; N uni287B ; G 2794 U 10364 ; WX 781 ; N uni287C ; G 2795 U 10365 ; WX 781 ; N uni287D ; G 2796 U 10366 ; WX 781 ; N uni287E ; G 2797 U 10367 ; WX 781 ; N uni287F ; G 2798 U 10368 ; WX 781 ; N uni2880 ; G 2799 U 10369 ; WX 781 ; N uni2881 ; G 2800 U 10370 ; WX 781 ; N uni2882 ; G 2801 U 10371 ; WX 781 ; N uni2883 ; G 2802 U 10372 ; WX 781 ; N uni2884 ; G 2803 U 10373 ; WX 781 ; N uni2885 ; G 2804 U 10374 ; WX 781 ; N uni2886 ; G 2805 U 10375 ; WX 781 ; N uni2887 ; G 2806 U 10376 ; WX 781 ; N uni2888 ; G 2807 U 10377 ; WX 781 ; N uni2889 ; G 2808 U 10378 ; WX 781 ; N uni288A ; G 2809 U 10379 ; WX 781 ; N uni288B ; G 2810 U 10380 ; WX 781 ; N uni288C ; G 2811 U 10381 ; WX 781 ; N uni288D ; G 2812 U 10382 ; WX 781 ; N uni288E ; G 2813 U 10383 ; WX 781 ; N uni288F ; G 2814 U 10384 ; WX 781 ; N uni2890 ; G 2815 U 10385 ; WX 781 ; N uni2891 ; G 2816 U 10386 ; WX 781 ; N uni2892 ; G 2817 U 10387 ; WX 781 ; N uni2893 ; G 2818 U 10388 ; WX 781 ; N uni2894 ; G 2819 U 10389 ; WX 781 ; N uni2895 ; G 2820 U 10390 ; WX 781 ; N uni2896 ; G 2821 U 10391 ; WX 781 ; N uni2897 ; G 2822 U 10392 ; WX 781 ; N uni2898 ; G 2823 U 10393 ; WX 781 ; N uni2899 ; G 2824 U 10394 ; WX 781 ; N uni289A ; G 2825 U 10395 ; WX 781 ; N uni289B ; G 2826 U 10396 ; WX 781 ; N uni289C ; G 2827 U 10397 ; WX 781 ; N uni289D ; G 2828 U 10398 ; WX 781 ; N uni289E ; G 2829 U 10399 ; WX 781 ; N uni289F ; G 2830 U 10400 ; WX 781 ; N uni28A0 ; G 2831 U 10401 ; WX 781 ; N uni28A1 ; G 2832 U 10402 ; WX 781 ; N uni28A2 ; G 2833 U 10403 ; WX 781 ; N uni28A3 ; G 2834 U 10404 ; WX 781 ; N uni28A4 ; G 2835 U 10405 ; WX 781 ; N uni28A5 ; G 2836 U 10406 ; WX 781 ; N uni28A6 ; G 2837 U 10407 ; WX 781 ; N uni28A7 ; G 2838 U 10408 ; WX 781 ; N uni28A8 ; G 2839 U 10409 ; WX 781 ; N uni28A9 ; G 2840 U 10410 ; WX 781 ; N uni28AA ; G 2841 U 10411 ; WX 781 ; N uni28AB ; G 2842 U 10412 ; WX 781 ; N uni28AC ; G 2843 U 10413 ; WX 781 ; N uni28AD ; G 2844 U 10414 ; WX 781 ; N uni28AE ; G 2845 U 10415 ; WX 781 ; N uni28AF ; G 2846 U 10416 ; WX 781 ; N uni28B0 ; G 2847 U 10417 ; WX 781 ; N uni28B1 ; G 2848 U 10418 ; WX 781 ; N uni28B2 ; G 2849 U 10419 ; WX 781 ; N uni28B3 ; G 2850 U 10420 ; WX 781 ; N uni28B4 ; G 2851 U 10421 ; WX 781 ; N uni28B5 ; G 2852 U 10422 ; WX 781 ; N uni28B6 ; G 2853 U 10423 ; WX 781 ; N uni28B7 ; G 2854 U 10424 ; WX 781 ; N uni28B8 ; G 2855 U 10425 ; WX 781 ; N uni28B9 ; G 2856 U 10426 ; WX 781 ; N uni28BA ; G 2857 U 10427 ; WX 781 ; N uni28BB ; G 2858 U 10428 ; WX 781 ; N uni28BC ; G 2859 U 10429 ; WX 781 ; N uni28BD ; G 2860 U 10430 ; WX 781 ; N uni28BE ; G 2861 U 10431 ; WX 781 ; N uni28BF ; G 2862 U 10432 ; WX 781 ; N uni28C0 ; G 2863 U 10433 ; WX 781 ; N uni28C1 ; G 2864 U 10434 ; WX 781 ; N uni28C2 ; G 2865 U 10435 ; WX 781 ; N uni28C3 ; G 2866 U 10436 ; WX 781 ; N uni28C4 ; G 2867 U 10437 ; WX 781 ; N uni28C5 ; G 2868 U 10438 ; WX 781 ; N uni28C6 ; G 2869 U 10439 ; WX 781 ; N uni28C7 ; G 2870 U 10440 ; WX 781 ; N uni28C8 ; G 2871 U 10441 ; WX 781 ; N uni28C9 ; G 2872 U 10442 ; WX 781 ; N uni28CA ; G 2873 U 10443 ; WX 781 ; N uni28CB ; G 2874 U 10444 ; WX 781 ; N uni28CC ; G 2875 U 10445 ; WX 781 ; N uni28CD ; G 2876 U 10446 ; WX 781 ; N uni28CE ; G 2877 U 10447 ; WX 781 ; N uni28CF ; G 2878 U 10448 ; WX 781 ; N uni28D0 ; G 2879 U 10449 ; WX 781 ; N uni28D1 ; G 2880 U 10450 ; WX 781 ; N uni28D2 ; G 2881 U 10451 ; WX 781 ; N uni28D3 ; G 2882 U 10452 ; WX 781 ; N uni28D4 ; G 2883 U 10453 ; WX 781 ; N uni28D5 ; G 2884 U 10454 ; WX 781 ; N uni28D6 ; G 2885 U 10455 ; WX 781 ; N uni28D7 ; G 2886 U 10456 ; WX 781 ; N uni28D8 ; G 2887 U 10457 ; WX 781 ; N uni28D9 ; G 2888 U 10458 ; WX 781 ; N uni28DA ; G 2889 U 10459 ; WX 781 ; N uni28DB ; G 2890 U 10460 ; WX 781 ; N uni28DC ; G 2891 U 10461 ; WX 781 ; N uni28DD ; G 2892 U 10462 ; WX 781 ; N uni28DE ; G 2893 U 10463 ; WX 781 ; N uni28DF ; G 2894 U 10464 ; WX 781 ; N uni28E0 ; G 2895 U 10465 ; WX 781 ; N uni28E1 ; G 2896 U 10466 ; WX 781 ; N uni28E2 ; G 2897 U 10467 ; WX 781 ; N uni28E3 ; G 2898 U 10468 ; WX 781 ; N uni28E4 ; G 2899 U 10469 ; WX 781 ; N uni28E5 ; G 2900 U 10470 ; WX 781 ; N uni28E6 ; G 2901 U 10471 ; WX 781 ; N uni28E7 ; G 2902 U 10472 ; WX 781 ; N uni28E8 ; G 2903 U 10473 ; WX 781 ; N uni28E9 ; G 2904 U 10474 ; WX 781 ; N uni28EA ; G 2905 U 10475 ; WX 781 ; N uni28EB ; G 2906 U 10476 ; WX 781 ; N uni28EC ; G 2907 U 10477 ; WX 781 ; N uni28ED ; G 2908 U 10478 ; WX 781 ; N uni28EE ; G 2909 U 10479 ; WX 781 ; N uni28EF ; G 2910 U 10480 ; WX 781 ; N uni28F0 ; G 2911 U 10481 ; WX 781 ; N uni28F1 ; G 2912 U 10482 ; WX 781 ; N uni28F2 ; G 2913 U 10483 ; WX 781 ; N uni28F3 ; G 2914 U 10484 ; WX 781 ; N uni28F4 ; G 2915 U 10485 ; WX 781 ; N uni28F5 ; G 2916 U 10486 ; WX 781 ; N uni28F6 ; G 2917 U 10487 ; WX 781 ; N uni28F7 ; G 2918 U 10488 ; WX 781 ; N uni28F8 ; G 2919 U 10489 ; WX 781 ; N uni28F9 ; G 2920 U 10490 ; WX 781 ; N uni28FA ; G 2921 U 10491 ; WX 781 ; N uni28FB ; G 2922 U 10492 ; WX 781 ; N uni28FC ; G 2923 U 10493 ; WX 781 ; N uni28FD ; G 2924 U 10494 ; WX 781 ; N uni28FE ; G 2925 U 10495 ; WX 781 ; N uni28FF ; G 2926 U 10496 ; WX 838 ; N uni2900 ; G 2927 U 10497 ; WX 838 ; N uni2901 ; G 2928 U 10498 ; WX 838 ; N uni2902 ; G 2929 U 10499 ; WX 838 ; N uni2903 ; G 2930 U 10500 ; WX 838 ; N uni2904 ; G 2931 U 10501 ; WX 838 ; N uni2905 ; G 2932 U 10502 ; WX 838 ; N uni2906 ; G 2933 U 10503 ; WX 838 ; N uni2907 ; G 2934 U 10504 ; WX 838 ; N uni2908 ; G 2935 U 10505 ; WX 838 ; N uni2909 ; G 2936 U 10506 ; WX 838 ; N uni290A ; G 2937 U 10507 ; WX 838 ; N uni290B ; G 2938 U 10508 ; WX 838 ; N uni290C ; G 2939 U 10509 ; WX 838 ; N uni290D ; G 2940 U 10510 ; WX 838 ; N uni290E ; G 2941 U 10511 ; WX 838 ; N uni290F ; G 2942 U 10512 ; WX 838 ; N uni2910 ; G 2943 U 10513 ; WX 838 ; N uni2911 ; G 2944 U 10514 ; WX 838 ; N uni2912 ; G 2945 U 10515 ; WX 838 ; N uni2913 ; G 2946 U 10516 ; WX 838 ; N uni2914 ; G 2947 U 10517 ; WX 838 ; N uni2915 ; G 2948 U 10518 ; WX 838 ; N uni2916 ; G 2949 U 10519 ; WX 838 ; N uni2917 ; G 2950 U 10520 ; WX 838 ; N uni2918 ; G 2951 U 10521 ; WX 838 ; N uni2919 ; G 2952 U 10522 ; WX 838 ; N uni291A ; G 2953 U 10523 ; WX 838 ; N uni291B ; G 2954 U 10524 ; WX 838 ; N uni291C ; G 2955 U 10525 ; WX 838 ; N uni291D ; G 2956 U 10526 ; WX 838 ; N uni291E ; G 2957 U 10527 ; WX 838 ; N uni291F ; G 2958 U 10528 ; WX 838 ; N uni2920 ; G 2959 U 10529 ; WX 838 ; N uni2921 ; G 2960 U 10530 ; WX 838 ; N uni2922 ; G 2961 U 10531 ; WX 838 ; N uni2923 ; G 2962 U 10532 ; WX 838 ; N uni2924 ; G 2963 U 10533 ; WX 838 ; N uni2925 ; G 2964 U 10534 ; WX 838 ; N uni2926 ; G 2965 U 10535 ; WX 838 ; N uni2927 ; G 2966 U 10536 ; WX 838 ; N uni2928 ; G 2967 U 10537 ; WX 838 ; N uni2929 ; G 2968 U 10538 ; WX 838 ; N uni292A ; G 2969 U 10539 ; WX 838 ; N uni292B ; G 2970 U 10540 ; WX 838 ; N uni292C ; G 2971 U 10541 ; WX 838 ; N uni292D ; G 2972 U 10542 ; WX 838 ; N uni292E ; G 2973 U 10543 ; WX 838 ; N uni292F ; G 2974 U 10544 ; WX 838 ; N uni2930 ; G 2975 U 10545 ; WX 838 ; N uni2931 ; G 2976 U 10546 ; WX 838 ; N uni2932 ; G 2977 U 10547 ; WX 838 ; N uni2933 ; G 2978 U 10548 ; WX 838 ; N uni2934 ; G 2979 U 10549 ; WX 838 ; N uni2935 ; G 2980 U 10550 ; WX 838 ; N uni2936 ; G 2981 U 10551 ; WX 838 ; N uni2937 ; G 2982 U 10552 ; WX 838 ; N uni2938 ; G 2983 U 10553 ; WX 838 ; N uni2939 ; G 2984 U 10554 ; WX 838 ; N uni293A ; G 2985 U 10555 ; WX 838 ; N uni293B ; G 2986 U 10556 ; WX 838 ; N uni293C ; G 2987 U 10557 ; WX 838 ; N uni293D ; G 2988 U 10558 ; WX 838 ; N uni293E ; G 2989 U 10559 ; WX 838 ; N uni293F ; G 2990 U 10560 ; WX 838 ; N uni2940 ; G 2991 U 10561 ; WX 838 ; N uni2941 ; G 2992 U 10562 ; WX 838 ; N uni2942 ; G 2993 U 10563 ; WX 838 ; N uni2943 ; G 2994 U 10564 ; WX 838 ; N uni2944 ; G 2995 U 10565 ; WX 838 ; N uni2945 ; G 2996 U 10566 ; WX 838 ; N uni2946 ; G 2997 U 10567 ; WX 838 ; N uni2947 ; G 2998 U 10568 ; WX 838 ; N uni2948 ; G 2999 U 10569 ; WX 838 ; N uni2949 ; G 3000 U 10570 ; WX 838 ; N uni294A ; G 3001 U 10571 ; WX 838 ; N uni294B ; G 3002 U 10572 ; WX 838 ; N uni294C ; G 3003 U 10573 ; WX 838 ; N uni294D ; G 3004 U 10574 ; WX 838 ; N uni294E ; G 3005 U 10575 ; WX 838 ; N uni294F ; G 3006 U 10576 ; WX 838 ; N uni2950 ; G 3007 U 10577 ; WX 838 ; N uni2951 ; G 3008 U 10578 ; WX 838 ; N uni2952 ; G 3009 U 10579 ; WX 838 ; N uni2953 ; G 3010 U 10580 ; WX 838 ; N uni2954 ; G 3011 U 10581 ; WX 838 ; N uni2955 ; G 3012 U 10582 ; WX 838 ; N uni2956 ; G 3013 U 10583 ; WX 838 ; N uni2957 ; G 3014 U 10584 ; WX 838 ; N uni2958 ; G 3015 U 10585 ; WX 838 ; N uni2959 ; G 3016 U 10586 ; WX 838 ; N uni295A ; G 3017 U 10587 ; WX 838 ; N uni295B ; G 3018 U 10588 ; WX 838 ; N uni295C ; G 3019 U 10589 ; WX 838 ; N uni295D ; G 3020 U 10590 ; WX 838 ; N uni295E ; G 3021 U 10591 ; WX 838 ; N uni295F ; G 3022 U 10592 ; WX 838 ; N uni2960 ; G 3023 U 10593 ; WX 838 ; N uni2961 ; G 3024 U 10594 ; WX 838 ; N uni2962 ; G 3025 U 10595 ; WX 838 ; N uni2963 ; G 3026 U 10596 ; WX 838 ; N uni2964 ; G 3027 U 10597 ; WX 838 ; N uni2965 ; G 3028 U 10598 ; WX 838 ; N uni2966 ; G 3029 U 10599 ; WX 838 ; N uni2967 ; G 3030 U 10600 ; WX 838 ; N uni2968 ; G 3031 U 10601 ; WX 838 ; N uni2969 ; G 3032 U 10602 ; WX 838 ; N uni296A ; G 3033 U 10603 ; WX 838 ; N uni296B ; G 3034 U 10604 ; WX 838 ; N uni296C ; G 3035 U 10605 ; WX 838 ; N uni296D ; G 3036 U 10606 ; WX 838 ; N uni296E ; G 3037 U 10607 ; WX 838 ; N uni296F ; G 3038 U 10608 ; WX 838 ; N uni2970 ; G 3039 U 10609 ; WX 838 ; N uni2971 ; G 3040 U 10610 ; WX 838 ; N uni2972 ; G 3041 U 10611 ; WX 838 ; N uni2973 ; G 3042 U 10612 ; WX 838 ; N uni2974 ; G 3043 U 10613 ; WX 838 ; N uni2975 ; G 3044 U 10614 ; WX 838 ; N uni2976 ; G 3045 U 10615 ; WX 1032 ; N uni2977 ; G 3046 U 10616 ; WX 838 ; N uni2978 ; G 3047 U 10617 ; WX 838 ; N uni2979 ; G 3048 U 10618 ; WX 960 ; N uni297A ; G 3049 U 10619 ; WX 838 ; N uni297B ; G 3050 U 10620 ; WX 838 ; N uni297C ; G 3051 U 10621 ; WX 838 ; N uni297D ; G 3052 U 10622 ; WX 838 ; N uni297E ; G 3053 U 10623 ; WX 838 ; N uni297F ; G 3054 U 10731 ; WX 494 ; N uni29EB ; G 3055 U 10764 ; WX 1782 ; N uni2A0C ; G 3056 U 10765 ; WX 610 ; N uni2A0D ; G 3057 U 10766 ; WX 610 ; N uni2A0E ; G 3058 U 10799 ; WX 838 ; N uni2A2F ; G 3059 U 10858 ; WX 838 ; N uni2A6A ; G 3060 U 10859 ; WX 838 ; N uni2A6B ; G 3061 U 11008 ; WX 838 ; N uni2B00 ; G 3062 U 11009 ; WX 838 ; N uni2B01 ; G 3063 U 11010 ; WX 838 ; N uni2B02 ; G 3064 U 11011 ; WX 838 ; N uni2B03 ; G 3065 U 11012 ; WX 838 ; N uni2B04 ; G 3066 U 11013 ; WX 838 ; N uni2B05 ; G 3067 U 11014 ; WX 838 ; N uni2B06 ; G 3068 U 11015 ; WX 838 ; N uni2B07 ; G 3069 U 11016 ; WX 838 ; N uni2B08 ; G 3070 U 11017 ; WX 838 ; N uni2B09 ; G 3071 U 11018 ; WX 838 ; N uni2B0A ; G 3072 U 11019 ; WX 838 ; N uni2B0B ; G 3073 U 11020 ; WX 838 ; N uni2B0C ; G 3074 U 11021 ; WX 838 ; N uni2B0D ; G 3075 U 11022 ; WX 838 ; N uni2B0E ; G 3076 U 11023 ; WX 838 ; N uni2B0F ; G 3077 U 11024 ; WX 838 ; N uni2B10 ; G 3078 U 11025 ; WX 838 ; N uni2B11 ; G 3079 U 11026 ; WX 945 ; N uni2B12 ; G 3080 U 11027 ; WX 945 ; N uni2B13 ; G 3081 U 11028 ; WX 945 ; N uni2B14 ; G 3082 U 11029 ; WX 945 ; N uni2B15 ; G 3083 U 11030 ; WX 769 ; N uni2B16 ; G 3084 U 11031 ; WX 769 ; N uni2B17 ; G 3085 U 11032 ; WX 769 ; N uni2B18 ; G 3086 U 11033 ; WX 769 ; N uni2B19 ; G 3087 U 11034 ; WX 945 ; N uni2B1A ; G 3088 U 11360 ; WX 703 ; N uni2C60 ; G 3089 U 11361 ; WX 380 ; N uni2C61 ; G 3090 U 11363 ; WX 752 ; N uni2C63 ; G 3091 U 11364 ; WX 831 ; N uni2C64 ; G 3092 U 11367 ; WX 945 ; N uni2C67 ; G 3093 U 11368 ; WX 727 ; N uni2C68 ; G 3094 U 11369 ; WX 869 ; N uni2C69 ; G 3095 U 11370 ; WX 693 ; N uni2C6A ; G 3096 U 11371 ; WX 730 ; N uni2C6B ; G 3097 U 11372 ; WX 568 ; N uni2C6C ; G 3098 U 11373 ; WX 848 ; N uni2C6D ; G 3099 U 11374 ; WX 1107 ; N uni2C6E ; G 3100 U 11375 ; WX 776 ; N uni2C6F ; G 3101 U 11376 ; WX 848 ; N uni2C70 ; G 3102 U 11377 ; WX 709 ; N uni2C71 ; G 3103 U 11378 ; WX 1221 ; N uni2C72 ; G 3104 U 11379 ; WX 984 ; N uni2C73 ; G 3105 U 11381 ; WX 779 ; N uni2C75 ; G 3106 U 11382 ; WX 601 ; N uni2C76 ; G 3107 U 11383 ; WX 905 ; N uni2C77 ; G 3108 U 11385 ; WX 571 ; N uni2C79 ; G 3109 U 11386 ; WX 667 ; N uni2C7A ; G 3110 U 11387 ; WX 617 ; N uni2C7B ; G 3111 U 11388 ; WX 313 ; N uni2C7C ; G 3112 U 11389 ; WX 489 ; N uni2C7D ; G 3113 U 11390 ; WX 722 ; N uni2C7E ; G 3114 U 11391 ; WX 730 ; N uni2C7F ; G 3115 U 11520 ; WX 773 ; N uni2D00 ; G 3116 U 11521 ; WX 635 ; N uni2D01 ; G 3117 U 11522 ; WX 804 ; N uni2D02 ; G 3118 U 11523 ; WX 658 ; N uni2D03 ; G 3119 U 11524 ; WX 788 ; N uni2D04 ; G 3120 U 11525 ; WX 962 ; N uni2D05 ; G 3121 U 11526 ; WX 756 ; N uni2D06 ; G 3122 U 11527 ; WX 960 ; N uni2D07 ; G 3123 U 11528 ; WX 617 ; N uni2D08 ; G 3124 U 11529 ; WX 646 ; N uni2D09 ; G 3125 U 11530 ; WX 962 ; N uni2D0A ; G 3126 U 11531 ; WX 631 ; N uni2D0B ; G 3127 U 11532 ; WX 646 ; N uni2D0C ; G 3128 U 11533 ; WX 962 ; N uni2D0D ; G 3129 U 11534 ; WX 846 ; N uni2D0E ; G 3130 U 11535 ; WX 866 ; N uni2D0F ; G 3131 U 11536 ; WX 961 ; N uni2D10 ; G 3132 U 11537 ; WX 645 ; N uni2D11 ; G 3133 U 11538 ; WX 645 ; N uni2D12 ; G 3134 U 11539 ; WX 959 ; N uni2D13 ; G 3135 U 11540 ; WX 945 ; N uni2D14 ; G 3136 U 11541 ; WX 863 ; N uni2D15 ; G 3137 U 11542 ; WX 644 ; N uni2D16 ; G 3138 U 11543 ; WX 646 ; N uni2D17 ; G 3139 U 11544 ; WX 645 ; N uni2D18 ; G 3140 U 11545 ; WX 649 ; N uni2D19 ; G 3141 U 11546 ; WX 688 ; N uni2D1A ; G 3142 U 11547 ; WX 936 ; N uni2D1B ; G 3143 U 11548 ; WX 982 ; N uni2D1C ; G 3144 U 11549 ; WX 681 ; N uni2D1D ; G 3145 U 11550 ; WX 676 ; N uni2D1E ; G 3146 U 11551 ; WX 852 ; N uni2D1F ; G 3147 U 11552 ; WX 1113 ; N uni2D20 ; G 3148 U 11553 ; WX 632 ; N uni2D21 ; G 3149 U 11554 ; WX 645 ; N uni2D22 ; G 3150 U 11555 ; WX 646 ; N uni2D23 ; G 3151 U 11556 ; WX 749 ; N uni2D24 ; G 3152 U 11557 ; WX 914 ; N uni2D25 ; G 3153 U 11800 ; WX 586 ; N uni2E18 ; G 3154 U 11807 ; WX 838 ; N uni2E1F ; G 3155 U 11810 ; WX 473 ; N uni2E22 ; G 3156 U 11811 ; WX 473 ; N uni2E23 ; G 3157 U 11812 ; WX 473 ; N uni2E24 ; G 3158 U 11813 ; WX 473 ; N uni2E25 ; G 3159 U 11822 ; WX 586 ; N uni2E2E ; G 3160 U 42564 ; WX 722 ; N uniA644 ; G 3161 U 42565 ; WX 563 ; N uniA645 ; G 3162 U 42566 ; WX 468 ; N uniA646 ; G 3163 U 42567 ; WX 380 ; N uniA647 ; G 3164 U 42576 ; WX 1333 ; N uniA650 ; G 3165 U 42577 ; WX 1092 ; N uniA651 ; G 3166 U 42580 ; WX 1287 ; N uniA654 ; G 3167 U 42581 ; WX 1025 ; N uniA655 ; G 3168 U 42582 ; WX 1287 ; N uniA656 ; G 3169 U 42583 ; WX 1039 ; N uniA657 ; G 3170 U 42648 ; WX 1448 ; N uniA698 ; G 3171 U 42649 ; WX 1060 ; N uniA699 ; G 3172 U 42760 ; WX 500 ; N uniA708 ; G 3173 U 42761 ; WX 500 ; N uniA709 ; G 3174 U 42762 ; WX 500 ; N uniA70A ; G 3175 U 42763 ; WX 500 ; N uniA70B ; G 3176 U 42764 ; WX 500 ; N uniA70C ; G 3177 U 42765 ; WX 500 ; N uniA70D ; G 3178 U 42766 ; WX 500 ; N uniA70E ; G 3179 U 42767 ; WX 500 ; N uniA70F ; G 3180 U 42768 ; WX 500 ; N uniA710 ; G 3181 U 42769 ; WX 500 ; N uniA711 ; G 3182 U 42770 ; WX 500 ; N uniA712 ; G 3183 U 42771 ; WX 500 ; N uniA713 ; G 3184 U 42772 ; WX 500 ; N uniA714 ; G 3185 U 42773 ; WX 500 ; N uniA715 ; G 3186 U 42774 ; WX 500 ; N uniA716 ; G 3187 U 42779 ; WX 384 ; N uniA71B ; G 3188 U 42780 ; WX 384 ; N uniA71C ; G 3189 U 42781 ; WX 276 ; N uniA71D ; G 3190 U 42782 ; WX 276 ; N uniA71E ; G 3191 U 42783 ; WX 276 ; N uniA71F ; G 3192 U 42790 ; WX 945 ; N uniA726 ; G 3193 U 42791 ; WX 712 ; N uniA727 ; G 3194 U 42792 ; WX 1003 ; N uniA728 ; G 3195 U 42793 ; WX 909 ; N uniA729 ; G 3196 U 42794 ; WX 696 ; N uniA72A ; G 3197 U 42795 ; WX 609 ; N uniA72B ; G 3198 U 42796 ; WX 634 ; N uniA72C ; G 3199 U 42797 ; WX 598 ; N uniA72D ; G 3200 U 42798 ; WX 741 ; N uniA72E ; G 3201 U 42799 ; WX 706 ; N uniA72F ; G 3202 U 42800 ; WX 592 ; N uniA730 ; G 3203 U 42801 ; WX 563 ; N uniA731 ; G 3204 U 42802 ; WX 1301 ; N uniA732 ; G 3205 U 42803 ; WX 986 ; N uniA733 ; G 3206 U 42804 ; WX 1261 ; N uniA734 ; G 3207 U 42805 ; WX 1004 ; N uniA735 ; G 3208 U 42806 ; WX 1168 ; N uniA736 ; G 3209 U 42807 ; WX 1008 ; N uniA737 ; G 3210 U 42808 ; WX 1016 ; N uniA738 ; G 3211 U 42809 ; WX 813 ; N uniA739 ; G 3212 U 42810 ; WX 1016 ; N uniA73A ; G 3213 U 42811 ; WX 813 ; N uniA73B ; G 3214 U 42812 ; WX 994 ; N uniA73C ; G 3215 U 42813 ; WX 847 ; N uniA73D ; G 3216 U 42814 ; WX 796 ; N uniA73E ; G 3217 U 42815 ; WX 609 ; N uniA73F ; G 3218 U 42816 ; WX 910 ; N uniA740 ; G 3219 U 42817 ; WX 722 ; N uniA741 ; G 3220 U 42822 ; WX 916 ; N uniA746 ; G 3221 U 42823 ; WX 581 ; N uniA747 ; G 3222 U 42826 ; WX 1010 ; N uniA74A ; G 3223 U 42827 ; WX 770 ; N uniA74B ; G 3224 U 42830 ; WX 1448 ; N uniA74E ; G 3225 U 42831 ; WX 1060 ; N uniA74F ; G 3226 U 42856 ; WX 787 ; N uniA768 ; G 3227 U 42857 ; WX 716 ; N uniA769 ; G 3228 U 42875 ; WX 694 ; N uniA77B ; G 3229 U 42876 ; WX 527 ; N uniA77C ; G 3230 U 42880 ; WX 703 ; N uniA780 ; G 3231 U 42881 ; WX 380 ; N uniA781 ; G 3232 U 42882 ; WX 872 ; N uniA782 ; G 3233 U 42883 ; WX 727 ; N uniA783 ; G 3234 U 42884 ; WX 694 ; N uniA784 ; G 3235 U 42885 ; WX 527 ; N uniA785 ; G 3236 U 42886 ; WX 796 ; N uniA786 ; G 3237 U 42887 ; WX 609 ; N uniA787 ; G 3238 U 42891 ; WX 439 ; N uniA78B ; G 3239 U 42892 ; WX 306 ; N uniA78C ; G 3240 U 42893 ; WX 913 ; N uniA78D ; G 3241 U 42896 ; WX 914 ; N uniA790 ; G 3242 U 42897 ; WX 727 ; N uniA791 ; G 3243 U 42922 ; WX 945 ; N uniA7AA ; G 3244 U 43000 ; WX 595 ; N uniA7F8 ; G 3245 U 43001 ; WX 647 ; N uniA7F9 ; G 3246 U 43002 ; WX 1069 ; N uniA7FA ; G 3247 U 43003 ; WX 710 ; N uniA7FB ; G 3248 U 43004 ; WX 752 ; N uniA7FC ; G 3249 U 43005 ; WX 1107 ; N uniA7FD ; G 3250 U 43006 ; WX 468 ; N uniA7FE ; G 3251 U 43007 ; WX 1286 ; N uniA7FF ; G 3252 U 62464 ; WX 705 ; N uniF400 ; G 3253 U 62465 ; WX 716 ; N uniF401 ; G 3254 U 62466 ; WX 765 ; N uniF402 ; G 3255 U 62467 ; WX 999 ; N uniF403 ; G 3256 U 62468 ; WX 716 ; N uniF404 ; G 3257 U 62469 ; WX 710 ; N uniF405 ; G 3258 U 62470 ; WX 776 ; N uniF406 ; G 3259 U 62471 ; WX 1038 ; N uniF407 ; G 3260 U 62472 ; WX 716 ; N uniF408 ; G 3261 U 62473 ; WX 716 ; N uniF409 ; G 3262 U 62474 ; WX 1309 ; N uniF40A ; G 3263 U 62475 ; WX 734 ; N uniF40B ; G 3264 U 62476 ; WX 733 ; N uniF40C ; G 3265 U 62477 ; WX 1004 ; N uniF40D ; G 3266 U 62478 ; WX 716 ; N uniF40E ; G 3267 U 62479 ; WX 733 ; N uniF40F ; G 3268 U 62480 ; WX 1050 ; N uniF410 ; G 3269 U 62481 ; WX 797 ; N uniF411 ; G 3270 U 62482 ; WX 850 ; N uniF412 ; G 3271 U 62483 ; WX 799 ; N uniF413 ; G 3272 U 62484 ; WX 996 ; N uniF414 ; G 3273 U 62485 ; WX 732 ; N uniF415 ; G 3274 U 62486 ; WX 987 ; N uniF416 ; G 3275 U 62487 ; WX 731 ; N uniF417 ; G 3276 U 62488 ; WX 739 ; N uniF418 ; G 3277 U 62489 ; WX 733 ; N uniF419 ; G 3278 U 62490 ; WX 780 ; N uniF41A ; G 3279 U 62491 ; WX 733 ; N uniF41B ; G 3280 U 62492 ; WX 739 ; N uniF41C ; G 3281 U 62493 ; WX 717 ; N uniF41D ; G 3282 U 62494 ; WX 780 ; N uniF41E ; G 3283 U 62495 ; WX 936 ; N uniF41F ; G 3284 U 62496 ; WX 716 ; N uniF420 ; G 3285 U 62497 ; WX 826 ; N uniF421 ; G 3286 U 62498 ; WX 717 ; N uniF422 ; G 3287 U 62499 ; WX 716 ; N uniF423 ; G 3288 U 62500 ; WX 716 ; N uniF424 ; G 3289 U 62501 ; WX 773 ; N uniF425 ; G 3290 U 62502 ; WX 1013 ; N uniF426 ; G 3291 U 62504 ; WX 904 ; N uniF428 ; G 3292 U 63173 ; WX 667 ; N uniF6C5 ; G 3293 U 63185 ; WX 500 ; N cyrBreve ; G 3294 U 63188 ; WX 500 ; N cyrbreve ; G 3295 U 64256 ; WX 821 ; N uniFB00 ; G 3296 U 64257 ; WX 727 ; N fi ; G 3297 U 64258 ; WX 727 ; N fl ; G 3298 U 64259 ; WX 1120 ; N uniFB03 ; G 3299 U 64260 ; WX 1117 ; N uniFB04 ; G 3300 U 64261 ; WX 871 ; N uniFB05 ; G 3301 U 64262 ; WX 971 ; N uniFB06 ; G 3302 U 65024 ; WX 0 ; N uniFE00 ; G 3303 U 65025 ; WX 0 ; N uniFE01 ; G 3304 U 65026 ; WX 0 ; N uniFE02 ; G 3305 U 65027 ; WX 0 ; N uniFE03 ; G 3306 U 65028 ; WX 0 ; N uniFE04 ; G 3307 U 65029 ; WX 0 ; N uniFE05 ; G 3308 U 65030 ; WX 0 ; N uniFE06 ; G 3309 U 65031 ; WX 0 ; N uniFE07 ; G 3310 U 65032 ; WX 0 ; N uniFE08 ; G 3311 U 65033 ; WX 0 ; N uniFE09 ; G 3312 U 65034 ; WX 0 ; N uniFE0A ; G 3313 U 65035 ; WX 0 ; N uniFE0B ; G 3314 U 65036 ; WX 0 ; N uniFE0C ; G 3315 U 65037 ; WX 0 ; N uniFE0D ; G 3316 U 65038 ; WX 0 ; N uniFE0E ; G 3317 U 65039 ; WX 0 ; N uniFE0F ; G 3318 U 65529 ; WX 0 ; N uniFFF9 ; G 3319 U 65530 ; WX 0 ; N uniFFFA ; G 3320 U 65531 ; WX 0 ; N uniFFFB ; G 3321 U 65532 ; WX 0 ; N uniFFFC ; G 3322 U 65533 ; WX 1113 ; N uniFFFD ; G 3323 EndCharMetrics StartKernData StartKernPairs 1408 KPX dollar seven -112 KPX dollar nine -149 KPX dollar colon -102 KPX dollar less -102 KPX dollar I -36 KPX dollar W -36 KPX dollar Y -83 KPX dollar Z -83 KPX dollar backslash -83 KPX dollar questiondown -83 KPX dollar Aacute -83 KPX dollar Hcircumflex -112 KPX dollar hcircumflex -36 KPX dollar Hbar -112 KPX dollar hbar -36 KPX dollar Kcommaaccent -102 KPX dollar kcommaaccent -83 KPX dollar kgreenlandic -102 KPX dollar Lacute -83 KPX dollar lacute -102 KPX dollar uni01DC -112 KPX dollar uni01DD -36 KPX dollar uni01F4 -102 KPX dollar uni01F5 -83 KPX percent ampersand 38 KPX percent asterisk 38 KPX percent two 38 KPX percent less -36 KPX percent Egrave 38 KPX percent Ecircumflex 38 KPX percent Igrave 38 KPX percent Icircumflex 38 KPX percent Thorn 38 KPX percent agrave 38 KPX percent acircumflex 38 KPX percent adieresis 38 KPX percent Dcaron 38 KPX percent Dcroat 38 KPX percent Emacron 38 KPX percent Ebreve 38 KPX percent kgreenlandic -36 KPX percent lacute -36 KPX percent uni01AC 38 KPX percent uni01AE 38 KPX percent uni01F0 38 KPX percent uni01F4 -36 KPX quotesingle nine -36 KPX parenright dollar -120 KPX parenright D -112 KPX parenright H -112 KPX parenright R -112 KPX parenright U -36 KPX parenright X -36 KPX parenright cent -112 KPX parenright sterling -112 KPX parenright currency -112 KPX parenright yen -112 KPX parenright brokenbar -112 KPX parenright section -112 KPX parenright dieresis -112 KPX parenright ordfeminine -112 KPX parenright guillemotleft -112 KPX parenright logicalnot -112 KPX parenright sfthyphen -112 KPX parenright acute -112 KPX parenright mu -112 KPX parenright paragraph -112 KPX parenright periodcentered -112 KPX parenright cedilla -112 KPX parenright ordmasculine -112 KPX parenright guillemotright -36 KPX parenright onequarter -36 KPX parenright onehalf -36 KPX parenright threequarters -36 KPX parenright Acircumflex -120 KPX parenright Atilde -112 KPX parenright Adieresis -120 KPX parenright Aring -112 KPX parenright AE -120 KPX parenright Ccedilla -112 KPX parenright Otilde -112 KPX parenright multiply -112 KPX parenright Ugrave -112 KPX parenright Ucircumflex -112 KPX parenright Yacute -112 KPX parenright dcaron -112 KPX parenright dmacron -112 KPX parenright emacron -112 KPX parenright ebreve -112 KPX parenright edotaccent -36 KPX parenright eogonek -36 KPX parenright ecaron -36 KPX parenright imacron -36 KPX parenright ibreve -36 KPX parenright iogonek -36 KPX parenright dotlessi -36 KPX parenright ij -36 KPX parenright jcircumflex -36 KPX parenright uni01A5 -112 KPX parenright uni01AD -112 KPX parenright Uhorn -112 KPX parenright uni01F1 -112 KPX period dollar -83 KPX period ampersand -55 KPX period two -55 KPX period eight -73 KPX period colon -73 KPX period less -55 KPX period H -45 KPX period R -45 KPX period X -45 KPX period backslash -92 KPX period ordfeminine -45 KPX period guillemotleft -45 KPX period logicalnot -45 KPX period sfthyphen -45 KPX period acute -45 KPX period mu -45 KPX period paragraph -45 KPX period periodcentered -45 KPX period cedilla -45 KPX period ordmasculine -36 KPX period guillemotright -45 KPX period onequarter -45 KPX period onehalf -45 KPX period threequarters -45 KPX period questiondown -92 KPX period Aacute -92 KPX period Egrave -55 KPX period Icircumflex -55 KPX period Yacute -45 KPX period Ebreve -55 KPX period ebreve -45 KPX period Idot -73 KPX period dotlessi -45 KPX period lacute -55 KPX slash seven -167 KPX slash eight -112 KPX slash nine -243 KPX slash colon -139 KPX slash less -131 KPX slash backslash -73 KPX slash questiondown -73 KPX slash Aacute -73 KPX slash Hbar -167 KPX slash Idot -112 KPX slash lacute -131 KPX two nine -36 KPX two semicolon -36 KPX three dollar -149 KPX three D -55 KPX three H -55 KPX three R -55 KPX three cent -55 KPX three sterling -55 KPX three currency -55 KPX three yen -55 KPX three brokenbar -55 KPX three section -55 KPX three dieresis -55 KPX three ordfeminine -55 KPX three guillemotleft -55 KPX three logicalnot -55 KPX three sfthyphen -55 KPX three acute -55 KPX three mu -55 KPX three paragraph -55 KPX three periodcentered -55 KPX three cedilla -55 KPX three ordmasculine -55 KPX three Yacute -55 KPX three ebreve -55 KPX five seven -36 KPX five nine -73 KPX five colon -45 KPX five less -63 KPX five D 47 KPX five backslash -36 KPX five cent 47 KPX five sterling 47 KPX five currency 47 KPX five yen 47 KPX five brokenbar 47 KPX five section 47 KPX five dieresis 47 KPX five ordmasculine 38 KPX five questiondown -36 KPX five Aacute -36 KPX five Hbar -36 KPX five lacute -63 KPX six six -45 KPX six Gdotaccent -45 KPX six Gcommaaccent -45 KPX seven dollar -112 KPX seven seven -73 KPX seven D -196 KPX seven F -235 KPX seven H -235 KPX seven R -235 KPX seven U -149 KPX seven V -188 KPX seven X -188 KPX seven Z -225 KPX seven backslash -225 KPX seven m -149 KPX seven braceright -149 KPX seven cent -96 KPX seven sterling -196 KPX seven currency -96 KPX seven yen -96 KPX seven brokenbar -96 KPX seven section -96 KPX seven dieresis -159 KPX seven copyright -235 KPX seven ordfeminine -175 KPX seven guillemotleft -235 KPX seven logicalnot -175 KPX seven sfthyphen -175 KPX seven acute -155 KPX seven mu -235 KPX seven paragraph -155 KPX seven periodcentered -155 KPX seven cedilla -155 KPX seven ordmasculine -159 KPX seven guillemotright -158 KPX seven onequarter -188 KPX seven onehalf -158 KPX seven threequarters -158 KPX seven questiondown -225 KPX seven Aacute -225 KPX seven Eacute -235 KPX seven Idieresis -235 KPX seven Yacute -235 KPX seven ebreve -159 KPX seven edotaccent -149 KPX seven ecaron -149 KPX seven gdotaccent -188 KPX seven gcommaaccent -188 KPX seven Hbar -73 KPX seven dotlessi -188 KPX eight dollar -63 KPX nine dollar -159 KPX nine two -36 KPX nine D -188 KPX nine H -188 KPX nine L -36 KPX nine R -188 KPX nine X -131 KPX nine backslash -83 KPX nine cent -188 KPX nine sterling -188 KPX nine currency -188 KPX nine yen -188 KPX nine brokenbar -188 KPX nine section -188 KPX nine dieresis -188 KPX nine ordfeminine -188 KPX nine guillemotleft -188 KPX nine logicalnot -188 KPX nine sfthyphen -188 KPX nine acute -188 KPX nine mu -188 KPX nine paragraph -188 KPX nine periodcentered -188 KPX nine cedilla -188 KPX nine ordmasculine -188 KPX nine guillemotright -131 KPX nine onequarter -131 KPX nine onehalf -131 KPX nine threequarters -131 KPX nine questiondown -83 KPX nine Aacute -83 KPX nine Yacute -188 KPX nine Ebreve -36 KPX nine ebreve -188 KPX nine dotlessi -131 KPX colon dollar -131 KPX colon D -178 KPX colon H -167 KPX colon L -36 KPX colon R -167 KPX colon U -92 KPX colon X -83 KPX colon backslash -45 KPX colon cent -178 KPX colon sterling -178 KPX colon currency -178 KPX colon yen -178 KPX colon brokenbar -178 KPX colon section -178 KPX colon dieresis -139 KPX colon ordfeminine -167 KPX colon guillemotleft -167 KPX colon logicalnot -167 KPX colon sfthyphen -167 KPX colon acute -167 KPX colon mu -167 KPX colon paragraph -167 KPX colon periodcentered -167 KPX colon cedilla -167 KPX colon ordmasculine -167 KPX colon guillemotright -83 KPX colon onequarter -83 KPX colon onehalf -83 KPX colon threequarters -83 KPX colon questiondown -45 KPX colon Aacute -45 KPX colon Yacute -167 KPX colon ebreve -167 KPX colon edotaccent -92 KPX colon ecaron -92 KPX colon dotlessi -83 KPX semicolon dollar -73 KPX semicolon ampersand -36 KPX semicolon two -36 KPX semicolon Egrave -36 KPX semicolon Icircumflex -36 KPX semicolon Ebreve -36 KPX less dollar -131 KPX less ampersand -36 KPX less D -159 KPX less H -178 KPX less L -36 KPX less R -178 KPX less X -178 KPX less cent -159 KPX less sterling -159 KPX less currency -159 KPX less yen -159 KPX less brokenbar -159 KPX less section -159 KPX less dieresis -159 KPX less ordfeminine -178 KPX less guillemotleft -178 KPX less logicalnot -178 KPX less sfthyphen -178 KPX less acute -178 KPX less mu -178 KPX less paragraph -178 KPX less periodcentered -178 KPX less cedilla -178 KPX less ordmasculine -178 KPX less guillemotright -178 KPX less onequarter -178 KPX less onehalf -178 KPX less threequarters -178 KPX less Egrave -36 KPX less Icircumflex -36 KPX less Yacute -178 KPX less ebreve -178 KPX less dotlessi -178 KPX m hyphen -73 KPX m seven -149 KPX m Hbar -149 KPX braceright hyphen -73 KPX braceright seven -149 KPX braceright Hbar -149 KPX Acircumflex seven -112 KPX Acircumflex nine -149 KPX Acircumflex colon -102 KPX Acircumflex less -102 KPX Acircumflex I -36 KPX Acircumflex W -36 KPX Acircumflex Y -83 KPX Acircumflex Z -83 KPX Acircumflex backslash -83 KPX Acircumflex questiondown -83 KPX Acircumflex Aacute -83 KPX Acircumflex Hcircumflex -112 KPX Acircumflex hcircumflex -36 KPX Acircumflex Hbar -112 KPX Acircumflex hbar -36 KPX Acircumflex Kcommaaccent -102 KPX Acircumflex kcommaaccent -83 KPX Acircumflex kgreenlandic -102 KPX Acircumflex Lacute -83 KPX Acircumflex lacute -102 KPX Acircumflex uni01DC -112 KPX Acircumflex uni01DD -36 KPX Acircumflex uni01F4 -102 KPX Acircumflex uni01F5 -83 KPX Adieresis seven -112 KPX Adieresis nine -149 KPX Adieresis colon -102 KPX Adieresis less -102 KPX Adieresis I -36 KPX Adieresis W -36 KPX Adieresis Y -83 KPX Adieresis Z -83 KPX Adieresis backslash -83 KPX Adieresis questiondown -83 KPX Adieresis Aacute -83 KPX Adieresis Hcircumflex -112 KPX Adieresis hcircumflex -36 KPX Adieresis Hbar -112 KPX Adieresis hbar -36 KPX Adieresis Kcommaaccent -102 KPX Adieresis kcommaaccent -83 KPX Adieresis kgreenlandic -102 KPX Adieresis Lacute -83 KPX Adieresis lacute -102 KPX Adieresis uni01DC -112 KPX Adieresis uni01DD -36 KPX Adieresis uni01F4 -102 KPX Adieresis uni01F5 -83 KPX AE seven -112 KPX AE nine -149 KPX AE colon -102 KPX AE less -102 KPX AE I -36 KPX AE W -36 KPX AE Y -83 KPX AE Z -83 KPX AE backslash -83 KPX AE questiondown -83 KPX AE Aacute -83 KPX AE Hcircumflex -112 KPX AE hcircumflex -36 KPX AE Hbar -112 KPX AE hbar -36 KPX AE Kcommaaccent -102 KPX AE kcommaaccent -83 KPX AE kgreenlandic -102 KPX AE Lacute -83 KPX AE lacute -102 KPX AE uni01DC -112 KPX AE uni01DD -36 KPX AE uni01F4 -102 KPX AE uni01F5 -83 KPX Eth nine -36 KPX Ograve nine -36 KPX ucircumflex seven -167 KPX ucircumflex eight -112 KPX ucircumflex nine -243 KPX ucircumflex colon -139 KPX ucircumflex less -131 KPX ucircumflex backslash -73 KPX ucircumflex questiondown -73 KPX ucircumflex Aacute -73 KPX ucircumflex Hbar -167 KPX ucircumflex Idot -112 KPX ucircumflex lacute -131 KPX ydieresis seven -167 KPX ydieresis eight -112 KPX ydieresis nine -243 KPX ydieresis colon -139 KPX ydieresis less -131 KPX ydieresis backslash -73 KPX ydieresis questiondown -73 KPX ydieresis Aacute -73 KPX ydieresis Hbar -167 KPX ydieresis Idot -112 KPX ydieresis lacute -131 KPX Abreve O -241 KPX abreve seven -167 KPX abreve eight -112 KPX abreve nine -243 KPX abreve colon -139 KPX abreve less -131 KPX abreve backslash -73 KPX abreve questiondown -73 KPX abreve Aacute -73 KPX abreve Hbar -167 KPX abreve Idot -112 KPX abreve lacute -131 KPX Edotaccent seven -36 KPX Edotaccent nine -73 KPX Edotaccent colon -45 KPX Edotaccent less -63 KPX Edotaccent D 47 KPX Edotaccent backslash -36 KPX Edotaccent cent 47 KPX Edotaccent sterling 47 KPX Edotaccent currency 47 KPX Edotaccent yen 47 KPX Edotaccent brokenbar 47 KPX Edotaccent section 47 KPX Edotaccent dieresis 47 KPX Edotaccent ordmasculine 38 KPX Edotaccent questiondown -36 KPX Edotaccent Aacute -36 KPX Edotaccent Hbar -36 KPX Edotaccent lacute -63 KPX Ecaron seven -36 KPX Ecaron nine -73 KPX Ecaron colon -45 KPX Ecaron less -63 KPX Ecaron D 47 KPX Ecaron backslash -36 KPX Ecaron cent 47 KPX Ecaron sterling 47 KPX Ecaron currency 47 KPX Ecaron yen 47 KPX Ecaron brokenbar 47 KPX Ecaron section 47 KPX Ecaron dieresis 47 KPX Ecaron ordmasculine 38 KPX Ecaron questiondown -36 KPX Ecaron Aacute -36 KPX Ecaron Hbar -36 KPX Ecaron lacute -63 KPX Gdotaccent six -45 KPX Gdotaccent Gdotaccent -45 KPX Gdotaccent Gcommaaccent -45 KPX Gcommaaccent six -45 KPX Gcommaaccent Gdotaccent -45 KPX Gcommaaccent Gcommaaccent -45 KPX Hbar dollar -112 KPX Hbar seven -73 KPX Hbar D -196 KPX Hbar F -235 KPX Hbar H -235 KPX Hbar R -235 KPX Hbar U -149 KPX Hbar V -188 KPX Hbar X -188 KPX Hbar Z -225 KPX Hbar backslash -225 KPX Hbar m -149 KPX Hbar braceright -149 KPX Hbar cent -196 KPX Hbar sterling -196 KPX Hbar currency -196 KPX Hbar yen -196 KPX Hbar brokenbar -196 KPX Hbar section -196 KPX Hbar dieresis -159 KPX Hbar copyright -235 KPX Hbar ordfeminine -235 KPX Hbar guillemotleft -235 KPX Hbar logicalnot -235 KPX Hbar sfthyphen -235 KPX Hbar acute -235 KPX Hbar mu -235 KPX Hbar paragraph -235 KPX Hbar periodcentered -235 KPX Hbar cedilla -235 KPX Hbar ordmasculine -159 KPX Hbar guillemotright -188 KPX Hbar onequarter -188 KPX Hbar onehalf -188 KPX Hbar threequarters -188 KPX Hbar questiondown -225 KPX Hbar Aacute -225 KPX Hbar Eacute -235 KPX Hbar Idieresis -235 KPX Hbar Yacute -235 KPX Hbar ebreve -159 KPX Hbar edotaccent -149 KPX Hbar ecaron -149 KPX Hbar gdotaccent -188 KPX Hbar gcommaaccent -188 KPX Hbar Hbar -73 KPX Hbar dotlessi -188 KPX Idot dollar -63 KPX lacute dollar -131 KPX lacute ampersand -36 KPX lacute D -159 KPX lacute H -178 KPX lacute L -36 KPX lacute R -178 KPX lacute X -178 KPX lacute cent -159 KPX lacute sterling -159 KPX lacute currency -159 KPX lacute yen -159 KPX lacute brokenbar -159 KPX lacute section -159 KPX lacute dieresis -159 KPX lacute ordfeminine -178 KPX lacute guillemotleft -178 KPX lacute logicalnot -178 KPX lacute sfthyphen -178 KPX lacute acute -178 KPX lacute mu -178 KPX lacute paragraph -178 KPX lacute periodcentered -178 KPX lacute cedilla -178 KPX lacute ordmasculine -178 KPX lacute guillemotright -178 KPX lacute onequarter -178 KPX lacute onehalf -178 KPX lacute threequarters -178 KPX lacute Egrave -36 KPX lacute Icircumflex -36 KPX lacute Yacute -178 KPX lacute ebreve -178 KPX lacute dotlessi -178 KPX uni027D dollar -282 EndKernPairs EndKernData EndFontMetrics dompdf/lib/fonts/DejaVuSans-BoldOblique.ttf 0000644 00002350334 15024772104 0014651 0 ustar 00 0FFTMs��� <